Page1, Page 2, Page 3, Page 4, Page 5, Page 6, Page 7

Game Programming Tutorial (Page 7)

This is page 7 of step by step guide to programming a 2D side-scrolling game with some 3D. Now we'll go ahead and build the tile editor and
gamemap editor.

I think the only thing we add to definitions and includes is the ALLOW_EDIT and #include "editor_vars.h"
Editor_vars.h compiles its stuff only if ALLOW_EDIT is defined (contains ifdef ALLOW_EDIT) so I put it after ALLOW_EDIT define.

#include "headers.h"
#define TIMING_DELAY 8 #define ALLOW_EDIT //to be commented out for the actual game release #define LEVEL_WIDTH 200 #define LEVEL_HEIGHT 50 #include "editor_vars.h" //TILE vars: stTileType tile[521]={0};
I thought 500 or so tile types should be plenty. If you use tile[521]={0}; <-- the ={0} part sets all the structs members to = 0 ... which saves a lot of time initializing variables...
stTile              g_tiles[LEVEL_WIDTH+14][LEVEL_HEIGHT+14]={0};

The above sets up a "2-dimensional" array - basically allocates a static(unchanging) amount of
system RAM to hold a grid of tiles (the gamemap) of size LEVEL_WIDTH x LEVEL_HEIGHT..
I added some padding to reduce chance of reading memory illegally(which could cause freeze).
You could also use ALLOC define to create some dynamically allocated level areas - but I find
just using a big chunk and using what I need from it works fine.

//text / input / file saving vars:
char                g_text[201];           
int                 last_key_state[256];  
D3DCOLOR            text_color, default_color = 0xFF008811;
RECT                text_position; //add
LPD3DXFONT          g_font              = NULL; 
LPD3DXFONT          g_font2             = NULL; 
TCHAR*              g_current_directory;
unsigned int        g_directory_buf_size, g_size_of_directory = 260;
bool                g_escape_key_pressed=false;
int                 g_text_index=0, last_char, flasher=0, character_repeat_time=0, key_waiting=20;

Just some vars for text based stuff and text input during render looping..
//device / directX object vars:
bool                DeviceLost=false;
LPD3DXSPRITE        g_sprite            = NULL;
D3DLIGHT9           g_light;
float               LPX=0.0f,LPY=0.0f,LPZ=200.0f; //light position
bool                g_scene_done=true;
bool                g_begin_scene=false;

g_scene_done and g_begin_scene are used to report any mistakes with Begin and 
End scene stuff -- ie: using BEGIN_SCENE twice in a row without ending the scene

g_sprite (we only need one of these) - as mentioned before, g_sprite is an interface for rendering 
sprites using the d3dx sprite functions. The sprites themselves are kept track of using RECT's 
and properties in our own structs.


//matrices / vectors:
D3DXMATRIX          g_viewing_matrix;
D3DXMATRIX          g_projection_matrix;
D3DXMATRIX          g_world_matrix;
D3DXMATRIX          g_matrix_2d;
D3DXVECTOR2         g_sprite_scale=D3DXVECTOR2(1,1);
D3DXVECTOR2         g_sprite_center=D3DXVECTOR2(0,0);
D3DXVECTOR2         g_sprite_trans=D3DXVECTOR2(0,0);
D3DXVECTOR2         g_sprite_rot_center=D3DXVECTOR2(0,0);

The first 3 variable are more useful if everything is rendered in OBJECTSPACE. If we do decide to 
render the game in "object space" instead of regular sprite mode - then these 3 variables will be
useful for moving the camera around and doing other visual type tricks. 
The other globals I put here for 2D scaling and rotation and translation stuff that need to be 
kept track of globally. 

// game / tile / drawing:
int                 g_level=1;
int                 tile_x=16,tile_y=16;        //actual tile location in tile map for center of screen
float               tile_x_off=0.0f, tile_y_off=0.0f;   //tile_map_position offset (0-64) for scrolling
D3DCOLOR            g_screen_tint=0xFFFFFFFF;
int                 g_num_tile_types=0; 
TEXTURE             tile_texture;
TEXTURE             helpers;
//RECT              g_screen_rect={0};
DRAWING_MEMORY      rec_images[800]; 
LPDIRECT3DVERTEXBUFFER9 g_vb=NULL;  
TEXTURE g_texture[521]; //these will store 3D cube texture maps:

tile_x and tile_y keep track of the current tile position the main character is 
at/over in the game map (center tile).
tile_x_off and tile_y_off -- The section of visible tiles is moved horizontally and vertically 
a bit each frame - and the total offset amount is kept track of by these variables... When the
tile_x_off for example exceeds a tile's typical width (64), then it will reset to a zero offset 
and change the tile_x... If it were moving in the negative direction and it went past -64 then 
since the background is moving left and the hero is travelling right - the tile_x is increased 
by 1 when the offset is reset to 0. This way we can loop through a section of visible tiles only,
 starting from tile_x-13 to tile_x+13 (and tile_y-10 to tile_y+10).. 

Here's the idea in psuedocode:

tile_x_off-=hero.velocity.x;
if (tile_x_off<-64) {tile_x_off=0; tile_x++;}

I'll explain more of this in the actual function..

//---------------
// M A I N 
//---------------
int WINAPI WinMain(HINSTANCE hInstC,HINSTANCE hInstP,LPSTR lpCmdLine,int nCmdShow) {
    BEGIN_MEMORY_LEAK_CHECK;    
    RESET_REPORT;
    INIT_WINDOW;
    g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); 
    INIT_D3D_SIMPLE;    
    InitVars(); 
    LoadGraphics(true);
    while(!g_exit_game) {       
        DWORD starting_point = GetTickCount();
        message_pump();
        if (g_WindowReactivate==1) { //begin
            g_WindowReactivate=0;ShowWindow(g_hwnd,SW_MAXIMIZE);UpdateWindow(g_hwnd);SetFocus(g_hwnd);continue;     
        }                                       
        if (!g_bWindowActive) continue;
        //ALL INPUT GOES HERE!!!            
        if (KEYPRESS(RIGHT)) {GameScroll(-1.0f,0.0f); }
        if (KEYPRESS(LEFT)) {GameScroll(1.0f,0.0f); }
        if (KEYPRESS(DOWN)) GameScroll(0.0f,-1.0f);
        if (KEYPRESS(UP)) GameScroll(0.0f,1.0f);
        if (KEYPRESS(A)) {ViewDesc.Position.z++; UpdateViewMatrix();}//zoom++;
        if (KEYPRESS(Z)) {ViewDesc.Position.z--; UpdateViewMatrix();}//zoom--;

^- At first, the sprites are rendered in OBJECTSPACE, so I added this (check out UpdateViewMatrix) 
to show how you can play with the camera -- this just zooms in and out - but you could also make 
the camera look in other directions or rotate - or even distort the perspective. 

Below I added the call to get_editor_input -- which only exists if this is an editor compile. 

#ifdef ALLOW_EDIT
        get_editor_input();     
#endif 
        if (PRESS_ESCAPE) PostMessage(g_hwnd, WM_DESTROY, 0, 0);                
        render();
        SAFE_DO; SAFE_WHILE((GetTickCount() - starting_point) < TIMING_DELAY); 
    }//<---main_loop---     
    CleanupGraphics();
    END_D3D;
    END_WINDOW;
    UnhookWindowsHookEx( g_hKeyboardHook );
    SHOW_PROBLEMS;
    SHOW_MEMORY_LEAK_DATA;
return 0;
}//-----------------------------end main------------------------------------------



You may notice I changed the game scroll method so that 2 images are used and swap places as
they scroll by for a repeating scrolling background.

//---------------------
// G A M E  S C R O L L
//---------------------
void GameScroll(float x, float y) {
if (((x<0)&&(tile_x<LEVEL_WIDTH))||((x>0)&&(tile_x>0))) {   
    float tempx=g_far_background.x; 
    tempx+=x; 
    if (tempx<0) tempx=g_width+tempx;
    if (tempx>=g_width) tempx=(tempx-g_width);  
    g_far_background.x=tempx;
    g_far_background.pos2.x=tempx-0.5f;
    g_far_background.pos1.x=tempx-g_far_background.texture.width;

    float mx=g_mid_background.x;
    mx=mx+x+x;  
    if (mx<0) mx=g_width+mx;
    if (mx>=g_width) mx=(mx-g_width);   
    g_mid_background.x=mx;
    g_mid_background.pos1.x=mx-1;
    g_mid_background.pos2.x=mx-g_mid_background.texture.width;
    tile_x_off+=(x*4);  
    if (tile_x_off>63) {
        tile_x_off=0+(tile_x_off-64);
        tile_x--;
    }
    if (tile_x_off<0) {
        tile_x_off=64-(0-tile_x_off);
        tile_x++;
    }
}
Above you can see that I added the code for scrolling the gamemap (tiles grid)
by adjusting the x and y offset based on speed input (multiplied by 4 because this layer 
of images move by 4 times faster than the far background because it's closer to us)
When the offset is greater than 64 I subjtract 64 to reset it and adjust the tiles we're 
looking at -- this creates the illusion that all the tiles are scrolling by smoothly.
tile_scrolling  

  if (((y<0)&&(tile_y<LEVEL_HEIGHT))||((y>0)&&(tile_y>0))) {    
    float my=g_mid_background.y;
    my+=(y/4.0f);   
    g_mid_background.y=my;
    g_mid_background.pos1.y=my; g_mid_background.pos2.y=my; 
    tile_y_off+=(y*4);
    if (tile_y_off>63) {
        tile_y_off=0+(tile_y_off-64);
        tile_y--;
    }
    if (tile_y_off<0) {
        tile_y_off=64-(0-tile_y_off);
        tile_y++;
    }
}
}//GameScroll




// I N I T  L I G H T S
void init_lights(void)
{   //LIGHT_TYPE   D3DLIGHT_SPOT D3DLIGHT_POINT //DIFFUSE_RGBA //AMBIENT_RGBA //SPECULAR_RGBA //Light Direction //Light Position //RANGE //Attenuation 1,2,3 //Falloff,Theta,Phi //light#
    CREATE_LIGHT(g_light,   D3DLIGHT_POINT,   
        1.0f,1.0f,1.0f,1.0f, 0.3f,0.3f,0.3f,0.3f,  0.6f,0.6f,0.6f,0.4f,      
        1.0f,1.0f,-1.0f, LPX,LPY,LPZ, 3000.0f,  1.0f,0.0f,0.0f, 0.12f,0.898f,1.047f,1);
    return;
}



//---------------------------------------------------------------
// U P D A T E  V I E W  M A T R I X
//---------------------------------------------------------------
void UpdateViewMatrix() {       
    D3DXMatrixLookAtLH(&g_viewing_matrix,&ViewDesc.Position,
                       &D3DXVECTOR3(0.0f,0.0f,0.0f),&D3DXVECTOR3(0.0f,-1.0f,0.0f));                          
    g_gpu->SetTransform(D3DTS_VIEW,&g_viewing_matrix);
}//END UpdateViewMatrix



UpdateViewMatrix is used for updating the camera position, orientation/angle by building a matrix
for view transformation based on what we want the camera to look at. We might use this in object 
space -- for 2D games, we'd probably only zoom in or make an earth quake effect or something like that.


#define SET_D3D(p1,p2) g_gpu->SetRenderState(p1, p2);
//------------------------------
// I N I T  R E N D E R  V A R S
//------------------------------
void InitRenderVars() { 
    SET_D3D(D3DRS_LIGHTING, TRUE); init_lights(); 
    SET_D3D(D3DRS_ZENABLE,  TRUE);  
    SET_D3D(D3DRS_AMBIENT, D3DCOLOR_XRGB(100,100,100));
    g_gpu->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_MODULATE);
    g_gpu->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
    g_gpu->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_CURRENT);

Normally we wouldn't use any lighting for 2D games - but since I want to play with
lights a bit with some 3D elements I'd like to add - I enable lighting. You will find
that setting lighting to false will enhance graphics performance a bit if your only 
rendering 2D and have tons and tons of sprites. 
Also normally you would want to set ZENABLE to false -- but for some 3D stuff I set
it to true -- performance is actually better with it as false and again - it's not 
needed for 2D -- just render in order of depth.

    
    //setup camera: 
    ViewDesc.Position.x=0.0f;ViewDesc.Position.y=0.0f;ViewDesc.Position.z=600;
    ViewDesc.Target.x=ViewDesc.Target.y=ViewDesc.Target.z=0.0f;
    ViewDesc.xa=ViewDesc.ya=0.0f;
    ViewDesc.uni_scale=1.0f;
    D3DXMatrixIdentity(&ViewDesc.RotationMatrix);
    D3DXMatrixIdentity(&ViewDesc.TranslationMatrix);
    D3DXMatrixIdentity(&ViewDesc.ScaleMatrix);
    D3DVIEWPORT9 vp; vp.X = 0; vp.Y = 0; vp.Width = g_width; vp.Height = g_height; vp.MinZ = 0.0f; vp.MaxZ = 100.0f; g_gpu->SetViewport(&vp);   
    D3DXMatrixIdentity(&g_world_matrix);
    D3DXMatrixIdentity(&g_viewing_matrix);
    D3DXMatrixIdentity(&g_projection_matrix);
    D3DXMatrixLookAtLH(&g_viewing_matrix,&ViewDesc.Position, &ViewDesc.Target, &D3DXVECTOR3 (0.0f, -1.0f, 0.0f));

    D3DXMatrixPerspectiveFovLH(&g_projection_matrix,D3DXToRadian(45),(float)g_width/(float)g_height,NEAR_Z,FAR_Z);  

    g_gpu->SetTransform(D3DTS_VIEW,&g_viewing_matrix);
    g_gpu->SetTransform(D3DTS_PROJECTION,&g_projection_matrix); 
    //(could set world matrix here too)
}//InitRenderVars



//---------------------------
// L O A D  G R A P H I C S 
//---------------------------
void LoadGraphics(bool restore_all) {   
if (restore_all) {
    //create 2D graphics processing objects: 
    InitRenderVars();
    if (!g_sprite) {
        if (FAILED(D3DXCreateSprite(g_gpu, &g_sprite))) FATAL("D3DXCreateSprite FAILED",Problem);
        D3DXFONT_DESC FontDesc = {14,0,800,0,false,DEFAULT_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_PITCH,"Arial"}; 
        D3DXFONT_DESC FontDesc2 = {16,0,800,0,false,DEFAULT_CHARSET,OUT_TT_PRECIS,CLIP_DEFAULT_PRECIS,DEFAULT_PITCH,"Arial"};
        D3DXCreateFontIndirect(g_gpu,&FontDesc,&g_font);
        D3DXCreateFontIndirect(g_gpu,&FontDesc2,&g_font2);  
    }   
    helpers.LoadTexture("Graphics/helpers.dds");  
  
Restore all is true only when we've lost the 3D device and we needed to reset all resources..
Here we created the sprite object and a couple different fonts to use in directX. The helpers.dds
is a texture map which I'll use for stuff like shadows or semi-transparent text boxes or whatever. 

    //set up 3D cube for box tiles: -------------------------
    int i=0,scal1=64; //(-1 to +1 gives us -64 to +64 which is 128x128 in size)
    do {
        g_box_verts[i].x=g_box_verts[i].x*scal1;
        g_box_verts[i].y=g_box_verts[i].y*scal1;
        g_box_verts[i].z=g_box_verts[i].z*scal1;        
        i++;
    }while(i<NUM_BOX_VERTICES);

Set scale to 64 and scaled all the vertices in the vertex list for the 3D cube object...
this way cube tiles will be the same size as a flat tile(if at correct distance)

    //I made it as write only - but we might want to use dynamic instead:
    ghr=g_gpu->CreateVertexBuffer(NUM_BOX_VERTICES*sizeof(stMyVertex), D3DUSAGE_WRITEONLY, 
                                  MY_CUSTOM_VERTEX, D3DPOOL_MANAGED, &g_vb,NULL);                                                               
    if(FAILED(ghr)) FatalError("Error creating vertex buffer");   
    unsigned char *vb_vertices;
    ghr=g_vb->Lock(0,0,(void **)&vb_vertices,0);
    if(FAILED(ghr)) FatalError("Error Locking triangle buffer");   
    memcpy(vb_vertices, g_box_verts, sizeof(g_box_verts) );
    g_vb->Unlock();
    //------------------------------------------------------

Here I created a vertex buffer on the graphics card and locked it so we can transfer the vertex
list into the buffer (using the vertex format we specified) -- and unlock it again so the
card is free to use it without interruption -- and thus we now have a 3D cube object we can
render set up in graphics card memory. 
.. 
Load textures for editor:
#ifdef ALLOW_EDIT   
    EDITOR_TEXTURE_LOADING; 
#endif                      
}   
switch(g_level) {
        case 1: 
            g_far_background.texture.LoadTexture("Graphics/far_background1.dds");
            g_mid_background.texture.LoadTexture("Graphics/mid_background1.dds");
            tile_texture.LoadTexture("Graphics/tiles1.dds");            
        break;      
}
load_tilemap(); //automatically loads tilemap based on level

We load the backgrounds and tile graphics -- load_tilemap however actually loads a map
of how tiles in the tile graphics map are to be understood. In the tile-map editor - we 
can customize tile properties and regions on a graphics image of the tiles -- this is 
saved as a so called tilemap file. This is different from the gamemap editor which will
allow us to build, load and save a game map (a grid of tile entries for an world / level). 

g_far_background.x=g_far_background.y=g_mid_background.x=g_mid_background.y=0;      
g_far_background.width=g_far_background.texture.width;   //set to same as image by default
g_far_background.height=g_far_background.texture.height; 
g_far_background.r1.left=1;
g_far_background.r1.top=0; g_far_background.r1.bottom=g_far_background.height;
g_far_background.r2.top=0; g_far_background.r2.bottom=g_far_background.height;
g_far_background.r2.right=g_width-1;
VEC_ZERO(g_far_background.pos1); VEC_ZERO(g_far_background.pos2);
g_mid_background.width=g_mid_background.texture.width;
g_mid_background.height=g_mid_background.texture.height;
g_mid_background.r1.left=1;
g_mid_background.r1.top=0; g_mid_background.r1.bottom=g_mid_background.height;
g_mid_background.r2.top=0; g_mid_background.r2.bottom=g_mid_background.height;
g_mid_background.r2.left=0; //added
g_mid_background.r2.right=g_mid_background.texture.width-1;
VEC_ZERO(g_mid_background.pos1); VEC_ZERO(g_mid_background.pos2);
g_mid_background.y=0.0f;
//Above we just initialize the background scroller vars
}//LoadGraphics



//----------------------------
// I N I T  V A R S 
//----------------------------
void InitVars() {   
#ifndef ALLOW_EDIT
    ShowCursor(FALSE);  
#else   
    INIT_EDITOR_VARS;
#endif
    mouse_pos.x=20.0f; mouse_pos.y=20.0f; mouse_pos.z=0.0f; 
    g_current_directory = new TCHAR[g_size_of_directory]; //DIRECTORY
    g_directory_buf_size = GetCurrentDirectory(g_size_of_directory, g_current_directory);   
}//InitVars


//-------------------------------
// C L E A N U P  G R A P H I C S
//-------------------------------
void CleanupGraphics() {
    //release graphics:
    SAFE_RELEASE_FINAL(g_far_background.texture.tx);
    SAFE_RELEASE_FINAL(g_mid_background.texture.tx); 
    SAFE_RELEASE_FINAL(tile_texture.tx); 
    SAFE_RELEASE_FINAL(helpers.tx); 
#ifdef ALLOW_EDIT   
    RELEASE_EDITOR_STUFF;
#endif
    //release objects:      
    SAFE_RELEASE_FINAL(g_vb);   
    SAFE_RELEASE_FINAL(g_font);
    SAFE_RELEASE_FINAL(g_font2);
    SAFE_RELEASE_FINAL(g_sprite);
    SAFE_RELEASE_FINAL(g_zbuffer);

    if (DeviceLost) return; //don't release stuff after this until game exits...        
    SAFE_DELETE(g_current_directory);
    ShowCursor(TRUE);
}//CleanupGraphics




//----------------
// R E N D E R 
//----------------
void render() {     
    HANDLE_LOST_DEVICES( , );   
    BEGIN_SCENE("render");

    D3DXMatrixIdentity(&g_world_matrix);
    D3DXMatrixTranslation(&g_world_matrix, (FLOAT)-g_half_width, (FLOAT)-g_half_height, 0);
    g_gpu->SetTransform(D3DTS_WORLD, &g_world_matrix); 
 
Actually we don't need to do this here - since the world matrix is not changing 
this should be moved into init_render_vars and only done once. If we were actually
changing the world matrix (like in a 3D game) then we'd do this - but not in the
render loop - but probably only when the world matrix actually changes - after the
player has actually changed position.

    g_sprite->Begin(D3DXSPRITE_ALPHABLEND | D3DXSPRITE_OBJECTSPACE); // | D3DXSPRITE_SORT_DEPTH_BACKTOFRONT
        
    DRAW(g_far_background.texture,NULL,&g_far_background.pos1); 
    DRAW(g_far_background.texture,NULL,&g_far_background.pos2); 
    DRAW(g_mid_background.texture,&g_mid_background.r2,&g_mid_background.pos1); 
    DRAW(g_mid_background.texture,&g_mid_background.r2,&g_mid_background.pos2);
    
    g_sprite->End();    

    #ifdef ALLOW_EDIT //show editor menu
        g_sprite->Begin(D3DXSPRITE_ALPHABLEND); DRAW(edit_menu_texture,NULL,NULL); g_sprite->End(); 
    #endif 

    END_SCENE("render");
}//render


So far, more as an example - initially I have it rendering 2D in objectspace when the program 
first starts -- and you can play with zooming in and out by pressing A or Z (which works 
by changing the viewing matrix)




// R E S E T  U S E R  I N P U T  M E M O R Y
// resets all input characters and all keys to pressed (so key must be unpressed before it can record the key as input)
void reset_user_input_memory() {
int a=0; SAFE_DO; g_text[a]='\0';a++;}while(a<200);
g_text_index=0;
a=0; TEST_ARRAY(last_key_state,255);
SAFE_DO;
    last_key_state[a]=1; //set as pressed so it must wait for an unpress before registering the key as useable
    a++;
}while(a<255); 
}//reset_user_input_memory

//----------------------------
// P O L L  U S E R  I N P U T
//----------------------------
//function that is used to update a text entry that a user is working on while other directX
//stuff is running in the background (without interuption) [returns true if enter is pressed]
bool PollUserInput(const char * dialog, LONG x, LONG y, int max_input_size) {
    //show message:
    int length=strlen(dialog)*4+3;
    RECT r; r.left=(LONG)(g_half_width-length);r.right=(LONG)(g_half_width+length);r.top=(LONG)(y-9);r.bottom=(LONG)(y+9);  
    D3DXMatrixIdentity(&g_matrix_2d); g_sprite->SetTransform(&g_matrix_2d); 
    TEXT_POSITION(x,y-24);
    PRINT_TEXT(dialog);
    TEXT_POSITION(x,(y-9)); 
    //show current input text after message
    TEST_ARRAY(g_text,g_text_index);
    flasher++;
    if (flasher<5) {
        g_text[g_text_index]=0x7C; g_text_index++;
    }
    g_font->DrawText(g_sprite, g_text,-1,&text_position,DT_LEFT | DT_TOP ,0xFFFF00AA);
    if (flasher<5) {
        g_text[g_text_index]='\0'; g_text_index--; g_text[g_text_index]='\0';
    }
    if (flasher>10) flasher=0;
    //update text based on input (using timing and handling key holding) 
    //return false but return true if last input was enter and size>0 
    int key=9999, add_key=9999; //add_key is any key that is added to actual text
    int a=0; bool shift=false;
    if (KEY_IS_DOWN(KEY_SHIFT)) shift=true;
    TEST_ARRAY(last_key_state,255);
    SAFE_DO;        
        if (KEY_IS_DOWN(a)) {
            if (last_key_state[a]==0) { //new keypress - capture it:
                key=a;
                if (key==0x20) add_key=key; //SPACE             
                if (key==KEY_MINUS) {add_key=0x2D; if (shift) add_key=0x5F;}//DASH
                if (key==KEY_QUOTE) {add_key=0x27;}             
                if ((key>=0x30)&&(key<=0x39)) add_key=key; //NUMBERS
                if ((key>=0x41)&&(key<=0x5A)) add_key=key; //LETTERS
                if (last_char!=key) key_waiting=20;
                character_repeat_time=0; last_char=key;
            } else {
                character_repeat_time++;
            }
            last_key_state[a]=1;
            if (character_repeat_time>key_waiting) {last_key_state[a]=0; character_repeat_time=0; key_waiting=10;}
        }
        if (KEY_IS_UP(a)) {
            last_key_state[a]=0;
        }
        a++;
    }while(a<255);
    if (key<9999) {
        if (add_key<9999) { //alphabetic key to add
            if (g_text_index<max_input_size) { //MAX_INPUT_SIZE
                g_text[g_text_index]=add_key; 
                g_text_index++; TEST_ARRAY(g_text,g_text_index);
            }           
        } else {
            //test for backspace or enter
            if (key==KEY_BACKSPACE) {
                if (g_text_index>0) {                   
                    g_text[g_text_index]='\0'; g_text_index--; g_text[g_text_index]='\0'; key_waiting=8;
                } else {g_text[0]='\0'; g_text[1]='\0';}
            }
            if (key==KEY_DELETE) {a=0; SAFE_DO; g_text[a]='\0';a++;}while(a<=g_text_index); g_text_index=0;}
            if (key==KEY_ENTER) {
                if (g_text_index>0) return true; //done
            }           
        }
    }   
    return false; //not done
}//PollUserInput


The above 2 functions are used when you need to get input from a user which needs to 
be updated in a rendering loop in DirectX. We could probably move these functions
into GameInput module.. maybe I'll do that later.. 


//--------------------
//P R O M P T  U S E R (Y/N)
//--------------------
bool PromptUser(const char* str) {
    bool ret_val=false;
    text_color=0xFFFF4400; TEXT_POSITION(5,g_height-80); 
    PRINT_TEXT(str); g_sprite->End(); END_SCENE("Prompt User");
    do { message_pump();
        if (KEYPRESS(N)) {ret_val=false; break;}
        if (KEYPRESS(Y)) {ret_val=true; break;}
    }while(!KEYPRESS(ESCAPE)); BEGIN_SCENE("Prompt User"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND); 
    return ret_val;
}//PromptUser


Just for doing a quick user prompt -- like "Are you sure you want to quit? (Y/N)
This one assumes a sprite scene was being rendered and thus quits and resumes 
the process appropriately.

// D R A W  B L A C K  R E C T
//useful for drawing a semi-transparent black square to draw text on over a background
void draw_black_rect(int x1, int y1, int x2, int y2) {
RECT r;
int b=y1, xr=x2;
SAFE_DO;
    int a=x1;
    SAFE_DO;
        xr=(x2-a);
        if (xr>255) xr=255;
        SET_RECT(r,0,0,xr,20);
        DRAW(helpers,&r,&VEC((FLOAT)a,(FLOAT)b,0));
        a+=255;
    }while(a<x2);
    b+=20;
}while(b<y2);
}

The helpers.dss image contains a semi-transparent black block which 
can be used for making a partly see-thru rectangle for writing text 
over (which makes the text easier to read). 



// C O P Y  T E X T U R E  D A T A
// I copy the piece of texture from the tile map into its own texture so I don't need to change
// the texture coordinates for cube tiles. 
void CopyTextureData(int a, RECT r) {
    RECT dr;
    dr.left=dr.top=0; dr.right=dr.bottom=128; // for now I'm just assuming 3D tiles are always 128x128 cubes
    D3DXCreateTexture(g_gpu,128,128,1,0,D3DFMT_A8R8G8B8,D3DPOOL_MANAGED, &g_texture[a].tx); 
    LPDIRECT3DSURFACE9 source = NULL;
    LPDIRECT3DSURFACE9 dest   = NULL;
    tile_texture.tx->GetSurfaceLevel(0, &source);
    g_texture[a].tx->GetSurfaceLevel(0, &dest); 
    D3DXLoadSurfaceFromSurface(dest, NULL, NULL, source, NULL, &r, D3DX_FILTER_NONE, 0);
    source->Release();
    dest->Release();
}

The comment basically explains what this does. 
1) create empty texture
2) get source surface
3) get destination surface
4) copy based on r (source region/rect to copy from)
Now when drawing a 3D cube -- all we need to do is change textures 
when drawing and can just use the same cube object (don't need unique texture coordinates)



//-------------------------------------
// L O A D  T I L E M A P (auto-loader)
//-------------------------------------
//loads data that determines how to interpret the tilemap(tiles bitmap) 
void load_tilemap() {
    FILE *f=NULL;
    char filename[100];
    sprintf_s(filename, _T("Tilemaps/tilemap%d.tma"), g_level);
    if (!FileExists(filename)) {REPORT_D("tilemap not yet created for this level",g_level); return;}
    fopen_s(&f, filename, "rb");
    if (!f) { REPORT("file read error"); } else {       
        fread(&g_num_tile_types, sizeof(g_num_tile_types), 1, f);                             
        fread(&tile, sizeof(tile[0]), g_num_tile_types, f);                                     
        fclose(f);          
    }
    //set up needed textures for cubes
    int a=0;
    SAFE_DO {
        if (tile[a].layer_type==LAYER_BOX) {            
            CopyTextureData(a,tile[a].r);
        }
        a++;
    }SAFE_WHILE(a<g_num_tile_types);
}

Go to the folder containing your source files and Graphics directory
and add another 2 directories/folders - the first one is Tilemaps 
and the second one you should add is Gamemaps. 
If the requested tilemap exists, we first find out how many tile
types are to be stored and then read that quantity into the
array of tile structs.




//-------------------
// R E S E T  T I L E 
//-------------------
void reset_tile(int a,int b) {
    ZeroMemory(&g_tiles[a][b], sizeof g_tiles[a][b]);
    g_tiles[a][b].color=0xFFFFFFFF;
    g_tiles[a][b].type=-1;
    g_tiles[a][b].depth=INFRONT;
    g_tiles[a][b].i2=-1;
    g_tiles[a][b].scale=g_tiles[a][b].scale2=1.0f;
}//reset_tile


Reset's a grid/gamemap tile to it's initial empty state. 


The following function takes the a and b coordinate of a tile 
on the gamemap and adds a tile of type tn (index into tile array 
which holds info about what the tile is)
so g_tiles[a][b].i = tn which looks up tile[tn]
If remove occupied is true - it replaces a tile whether occupied
or not.. 
All gamemap tiles that the tile[tn] would cover will be set to 
point to the master tile(top-left corner tile) which points to
which holds the index and other info about this tile... 
I use a master tile because it speeds up the loop and prevents
redundant rendering.
//---------------
// A D D  T I L E  
//---------------
void add_tile(int a,int b,int tn,bool allow_occupied,bool remove_occupied) {
    int ma,mb,mi;
    int b1=0,b2=b;
    bool occupied=false;
    if (remove_occupied) allow_occupied=false;
    if (!allow_occupied) {
        //check if already occupied:                
        SAFE_DO;
            int a1=0,a2=a;
            SAFE_DO;                
                if (g_tiles[a2][b2].type>-1) {
                    occupied=true;                                                      
                    if (remove_occupied) {
                      ma=g_tiles[a2][b2].m_a;
                      mb=g_tiles[a2][b2].m_b;
                      mi=g_tiles[ma][mb].i;
                      int b3=mb, b4=0;
                      SAFE_DO {
                        int a3=ma, a4=0;
                        SAFE_DO {
                            reset_tile(a3,b3);
                            a3++; a4++;
                        }SAFE_WHILE(a4<tile[mi].tiles_wide);
                        b3++; b4++;
                      }SAFE_WHILE(b4<tile[mi].tiles_high);
                   }
                }
                a1++;a2++;          
            }while(a1<tile[tn].tiles_wide);
            b1++;b2++;
        }while(b1<tile[tn].tiles_high); 
    }   

Here we loop thru tiles on the gamemap based on width and height in
tiles and if they are occupied (g_tiles[a2][b2].type>-1) then we set
it as occupied -- in which case if they are to be removed - we reset them..

    if (remove_occupied) occupied=false;
    if (!occupied) {
        b1=0;b2=b;
        SAFE_DO;
            int a1=0,a2=a;
            SAFE_DO;    
                g_tiles[a2][b2].type=tile[tn].type;
                g_tiles[a2][b2].m_a=a; g_tiles[a2][b2].m_b=b;
                g_tiles[a2][b2].depth=tile[tn].depth;
                a1++;a2++;
            }while(a1<tile[tn].tiles_wide);
            b1++;b2++;
        }while(b1<tile[tn].tiles_high);                         
        g_tiles[a][b].type=tile[tn].type;
        g_tiles[a][b].i=tn;
        g_tiles[a][b].i2=-1; g_tiles[a][b].scale=g_tiles[a][b].scale2=1.0f;
        COPY_VECTOR(g_tiles[a][b].offset,tile[tn].offset);
        g_gamemap_changed=true;

So if we're allowed to fill in these tiles - we go ahead
and fill in the gamemap tile's details - such as which tile 
type it is (SOLID, PASS, LIQUID, etc..), which tile is the 
master tile, what the tile's depth, the index to the tilemap info
(tn), and copy the initial offset preset in the tile info 
(we set gamemap changed to true so it will prompt for save if exiting)

If the ALT key was pressed - it allows insertion of a second tile in 
this place:
    } else if (KEYPRESS(ALT)) {
        ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b;
        g_tiles[ma][mb].i2=tn;
        COPY_VECTOR(g_tiles[ma][mb].offset2,tile[tn].offset);
        float adif=(a-ma)*64.0f;
        float bdif=(b-mb)*64.0f;                
        g_tiles[ma][mb].offset2.x+=adif;
        g_tiles[ma][mb].offset2.y+=bdif;        
    }
}//add_tile



//---------------------
// R E M O V E  T I L E
//---------------------
void remove_tile(int a, int b) {
    int ma=g_tiles[a][b].m_a, mb=g_tiles[a][b].m_b;
    int mi=g_tiles[ma][mb].i; 
    int wide=tile[mi].tiles_wide, high=tile[mi].tiles_high;
    int b1=0,b2=mb;
    SAFE_DO;
        int a1=0,a2=ma;
        SAFE_DO;
            reset_tile(a2,b2);
            a1++; a2++;
        }while(a1<wide);
        b1++; b2++;
    }while(b1<high);    
    reset_tile(a,b);
    g_gamemap_changed=true;
}//remove tile

The above just resets an occupied tile region on the gamemap
to what it was before any tiles were layed there. 



// C H A N G E  T I L E  T Y P E
// useful if a wall or something becomes passable (also used in editor to make tiles act unusually)
void change_tile_type(int a, int b, int d, bool all) {
    int ma, mb, mi;
    int width, height;
    Sleep(160);
    if (!all) {
        g_tiles[a][b].type+=d;
        if (g_tiles[a][b].type>=NUM_TYPES) g_tiles[a][b].type=0;
        if (g_tiles[a][b].type<0) g_tiles[a][b].type=NUM_TYPES-1;
        return;
    }
If we only want to change one gamemap tile's type - we
return right away after changing the 1 at (a,b)

Otherwise as below - we change all the tiles occupied by this game-tile 
to the new type (SOLID, LIQUID, PASS, etc...):
    ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b;
    mi=g_tiles[ma][mb].i;
    width=tile[mi].tiles_wide; height=tile[mi].tiles_high;
    int new_type=g_tiles[ma][mb].type+d;
    if (new_type>=NUM_TYPES) new_type=0;
    if (new_type<0) new_type=NUM_TYPES-1;
    int y=0, bb=mb; 
    SAFE_DO {
        int x=0, aa=ma;
        SAFE_DO {
            g_tiles[aa][bb].type=new_type;          
            x++; aa++;
        }SAFE_WHILE(x<width);
        y++; bb++;
    }SAFE_WHILE(y<height);
}//change tile type



//-----------------
// D R A W  C U B E
//-----------------
// draws a 3D tile
void draw_cube(int a, int b, VEC pz, D3DCOLOR col) {
    int i=g_tiles[a][b].i;
    D3DXMATRIX matWorld, matRotate, matScale, matTrans;
    D3DXMatrixIdentity(&matWorld); g_sprite->SetTransform(&g_matrix_2d);    
    //rotate locally first: 
    D3DXMatrixRotationZ(&matRotate, g_tiles[a][b].rotation);
    //scale locally first:
    D3DXMatrixScaling(&matScale, g_tiles[a][b].scale, g_tiles[a][b].scale, g_tiles[a][b].scale);
    //translation to new coordinates:
    D3DXMatrixTranslation(&matTrans,pz.x-332,pz.y-236,-200.0f);
    //*Make sure you multiply by the translation last!
    matWorld = matRotate *matScale * matTrans;
    g_gpu->SetTransform(D3DTS_WORLD,&matWorld); 
Basically what we just did was rotate the object,
scale it, then translate it(move it) and set a matrix to represent that
desired set of actions - and tell the graphics processor to set that
as the current world transformation matrix (in other words do the
desired stuff to whatever object(s) we're about to render).. 

As below - we set the texture for this 3D cube and since there
are 3 vertices per face - the number of faces to draw is the
number of existing vertices in the vertex-list divided by 3
    g_gpu->SetTexture(0,g_texture[i].tx);           
    g_gpu->DrawPrimitive(D3DPT_TRIANGLELIST,0,NUM_BOX_VERTICES/3);
    g_gpu->SetTexture(0,NULL);
    D3DXMatrixIdentity(&g_matrix_2d); g_sprite->SetTransform(&g_matrix_2d); 
}//draw_cube




//-----------------
// D R A W  T I L E
//-----------------
Draws a tile for gamemap g_tiles[a][b] at screen position pz with tint col
void draw_tile(int a, int b, VEC pz, bool overlapper, D3DCOLOR col) {
    int i;  
    float scale,rot;
(If a secondary tile, use index2, scale2, rotation2...) :
    if (overlapper) {
        i=g_tiles[a][b].i2;
        scale=g_tiles[a][b].scale2;
        rot=g_tiles[a][b].rotation2;        
    } else {
        i=g_tiles[a][b].i;
        scale=g_tiles[a][b].scale;
        rot=g_tiles[a][b].rotation;     
    }   
    //Rotate and scale around the center of the tile cluster:
    g_sprite_rot_center=D3DXVECTOR2((float)tile[i].tiles_wide*32.0f,(float)tile[i].tiles_high*32.0f);
    g_sprite_center=g_sprite_rot_center;
    g_sprite_scale=D3DXVECTOR2(scale,scale);
//set tile position in screen coordinates:
    g_sprite_trans=D3DXVECTOR2(pz.x,pz.y);  
if non-animated - just set the transformation matrix and draw the tile...
source coordinates are kept in tile[i].r and it is tinted by col
    if (tile[i].animation_type==NO_ANIM) {
        D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,rot,&g_sprite_trans); g_sprite->SetTransform(&g_matrix_2d);
        g_sprite->Draw(tile_texture.tx,&tile[i].r,NULL,NULL,col);       
    }   
    else if (tile[i].num_frames>=2) {     
for animated ones, source coordinates are in tile[i].animation[]
and frame is selected by tile[i].frame  
        D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,rot,&g_sprite_trans); g_sprite->SetTransform(&g_matrix_2d);
        g_sprite->Draw(tile_texture.tx,&tile[i].animation[tile[i].frame],NULL,NULL,col);
    }
    if ((tile[i].animation_type==SCROLL_X)||(tile[i].animation_type==SCROLL_Y)) {
//this works like background scroller did originally, but we're 
scrolling a tile in place instead - like for waterfalls or conveyor belts
        RECT dum_r;         
        LONG overflow=0;
        VEC p; p.z=0; p.x=pz.x; p.y=pz.y;
        if (tile[i].animation_type==SCROLL_X) {
            overflow=(LONG)tile[i].scroll_pos.x-tile[i].r.left;
            CopyRect(&dum_r,&tile[i].r);
            dum_r.left=tile[i].r.left;
            dum_r.right=tile[i].r.right-overflow;
            p.x=tile[i].scroll_pos.x-tile[i].r.left+pz.x;           
            g_sprite_trans=D3DXVECTOR2(p.x,p.y);
            D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,rot,&g_sprite_trans); g_sprite->SetTransform(&g_matrix_2d);
            g_sprite->Draw(tile_texture.tx,&dum_r,NULL,NULL,col);           
            dum_r.left=dum_r.right;
            dum_r.right=tile[i].r.right;
            p.x=pz.x;           
            g_sprite_trans=D3DXVECTOR2(p.x,p.y);
            D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,rot,&g_sprite_trans); g_sprite->SetTransform(&g_matrix_2d);
            g_sprite->Draw(tile_texture.tx,&dum_r,NULL,NULL,col);           
        }
        if (tile[i].animation_type==SCROLL_Y) {
            overflow=(LONG)tile[i].scroll_pos.y-tile[i].r.top;
            CopyRect(&dum_r,&tile[i].r);
            dum_r.top=tile[i].r.top;
            dum_r.bottom=tile[i].r.bottom-overflow;
            p.y=tile[i].scroll_pos.y-tile[i].r.top+pz.y;            
            g_sprite_trans=D3DXVECTOR2(p.x,p.y);
            D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,rot,&g_sprite_trans); g_sprite->SetTransform(&g_matrix_2d);
            g_sprite->Draw(tile_texture.tx,&dum_r,NULL,NULL,col);
            dum_r.top=dum_r.bottom;
            dum_r.bottom=tile[i].r.bottom;
            p.y=pz.y;           
            g_sprite_trans=D3DXVECTOR2(p.x,p.y);
            D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,rot,&g_sprite_trans); g_sprite->SetTransform(&g_matrix_2d);
            g_sprite->Draw(tile_texture.tx,&dum_r,NULL,NULL,col);
        }
    }
Always a good idea to reset the 2D transform matrix so we don't 
get any weird unwanted off-screen drawing problems somewhere else   
    D3DXMatrixIdentity(&g_matrix_2d); g_sprite->SetTransform(&g_matrix_2d);
}//draw_tile




//-------------------------
// A N I M A T E  T I L E S
//-------------------------
void animate_tiles() {
    int i=0;
    SAFE_DO {
      if (tile[i].num_frames>0) {      
        if (tile[i].num_frames>=2) {            
            tile[i].timer++;                        
            if (tile[i].animation_type==PING_PONG) {
                if (tile[i].timer>tile[i].wait_time) {tile[i].frame+=tile[i].frame_direction; tile[i].timer=0;}
                if (tile[i].frame>=tile[i].num_frames) {tile[i].frame_direction=-1; tile[i].frame--;}
                if (tile[i].frame<=0) tile[i].frame_direction=1;
            } else {
                if (tile[i].timer>tile[i].wait_time) {tile[i].frame++; tile[i].timer=0;}
                if (tile[i].frame>=tile[i].num_frames) tile[i].frame=0;
            }                       
        }
      }

The animation frame updates every time the tile[i].timer var exceeds 
the wait_time and resets timer to 0. If it's a ping pong type then the frame
goes 0 to numframes and then numframes to 0 and so on.. (thus frame_direction)

If it is a type SCROLL_X or SCROLL_Y, then we update the scroll position
at the speed which is stored in wait_time(for this): 
      if (tile[i].animation_type==SCROLL_Y) {
        tile[i].scroll_pos.y+=tile[i].wait_time;
        if (tile[i].scroll_pos.y<=tile[i].r.top) tile[i].scroll_pos.y=tile[i].r.bottom-(tile[i].r.top-tile[i].scroll_pos.y);
        if (tile[i].scroll_pos.y>=tile[i].r.bottom) tile[i].scroll_pos.y=tile[i].r.top+(tile[i].scroll_pos.y-tile[i].r.bottom);
      }
      if (tile[i].animation_type==SCROLL_X) {
        tile[i].scroll_pos.x+=tile[i].wait_time;
        if (tile[i].scroll_pos.x<=tile[i].r.left) tile[i].scroll_pos.x=tile[i].r.right-(tile[i].r.left-tile[i].scroll_pos.x);
        if (tile[i].scroll_pos.x>=tile[i].r.right) tile[i].scroll_pos.x=tile[i].r.left+(tile[i].scroll_pos.x-tile[i].r.right);
      }     
      i++;
    }SAFE_WHILE(i<g_num_tile_types);
}//draw_tile




//---------------------------------------
// D R A W  B A C K  T I L E  L A Y E R S
//---------------------------------------
If there are layers further back - this will draw them scaled 
and scrolled at a scaled offset so that they appear to move
by further away from the camera. This can be done in an 
entirely 2D mode (not OBJECTSPACE) and still look 3D. 
Actually - the entire editor's 2D rendering is done in 
2D mode - with 3D stuff rendered behind everything first. 
void draw_back_tile_layers(float scale) {
    VEC pos; pos.z=0;
    int a,b,ar; 
    g_sprite_scale=D3DXVECTOR2(scale,scale);
    g_sprite_center=D3DXVECTOR2(0,0);
    g_sprite_rot_center=D3DXVECTOR2(0,0);
    b=tile_y-10;        
    float aam=64*scale;
    float xam=g_half_width-aam*13+(int)tile_x_off*scale;
    float yy=g_half_height-aam*10+(int)tile_y_off*scale;    
aam -- the normal tile size scaled - memorized into aam 
to reduce redundant calculation and make code look less chaotic
xam -- the starting left position of the tiles for the tile rendering loop
(with the offset and tile size scaled for a depth distance effect/look)
[note - after each loop thru y position - xx will be set to this
precalculated value]
yy -- starting y or top position of tiles for tile rendering loop
(same idea as with xam)
[note - this is not same as with xx cuz yy is the outside loop]
        
    int ma,mb;
    VEC pz; pz.z=0.0f;
    ar=tile_x-13;
    SAFE_DO { 
        if ((b<0)||(b>LEVEL_HEIGHT)) {b++; yy+=aam; continue;} //skip if out of bounds          
        if (yy>(g_height+100)) break;
        a=ar;
        if (a>=0) if ((tile[g_tiles[a][b].i].r.bottom+yy)<-100) {b++; yy+=aam; continue;}
    
Note - we must make sure a and b are valid array indices before attempting 
a memory access with them - which is why we have if a>=0 for example.. 
We loop through the tiles vertically (based on scaled amount 64*scale - aam) and 
skip ahead through the loop if out of bounds - or stop if below the bottom row 
(b>y>g_height+100) [+100 as padding in case of large negative offset of tile - might need more]

        float xx=xam;  
After each yy loop we reset xx to xam for the horizontal tile loop
        SAFE_DO {
            if ((a<0)||(a>LEVEL_WIDTH)) {a++; xx+=aam; continue;} //skip it if there's nothing there                            
            ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b;
            if ((ma!=a)||(mb!=b)) {a++; xx+=aam; continue;} //only draw the master tile!                
//Only draw if it's actually occupied by a valid tile type:
            if (g_tiles[a][b].type>-1) {
                int ind=g_tiles[a][b].i;                
                if ((tile[ind].layer_type==LAYER_FLAT)||(tile[ind].layer_type==LAYER_BOX)) {a++; xx+=aam; continue;}
                if (xx>(g_width+100)) break;
                if ((tile[ind].r.right+xx)<-100) {a++; xx+=aam; continue;}              
Get the tile index for checking tile info - skip
ahead if the tile doesn't have back layers or is out of bounds...

Next - get the actual rendering position by adding the scaled offset of
the tile to its position - and set the transformation and draw it...
                pz.x=xx+g_tiles[ma][mb].offset.x*scale; 
                pz.y=yy+g_tiles[ma][mb].offset.y*scale;
                g_sprite_trans=D3DXVECTOR2(pz.x,pz.y);
                D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,0,&g_sprite_trans);
                g_sprite->SetTransform(&g_matrix_2d);                   
                g_sprite->Draw(tile_texture.tx,&tile[ind].r,NULL,NULL,0xBBAAAAAA);
            }
            xx+=aam; a++;
        }SAFE_WHILE(a<tile_x+13);
        yy+=aam; b++;
    }SAFE_WHILE(b<tile_y+10);
    D3DXMatrixIdentity(&g_matrix_2d); g_sprite->SetTransform(&g_matrix_2d);
}//draw_back_tile_layers
 


// D R A W  F R O N T  T I L E  L A Y E R S 
This works basically the same way as the last one
except that we are drawing in front of the tiles (layer2 and layer3)
With front layers - if done well - we can make a fur like effect
adding more depth to fiberous materials like grass, fuzz, feather, hay, etc...
I found 2 extra layers works fine - in fact you could use multiple scales
applied to only one extra layer image to do the same trick.
void draw_front_tile_layers(float scale, int layer) {
    float aam=64*scale;
    g_sprite_scale=D3DXVECTOR2(scale,scale);
    int a,ar,ma,mb,b=tile_y-10;     
    float yy=g_half_height-64*scale*10+(int)tile_y_off*scale; 
    float xam=g_half_width-aam*13+(int)tile_x_off*scale;        
    ar=tile_x-13;
    VEC pz; pz.z=0.0f;
    SAFE_DO;
        if ((b<0)||(b>LEVEL_HEIGHT)) {b++; yy+=aam; continue;} //skip if out of bounds
        if (yy>(g_height+100)) break;
        a=ar;           
        if (a>=0) if ((tile[g_tiles[a][b].i].r.bottom+yy)<-100) {b++; yy+=aam; continue;}           
        float xx=xam;
        SAFE_DO;
            if ((a<0)||(a>LEVEL_WIDTH)) {a++; xx+=aam; continue;} //skip it if there's nothing there                
            ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b;
            if ((a!=ma)||(b!=mb)) {a++; xx+=aam; continue;}
            if (g_tiles[a][b].type>-1) {
                int ind=g_tiles[a][b].i;
                if (layer==2) if (tile[ind].layer2.right<1) {a++; xx+=aam; continue;}
                if (layer==3) if (tile[ind].layer3.right<1) {a++; xx+=aam; continue;}
                if (xx>(g_width+100)) break;
                if ((tile[ind].r.right+xx)<-100) {a++; xx+=aam; continue;}              
                pz.x=xx+g_tiles[ma][mb].offset.x*scale; 
                pz.y=yy+g_tiles[ma][mb].offset.y*scale;                 
                g_sprite_trans=D3DXVECTOR2(pz.x,pz.y);
                D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,0,&g_sprite_trans);
                g_sprite->SetTransform(&g_matrix_2d);                   
                if (layer==2) g_sprite->Draw(tile_texture.tx,&tile[ind].layer2,NULL,NULL,0xFFFFFFFF);
                if (layer==3) g_sprite->Draw(tile_texture.tx,&tile[ind].layer2,NULL,NULL,0xFFFFFFFF);
            }
            xx+=aam; a++;
        }while(a<tile_x+13);
        yy+=aam; b++;
    }while(b<tile_y+10);
    D3DXMatrixIdentity(&g_matrix_2d); g_sprite->SetTransform(&g_matrix_2d);
}//draw_front_tile_layers







//------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------







//------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------





//------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------




I keep all the editor code below and all the game code above. 
If you want you can right click and use outlining options to
collapse selected sections of code... Unfortunately it doesn't save the
customized selection outline -- but if you're doing a long session... 
You could also do this in another module(a bit tricky tho) - but personally I 
prefer to leave it below... 

//collapsable editor code - region starts here.......................

#ifdef ALLOW_EDIT //--------- G A M E  E D I T O R--------------------------------

float panx=0, pany=0;
panx and pany are for keeping track of horizontal and vertical 
panning (done with dragging with left mouse button held down)

// S T R I N G  C O N T A I N S
//can send strings of minimum 4 characters long to use as filters. 
//you can use NULL if you are only using 1 or 2 filters like this: string_contains(data,"blab",NULL,NULL)
bool string_contains(WIN32_FIND_DATA &data, const char* str1, const char* str2, const char* str3) { if (str1==NULL) return false;
    if ((data.cFileName[0]==str1[0])&&(data.cFileName[1]==str1[1])&&(data.cFileName[2]==str1[2])&&(data.cFileName[3]==str1[3])) return true; if (str2==NULL) return false;
    if ((data.cFileName[0]==str2[0])&&(data.cFileName[1]==str2[1])&&(data.cFileName[2]==str2[2])&&(data.cFileName[3]==str2[3])) return true; if (str3==NULL) return false;
    if ((data.cFileName[0]==str3[0])&&(data.cFileName[1]==str3[1])&&(data.cFileName[2]==str3[2])&&(data.cFileName[3]==str3[3])) return true;
    return false;
}
I'm just using the above to filter out file names that start
with certain 4 character words - like if I don't want to
see backup files for example - I could add the filter str as BACK 
so any file that starts with BACK will not be listed... 

//---------------------
// C H O O S E  F I L E
//---------------------
//allow user to select a file from list of available files of some type
bool choose_file(char filename[100], const char * file_directory, const char* search_for, const char* ignore1, const char* ignore2, const char* ignore3,
const char * str1) {
Allow user to choose a file specifying directory and file type 
to search for (ie: .gmp) -- and also can specify some file names to ignore...
    bool done=false, b_use_backup_directory=false;
    POINT cp;           
    WIN32_FIND_DATA data;
    char directory[MAX_PATH];
    char original_file[100]; strcpy_s(original_file, filename); 
    int num_files;
    char files[300][100]; //allow up to 300 files of 100 char
 
files[][] -- will be a list of file names available to choose from within
the directory / search-path

(goto label)
find_files_addr:
    num_files=0;
    strcpy_s(directory, MAX_PATH, g_current_directory);     
    strcat_s(directory, MAX_PATH, file_directory);  
    if (b_use_backup_directory) strcat_s(directory,"\\backup\\"); 
    strcat_s(directory, search_for); 
ie: c:\...\projects\game\tilemaps\*.tmp
    HANDLE h=FindFirstFile(directory,&data);    
    if(h!=INVALID_HANDLE_VALUE) {
        SAFE_DO;            
            if (string_contains(data,ignore1,ignore2,ignore3)) continue; //filter out stuff we don't want to see            
            for(int i=0; i<lstrlen(data.cFileName); i++) files[num_files][i]=char(data.cFileName[i]); 
            files[num_files][lstrlen(data.cFileName)]='\0';
(gets current file name into files[][])            
            num_files++; 
        } while(FindNextFile(h,&data));
    } else {ERR("Directory doesn't exist."); FindClose(h); return b_use_backup_directory;}  
    FindClose(h);   
Now that we have all the file names - let's loop
for user input and wait for them to select one of the files    
    int a=0,scroll_pos=0,marked=-1;
    SAFE_DO;
        HANDLE_LOST_DEVICES( , );   BEGIN_SCENE("choose file"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
        TEXT_POSITION(10,10); PRINT_TEXT(str1); //ie: "please choose a file to configure"
        if (PRESS_ESCAPE) {strcpy_s(filename,100,original_file); done=true;}
        if (KEYPRESS(ENTER)) done=true;
        if (KEYPRESS(UP)) if (scroll_pos>0) scroll_pos--;
        if (KEYPRESS(DOWN)) if (scroll_pos<(num_files-1)) scroll_pos++;
        if (KEYPRESS(CONTROL)) {
            if (KEYPRESS(B)) {
                if (!b_use_backup_directory) {b_use_backup_directory=true; Sleep(160); g_sprite->End(); END_SCENE("choose file"); goto find_files_addr;}
                else {b_use_backup_directory=false; Sleep(160); g_sprite->End(); END_SCENE("choose file"); goto find_files_addr;}
            }
        }

Get input for scrolling list up and down and if CNTRL+B - set 
it to get the list from the backup directory (goto find_files_addr)

        LONG p=40; a=0; 
        int sp=scroll_pos;
        SAFE_DO;
            if (sp==marked) text_color=0xFFAA0000;
            TEXT_POSITION(10,p); 
            PRINT_TEXT(files[sp]); text_color=default_color;
            p+=20; a++; sp++; if (sp>=num_files) break;
        }while(a<25); //show up to 25 files on screen
Shows the list of files

Next we show some instructions:
        TEXT_POSITION(500,270); PRINT_TEXT("Default file selection: ");
        TEXT_POSITION(500,290); text_color=0xFFAA0000; PRINT_TEXT(filename); text_color=default_color;  
        TEXT_POSITION(500,330); PRINT_TEXT("Press ENTER to choose this.");
        TEXT_POSITION(500,530); text_color=0xFF443322; PRINT_TEXT("Press CTRL+B to switch to backup directory."); text_color=default_color;
        if (left_mouse_clicked) {
            left_mouse_clicked=false;
            GetCursorPos(&cp); UpdateMousePos(&cp);
            p=40; a=0; sp=scroll_pos;
            SAFE_DO;
                TEXT_POSITION(10,p); RECT tp; tp.left=text_position.left; tp.right=100; tp.top=text_position.top-2; tp.bottom=tp.top+20;
(looping) for each text's RECT check if there's a mouse
click with mouse coordinates set inside that rect.. - check for selection...
(..make sure to call message_pump so mouse input and stuff us updated too)
                if (mouse_inside(tp)) {                 
                    strcpy_s(filename, 100, files[sp]); marked=sp;                  
                    break;
                }
                p+=20; a++; sp++; if (sp>=num_files) break;
            }while(a<25);
        }
        g_sprite->End(); END_SCENE("choose file");  message_pump();     
    }while(!done); 
    return b_use_backup_directory;
}//choose file


// G E T  N U M B E R
int GetNumber(const char * str, LONG x, LONG y) {   
    if (PollUserInput(str,x, y, 5)) {
        return STR_TO_INT(g_text);
    } 
    return -1000;
}

A function which polls for number input and returns as actual int.



// C H E C K  I D  C L I C K
//used for determining which coordinate we're trying to input for (for manual input of bounding rectangle)
enum CLICK_ID {NO_ID, START_X, START_Y, END_X, END_Y} this_clickid;
void check_id_click(int new_id, int &id, RECT &tile_rect) {
    if (new_id!=id) {
        int n=STR_TO_INT(g_text);
        switch(id) {
            case START_X: tile_rect.left=n; break;
            case START_Y: tile_rect.top=n; break;
            case END_X: tile_rect.right=n; break;
            case END_Y: tile_rect.bottom=n; break;
        }
        if (new_id>0) {
            switch(new_id) {
                case START_X: sprintf_s(g_text,_T("%d"),tile_rect.left); break;
                case START_Y: sprintf_s(g_text,_T("%d"),tile_rect.top); break;
                case END_X: sprintf_s(g_text,_T("%d"),tile_rect.right); break; 
                case END_Y: sprintf_s(g_text,_T("%d"),tile_rect.bottom); break;
            }           
            g_text_index=strlen(g_text);
            int a=g_text_index; SAFE_DO; g_text[a]='\0';a++;}while(a<200);
        } else reset_user_input_memory();
    }
    id=new_id;
}


// D R A W  B O X (bounding selection rect)
void draw_box(RECT rect, D3DCOLOR color) {
    if (!g_sprite) {REPORT("g_sprite?"); return;} if (!line) {REPORT("line?"); return;}
    D3DXVECTOR2 lineList[]={D3DXVECTOR2((FLOAT)rect.left, (FLOAT)rect.top),D3DXVECTOR2((FLOAT)rect.right,(FLOAT)rect.top),D3DXVECTOR2((FLOAT)rect.right,(FLOAT)rect.bottom),D3DXVECTOR2((FLOAT)rect.left,(FLOAT)rect.bottom),D3DXVECTOR2((FLOAT)rect.left,(FLOAT)rect.top)};                             
    line->SetWidth(1.0f);
    g_sprite->End();
    line->Begin();
    line->Draw(lineList, 5, color);
    line->End();
    g_sprite->Begin(D3DXSPRITE_ALPHABLEND); 
}
I probably should have called this function draw_rect or draw_selection..
especially since we have a function for drawing cube tiles - it could
be confusing... This sets up a line list from a supplied rect and draws
the lines that make up the selection rect. 


// S N A P  C O O R D
void snap_coord(LONG &coord, int unit) {
    if (unit<1) return;
    LONG num=coord/unit*unit;
    coord=num;
}
This takes a number and snaps it to the closest point that 
is part of a unit of divisions of some hidden snap grid. Let's
say I have a point(22,46) and I want the closest point in units
of 5 for example -- this would make the coordinate (20,45)
This is useful for example for lining up images with each other more easily.


// T I L E  D E L E T E
void TileDelete(int &i) {
    if (g_num_tile_types==0) {
        if (i!=0) REPORT_D("i!=1 : ",i);
        ZeroMemory(&tile[0], sizeof(tile[0])); tile[0].depth=INFRONT; tile[0].frame_direction=1; tile[0].type=SOLID;        
        g_num_tile_types=0; i=0;
        return;
    }
    if (i==g_num_tile_types) {
        ZeroMemory(&tile[i], sizeof(tile[i])); tile[i].depth=INFRONT; tile[i].frame_direction=1; tile[i].type=SOLID;
        if (i>0) i--; if (g_num_tile_types>0) g_num_tile_types--;
        return;
    }
    int a=i;
    SAFE_DO;
        memcpy(&tile[a], &tile[(a+1)], sizeof tile[a]);
        a++;
    }while(a<=g_num_tile_types); a=g_num_tile_types;
    ZeroMemory(&tile[a], sizeof(tile[a])); tile[a].depth=INFRONT; tile[a].frame_direction=1; tile[a].type=SOLID;    
    if (i>0) i--; if (g_num_tile_types>0) g_num_tile_types--;
}

This deletes a tile type (for tilemap editor).
First I make sure to just delete the tile if it is the last tile 
in the list (and then return) - but if it is not - I need to squash
the list a bit - so I copy all the tiles after the one being deleted,
back one index in the list - and then delete the last entry cuz
it's now a redundant copy of the 2nd last one. Then subtract 1 from 
number of tile types.. 




//-----------------------
// S A V E  T I L E M A P
//-----------------------
void SaveTilemap() {
    int done=0; 
    char filename[100];
    g_sprite->End(); END_SCENE("save tilemap");
It is assumed that we were rendering a scene when this
function was called - so we end it and start a new one
for the user input loop below.. 
    reset_user_input_memory(); Sleep(250);
    sprintf_s(filename, _T("tilemap%d.tma"), g_level); //default tilemap to save as (based on g_level)      
 Reset user input stuff and set the default file
name to be tilemap1.tma or tilemap2.tma, etc - depending on current
level the editor's set to edit.
   char file[100]; 
    do {
        BEGIN_SCENE("save tilemap"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);        
        TEXT_POSITION(10,g_half_height-20); text_color=default_color;       
        PRINT_TEXT("Press ENTER to accept default filename or type in a new file name to SAVE AS: ");
        if (PollUserInput("NEW FILE: ", 10, g_half_height+25, 20)) { 
            strcat_s(g_text,".tma"); 
            strcpy_s(filename,100,g_text); 
            strcpy_s(file,"Tilemaps/"); strcat_s(file,filename);
            REPORT_S("file = ",file);
            done=1;
        } else if (KEYPRESS(ENTER)) { strcpy_s(file,"Tilemaps/"); strcat_s(file,filename); REPORT_S("file = ",file); done=1;}
        g_sprite->End(); END_SCENE("save tilemap");
        if (KEYPRESS(ESCAPE)) done=2;
    }while(done==0);

If PollUserInput receives a valid file name, the filename is created from
g_text(input from PollUserInput) with .tma extension added and file
becomes the filename with the Tilemaps path added in front of it - so
it will be saved in to the correct folder. 
If PollUserInput does not receive a valid file name - we check
if the user pressed ENTER and if so we use the default level based
filename (ie: Tilemaps/tilemap1.tma)
If escape was pressed - we just exit the loop by setting done to 2 

    if (done==1) {
        FILE *f=NULL;
        fopen_s(&f,file,"wb");
        if (!f) { REPORT("file creation error"); } 
        else {
            fwrite(&g_num_tile_types, sizeof(g_num_tile_types), 1, f); 
            fwrite(&tile, sizeof(tile[0]), g_num_tile_types, f);
            fclose(f);  
        }               
We open the file for wrtie binary (wb) and first specify
the number of tile types(so we can load properly later) - and then write 
then actual array of structs by specifying the size and quantity.
    }
    BEGIN_SCENE("save tilemap"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
}




//-----------------------
// L O A D  T I L E M A P (choice loader)
//-----------------------
void LoadTilemap() {
    char file[100]; 
    char filename[100];
    FILE *f=NULL;
    sprintf_s(filename, _T("tilemap%d.tma"), g_level); //default tilemap to load (based on g_level)     
    g_sprite->End(); END_SCENE("load tilemap");
    //file,  what to look for,  what to ignore
    if (!choose_file(filename, "\\Tilemaps\\", "*.tma", "far_","mid_","edit", "Please choose a tile bitmap to conf: "))
        strcpy_s(file,"Tilemaps/"); 
    else strcpy_s(file,"Tilemaps/backup/");
Allow user to choose the file to load from a list
(choose_file function - excluding files that start with far_,mid_,edit...)
and if it was a backup file - we'll load from the Tilemaps/backup directory
    strcat_s(file,filename); 
    REPORT_S("file = ",file);
    fopen_s(&f, file, "rb");
    if (!f) { REPORT("file read error(tilemap)"); } else {      
        fread(&g_num_tile_types, sizeof(g_num_tile_types), 1, f); 
        //fread(&tile, sizeof(tile[0]), 521, f);
        fread(&tile, sizeof(tile[0]), g_num_tile_types, f);
        fclose(f);          
    }       
    Load in the tile array based on g_num_tile_types
    and restart previous scene...        
    BEGIN_SCENE("load tilemap"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
}





//-----------------------
// C O N F I G  T I L E S
//-----------------------
// for user configuration of how tiles are set up (tilemap / tiles bitmap information setup)
enum CONFIG_MODE {SELECT_TILE, CONFIG_RECT, CONFIG_TILE_TYPES, ADD_LAYER2, ADD_LAYER3, ADD_ANIMATION} this_configmode;
void config_tiles() {       
    int config_mode=SELECT_TILE;
    bool last_left_mouse_button=false, first_hit=true, snap=true, s_pressed=false;
    int last_mouse_pos_x=-1, last_mouse_pos_y=-1;
    int copy_offset_from=0, copy_rect_from=0;
    int id=0, num_tile_clusters=0, num=0, last_id=0, current_tile=g_num_tile_types; 
    bool new_tile=true, delete_key=true, press_a=true, press_d=true;
    POINT cp;
    TEXTURE texture;
    char filename[100]; //must be 100 (must be same as others for strcpy to work)
    char text[100]; 
    VEC last_mouse; GetCursorPos(&cp); UpdateMousePos(&cp); last_mouse.x=mouse_pos.x; last_mouse.y=mouse_pos.y;

    int mouse_button=VK_RBUTTON; 
    if (GetSystemMetrics(SM_SWAPBUTTON)) mouse_button=VK_LBUTTON;   

(I preset mouse_button as the right mouse button - but if the mouse is set up
backwards - it interprets the signal for left mouse button as right)

    sprintf_s(filename, _T("tiles%d.dds"), g_level); //default tiles to edit (based on g_level) 
    
    //file,  what to look for,  what to ignore
    char file[100];
    if (!choose_file(filename, "\\Graphics\\", "*.dds", "far_","mid_","edit", "Please choose a tile bitmap to configure: "))
        strcpy_s(file,"Graphics/"); 
    else strcpy_s(file,"Graphics/backup/"); 
strcat_s(file,filename);

First we call choose_file - to allow the user to select a bitmap file (.dds)
which holds images for tiles we'd like to prepare.. 

    left_mouse_clicked=false; Sleep(300);   
    texture.LoadTexture(file);
   then - actually load the file/image into a texture
    bool done=false;
    RECT r, tile_rect, panned_rect;>
    Initialize tile rect (used for showing selected region that is tile):     
    SetRectEmpty(&tile_rect); 
    VEC pos; pos.z=0;
    Start the main config tiles loop:
    SAFE_DO;
        DWORD starting_point = GetTickCount();
        
        HANDLE_LOST_DEVICES( SAFE_RELEASE(texture.tx);, texture.LoadTexture(file); );

        BEGIN_SCENE("config tiles"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
        if (PRESS_ESCAPE) done=true;

First we store the time at the beginning of the loop (for timing)
Handle any lost graphics device - restoring also the additional texture
Start the sprite scene - this time just in plain 2D rendering mode (not OBJECTSPACE)
                
        pos.x=0+panx; pos.y=0+pany;
        DRAWC(grid,NULL,&pos,0xFF999999);
        DRAW(texture,NULL,&pos);
        TEXT_POSITION(g_half_width,10); PRINT_TEXT(filename);   
        
position the grid and tiles texture at 0,0 with offset of panx, pany
(which is so you can pan the view by right-dragging the mouse) 	

        GetCursorPos(&cp); UpdateMousePos(&cp); 
        mouse_pos.x-=panx; mouse_pos.y-=pany; //move mouse coords relative to pan
        sprintf_s(text, _T("(%d, %d)"), (int)mouse_pos.x, (int)mouse_pos.y);
        TEXT_POSITION(g_width-120,10); text_color=0xFFAA0000; PRINT_TEXT(text); text_color=0xFFAA0088;   
(shows mouse coords over tiles map - useful for precision selecting)
           
        switch(config_mode) {
            case SELECT_TILE: 
                if (g_num_tile_types<1) {config_mode=CONFIG_RECT; new_tile=true; break;}
                draw_black_rect(0,g_half_height+100,250,g_half_height+200);
                PRINT_TEXT_EXT(10,g_half_height+120,0xFFDD0033,"Left click to select a tile to edit");
                PRINT_TEXT_EXT(10,g_half_height+140,0xFFAA0022,"(or press S to start a new tile)");
                draw_black_rect(0,g_height-40,250,g_height);
                TEXT_POSITION(10,g_height-40); PRINT_TEXT("(hold right-mouse button to pan)");
                if (left_mouse_clicked) {
                    int a=0;
                    SAFE_DO;
                        if (mouse_inside(tile[a].r)) {REPORT_D("Found: ",a); current_tile=a; CopyRect(&tile_rect,&tile[a].r); config_mode=CONFIG_RECT; new_tile=false; break;}
                        a++;
                    }while(a<=g_num_tile_types);                    
                    left_mouse_clicked=false;
                }
                if (KEYPRESS(S)) {current_tile=g_num_tile_types; config_mode=CONFIG_RECT; new_tile=true;}
            break;
            case ADD_ANIMATION:
            case ADD_LAYER2: 
            case ADD_LAYER3: 
            case CONFIG_RECT: {// C O N F I G  R E C T
                int i=current_tile;
                PRINT_T(g_width-200,10,0xFFCCCC00,"Edit/Files:");
                PRINT_T(g_width-200,10,0xFFDD2200,"ALT+S / ALT+O SAVE/OPEN tile layout");
                if (KEYPRESS(ALT)) {
                    if (KEYPRESS(S)) SaveTilemap(); 
                    if (KEYPRESS(O)) LoadTilemap(); 
                }
                draw_black_rect(0,g_half_height+100,250,g_half_height+200);
                TEXT_POSITION(10,g_half_height+100); PRINT_TEXT_D("Starting position X: ",tile_rect.left,175);
                TEXT_POSITION(10,g_half_height+120); PRINT_TEXT_D("Starting position Y: ",tile_rect.top,175);
                TEXT_POSITION(10,g_half_height+140); text_color=0xFF007722; PRINT_TEXT("(press S to toggle snap)"); 
                TEXT_POSITION(200, g_half_height+140); text_color=0xFF3300AA;
                if (snap) PRINT_TEXT("(on)"); if (!snap) PRINT_TEXT("(off)");
                text_color=text_color=0xFFAA0088;
                TEXT_POSITION(10,g_half_height+160); PRINT_TEXT_D("Ending position X: ",tile_rect.right,175);
                TEXT_POSITION(10,g_half_height+180); PRINT_TEXT_D("Ending position Y: ",tile_rect.bottom,175);
                if (g_num_tile_types>0) {
                    PRINT_TEXT_EXT(10,g_height-30,0xFF882233,"TAB to go to tile selector");
                    if (KEYPRESS(TAB)) config_mode=SELECT_TILE;
                }
                draw_black_rect(0,g_height-40,250,g_height);
                TEXT_POSITION(10,g_height-40); PRINT_TEXT("(hold right-mouse button to pan)");
                switch(config_mode) {
                    case CONFIG_RECT: TEXT_POSITION(10,g_height-20); PRINT_TEXT("Press SPACE to accept RECT coordinates"); break;
                    case ADD_LAYER2: TEXT_POSITION(10,g_height-20); PRINT_TEXT("Press SPACE to accept Layer2 coordinates"); break;
                    case ADD_LAYER3: TEXT_POSITION(10,g_height-20); PRINT_TEXT("Press SPACE to accept Layer3 coordinates"); break;
                    case ADD_ANIMATION: 
                        TEXT_POSITION(10,g_height-20); PRINT_TEXT("Press SPACE to accept animation frame coordinates"); 
                    break;
                }
                if (!left_mouse_clicked) {
                    first_hit=true; last_mouse_pos_x=-1; last_mouse_pos_y=-1;last_left_mouse_button=false;
                    if ((tile_rect.right>tile_rect.left)&&(tile_rect.bottom>tile_rect.top)) {
                        CopyRect(&panned_rect,&tile_rect); MoveRect(panned_rect,(LONG)panx,(LONG)pany);
                        draw_box(panned_rect,0xFFEE2200);
                    }
                }
                if (left_mouse_clicked) { 
                    bool hit=false; 
                    mouse_pos.x+=panx; mouse_pos.y+=pany; 
                    r.left=10; r.right=260; r.top=g_half_height+100; r.bottom=r.top+18;
                    if (mouse_inside(r)) {check_id_click(START_X,id,tile_rect); hit=true;}
                    else {
                        r.top+=20; r.bottom+=20;
                        if (mouse_inside(r)) {check_id_click(START_Y,id,tile_rect); hit=true;}
                        else {
                            r.top+=40; r.bottom+=40;
                            if (mouse_inside(r)) {check_id_click(END_X,id,tile_rect); hit=true;}
                            else {
                                r.top+=20; r.bottom+=20;
                                if (mouse_inside(r)) {check_id_click(END_Y,id,tile_rect); hit=true;}
                            }
                        }
                    }
                    mouse_pos.x-=panx; mouse_pos.y-=pany; 
                    if (hit) left_mouse_clicked=false;
                    if (!hit) {
                        check_id_click(0,id,tile_rect);
                        //test for mouse-based selection:
                        if (last_left_mouse_button) {                                                       
                            if ((first_hit)||((mouse_pos.x>tile_rect.left)&&(mouse_pos.y>tile_rect.top))) {
                                if (first_hit) {
                                    first_hit=false; 
                                    tile_rect.left=last_mouse_pos_x; tile_rect.right=(LONG)mouse_pos.x;
                                    tile_rect.top=last_mouse_pos_y; tile_rect.bottom=(LONG)mouse_pos.y;
                                    if (snap) snap_coord(tile_rect.left,16);
                                    if (snap) snap_coord(tile_rect.right,16); 
                                    if (snap) snap_coord(tile_rect.top,16);
                                    if (snap) snap_coord(tile_rect.bottom,16);
                                } else {
                                    tile_rect.right=(LONG)mouse_pos.x;
                                    tile_rect.bottom=(LONG)mouse_pos.y;
                                    if (snap) snap_coord(tile_rect.right,16);
                                    if (snap) snap_coord(tile_rect.bottom,16);
                                    CopyRect(&panned_rect,&tile_rect); MoveRect(panned_rect,(LONG)panx,(LONG)pany);
                                    draw_box(panned_rect,0xffeebbff);                           
                                }                       
                            }                           
                        } else first_hit=true;
                        last_mouse_pos_x=(int)mouse_pos.x;
                        last_mouse_pos_y=(int)mouse_pos.y;                      
                        last_left_mouse_button=true;                    
                    }
                }
                if (id>0) { //if get number: 
                    num=GetNumber(" ",175,r.top+10);            
                    if (num>-1000) {
                        switch(id) {
                            case START_X: tile_rect.left=num; break;
                            case START_Y: tile_rect.top=num; REPORT_D("top=",tile_rect.top);break;
                            case END_X: tile_rect.right=num; REPORT_D("right=",tile_rect.right);break;
                            case END_Y: tile_rect.bottom=num; REPORT_D("bottom=",tile_rect.bottom);break;
                        }               
                        id=0;
                        reset_user_input_memory();
                    }                                   
                }               
                if (config_mode==ADD_ANIMATION) {
                    if (((tile_rect.right-tile_rect.left)>0)&&((tile_rect.bottom-tile_rect.top)>0)) {
                        if ((!press_a)&&(KEYPRESS(A))) { 
                            press_a=true;
                            int k=0; SAFE_DO; //make sure not to make unwanted copies of this animation frame
                                if ((tile[i].animation[k].left==tile_rect.left)&&(tile[i].animation[k].right==tile_rect.right)&&(tile[i].animation[k].top==tile_rect.top)&&(tile[i].animation[k].bottom==tile_rect.bottom)) {k=99999; break;}
                                k++;
                            }while(k<tile[i].num_frames);
                            if (k<99999) { 
                                CopyRect(&tile[i].animation[tile[i].num_frames],&tile_rect); 
                                CopyRect(&tile_rect, &tile[i].r); 
                                if (tile[i].num_frames<MAX_TILE_ANIMATION) tile[i].num_frames++; else REPORT_D("tile num_frames will exceed MAX_TILE_ANIMATION ",tile[i].num_frames);                       
                            }
                        }
                        if (KEY_IS_UP(KEY_A)) press_a=false;                                            
                    }
                    if ((!press_d)&&(KEYPRESS(D))) {
                        press_d=true;
                        if (tile[i].num_frames>0) {
                            tile[i].num_frames--;
                        } else tile[i].num_frames=0;
                    }
                    if (KEY_IS_UP(KEY_D)) press_d=false;
                    //show animation frame selections:
                    RECT dum_r; SetRectEmpty(&dum_r);
                    if (tile[i].num_frames>0) {
                        int u=0; SAFE_DO(u);
                            CopyRect(&dum_r,&tile[i].animation[u]); MoveRect(dum_r,(LONG)panx,(LONG)pany);                  
                            int k=0; k=u*10; if (k>255) k=255;
                            D3DCOLOR col=D3DCOLOR_ARGB(255,k,(255-k),0);
                            draw_box(dum_r, col); TEXT_POSITION(dum_r.left, dum_r.top); text_color=col; 
                            sprintf_s(g_str, _T("%d"), (u+1)); PRINT_TEXT(g_str);
                            u++;
                        }while(u<tile[i].num_frames);
                    }
                }
                if (KEYPRESS(SHIFT)) {
                    if (KEYPRESS(C)) {copy_rect_from=i;}
                    if (KEYPRESS(V)) {
                        tile_rect.right=tile_rect.left+(tile[copy_rect_from].r.right-tile[copy_rect_from].r.left); 
                        tile_rect.bottom=tile_rect.top+(tile[copy_rect_from].r.bottom-tile[copy_rect_from].r.top); 
                    };
                }
                if (KEYPRESS(SPACE)) {              
                    i=current_tile;
                    TEST_ARRAY(tile,g_num_tile_types); TEST_ARRAY(tile,i);
                    if (((tile_rect.right-tile_rect.left)>0)&&((tile_rect.bottom-tile_rect.top)>0)) {                       
                        switch(config_mode) {                       
                            case ADD_LAYER2: CopyRect(&tile[i].layer2,&tile_rect); CopyRect(&tile_rect,&tile[i].r); break;
                            case ADD_LAYER3: CopyRect(&tile[i].layer3,&tile_rect); CopyRect(&tile_rect,&tile[i].r); break;
                            case CONFIG_RECT: 
                                CopyRect(&tile[i].r,&tile_rect); CopyRect(&tile[i].o, &tile[i].r); 
                                tile[i].ref=g_reference_maker; g_reference_maker++;
                            break;
                            case ADD_ANIMATION: 
                                int k=0; SAFE_DO; //make sure not to make unwanted copies of this animation frame
                                    if ((tile[i].animation[k].left==tile_rect.left)&&(tile[i].animation[k].right==tile_rect.right)&&(tile[i].animation[k].top==tile_rect.top)&&(tile[i].animation[k].bottom==tile_rect.bottom)) {k=99999; break;}
                                    k++;
                                }while(k<tile[i].num_frames);
                                if (k>=99999) break;
                                if ((tile[i].r.left!=tile_rect.left)||(tile[i].r.right!=tile_rect.right)||(tile[i].r.top!=tile_rect.top)||(tile[i].r.bottom!=tile_rect.bottom)) {
                                    CopyRect(&tile[i].animation[tile[i].num_frames],&tile_rect); 
                                    CopyRect(&tile_rect, &tile[i].r); 
                                    if (tile[i].num_frames<MAX_TILE_ANIMATION) tile[i].num_frames++; else REPORT_D("tile num_frames will exceed MAX_TILE_ANIMATION ",tile[i].num_frames);
                                }
                            break;
                        }
                    config_mode=CONFIG_TILE_TYPES;
                    } else {                        
                        if (PromptUser("No coordinates selected. Continue Anyway? (Y/N)")) {
                            config_mode=CONFIG_TILE_TYPES; 
                        }                       
                    }
                    //offx=(LONG)tile[current_tile].offset.x; offy=(LONG)tile[current_tile].offset.y;
                    //if ((tile[current_tile].tiles_wide>0)&&(tile[current_tile].tiles_high>0)) CopyRect(&tile_rect,&tile[current_tile].o);                 
                    delete_key=true;
                }
            } break; //^^^config_rect^^^
            case CONFIG_TILE_TYPES: // C O N F I G  T I L E  T Y P E S              
                RECT dum_r; SetRectEmpty(&dum_r);
                int i=current_tile;
                TEST_ARRAY(tile,i);
                CopyRect(&panned_rect,&tile[i].r); MoveRect(panned_rect,(LONG)panx,(LONG)pany); draw_box(panned_rect, 0xFFEE0000);
                if (tile[i].layer2.right>0) {CopyRect(&dum_r,&tile[i].layer2); MoveRect(dum_r,(LONG)panx,(LONG)pany); draw_box(dum_r, 0xFF330099);}
                if (tile[i].layer3.right>0) {CopyRect(&dum_r,&tile[i].layer3); MoveRect(dum_r,(LONG)panx,(LONG)pany); draw_box(dum_r, 0xFF110077);}
                
                //show animation frame selections:
                if (tile[i].num_frames>0) {
                    int u;
                    u=0; SAFE_DO(u);
                        CopyRect(&dum_r,&tile[i].animation[u]); MoveRect(dum_r,(LONG)panx,(LONG)pany);
                        int k=0; k=u*10; if (k>255) k=255;
                        D3DCOLOR col=D3DCOLOR_ARGB(255,k,(255-k),0);
                        draw_box(dum_r, col); TEXT_POSITION(dum_r.left, dum_r.top); text_color=col; 
                        sprintf_s(g_str, _T("%d"), (u+1)); PRINT_TEXT(g_str);
                        u++;
                    }while(u<tile[i].num_frames);
                    if (tile[i].num_frames>=2) {
                        if (tile[i].animation_type==NO_ANIM) {tile[i].animation_type=LOOPING; if (tile[i].wait_time==0) tile[i].wait_time=12;}
                        tile[i].timer++;                        
                        if (tile[i].animation_type==PING_PONG) {
                            if (tile[i].timer>tile[i].wait_time) {tile[i].frame+=tile[i].frame_direction; tile[i].timer=0;}
                            if (tile[i].frame>=tile[i].num_frames) {tile[i].frame_direction=-1; tile[i].frame--;}
                            if (tile[i].frame<=0) tile[i].frame_direction=1;
                        } else {
                            if (tile[i].timer>tile[i].wait_time) {tile[i].frame++; tile[i].timer=0;}
                            if (tile[i].frame>=tile[i].num_frames) tile[i].frame=0;
                        }
                        u=tile[i].frame;                        
                        VEC p; p.z=0; p.x=(float)tile[i].r.left+panx; p.y=(float)tile[i].r.top+pany;
                        DRAW(texture,&tile[i].animation[u],&p);
                    }
                }
                if (tile[i].animation_type==SCROLL_Y) {
                    tile[i].scroll_pos.y+=tile[i].wait_time;//((float)tile[i].wait_time/10.0f);
                    if (tile[i].scroll_pos.y<=tile[i].r.top) tile[i].scroll_pos.y=tile[i].r.bottom-(tile[i].r.top-tile[i].scroll_pos.y);
                    if (tile[i].scroll_pos.y>=tile[i].r.bottom) tile[i].scroll_pos.y=tile[i].r.top+(tile[i].scroll_pos.y-tile[i].r.bottom);
                }
                if (tile[i].animation_type==SCROLL_X) {
                    tile[i].scroll_pos.x+=tile[i].wait_time;//((float)tile[i].wait_time/10.0f);
                    if (tile[i].scroll_pos.x<=tile[i].r.left) tile[i].scroll_pos.x=tile[i].r.right-(tile[i].r.left-tile[i].scroll_pos.x);
                    if (tile[i].scroll_pos.x>=tile[i].r.right) tile[i].scroll_pos.x=tile[i].r.left+(tile[i].scroll_pos.x-tile[i].r.right);
                }
                if ((tile[i].animation_type==SCROLL_X)||(tile[i].animation_type==SCROLL_Y)) {
                    LONG overflow=0;
                    VEC p; p.z=0; p.x=(float)tile[i].r.left+panx; p.y=(float)tile[i].r.top+pany;
                    if (tile[i].animation_type==SCROLL_X) {
                        overflow=(LONG)tile[i].scroll_pos.x-tile[i].r.left;
                        CopyRect(&dum_r,&tile[i].r);
                        dum_r.left=tile[i].r.left+1; dum_r.right=tile[i].r.right-overflow;
                        p.x=tile[i].scroll_pos.x+panx;
                        DRAW(texture,&dum_r,&p);
                        dum_r.left=dum_r.right;//-1;//
                        dum_r.right=tile[i].r.right;//-1;
                        p.x=(float)tile[i].r.left+panx;//+1;
                        DRAW(texture,&dum_r,&p);
                    }
                    if (tile[i].animation_type==SCROLL_Y) {
                        overflow=(LONG)tile[i].scroll_pos.y-tile[i].r.top;
                        CopyRect(&dum_r,&tile[i].r);
                        dum_r.top=tile[i].r.top+1; dum_r.bottom=tile[i].r.bottom-overflow;
                        p.y=tile[i].scroll_pos.y+pany;
                        DRAW(texture,&dum_r,&p);
                        dum_r.top=dum_r.bottom;//-1;// 
                        dum_r.bottom=tile[i].r.bottom;//-1;
                        p.y=(float)tile[i].r.top+pany;//+1;
                        DRAW(texture,&dum_r,&p);
                    }
                }

                if ((tile[i].tiles_wide<=0)&&(tile[i].tiles_high<=0)) { //make a guess if this is the first time
                    if (tile[i].r.right>tile[i].r.left) {                       
                        if (tile[i].tiles_wide<1) tile[i].tiles_wide=(tile[i].r.right+1-tile[i].r.left)/64; 
                    }
                    if (tile[i].tiles_wide<1) tile[i].tiles_wide=1;
                    if (tile[i].r.bottom>tile[i].r.top) {
                        if (tile[i].tiles_high<1) tile[i].tiles_high=(tile[i].r.bottom+1-tile[i].r.top)/64; 
                    }
                    if (tile[i].tiles_high<1) tile[i].tiles_high=1;
                    REPORT("reset");
                } else ("not reset");
                int hp=g_width-220;
                draw_black_rect(hp,20,g_width,580); text_color=0xFFCCCC00;
                TEXT_POSITION(hp,20); PRINT_TEXT("Type:");
                PRINT_T(hp,10,0xFFBB0000,"1 = SOLID");              
                PRINT_T(hp,10,0xFF999999,"2 = PASSABLE");
                PRINT_T(hp,10,0xFF0000BB,"3 = LIQUID");
                PRINT_T(hp,10,0xFF5511AA,"4 = BOUNCE");
                PRINT_T(hp,10,0xFFAA2266,"5 = BUTTON");
                PRINT_T(hp,10,0xFF8833AA,"6 = Jump Passable Solid");
                PRINT_T(hp,20,0xFFCCCC00,"Infront/Behind:");
                PRINT_T(hp,10,0xFF888888,"Q = BEHIND");
                PRINT_T(hp,10,0xFFAAAAAA,"W = INFRONT");
                PRINT_T(hp,10,0xFF555577,"SHIFT Q = FAR BEHIND");
                PRINT_T(hp,10,0xFFCCAAAA,"SHIFT W = NEAR INFRONT");
                PRINT_T(hp,20,0xFFCCCC00,"Effect:");
                PRINT_T(hp,10,0xFF777777,"Numpad 0 = NONE");
                PRINT_T(hp,10,0xFFDD7700,"Numpad 5 = DAMAGE");
                PRINT_T(hp,10,0xFFDD7700,"(numpad)SPIKES(up,dwn,left,rght)");
                PRINT_T(hp,10,0xFF009933,"Break/Pause = BREAKS");
                PRINT_T(hp,10,0xFF0055AA,"* = slippery");
                PRINT_T(hp,10,0xFF995522,"+ = door");
                PRINT_T(hp,10,0xFF9955FF,"- = portal");
                PRINT_T(hp,20,0xFFCCCC00,"Layers:");
                PRINT_T(hp,10,0xFF888888,"E = add layer2 (shift+E=delete)");
                PRINT_T(hp,10,0xFFAAAAAA,"R = add layer3 (shift+R=delete)");
                PRINT_T(hp,10,0xFF990011,"T = change layering type");
                PRINT_T(hp,20,0xFFCCCC00,"Animation:");
                PRINT_T(hp,10,0xFF998888,"A = Add animation frame");
                PRINT_T(hp,10,0xFF11DD88,"H = No animation");
                PRINT_T(hp,10,0xFF775555,"D = delete animation frame");
                PRINT_T(hp,10,0xFF008888,"K = scroll horizontally (timer=speed)");
                PRINT_T(hp,10,0xFF888800,"L = scroll vertically (timer=speed)");
                PRINT_T(hp,10,0xFF1234BB,"F = Loop animation (1234,1234)");
                PRINT_T(hp,10,0xFF12BB34,"G = Ping-Pong animation (1234,4321)");
                PRINT_T(hp,10,0xFFAA8800,"{ = set timer/timing (slower)");
                PRINT_T(hp,10,0xFFAA8800,"} = set timer/timing (faster)");
                PRINT_T(hp,10,0xFF333333,"...");
                PRINT_T(hp,10,0xFFCCCC00,"Edit/Files:");
                PRINT_T(hp,10,0xFFDD2200,"ALT+S / ALT+O SAVE/OPEN tile layout"); 
                PRINT_T(hp,10,0xFFAA8800,"PageUp/PageDown = tab thru tiles");           
                PRINT_T(hp,10,0xFF889900,"(Delete = delete tile)");
                PRINT_T(hp,10,0xFF00DD33,"(arrow keys = tile offset)");
                PRINT_T(hp,10,0xFFAA7700,"Ctrl+C/Ctrl+V (copy paste offset)");
                PRINT_T(hp,10,0xFF449900,"Shift+C/Shift+V(copy paste RECT size)");
                TEXT_POSITION(hp,500); PRINT_TEXT("(S = toggle snap)");
                TEXT_POSITION(hp,520); PRINT_TEXT("(left click: set top-left tile)");
                PRINT_TEXT_EXT(hp,540,0xFF008800,"(hold right-mouse button to pan)");
                PRINT_TEXT_EXT(hp,560,0xFFDD2200,"Press ENTER when finished with tile");
                PRINT_TEXT_EXT(hp,580,0xFF006600,"(TAB to return to rect edit)");
                text_color=0xFFAA0088;
                //draw the effected tiles:              
                if (KEYPRESS(TAB)) {config_mode=CONFIG_RECT; Sleep(150);}
                if (KEYPRESS(1)) tile[i].type=SOLID;
                if (KEYPRESS(2)) tile[i].type=PASS;
                if (KEYPRESS(3)) tile[i].type=LIQUID;
                if (KEYPRESS(4)) tile[i].type=BOUNCE;
                if (KEYPRESS(5)) tile[i].type=BUTTON;
                if (KEYPRESS(6)) tile[i].type=JUMP_PASSABLE;
                if (KEYPRESS(Q)) {tile[i].depth=BEHIND; tile[i].offset.z=0.3f; if (KEYPRESS(SHIFT)) {tile[i].depth=FAR_BEHIND; tile[i].offset.z=0.1f;}}
                if (KEYPRESS(W)) {tile[i].depth=INFRONT; tile[i].offset.z=0.5f; if (KEYPRESS(SHIFT)) {tile[i].depth=NEAR_INFRONT; tile[i].offset.z=0.7f;}}
                if (KEYPRESS(T)) {tile[i].layer_type++; if (tile[i].layer_type>=MAX_LAYER_TYPES) tile[i].layer_type=0; Sleep(150);}
                if (KEYPRESS(NUMPAD0)) tile[i].effect=NONE;
                if (KEYPRESS(NUMPAD5)) tile[i].effect=DAMAGE;
                if (KEYPRESS(NUMUP)) tile[i].effect=SPIKE_UP;
                if (KEYPRESS(NUMLEFT)) tile[i].effect=SPIKE_LEFT;
                if (KEYPRESS(NUMRIGHT)) tile[i].effect=SPIKE_RIGHT;
                if (KEYPRESS(NUMDOWN)) tile[i].effect=SPIKE_DOWN;
                if (KEYPRESS(PAUSE)) tile[i].effect=BREAKS;
                if (KEYPRESS(MULTIPLY)) tile[i].effect=SLIPPERY;
                if (KEYPRESS(ADD)) tile[i].effect=DOOR;
                if (KEYPRESS(SUBTRACT)) tile[i].effect=PORTAL;
                if (KEYPRESS(E)) {if (!KEYPRESS(SHIFT)) config_mode=ADD_LAYER2; else SetRectEmpty(&tile[i].layer2); }
                if (KEYPRESS(R)) {if (!KEYPRESS(SHIFT)) config_mode=ADD_LAYER3; else SetRectEmpty(&tile[i].layer3); }
                if (KEYPRESS(A)) {
                    config_mode=ADD_ANIMATION;
                    if (tile[i].num_frames==0) {
                        CopyRect(&tile[i].animation[0],&tile[i].r);
                        tile[i].num_frames=1;
                    }
                }
                if (KEYPRESS(K)) {tile[i].animation_type=SCROLL_X; VEC_ZERO(tile[i].scroll_pos); tile[i].scroll_pos.x=(float)tile[i].r.left; tile[i].scroll_pos.y=(float)tile[i].r.top; if (tile[i].wait_time==0) tile[i].wait_time=1; }
                if (KEYPRESS(L)) {tile[i].animation_type=SCROLL_Y; VEC_ZERO(tile[i].scroll_pos); tile[i].scroll_pos.x=(float)tile[i].r.left; tile[i].scroll_pos.y=(float)tile[i].r.top; if (tile[i].wait_time==0) tile[i].wait_time=1; }
                if (KEYPRESS(F)) tile[i].animation_type=LOOPING;
                if (KEYPRESS(G)) tile[i].animation_type=PING_PONG;
                if (KEYPRESS(H)) tile[i].animation_type=NO_ANIM;
                if (KEYPRESS(LBRACKET)) {tile[i].wait_time--; Sleep(150);}
                if (KEYPRESS(RBRACKET)) {tile[i].wait_time++; Sleep(150);}
                if (KEYPRESS(CONTROL)) {
                    if (KEYPRESS(C)) {copy_offset_from=i;}
                    if (KEYPRESS(V)) {COPY_VECTOR(tile[i].offset,tile[copy_offset_from].offset); tile[i].offx=tile[copy_offset_from].offx; tile[i].offy=tile[copy_offset_from].offy; }
                }       
                if (KEYPRESS(SHIFT)) {
                    if (KEYPRESS(C)) {copy_rect_from=i;}
                    if (KEYPRESS(V)) {
                        tile[i].r.right=tile[i].r.left+(tile[copy_rect_from].r.right-tile[copy_rect_from].r.left); 
                        tile[i].r.bottom=tile[i].r.top+(tile[copy_rect_from].r.bottom-tile[copy_rect_from].r.top); 
                    };
                }
                if (KEYPRESS(ALT)) {
                    if (KEYPRESS(S)) SaveTilemap(); 
                    if (KEYPRESS(O)) LoadTilemap(); 
                }
                if ((!delete_key)&&(KEYPRESS(DELETE))) {if (g_num_tile_types<1) {TileDelete(i); current_tile=i; delete_key=true; config_mode=CONFIG_RECT;} TileDelete(i); current_tile=i; delete_key=true;}
                if ((!press_d)&&(KEYPRESS(D))) {
                    press_d=true;
                    if (tile[current_tile].num_frames>0) {
                        tile[current_tile].num_frames--;
                    } else tile[current_tile].num_frames=0;
                }
                if (KEY_IS_UP(KEY_D)) press_d=false;
                if (KEY_IS_UP(KEY_DELETE)) delete_key=false;
                if (KEYPRESS(PAGEUP)) {
                    if (current_tile>0) {
                        current_tile--; i=current_tile;                     
                        Sleep(200); break;
                    }
                }
                if (KEYPRESS(PAGEDOWN)) {
                    if (current_tile<g_num_tile_types) {
                        current_tile++; i=current_tile; 
                        Sleep(200); break;
                    }
                }               
                if (done) {g_num_tile_types++; current_tile=g_num_tile_types;}
                if (KEYPRESS(ENTER)) {
                    config_mode=CONFIG_RECT;                    
                    g_num_tile_types++; current_tile=g_num_tile_types;                  
                }
                draw_black_rect(0,g_half_height+100,250,g_half_height+200); text_color=default_color;
                TEXT_POSITION(10,g_half_height+100); PRINT_TEXT("Tile type: "); TEXT_POSITION(160,g_half_height+100);
                switch(tile[i].type) {
                    case SOLID:  text_color=0xFFBB0000; PRINT_TEXT("SOLID"); break;
                    case PASS:   text_color=0xFF999999; PRINT_TEXT("PASSABLE"); break;
                    case LIQUID: text_color=0xFF0000BB; PRINT_TEXT("LIQUID"); break;
                    case BOUNCE: text_color=0xFF551188; PRINT_TEXT("BOUNCE"); break;
                    case BUTTON: text_color=0xFFAA2266; PRINT_TEXT("BUTTON"); break;
                    case JUMP_PASSABLE: text_color=0xFF8833AA; PRINT_TEXT("JUMP PASSABLE (solid)"); break;
                }
                text_color=default_color; TEXT_POSITION(10,g_half_height+110); PRINT_TEXT("Depth: "); TEXT_POSITION(160,g_half_height+110);
                switch(tile[i].depth) {
                    case BEHIND:  text_color=0xFF888888; PRINT_TEXT("BEHIND"); break;
                    case INFRONT: text_color=0xFFAAAAAA; PRINT_TEXT("INFRONT"); break;
                    case FAR_BEHIND:  text_color=0xFF555577; PRINT_TEXT("FAR BEHIND"); break;
                    case NEAR_INFRONT: text_color=0xFFCCAAAA; PRINT_TEXT("NEAR INFRONT"); break;
                }
                text_color=default_color; TEXT_POSITION(10,g_half_height+120); PRINT_TEXT("Effect: "); TEXT_POSITION(160,g_half_height+120);
                switch(tile[i].effect) {
                    case NONE:        text_color=0xFF777777; PRINT_TEXT("NONE"); break;
                    case DAMAGE:      text_color=0xFFDD7700; PRINT_TEXT("DAMAGE"); break;
                    case SPIKE_UP:    text_color=0xFFDD7700; PRINT_TEXT("SPIKE UP"); break;
                    case SPIKE_DOWN:  text_color=0xFFDD7700; PRINT_TEXT("SPIKE DOWN"); break;
                    case SPIKE_LEFT:  text_color=0xFFDD7700; PRINT_TEXT("SPIKE LEFT"); break;
                    case SPIKE_RIGHT: text_color=0xFFDD7700; PRINT_TEXT("SPIKE RIGHT"); break;
                    case BREAKS:      text_color=0xFF009933; PRINT_TEXT("BREAKS"); break;
                    case SLIPPERY:    text_color=0xFF0055AA; PRINT_TEXT("SLIPPERY"); break;
                    case DOOR:        text_color=0xFF995522; PRINT_TEXT("DOOR"); break;
                    case PORTAL:      text_color=0xFF9955FF; PRINT_TEXT("PORTAL"); break;
                }
                text_color=default_color; TEXT_POSITION(10,g_half_height+130); PRINT_TEXT("Animated effect: "); TEXT_POSITION(160,g_half_height+130);
                switch(tile[i].animation_type) {
                    case NO_ANIM:     text_color=0xFF554433; PRINT_TEXT("No effects for animation"); break;
                    case SCROLL_X:    text_color=0xFF008888; PRINT_TEXT("Scrolls horizontally"); break;
                    case SCROLL_Y:    text_color=0xFF888800; PRINT_TEXT("Scrolls vertically"); break;
                    case LOOPING:     text_color=0xFFBB8800; PRINT_TEXT("Looping"); break;
                    case PING_PONG:   text_color=0xFFBBDD00; PRINT_TEXT("Ping-Pong"); break;
                }
                sprintf_s(g_str, _T("TIMING/speed:                         %d"), tile[i].wait_time);
                text_color=default_color; TEXT_POSITION(10,g_half_height+140); PRINT_TEXT(g_str);
                text_color=0xFF990011; TEXT_POSITION(10,g_half_height+150); PRINT_TEXT("Layering Type: "); TEXT_POSITION(160,g_half_height+150);
                switch(tile[i].layer_type) {
                    case LAYER_FLAT: text_color=0xFF0000BB; PRINT_TEXT("FLAT"); break;
                    case LAYER_BOX: text_color=0xFFBB4422; PRINT_TEXT("BOX"); break;
                    case LAYER_DEPTH: text_color=0xFF880044; PRINT_TEXT("DEPTH layers"); break;
                    case LAYER_MAX: text_color=0xFF00EE00; PRINT_TEXT("MAX layers"); break;
                }

                text_color=default_color; TEXT_POSITION(10,g_height-80);
                PRINT_TEXT_D("Offset X: ",(int)tile[i].offset.x,196); 
                TEXT_POSITION(10,g_height-60); PRINT_TEXT_D("Offset Y: ",(int)tile[i].offset.y,196);
                TEXT_POSITION(10,g_height-40); PRINT_TEXT("(use arrow keys to change [active]tiles offset)");
                PRINT_TEXT_EXT(10,g_height-20,0xFF007722,"(press S to toggle snap)"); 
                PRINT_TEXT_EXT(10,g_height-100,0xFFBBAA00,"Hold+Drag left mouse button to define active tiles region")
                TEXT_POSITION(340,g_height-80); PRINT_TEXT_D("tiles wide: ",tile[i].tiles_wide,440);
                TEXT_POSITION(340,g_height-60); PRINT_TEXT_D("tiles high: ",tile[i].tiles_high,440);
                if (KEYPRESS(UP)) {tile[i].offy++; Sleep(10);}
                if (KEYPRESS(DOWN)) {tile[i].offy--; Sleep(10);}
                if (KEYPRESS(LEFT)) {tile[i].offx++; Sleep(10);}
                if (KEYPRESS(RIGHT)) {tile[i].offx--; Sleep(10);}               
                LONG ofx=tile[i].offx, ofy=tile[i].offy; 
                if (snap) {ofx=ofx/4*4; ofy=ofy/4*4;}               
                tile[i].offset.x=(float)(tile[i].r.left-tile[i].o.left+ofx);
                tile[i].offset.y=(float)(tile[i].r.top-tile[i].o.top+ofy);
                tile[i].tiles_wide=(tile[i].o.right+1-tile[i].o.left)/64;
                tile[i].tiles_high=(tile[i].o.bottom+1-tile[i].o.top)/64;
                float tx, ty=(float)tile[i].r.top-tile[i].offset.y;
                RECT rc;
                int b=0; //draw tile types:
                SAFE_DO;
                    int a=0; tx=(float)tile[i].r.left-tile[i].offset.x;
                    SAFE_DO;
                        pos.x=tx+panx; pos.y=ty+pany;
                        switch(tile[i].type) {
                            case SOLID: SET_RECT(rc,0,0,64,64); DRAW(tile_types_texture,&rc,&pos); break;
                            case PASS:  SET_RECT(rc,0,64,64,128); DRAW(tile_types_texture,&rc,&pos); break;
                            case LIQUID: SET_RECT(rc,64,0,128,64); DRAW(tile_types_texture,&rc,&pos); break;
                            case BOUNCE: SET_RECT(rc,128,0,192,64); DRAW(tile_types_texture,&rc,&pos); break;
                            case BUTTON: SET_RECT(rc,128,64,192,128); DRAW(tile_types_texture,&rc,&pos); break;
                            case JUMP_PASSABLE: SET_RECT(rc,0,0,64,64); DRAWC(tile_types_texture,&rc,&pos,0xFF7700FF); break;
                        }
                        a++; tx+=64;
                    }while(a<tile[i].tiles_wide);
                    b++; ty+=64;
                }while(b<tile[i].tiles_high);
                //test mouse clicks:
                if (!left_mouse_clicked) {
                    first_hit=true; last_mouse_pos_x=-1; last_mouse_pos_y=-1;last_left_mouse_button=false;
                    if ((tile[i].o.right>tile[i].o.left)&&(tile[i].o.bottom>tile[i].o.top)) {
                        CopyRect(&panned_rect,&tile[i].o); MoveRect(panned_rect,(LONG)panx,(LONG)pany);
                        draw_box(panned_rect,0xFFEE2200);                                               
                    }
                }
                if (left_mouse_clicked) {
                    bool hit=false;
                    if (hit) left_mouse_clicked=false;
                    if (!hit) {
                        if (last_left_mouse_button) {
                            if ((first_hit)||((mouse_pos.x>tile[i].o.left)&&(mouse_pos.y>tile[i].o.top))) {
                                if (first_hit) {
                                    first_hit=false; 
                                    tile[i].offx=0; tile[i].offy=0;
                                    tile[i].o.left=last_mouse_pos_x; tile[i].o.right=(LONG)mouse_pos.x+64; //default assumes 64x64 minimum
                                    tile[i].o.top=last_mouse_pos_y; tile[i].o.bottom=(LONG)mouse_pos.y+64; //default assumes 64x64 minimum
                                    if (snap) snap_coord(tile[i].o.left,16);
                                    if (snap) snap_coord(tile[i].o.right,16);
                                    if (snap) snap_coord(tile[i].o.top,16);
                                    if (snap) snap_coord(tile[i].o.bottom,16);
                                } else {
                                    tile[i].o.right=(LONG)mouse_pos.x;
                                    tile[i].o.bottom=(LONG)mouse_pos.y;
                                    if (snap) snap_coord(tile[i].o.right,16);
                                    if (snap) snap_coord(tile[i].o.bottom,16);
                                    CopyRect(&panned_rect,&tile[i].o); MoveRect(panned_rect,(LONG)panx,(LONG)pany);
                                    draw_box(panned_rect,0xffeebbff);                                           
                                }
                            }
                        } else first_hit=true;
                        last_mouse_pos_x=(int)mouse_pos.x;
                        last_mouse_pos_y=(int)mouse_pos.y;                      
                        last_left_mouse_button=true;                    
                    }
                } 
                TEXT_POSITION(230,g_height-20);
                if (snap) PRINT_TEXT("(on)"); if (!snap) PRINT_TEXT("(off)");
            break;
        }
        if (KEYPRESS(S)) {
            if (!s_pressed) {s_pressed=true;
                if (snap) snap=false; else snap=true;
            }
        } else s_pressed=false;
        mouse_pos.x+=panx; mouse_pos.y+=pany; //redo what we undid above to prevent jerkiness
        if (KEY_IS_DOWN(mouse_button)) {
            panx+=mouse_pos.x-last_mouse.x;
            pany+=mouse_pos.y-last_mouse.y;
        }
        last_mouse.x=mouse_pos.x; last_mouse.y=mouse_pos.y;
        text_color=default_color;

        g_sprite->End(); END_SCENE("config_tiles"); message_pump();
        SAFE_DO; }while ((GetTickCount() - starting_point) < TIMING_DELAY); 
        if (KEYPRESS(PRINTSCREEN)) GetScreenShot("screenshot.bmp",-1);
    }while(!done); 
    SAFE_RELEASE(texture.tx);
}//config_tiles




#define COL 0xFFFFFFFF
//not sure I like the following define - it's often not necessary to set all of these - but here it is anyway:
#define DRAW_ROTATE_SCALE(tex,rec,rot,scale,trans_x,trans_y,sc1,rc1,clr)\
        g_sprite_scale=D3DXVECTOR2(scale,scale);\
        g_sprite_center=D3DXVECTOR2(sc1,sc1);\
        g_sprite_trans=D3DXVECTOR2(trans_x,trans_y);\
        g_sprite_rot_center=D3DXVECTOR2(rc1,rc1);\
        D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,rot,&g_sprite_trans);\
        g_sprite->SetTransform(&g_matrix_2d);\
        if(rec.right==0) g_sprite->Draw(tex##.tx,NULL,NULL,NULL,clr);\
        else if(tex##.tx!=NULL) g_sprite->Draw(tex##.tx,&##rec,NULL,NULL,clr);\
        D3DXMatrixIdentity(&g_matrix_2d); g_sprite->SetTransform(&g_matrix_2d);
//-------------------------
// T I L E  S E L E C T O R
//-------------------------
// for picking tiles to paint into the world
int tile_selector() {
    POINT cp;
    RECT r;
    int i=-1;
    float scale=0.5f;
    bool done=false;
    g_sprite->End(); END_SCENE("tile_selector");
    SAFE_DO;
        BEGIN_SCENE("tile_selector"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
        HANDLE_LOST_DEVICES( , );
        GetCursorPos(&cp); UpdateMousePos(&cp); 
        if (PRESS_ESCAPE) done=true;        
        RECT rec={0}; DRAW_ROTATE_SCALE(tile_texture,rec,0,scale,0,0,0,0,COL);              
        int a=0;
        do {
            CopyRect(&r,&tile[a].r);
            r.left=(LONG)((float)r.left*scale); r.right=(LONG)((float)r.right*scale); r.top=(LONG)((float)r.top*scale); r.bottom=(LONG)((float)r.bottom*scale); 
            if (mouse_inside(r)) {                              
                draw_box(r,0xFFAA0000);
                if(left_mouse_clicked) {left_mouse_clicked=false; i=a; done=true;}
            }
            a++;
        }while(a<g_num_tile_types);     
        g_sprite->End(); END_SCENE("tile_selector");    message_pump();
    }while(!done);
    BEGIN_SCENE("tile_selector"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
    return i;
}//tile_selector




//-----------------------
// S A V E  G A M E M A P (custom)
//-----------------------
void SaveGamemap() {
    int done=0; 
    char filename[100];
    g_sprite->End(); END_SCENE("save gamemap");
    reset_user_input_memory(); Sleep(250);
    if (strlen(g_custom_file)>0) {strcpy_s(filename,g_custom_file); REPORT("strlen>0");} else sprintf_s(filename, _T("gamemap%d.lev"), g_level); //default gamemap to save as (based on g_level)        
    char file[100]; 
    do {
        BEGIN_SCENE("save gamemap"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);        
        TEXT_POSITION(10,g_half_height-60); text_color=0xFF88DD00;
        PRINT_TEXT(filename);
        TEXT_POSITION(10,g_half_height-20); text_color=default_color;
        PRINT_TEXT("Press ENTER to use default filename or type in a new file name to SAVE AS: ");
        if (PollUserInput("NEW FILE: ", 10, g_half_height+25, 20)) { 
            strcat_s(g_text,".lev"); 
            strcpy_s(filename,100,g_text); 
            strcpy_s(file,"Gamemaps/"); strcat_s(file,filename);            
            done=1;
        } else if (KEYPRESS(ENTER)) { strcpy_s(file,"Gamemaps/"); strcat_s(file,filename); REPORT_S("file = ",file); done=1;}
        g_sprite->End(); END_SCENE("save gamemap");
        if (KEYPRESS(ESCAPE)) done=2;
    }while(done==0);
    if (done==1) {
        FILE *f=NULL;
        fopen_s(&f,file,"wb");
        if (!f) { REPORT("file creation error"); } 
        else {          
            fwrite(&g_tiles, sizeof(g_tiles[0][0]), ((LEVEL_WIDTH+10)*(LEVEL_HEIGHT+10)), f );              
            fclose(f);
            if (strlen(g_custom_file)<1) {strcat_s(g_custom_file,filename); REPORT("strlen < 1");}
        }
    }
    BEGIN_SCENE("save gamemap"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
}




//--------------------------------
// E D I T O R  F I L E S  M E N U
//--------------------------------
void editor_files_menu() {  
    // for loading or saving game / level maps
    FILE *f=NULL;
    char filename[100];
    POINT cp;
    D3DCOLOR col=default_color;
    RECT r; 
    enum SELECT_TYPE {NO_SELECT, GET_SELECT, OPEN_DEFAULT, SAVE_DEFAULT, OPEN_CUSTOM, SAVE_CUSTOM, NEW_FILE}; 
    int selection=GET_SELECT;
    bool done=false, open_ok=false;
    sprintf_s(filename, _T("Gamemaps/Gamemap%d.lev"), g_level); //default level name
    g_sprite->End(); END_SCENE("editor_files_menu");
    SAFE_DO;
        BEGIN_SCENE("editor_files_menu"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
        HANDLE_LOST_DEVICES( , );
        GetCursorPos(&cp); UpdateMousePos(&cp); 
        switch(selection) {
        case GET_SELECT:
            SET_RECT(r,10,100,130,112); if (mouse_inside(r)) {col=0xFF66BB00; if (left_mouse_clicked) {left_mouse_clicked=false; selection=OPEN_DEFAULT;}}
            PRINT_TEXT_EXT(10,100,col,"OPEN default level file (Alt+O)"); PRINT_TEXT_EXT(260,100,col,filename); col=default_color; TEXT_POSITION(10,100);
            SET_RECT(r,10,120,130,132); if (mouse_inside(r)) {col=0xFF66BB00; if (left_mouse_clicked) {left_mouse_clicked=false; selection=SAVE_DEFAULT;}}
            PRINT_T(10,20,col,"SAVE default level file (Alt+S)"); PRINT_TEXT_EXT(260,120,col,filename); col=default_color; TEXT_POSITION(10,120);
            SET_RECT(r,10,160,100,172); if (mouse_inside(r)) {col=0xFF66BB00; if (left_mouse_clicked) {left_mouse_clicked=false; selection=OPEN_CUSTOM;}}
            PRINT_T(10,40,col,"OPEN custom file"); col=default_color;
            SET_RECT(r,10,180,100,192); if (mouse_inside(r)) {col=0xFF66BB00; if (left_mouse_clicked) {left_mouse_clicked=false; selection=SAVE_CUSTOM;}}
            PRINT_T(10,20,col,"SAVE custom file"); col=default_color;
            SET_RECT(r,10,220,42,232); if (mouse_inside(r)) {col=0xFF66BB00; if (left_mouse_clicked) {left_mouse_clicked=false; selection=NEW_FILE;}}
            PRINT_T(10,40,col,"NEW"); col=default_color;
            SET_RECT(r,10,240,42,252); if (mouse_inside(r)) {col=0xFF66BB00; if (left_mouse_clicked) {left_mouse_clicked=false; selection=NO_SELECT; done=true;}}
            PRINT_T(10,20,col,"EXIT"); col=default_color;       
            if (KEYPRESS(ALT)) {
                if (KEYPRESS(O)) selection=OPEN_DEFAULT;
                if (KEYPRESS(S)) selection=SAVE_DEFAULT;
            }
        break;
        case OPEN_DEFAULT:
            open_ok=true;
            if (g_gamemap_changed) {
                open_ok=PromptUser("Open file without saving??? (Y/N)");
            }
            if (open_ok) {
                fopen_s(&f,filename,"rb");
                if (!f) { REPORT("file read error"); } 
                else {  
                    fread(&g_tiles, sizeof(g_tiles[0][0]), ((LEVEL_WIDTH+10)*(LEVEL_HEIGHT+10)), f );               
                    fclose(f);  
                }
                g_gamemap_changed=false;
            }
            done=true;
        break;
        case SAVE_DEFAULT:  
            if (FileExists(filename)) {
                SYSTEMTIME st;
                GetSystemTime(&st);
                char backupfilename[100];
                sprintf_s(backupfilename, _T("Gamemaps/backup/Gamemap%d_%d_%d.lev"), g_level,st.wMonth,st.wDay); //default level name
                CopyFile(filename,backupfilename,false);
            }
            fopen_s(&f,filename,"wb");
            if (!f) { REPORT("file creation error"); } 
            else {              
                fwrite(&g_tiles, sizeof(g_tiles[0][0]), ((LEVEL_WIDTH+10)*(LEVEL_HEIGHT+10)), f );              
                fclose(f);  
            }
            done=true;
        break;
        case OPEN_CUSTOM:
            open_ok=true;
            if (g_gamemap_changed) {
                open_ok=PromptUser("Open file without saving??? (Y/N)");
            }
            if (open_ok) {
                char file[100]; 
                sprintf_s(filename, _T("Gamemap%d.lev"), g_level); //default tilemap to load (based on g_level)     
                g_sprite->End(); END_SCENE("editor files menu(open custom)");
                //file,  what to look for,  what to ignore
                if (!choose_file(filename, "\\Gamemaps\\", "*.lev", "____","lock","edit", "Please choose a file to load: ")) 
                    strcpy_s(file,"Gamemaps/"); 
                else strcpy_s(file,"Gamemaps/backup/"); 
                strcat_s(file,filename);                            
                fopen_s(&f, file, "rb");
                if (!f) { REPORT_S("Can't read gamemap file: ",file); } else {      
                    fread(&g_tiles, sizeof(g_tiles[0][0]), ((LEVEL_WIDTH+10)*(LEVEL_HEIGHT+10)), f );               
                    fclose(f);                  
                }                       
                BEGIN_SCENE("load tilemap"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
                g_gamemap_changed=false;
                strcpy_s(g_custom_file,filename); 
            }
            done=true;
        break;
        case SAVE_CUSTOM:
            SaveGamemap();
            done=true;
        break;
        case NEW_FILE:
            bool delete_changes=true;
            if (g_gamemap_changed) {
                delete_changes=PromptUser("Delete changes? (Y/N)");
            }
            if (delete_changes) {
                TEST_ARRAY2(g_tiles,LEVEL_WIDTH+10,LEVEL_HEIGHT+10);
                int b=0,a=0;
                SAFE_DO;
                    a=0;
                    SAFE_DO;
                        reset_tile(a,b);
                    a++;SAFE_WHILE(a<LEVEL_WIDTH+10);               
                b++;SAFE_WHILE(b<LEVEL_HEIGHT+10);
                g_gamemap_changed=false;
            }
            done=true;
        break;
        }
        if (PRESS_ESCAPE) {
            if (g_gamemap_changed) {
                if (PromptUser("Exit without saving? (Y/N)")) done=true;
            } else done=true;       
        }
        g_sprite->End(); END_SCENE("editor_files_menu");    message_pump();
    }while(!done);
    BEGIN_SCENE("editor_files_menu"); g_sprite->Begin(D3DXSPRITE_ALPHABLEND);   
}//editor_files_menu
    
    
    
// S E T  T I L E  O F F S E T
void set_tile_offset(int a, int b, float x, float y) {
    int ma, mb;//, mi;  
    Sleep(25);  
    ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b; 
    g_tiles[ma][mb].offset.x+=x; 
    g_tiles[ma][mb].offset.y+=y; 
}//set_tile_offset



// R E S E T  T I L E  O F F S E T
void reset_tile_offset(int a, int b) {
    int ma, mb, mi; 
    Sleep(25);  
    ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b; 
    mi=g_tiles[a][b].i;
    g_tiles[ma][mb].offset.x=tile[mi].offset.x;
    g_tiles[ma][mb].offset.y=tile[mi].offset.y;
    g_tiles[ma][mb].rotation=g_tiles[ma][mb].rotation2=0.0f;
    g_tiles[ma][mb].scale=g_tiles[ma][mb].scale2=1.0f;
    g_tiles[ma][mb].type=tile[mi].type;
}//reset_tile_offset




//----------------------------
// E D I T O R  M I N I  M A P
//----------------------------
void editor_mini_map() {
    POINT cp;
    RECT rc;
    int a,b,ta,tb,x,y,i;//,td;
    float scale=0.25f;
    g_sprite_scale=D3DXVECTOR2(scale,scale);
    g_sprite_center=D3DXVECTOR2(0,0); g_sprite_rot_center=D3DXVECTOR2(0,0);
    g_sprite->End(); END_SCENE("editor_mini_map");
    bool done=false, first_click=true;
    RECT r,sel={0};
    int copy_a1=-1, copy_b1=-1, copy_a2=-1, copy_b2=-1;
    do {
        GetCursorPos(&cp); UpdateMousePos(&cp); 
        g_sprite->Begin(D3DXSPRITE_ALPHABLEND); BEGIN_SCENE("editor_mini_map");     
        b=-40; 
        tb=tile_y+b;
        if (tb<0) {b-=tb; tb=0;}
        y=b*16+g_half_height+(int)tile_y_off;
        SAFE_DO {
            tb=tile_y+b;   if (tb<0) {b++; y+=16; continue;}
            if (tb>=LEVEL_HEIGHT) break;
            a=-52;
            ta=tile_x+a;
            if (ta<0) {a-=ta; ta=0;}
            x=a*16+g_half_width+(int)tile_x_off;
            SAFE_DO {
                ta=tile_x+a;   if (ta<0) {a++; x+=16; continue;}
                if (ta>=LEVEL_WIDTH) break;

                r.left=(LONG)x-1; r.right=(LONG)x+17; r.top=(LONG)y-1; r.bottom=(LONG)y+17;
                if (mouse_inside(r)) {                      
                    if (left_mouse_clicked) {                                               
                        if (KEYPRESS(SHIFT)) {                          
                            if (first_click) {                          
                                sel.left=sel.right=(LONG)x; copy_a1=ta; copy_a2=ta;
                                sel.top=sel.bottom=(LONG)y; copy_b1=tb; copy_b2=tb;
                                first_click=false;
                            } else {                                
                                sel.right=(LONG)x+64L; copy_a2=ta;
                                sel.bottom=(LONG)y+64L; copy_b2=tb;
                            }                                                       
                        }
                    } else first_click=true;

                    if (copy_a2>-1) {
                        bool move_it=false;
                        if (KEYPRESS(M)) move_it=true;
                        if ((KEYPRESS(V))||(move_it)) {
                            int sb=tb;
                            int v1=copy_b1;
                            SAFE_DO {
                                int sa=ta;
                                int h1=copy_a1;
                                SAFE_DO {
                                    TEST_ARRAY2(g_tiles,sa,sb); TEST_ARRAY2(g_tiles,h1,v1); 
                                    if (g_tiles[h1][v1].type>-1) //don't copy empty tiles
                                        memcpy(&g_tiles[sa][sb],&g_tiles[h1][v1],sizeof g_tiles[h1][v1]);
                                    int adif=h1-g_tiles[h1][v1].m_a;
                                    int bdif=v1-g_tiles[h1][v1].m_b;
                                    g_tiles[sa][sb].m_a=sa+adif;
                                    g_tiles[sa][sb].m_b=sb+bdif;                                        
                                    h1++; sa++;
                                }SAFE_WHILE(h1<copy_a2);
                                v1++; sb++;
                            }SAFE_WHILE(v1<copy_b2);
                        }
                        if (move_it) {                              
                            int v1=copy_b1;
                            SAFE_DO {                                   
                                int h1=copy_a1;
                                SAFE_DO {
                                    TEST_ARRAY2(g_tiles,h1,v1);
                                    remove_tile(h1,v1);
                                    h1++;
                                }SAFE_WHILE(h1<copy_a2);
                                v1++;
                            }SAFE_WHILE(v1<copy_b2);
                            copy_a1=copy_a2=copy_b1=copy_b2=-1;
                        }
                    }                   
                }
                
                g_sprite_trans=D3DXVECTOR2((float)x,(float)y);
                D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,0,&g_sprite_trans);
                g_sprite->SetTransform(&g_matrix_2d);
                SET_RECT(rc,0,64,64,128); 
                g_sprite->Draw(g_far_background.texture.tx,&rc,NULL,NULL,0x66888888);               

                if ((g_tiles[ta][tb].m_a!=ta)||(g_tiles[ta][tb].m_b!=tb)) {a++; x+=16; continue;}

                i=g_tiles[ta][tb].i;
                //if ((ta==0)&&(tb==0)) {a++; x+=16; continue;}
                if (g_tiles[ta][tb].type>-1) {                  
                    g_sprite_trans=D3DXVECTOR2((float)x,(float)y);
                    D3DXMatrixTransformation2D(&g_matrix_2d,&g_sprite_center,0.0,&g_sprite_scale,&g_sprite_rot_center,0,&g_sprite_trans);
                    g_sprite->SetTransform(&g_matrix_2d);                   
                    g_sprite->Draw(tile_texture.tx,&tile[i].r,NULL,NULL,0xFFDDDDDD);                
                } 
                
                a++; x+=16;
            }SAFE_WHILE(a<tile_x+52);
            b++; y+=16;
        }SAFE_WHILE(b<tile_y+40);       

        if (KEYPRESS(RIGHT)) GameScroll(-16.0f,0.0f);
        if (KEYPRESS(LEFT)) GameScroll(16.0f,0.0f);
        if (KEYPRESS(DOWN)) GameScroll(0.0f,-16.0f);
        if (KEYPRESS(UP)) GameScroll(0.0f,16.0f);
        if ((KEYPRESS(ESCAPE))||(KEYPRESS(ENTER))) {done=true;}
        if (copy_a2>-1) {           
            sel.left=g_half_width-832+(copy_a1-(tile_x-52))*16+(int)tile_x_off; //52*16
            sel.top=g_half_height-640+(copy_b1-(tile_y-40))*16+(int)tile_y_off; //40*16
            sel.right=sel.left+(copy_a2-copy_a1)*16;
            sel.bottom=sel.top+(copy_b2-copy_b1)*16;                
            D3DCOLOR col=D3DCOLOR_ARGB(255,RAND_INT(255),RAND_INT(255),RAND_INT(255));
            draw_box(sel,col);      
        }

        g_sprite->End(); END_SCENE("editor_mini_map"); message_pump();
    }while(!done);
    Sleep(100);
    g_sprite->Begin(D3DXSPRITE_ALPHABLEND); BEGIN_SCENE("editor_mini_map");
    D3DXMatrixIdentity(&g_matrix_2d); g_sprite->SetTransform(&g_matrix_2d);
}//editor_mini_map




//-------------------
// E D I T  L E V E L
//-------------------
void edit_level() {
    bool done=false,remove_occupied=false,first_click=true;
    RECT rc,r,sel={0};
    VEC pos; pos.z=0;
    POINT cp;
    int tn=0; //<--index of current tile to paint
    D3DCOLOR col=0xFF999999;    
    bool show_types=true, show_instructions=false;
    int do_fill=0; //determine whether to fill an area or not
    int la,ga,lb,gb; //filling variables
    int copy_a1=-1, copy_b1=-1, copy_a2=-1, copy_b2=-1;
    int mouse_button=VK_RBUTTON; 
    int sp_a, sp_b; //starting point for tiles fill
    if (GetSystemMetrics(SM_SWAPBUTTON)) mouse_button=VK_LBUTTON;   

    D3DXMatrixIdentity(&g_world_matrix);    
    g_gpu->SetTransform(D3DTS_WORLD, &g_world_matrix);
    g_gpu->SetFVF(MY_CUSTOM_VERTEX);    
    g_gpu->SetStreamSource(0,g_vb,0,sizeof(stMyVertex));        

    SAFE_DO;
        DWORD starting_point = GetTickCount();
        HANDLE_LOST_DEVICES( , );
        BEGIN_SCENE("edit level"); 
        g_sprite->Begin(D3DXSPRITE_ALPHABLEND);
        
        GetCursorPos(&cp); UpdateMousePos(&cp); 
        if (PRESS_ESCAPE) done=true;
        if (KEYPRESS(SHIFT)) {
            if (KEYPRESS(QUESTION)) {
                if (show_types) show_types=false; else show_types=true; Sleep(150);
            }
        }
        if (KEYPRESS(ALT)) {
            if (KEYPRESS(R)) {
                if (remove_occupied) {remove_occupied=false; Sleep(160);} else {remove_occupied=true; Sleep(160);}
            }
        }
        if (KEYPRESS(RIGHT)) {GameScroll(-2.0f,0.0f); }
        if (KEYPRESS(LEFT)) {GameScroll(2.0f,0.0f); }
        if (KEYPRESS(DOWN)) GameScroll(0.0f,-2.0f);
        if (KEYPRESS(UP)) GameScroll(0.0f,2.0f);        
        if (KEYPRESS(DELETE)) if ((tile_x-1)>0) {tile_x-=1; tile_x_off=0; GameScroll(0.0f,0.0f);}
        if (KEYPRESS(END)) if ((tile_x+1)<LEVEL_WIDTH) {tile_x+=1; tile_x_off=0; GameScroll(0.0f,0.0f);}
        if (KEYPRESS(PAGEUP)) if ((tile_y-1)>0) {tile_y-=1; tile_y_off=0; GameScroll(0.0f,0.0f);}
        if (KEYPRESS(PAGEDOWN)) if ((tile_y+1)<LEVEL_HEIGHT) {tile_y+=1; tile_y_off=0; GameScroll(0.0f,0.0f);}                          
        
        D3DCOLOR bcl=0xFF666666; 
        if (!show_types) bcl=0xFFFFFFFF;    
        g_far_background.pos1.z=1;g_mid_background.pos1.z=1;
        g_far_background.pos2.z=1;g_mid_background.pos2.z=1;
        DRAWC(g_far_background.texture,NULL,&g_far_background.pos1,bcl);    
        DRAWC(g_far_background.texture,NULL,&g_far_background.pos2,bcl);
        DRAWC(g_mid_background.texture,&g_mid_background.r2,&g_mid_background.pos1,bcl);    
        DRAWC(g_mid_background.texture,&g_mid_background.r2,&g_mid_background.pos2,bcl);    
        
        bool control_clicked=false; //control button clicked or just painting tiles?
        if (left_mouse_clicked) {           
            r.left=450; r.top=g_height-25; r.right=560; r.bottom=g_height;
            if (mouse_inside(r)) {
                left_mouse_clicked=false;
                int i=tile_selector(); 
                if (i>-1) tn=i;
                control_clicked=true;
            }
            r.left=50; r.top=g_height-25; r.right=160; r.bottom=g_height;
            if (mouse_inside(r)) {
                left_mouse_clicked=false;
                editor_files_menu();
                control_clicked=true;
            }
            r.left=260; r.top=g_height-25; r.right=370; r.bottom=g_height;
            if (mouse_inside(r)) {
                left_mouse_clicked=false;
                editor_mini_map();
                control_clicked=true;
            }
            r.left=656; r.top=g_height-25; r.right=785; r.bottom=g_height;
            if (mouse_inside(r)) {
                left_mouse_clicked=false;
                if (!show_instructions) show_instructions=true; else show_instructions=false;
                control_clicked=true;
            }
        }   
        int ln=tn;      
        if (KEYPRESS(X)) {tn++; Sleep(200); if (tn>=g_num_tile_types) tn=0;}
        if (KEYPRESS(Z)) {tn--; Sleep(200); if (tn<0) tn=g_num_tile_types-1;} 
        if (KEYPRESS(SQUIGGLE)) tn=0;
        if (KEYPRESS(1)) tn=1; if (KEYPRESS(2)) tn=2; if (KEYPRESS(3)) tn=3; if (KEYPRESS(4)) tn=4; if (KEYPRESS(5)) tn=5; if (KEYPRESS(6)) tn=6; if (KEYPRESS(7)) tn=7; if (KEYPRESS(8)) tn=8; if (KEYPRESS(9)) tn=9; if (KEYPRESS(0)) tn=10;
        if (KEYPRESS(Q)) tn=11; if (KEYPRESS(W)) tn=12; if (KEYPRESS(E)) tn=13; if (KEYPRESS(R)) tn=14; if (KEYPRESS(T)) tn=15; if (KEYPRESS(Y)) tn=16; if (KEYPRESS(U)) tn=17; if (KEYPRESS(I)) tn=18; if (KEYPRESS(O)) tn=19; if (KEYPRESS(P)) tn=20;
        if (KEYPRESS(A)) tn=21; if (KEYPRESS(S)) tn=22; if (KEYPRESS(D)) tn=23; if (KEYPRESS(F)) tn=24; if (KEYPRESS(G)) tn=25; if (KEYPRESS(H)) tn=26; if (KEYPRESS(J)) tn=27; if (KEYPRESS(K)) tn=28; if (KEYPRESS(L)) tn=29;     
        if (tn>=g_num_tile_types) tn=ln;
        
        g_gpu->SetFVF(MY_CUSTOM_VERTEX);        
        g_gpu->SetStreamSource(0,g_vb,0,sizeof(stMyVertex));

        animate_tiles();        

        g_sprite->End(); 
        int a,b,y,ar=tile_x-13;
        int ma,mb;      
        int cnt=0; //for counting images to be drawn closer
        VEC p2; p2.z=0;
        b=tile_y-10; y=g_half_height-64*10+(int)tile_y_off; 
        SAFE_DO;
            if ((b<0)||(b>LEVEL_HEIGHT)) {b++; y+=64; continue;} //skip if out of bounds            
            if (y>(g_height+100)) break;
            a=ar;
            if (a>=0) if ((tile[g_tiles[a][b].i].r.bottom+y)<-100) {b++; y+=64; continue;}
            int x=g_half_width-64*13+(int)tile_x_off;
            SAFE_DO;
                if ((a<0)||(a>LEVEL_WIDTH)) {a++; x+=64; continue;} //skip it if there's nothing there                              
                //TEST_ARRAY2(g_tiles,a,b);
                ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b;             
                if (x>(g_width+100)) break;

                if ((tile[g_tiles[ma][mb].i].r.right+x)<-100) {a++; x+=64; continue;}
                pos.x=(float)x; pos.y=(float)y;                                 
                if (g_tiles[a][b].type>-1) {                    
                    ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b;
                    if ((ma==a)&&(mb==b)) {
                        float offx=g_tiles[ma][mb].offset.x, offy=g_tiles[ma][mb].offset.y;
                        float off2x=g_tiles[ma][mb].offset2.x, off2y=g_tiles[ma][mb].offset2.y;
                        int ind=g_tiles[a][b].i;
                        VEC pz, pz2; pz.z=pz2.z=0.0f; 
                        pz.x=pos.x+offx; pz.y=pos.y+offy;
                        pz2.x=pos.x+off2x; pz2.y=pos.y+off2y;                   
                        if (tile[ind].layer_type==LAYER_BOX) draw_cube(a,b,pz2,0xFFFFFFFF); 

                        rec_images[cnt].a=a; rec_images[cnt].b=b; rec_images[cnt].tn=ind; 
                        COPY_VECTOR(rec_images[cnt].pos, pz); 
                        COPY_VECTOR(rec_images[cnt].pos2, pz2);
                        cnt++;                      
                    }
                }               
                x+=64; a++;
            }while(a<tile_x+13);
            y+=64; b++;
        }while(b<tile_y+10);        
        g_sprite->Begin(D3DXSPRITE_ALPHABLEND);     

        draw_back_tile_layers(0.92f);       

        if (cnt>0) {
            int c=0;
            SAFE_DO {
                a=rec_images[c].a; b=rec_images[c].b;
                if (g_tiles[a][b].depth==FAR_BEHIND) {                  
                    if (g_tiles[a][b].i2>-1) draw_tile(a,b,rec_images[c].pos2,true,0xFFFFFFFF);
                    draw_tile(a,b,rec_images[c].pos,false,0xFFFFFFFF);      
                }           
                c++;
            }SAFE_WHILE(c<cnt);
            c=0;
            SAFE_DO {
                a=rec_images[c].a; b=rec_images[c].b;
                if (g_tiles[a][b].depth==BEHIND) {                  
                    if (g_tiles[a][b].i2>-1) draw_tile(a,b,rec_images[c].pos2,true,0xFFFFFFFF);
                    draw_tile(a,b,rec_images[c].pos,false,0xFFFFFFFF);                  
                }
                c++;
            }SAFE_WHILE(c<cnt);
            c=0;
            SAFE_DO {
                a=rec_images[c].a; b=rec_images[c].b;
                if (g_tiles[a][b].depth==INFRONT) {
                    if (g_tiles[a][b].i2>-1) draw_tile(a,b,rec_images[c].pos2,true,0xFFFFFFFF);
                    draw_tile(a,b,rec_images[c].pos,false,0xFFFFFFFF);                  
                }
                c++;
            }SAFE_WHILE(c<cnt);
            c=0;
            SAFE_DO {
                a=rec_images[c].a; b=rec_images[c].b;
                if (g_tiles[a][b].depth==NEAR_INFRONT) {
                    if (g_tiles[a][b].i2>-1) draw_tile(a,b,rec_images[c].pos2,true,0xFFFFFFFF);
                    draw_tile(a,b,rec_images[c].pos,false,0xFFFFFFFF);                  
                }
                c++;
            }SAFE_WHILE(c<cnt);
        }
        draw_front_tile_layers(1.02f,2); 
        draw_front_tile_layers(1.04f,3); 
        
        b=tile_y-10; y=g_half_height-64*10+(int)tile_y_off; 
        SAFE_DO;
            if ((b<0)||(b>LEVEL_HEIGHT)) {b++; y+=64; continue;} //skip if out of bounds            
            if (y>(g_height+100)) break;
            a=ar;
            if (a>=0) if ((tile[g_tiles[a][b].i].r.bottom+y)<-100) {b++; y+=64; continue;}
            int x=g_half_width-64*13+(int)tile_x_off;
            SAFE_DO;
                if ((a<0)||(a>LEVEL_WIDTH)) {a++; x+=64; continue;} //skip it if there's nothing there              
                //TEST_ARRAY2(g_tiles,a,b);
                ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b;             
                if (x>(g_width+100)) break;
                if ((tile[g_tiles[ma][mb].i].r.right+x)<-100) {a++; x+=64; continue;}
                pos.x=(float)x; pos.y=(float)y; 
                r.left=(LONG)pos.x-1; r.right=(LONG)pos.x+65; r.top=(LONG)pos.y-1; r.bottom=(LONG)pos.y+65;
                col=0xFF999999;
                if (mouse_inside(r)) {
                    col=0xFFFFBB00;

                    VEC pz; pz.z=0.0f; pz.x=pos.x+tile[tn].offset.x; pz.y=pos.y+tile[tn].offset.y;
                    DRAWC(tile_texture,&tile[tn].r,&pz,0x77FFFFFF);
                    
                    if (KEYPRESS(PERIOD)) {
                        if (KEYPRESS(SHIFT)) change_tile_type(a,b,1,true);
                        else change_tile_type(a,b,1,false);
                    }
                    if (KEYPRESS(COMMA)) {
                        if (KEYPRESS(SHIFT)) change_tile_type(a,b,-1,true);
                        else change_tile_type(a,b,-1,false);                        
                    }                   
                    if (KEYPRESS(ADD)) {
                        g_tiles[ma][mb].depth--;
                        if (g_tiles[ma][mb].depth<0) g_tiles[ma][mb].depth=0;
                    }
                    if (KEYPRESS(SUBTRACT)) {
                        g_tiles[ma][mb].depth++;
                        if (g_tiles[ma][mb].depth>NEAR_INFRONT) g_tiles[ma][mb].depth=NEAR_INFRONT;
                    }
                    if (KEYPRESS(LBRACKET)) {g_tiles[ma][mb].rotation-=0.01f; if (g_tiles[ma][mb].rotation<0.0f) g_tiles[ma][mb].rotation=6.28f;}
                    if (KEYPRESS(RBRACKET)) {g_tiles[ma][mb].rotation+=0.01f; if (g_tiles[ma][mb].rotation>6.28f) g_tiles[ma][mb].rotation=0.0f;}
                    if (KEYPRESS(COLON)) {g_tiles[ma][mb].scale-=0.01f; if (g_tiles[ma][mb].scale<0.001f) g_tiles[ma][mb].scale=0.001f;}
                    if (KEYPRESS(QUOTE)) {g_tiles[ma][mb].scale+=0.01f; if (g_tiles[ma][mb].scale>100.0f) g_tiles[ma][mb].scale=100.0f;}                    
                    if (copy_a2>-1) {
                        bool move_it=false;
                        if (KEYPRESS(M)) move_it=true;
                        if ((KEYPRESS(V))||(move_it)) {
                            int sb=b;
                            int v1=copy_b1;
                            SAFE_DO {
                                int sa=a;
                                int h1=copy_a1;
                                SAFE_DO {
                                    TEST_ARRAY2(g_tiles,sa,sb); TEST_ARRAY2(g_tiles,h1,v1);
                                    if (g_tiles[h1][v1].type>-1) //don't copy empty tiles
                                        memcpy(&g_tiles[sa][sb],&g_tiles[h1][v1],sizeof g_tiles[h1][v1]);
                                    int adif=h1-g_tiles[h1][v1].m_a;
                                    int bdif=v1-g_tiles[h1][v1].m_b;
                                    g_tiles[sa][sb].m_a=sa+adif;
                                    g_tiles[sa][sb].m_b=sb+bdif;                                        
                                    h1++; sa++;
                                }SAFE_WHILE(h1<copy_a2);
                                v1++; sb++;
                            }SAFE_WHILE(v1<copy_b2);
                        }
                        if (move_it) {                              
                            int v1=copy_b1;
                            SAFE_DO {                                   
                                int h1=copy_a1;
                                SAFE_DO {
                                    TEST_ARRAY2(g_tiles,h1,v1);
                                    remove_tile(h1,v1);
                                    h1++;
                                }SAFE_WHILE(h1<copy_a2);
                                v1++;
                            }SAFE_WHILE(v1<copy_b2);
                            copy_a1=copy_a2=copy_b1=copy_b2=-1;
                        }
                    }                   
                    if (KEYPRESS(NUMLEFT)) set_tile_offset(a,b,-1,0);
                    if (KEYPRESS(NUMRIGHT)) set_tile_offset(a,b,1,0);
                    if (KEYPRESS(NUMUP)) set_tile_offset(a,b,0,-1);
                    if (KEYPRESS(NUMDOWN)) set_tile_offset(a,b,0,1);
                    if (KEYPRESS(NUMPAD5)) reset_tile_offset(a,b);
                    if (left_mouse_clicked) {                       
                        if (KEYPRESS(CONTROL)) {sp_a=a;sp_b=b; do_fill=1; left_mouse_clicked=false;}
                        else if (do_fill==1) {                                                      
                            if (a<sp_a) {la=a; ga=sp_a;} else {la=sp_a; ga=a;} 
                            if (b<sp_b) {lb=b; gb=sp_b;} else {lb=sp_b; gb=b;}                          
                            do_fill=2;
                        }
                        if (!control_clicked) {                                                                                                         
                            //filling code begin----------------:
                            if (do_fill==2) {
                                do_fill=0;                          
                                SAFE_DO;                                
                                    int al=la; 
                                    SAFE_DO;                                        
                                        add_tile(al,lb,tn,false,remove_occupied); 
                                        al++;
                                    }while(al<ga); 
                                lb++;
                                }while(lb<gb);                          
                            }   
                            //----------------------------------^
                            if ((do_fill<1)||(do_fill>2)) {
                                //SELECT region for copy and paste-----------------:
                                if (KEYPRESS(SHIFT)) {
                                    if (first_click) {
                                        sel.left=sel.right=(LONG)pos.x; copy_a1=a; copy_a2=a;
                                        sel.top=sel.bottom=(LONG)pos.y; copy_b1=b; copy_b2=b;
                                        first_click=false;
                                    } else {
                                        sel.right=(LONG)pos.x+64L; copy_a2=a;
                                        sel.bottom=(LONG)pos.y+64L; copy_b2=b;
                                    }                                                       
                                } else {
                                    first_click=true;
                                    add_tile(a,b,tn,false,remove_occupied); 
                                    if (do_fill>2) do_fill=0;
                                    if ((do_fill==0)&&(KEYPRESS(INSERT))) {add_tile(a,b,tn,true,remove_occupied); if (do_fill>2) do_fill=0;}
                                }
                            }                           
                        }
                    } else first_click=true;
                    if (KEY_IS_DOWN(mouse_button)) {
                        SetRectEmpty(&sel); copy_a1=copy_a2=copy_b1=copy_b2=-1;
                        if (KEYPRESS(CONTROL)) {do_fill=3; sp_a=a; sp_b=b;} 
                        else if (do_fill==3) {
                            if (a<sp_a) {la=a; ga=sp_a;} else {la=sp_a; ga=a;} 
                            if (b<sp_b) {lb=b; gb=sp_b;} else {lb=sp_b; gb=b;}                          
                            do_fill=4;
                        }
                        //erase area code begin----------------
                        if (do_fill==4) {
                            if (PromptUser("Delete tiles? (Y/N)")) do_fill=5; else do_fill=0;                           
                            if (do_fill==5) {
                                do_fill=0;
                                SAFE_DO;                                
                                    int al=la; 
                                    SAFE_DO;                                    
                                        remove_tile(al,lb);
                                        al++;
                                    }while(al<ga); 
                                    lb++;
                                }while(lb<gb);                          
                            }
                        }
                        //-------------------------------------
                        remove_tile(a,b);
                    }
                }
                if (show_types) {SET_RECT(rc,0,64,64,128); DRAWC(tile_types_texture,&rc,&pos,col);}
                x+=64; a++;
            }while(a<tile_x+13);
            y+=64; b++;
        }while(b<tile_y+10);        

        if (show_types) {
            b=tile_y-10; y=g_half_height-64*10+(int)tile_y_off; 
            SAFE_DO;
                if ((b<0)||(b>LEVEL_HEIGHT)) {b++; y+=64; continue;} //skip if out of bounds            
                if (y>(g_height+100)) break;
                a=ar;
                if (a>=0) if ((tile[g_tiles[a][b].i].r.bottom+y)<-100) {b++; y+=64; continue;}
                int x=g_half_width-64*13+(int)tile_x_off;
                SAFE_DO;
                    if ((a<0)||(a>LEVEL_WIDTH)) {a++; x+=64; continue;} //skip it if there's nothing there                              
                    ma=g_tiles[a][b].m_a; mb=g_tiles[a][b].m_b;             
                    if (x>(g_width+100)) break;
                    if ((tile[g_tiles[ma][mb].i].r.right+x)<-100) {a++; x+=64; continue;}
                    pos.x=(float)x; pos.y=(float)y;                                 
                    switch(g_tiles[a][b].type) {                    
                        case SOLID: SET_RECT(rc,0,0,64,64); DRAW(tile_types_texture,&rc,&pos); break;
                        case PASS:  SET_RECT(rc,0,64,64,128); DRAW(tile_types_texture,&rc,&pos); break;
                        case LIQUID: SET_RECT(rc,64,0,128,64); DRAW(tile_types_texture,&rc,&pos); break;
                        case BOUNCE: SET_RECT(rc,128,0,192,64); DRAW(tile_types_texture,&rc,&pos); break;
                        case BUTTON: SET_RECT(rc,128,64,192,128); DRAW(tile_types_texture,&rc,&pos); break;
                        case JUMP_PASSABLE: SET_RECT(rc,0,0,64,64); DRAWC(tile_types_texture,&rc,&pos,0xFF7700FF); break;                   
                    }                               
                    x+=64; a++;
                }while(a<tile_x+13);
                y+=64; b++;
            }while(b<tile_y+10);
        }

        if (copy_a2>-1) {           
            sel.left=g_half_width-832+(copy_a1-(tile_x-13))*64+(int)tile_x_off; //13*64
            sel.top=g_half_height-640+(copy_b1-(tile_y-10))*64+(int)tile_y_off; //10*64
            sel.right=sel.left+(copy_a2-copy_a1)*64;
            sel.bottom=sel.top+(copy_b2-copy_b1)*64;
            D3DCOLOR col=D3DCOLOR_ARGB(255,RAND_INT(255),RAND_INT(255),RAND_INT(255));
            draw_box(sel,col);          //draw_box(sel,0xFF00DD11);         
        }

        //show files tab    
        pos.x=40; pos.y=(float)g_height-27; SET_RECT(rc,0,226,162,255); DRAW(tile_types_texture,&rc,&pos);
        pos.x=240; DRAW(tile_types_texture,&rc,&pos);
        pos.x=440; DRAW(tile_types_texture,&rc,&pos);
        pos.x=640; DRAW(tile_types_texture,&rc,&pos);
        PRINT_TEXT_EXT(70,g_height-18,0xFF550066,"FILES");
        PRINT_TEXT_EXT(270,g_height-18,0xFF550066,"MINI-MAP");
        PRINT_TEXT_EXT(470,g_height-18,0xFF550066,"TILE SELECTOR");
        PRINT_TEXT_EXT(670,g_height-18,0xFF550066,"INSTRUCTIONS");
        PRINT_TEXT_EXT(10,10,default_color,"ALT+R = remove occupied");
        if (remove_occupied) {PRINT_TEXT_EXT(170,10,0xFF88DD00,"(ON)");}
        if (!remove_occupied) {PRINT_TEXT_EXT(170,10,0xFF666600,"(OFF)");}
        PRINT_TEXT_EXT(10,20,default_color,"<  >  to change tile-type (shift<> = all)");
        PRINT_TEXT_EXT(10,30,default_color,"-  +  to change tiles-depth");
        PRINT_TEXT_EXT(10,40,default_color,"numpad = change tiles-offset");

        if (show_instructions) {
            draw_black_rect(g_width-200,0,g_width,g_height);
            int hp=g_width-190;
            PRINT_TEXT_EXT(hp,20,0xFFBB1100,"SHIFT+drag = selection");
            PRINT_T(hp,20,0xFF992200,"selection: V = paste copy");              
            PRINT_T(hp,20,0xFF881100,"selection: M = remove and paste");
            PRINT_T(hp,30,default_color,"PageUp/PageDown = fast up/down");
            PRINT_T(hp,20,default_color,"Delete/End = fast left/right");
            PRINT_T(hp,30,0xFFBB1100,"Z, X = change tile selection");
            PRINT_T(hp,20,0xFFFF1100,"< > [,.] = change tile type");
            PRINT_T(hp,20,0xFFAA3300,"- + = change tile depth setting");
            PRINT_T(hp,30,0xFF11BB00,"ALT+R = toggle 'remove selected'");
            PRINT_T(hp,20,0xFF0033BB,"? = toggle grid view on/off");            
            PRINT_T(hp,30,0xFF887700,"[ ] = rotate");
            PRINT_T(hp,20,0xFF66AA00,"; ' = scale");
            PRINT_T(hp,30,default_color,"right-click = delete");
            PRINT_T(hp,20,0xFF009933,"Control+click = start-point for fill");
            PRINT_T(hp,20,0xFF008844,"start_fill + click = fill (endpoint)");
            PRINT_T(hp,30,0xFF007744,"Ctrl+Rclick = start-point delete");
            PRINT_T(hp,20,0xFF006644,"Shift+Rclick = end-point delete");
            PRINT_T(hp,20,0xFF005544,"insert+click = forced tile insertion");
            PRINT_T(hp,20,0xFF004466,"alt+click = add tile behind a tile");
        }
        g_sprite->End(); 
        
        END_SCENE("config_tiles");  message_pump();
        SAFE_DO; }while ((GetTickCount() - starting_point) < TIMING_DELAY); 
        if (KEYPRESS(PRINTSCREEN)) GetScreenShot("screenshot.bmp",-1);
    }while(!done); 

}//edit_level




//------------------------------
// G E T  E D I T O R  I N P U T
//------------------------------
enum EDIT_MENU_ID {NONE_SELECTED, CONFIG_TILES, EDIT_LEVEL, TEST_GAME, CONFIG_AI} this_editmenuid;
void get_editor_input() {
int editor_menu=NONE_SELECTED;
POINT cp;
GetCursorPos(&cp); UpdateMousePos(&cp);
if (left_mouse_clicked) {   
    left_mouse_clicked=false;
    if (mouse_pos.y<33) {
        if ((mouse_pos.x>0)&&(mouse_pos.x<184)) editor_menu=CONFIG_TILES;
        if ((mouse_pos.x>187)&&(mouse_pos.x<367)) editor_menu=EDIT_LEVEL;
        if ((mouse_pos.x>381)&&(mouse_pos.x<571)) editor_menu=TEST_GAME;
        if ((mouse_pos.x>592)&&(mouse_pos.x<774)) editor_menu=CONFIG_AI;
    }
}
switch(editor_menu) {
    case CONFIG_TILES:
        config_tiles(); editor_menu=NONE_SELECTED;
    break;
    case EDIT_LEVEL:
        edit_level(); editor_menu=NONE_SELECTED;
    break;
    case TEST_GAME:
    break;
    case CONFIG_AI:
    break;
}
}//get_editor_input

//.........................collapsable editor code (region ends here)
#endif

//-----------------------G A M E   E D I T O R ---------------------------------


(^_^) (updated April 4, 2011)

(Note that the following source code has some instructions in main.cpp (comments at top) for fixing some build errors)

Project Source Code

 

Return to Home Page