#include "editor.h" #include "../game.h" #include "../game/object.h" GAME_EDITOR* editor = 0; void editor_clear_gui_state_refs( GAME_EDITOR* e ) { if( !e ) return; e->gui.new_map_popup = 0; e->gui.v2d = 0; e->gui.v3d = 0; e->gui.gridlabel = 0; e->gui.props = 0; e->gui.tool = 0; e->gui.assets = 0; e->gui.status = 0; e->gui.assets_scroll = 0; e->gui.contextmenu = 0; e->gui.header_toolbar = 0; e->gui.header_viewtype_label = 0; e->gui.header_back = 0; e->gui.header_mode_2d = 0; e->gui.header_mode_3d = 0; e->gui.header_mode_sim = 0; e->gui.header_viewtype = 0; } static void editor_push_undo_action( GAME_EDITOR* e, const GAME_EDITOR::EDITOR_UNDO_ACTION& action ) { if( !e ) return; e->redo_actions.clear(); e->undo_actions.push( action ); } static void editor_refresh_after_map_change( GAME_EDITOR* e ) { if( !e || !e->map ) return; map_check_bounds( e->map ); if( e->gui.props && e->propgrid ) editor_update_properties_column( e ); if( e->gui.tool ) gui_editor_toolview_update( e->gui.tool ); } static U8 editor_clr_eq( const CLR& a, const CLR& b ) { return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; } static U8 editor_vertex_eq( const MAP_VERTEX& a, const MAP_VERTEX& b ) { return a.pos == b.pos && a.normal == b.normal && a.uv == b.uv && editor_clr_eq( a.clr, b.clr ); } static U8 editor_wall_eq( const MAP_WALL& a, const MAP_WALL& b ) { return a.start == b.start && a.end == b.end && a.uvstart == b.uvstart && a.uvend == b.uvend && a.propid == b.propid; } static U8 editor_poly_eq( const MAP_POLYGON& a, const MAP_POLYGON& b ) { if( a.type != b.type || a.propid != b.propid ) return 0; if( !( a.mins == b.mins ) || !( a.maxs == b.maxs ) ) return 0; if( a.vertices.size != b.vertices.size ) return 0; for( U32 i = 0; i < a.vertices.size; ++i ) { if( !editor_vertex_eq( a.vertices.data[i], b.vertices.data[i] ) ) return 0; } return 1; } static U8 editor_sprite_eq( const MAP_SPRITE& a, const MAP_SPRITE& b ) { return a.pos == b.pos && a.size == b.size && editor_clr_eq( a.clr, b.clr ) && a.tex == b.tex; } static U8 editor_entity_eq( const MAP_ENTITY& a, const MAP_ENTITY& b ) { return a.pos == b.pos && a.classid == b.classid; } static I32 editor_clamp_valid_idx( I32 idx, I32 size ) { if( size <= 0 ) return -1; if( idx < 0 ) return 0; if( idx >= size ) return size - 1; return idx; } static I32 editor_idx_distance( I32 a, I32 b ) { return a > b ? a - b : b - a; } template static I32 editor_find_nearest_idx( I32 size, I32 expected_idx, MATCH_FN match ) { if( size <= 0 ) return -1; expected_idx = editor_clamp_valid_idx( expected_idx, size ); if( match( expected_idx ) ) return expected_idx; I32 best = -1; I32 best_dist = 0x7fffffff; for( I32 i = 0; i < size; ++i ) { if( !match( i ) ) continue; I32 dist = editor_idx_distance( i, expected_idx ); if( best == -1 || dist < best_dist ) { best = i; best_dist = dist; } } return best; } static I32 editor_find_wall_idx( WORLD_MAP* map, const MAP_WALL& want, I32 expected_idx ) { if( !map ) return -1; return editor_find_nearest_idx( (I32)map->walls.size, expected_idx, fn( I32 i ) { return editor_wall_eq( map->walls[i], want ); } ); } static I32 editor_find_poly_idx( WORLD_MAP* map, const MAP_POLYGON& want, I32 expected_idx ) { if( !map ) return -1; return editor_find_nearest_idx( (I32)map->polygons.size, expected_idx, fn( I32 i ) { return editor_poly_eq( map->polygons[i], want ); } ); } static I32 editor_find_sprite_idx( WORLD_MAP* map, const MAP_SPRITE& want, I32 expected_idx ) { if( !map ) return -1; return editor_find_nearest_idx( (I32)map->sprites.size, expected_idx, fn( I32 i ) { return editor_sprite_eq( map->sprites[i], want ); } ); } static I32 editor_find_entity_idx( WORLD_MAP* map, const MAP_ENTITY& want, I32 expected_idx ) { if( !map ) return -1; return editor_find_nearest_idx( (I32)map->entities.size, expected_idx, fn( I32 i ) { return editor_entity_eq( map->entities[i], want ); } ); } static void editor_mark_clear_wall_refs( GAME_EDITOR* e, MAP_WALL* w, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) { if( e->gui.props ) { if( e->gui.props->seltype == EDITOR_SELECT_WALL && e->gui.props->curselect == w ) *clear_props_select = 1; if( e->gui.props->seltype == EDITOR_SELECT_WVERTEX && ( e->gui.props->curselect == &w->start || e->gui.props->curselect == &w->end ) ) *clear_props_select = 1; } if( e->gui.v2d ) { if( e->gui.v2d->seltype == EDITOR_SELECT_WALL && e->gui.v2d->curselect == w ) *clear_view_select = 1; if( e->gui.v2d->seltype == EDITOR_SELECT_WVERTEX && ( e->gui.v2d->curselect == &w->start || e->gui.v2d->curselect == &w->end ) ) *clear_view_select = 1; if( e->gui.v2d->dragtype == EDITOR_SELECT_WALL && e->gui.v2d->curdrag == w ) *clear_view_drag = 1; if( e->gui.v2d->dragtype == EDITOR_SELECT_WVERTEX && ( e->gui.v2d->curdrag == &w->start || e->gui.v2d->curdrag == &w->end ) ) *clear_view_drag = 1; } } static void editor_mark_clear_poly_refs( GAME_EDITOR* e, MAP_POLYGON* p, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) { if( e->gui.props ) { if( e->gui.props->seltype == EDITOR_SELECT_POLY && e->gui.props->curselect == p ) *clear_props_select = 1; if( e->gui.props->seltype == EDITOR_SELECT_PVERTEX ) { p->vertices.each( fn( MAP_VERTEX* v ) { if( e->gui.props->curselect == v ) *clear_props_select = 1; } ); } } if( e->gui.v2d ) { if( e->gui.v2d->seltype == EDITOR_SELECT_POLY && e->gui.v2d->curselect == p ) *clear_view_select = 1; if( e->gui.v2d->seltype == EDITOR_SELECT_PVERTEX ) { p->vertices.each( fn( MAP_VERTEX* v ) { if( e->gui.v2d->curselect == v ) *clear_view_select = 1; } ); } if( e->gui.v2d->dragtype == EDITOR_SELECT_POLY && e->gui.v2d->curdrag == p ) *clear_view_drag = 1; if( e->gui.v2d->dragtype == EDITOR_SELECT_PVERTEX ) { p->vertices.each( fn( MAP_VERTEX* v ) { if( e->gui.v2d->curdrag == v ) *clear_view_drag = 1; } ); } } } static void editor_mark_clear_sprite_refs( GAME_EDITOR* e, MAP_SPRITE* s, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) { if( e->gui.props && e->gui.props->seltype == EDITOR_SELECT_SPRITE && e->gui.props->curselect == s ) *clear_props_select = 1; if( e->gui.v2d ) { if( e->gui.v2d->seltype == EDITOR_SELECT_SPRITE && e->gui.v2d->curselect == s ) *clear_view_select = 1; if( e->gui.v2d->dragtype == EDITOR_SELECT_SPRITE && e->gui.v2d->curdrag == s ) *clear_view_drag = 1; } } static void editor_mark_clear_entity_refs( GAME_EDITOR* e, MAP_ENTITY* ent, U8* clear_props_select, U8* clear_view_select, U8* clear_view_drag ) { if( e->gui.props && e->gui.props->seltype == EDITOR_SELECT_ENT && e->gui.props->curselect == ent ) *clear_props_select = 1; if( e->gui.v2d ) { if( e->gui.v2d->seltype == EDITOR_SELECT_ENT && e->gui.v2d->curselect == ent ) *clear_view_select = 1; if( e->gui.v2d->dragtype == EDITOR_SELECT_ENT && e->gui.v2d->curdrag == ent ) *clear_view_drag = 1; } } void editor_undo_clear( GAME_EDITOR* e ) { if( !e ) return; e->undo_actions.clear(); e->redo_actions.clear(); } void editor_undo_record_create_walls( GAME_EDITOR* e, I32 start_idx, I32 count ) { if( !e || !e->map || count <= 0 ) return; WORLD_MAP* map = e->map; if( start_idx < 0 || start_idx >= (I32)map->walls.size ) return; I32 available = (I32)map->walls.size - start_idx; if( count > available ) count = available; if( count <= 0 ) return; GAME_EDITOR::EDITOR_UNDO_ACTION action{}; action.type = EDITOR_UNDO_CREATE_WALLS; action.start_idx = start_idx; for( I32 i = 0; i < count; ++i ) action.walls.push( map->walls[start_idx + i] ); editor_push_undo_action( e, action ); } void editor_undo_record_create_poly( GAME_EDITOR* e, I32 start_idx ) { if( !e || !e->map ) return; WORLD_MAP* map = e->map; if( start_idx < 0 || start_idx >= (I32)map->polygons.size ) return; GAME_EDITOR::EDITOR_UNDO_ACTION action{}; action.type = EDITOR_UNDO_CREATE_POLY; action.start_idx = start_idx; action.polys.push( map->polygons[start_idx] ); editor_push_undo_action( e, action ); } void editor_undo_record_create_sprite( GAME_EDITOR* e, I32 start_idx ) { if( !e || !e->map ) return; WORLD_MAP* map = e->map; if( start_idx < 0 || start_idx >= (I32)map->sprites.size ) return; GAME_EDITOR::EDITOR_UNDO_ACTION action{}; action.type = EDITOR_UNDO_CREATE_SPRITES; action.start_idx = start_idx; action.sprites.push( map->sprites[start_idx] ); editor_push_undo_action( e, action ); } void editor_undo_record_create_entity( GAME_EDITOR* e, I32 start_idx ) { if( !e || !e->map ) return; WORLD_MAP* map = e->map; if( start_idx < 0 || start_idx >= (I32)map->entities.size ) return; GAME_EDITOR::EDITOR_UNDO_ACTION action{}; action.type = EDITOR_UNDO_CREATE_ENTITIES; action.start_idx = start_idx; action.entities.push( map->entities[start_idx] ); editor_push_undo_action( e, action ); } static U8 editor_apply_undo_action_reverse( GAME_EDITOR* e, GAME_EDITOR::EDITOR_UNDO_ACTION& action ) { if( !e || !e->map ) return 0; WORLD_MAP* map = e->map; U8 clear_props_select = 0; U8 clear_view_select = 0; U8 clear_view_drag = 0; switch( action.type ) { case EDITOR_UNDO_CREATE_WALLS: { I32 count = (I32)action.walls.size; for( I32 i = count - 1; i >= 0; --i ) { if( !map->walls.size ) break; I32 expected = action.start_idx + i; I32 idx = editor_find_wall_idx( map, action.walls[i], expected ); if( idx < 0 ) idx = editor_clamp_valid_idx( expected, (I32)map->walls.size ); MAP_WALL* w = &map->walls[idx]; editor_mark_clear_wall_refs( e, w, &clear_props_select, &clear_view_select, &clear_view_drag ); map->walls.erase( idx ); } } break; case EDITOR_UNDO_CREATE_POLY: { I32 count = (I32)action.polys.size; if( count <= 0 ) return 0; for( I32 i = count - 1; i >= 0; --i ) { if( !map->polygons.size ) break; I32 expected = action.start_idx + i; I32 idx = i < (I32)action.polys.size ? editor_find_poly_idx( map, action.polys[i], expected ) : -1; if( idx < 0 ) idx = editor_clamp_valid_idx( expected, (I32)map->polygons.size ); MAP_POLYGON* p = &map->polygons[idx]; editor_mark_clear_poly_refs( e, p, &clear_props_select, &clear_view_select, &clear_view_drag ); map->polygons.erase( idx ); } } break; case EDITOR_UNDO_CREATE_SPRITES: { I32 count = (I32)action.sprites.size; for( I32 i = count - 1; i >= 0; --i ) { if( !map->sprites.size ) break; I32 expected = action.start_idx + i; I32 idx = editor_find_sprite_idx( map, action.sprites[i], expected ); if( idx < 0 ) idx = editor_clamp_valid_idx( expected, (I32)map->sprites.size ); MAP_SPRITE* s = &map->sprites[idx]; editor_mark_clear_sprite_refs( e, s, &clear_props_select, &clear_view_select, &clear_view_drag ); map->sprites.erase( idx ); } } break; case EDITOR_UNDO_CREATE_ENTITIES: { I32 count = (I32)action.entities.size; for( I32 i = count - 1; i >= 0; --i ) { if( !map->entities.size ) break; I32 expected = action.start_idx + i; I32 idx = editor_find_entity_idx( map, action.entities[i], expected ); if( idx < 0 ) idx = editor_clamp_valid_idx( expected, (I32)map->entities.size ); MAP_ENTITY* ent = &map->entities[idx]; editor_mark_clear_entity_refs( e, ent, &clear_props_select, &clear_view_select, &clear_view_drag ); map->entities.erase( idx ); } } break; default: return 0; } if( e->gui.props && clear_props_select ) gui_editor_propview_select( e->gui.props, 0, EDITOR_SELECT_NONE ); if( e->gui.v2d ) { if( clear_view_select ) { e->gui.v2d->curselect = 0; e->gui.v2d->seltype = EDITOR_SELECT_NONE; } if( clear_view_drag ) { e->gui.v2d->curdrag = 0; e->gui.v2d->dragtype = EDITOR_SELECT_NONE; } } editor_refresh_after_map_change( e ); return 1; } static U8 editor_apply_undo_action_forward( GAME_EDITOR* e, GAME_EDITOR::EDITOR_UNDO_ACTION& action ) { if( !e || !e->map ) return 0; WORLD_MAP* map = e->map; switch( action.type ) { case EDITOR_UNDO_CREATE_WALLS: { if( !action.walls.size ) return 0; action.walls.each( fn( MAP_WALL* w ) { map->walls.push( *w ); } ); } break; case EDITOR_UNDO_CREATE_POLY: { if( !action.polys.size ) return 0; action.polys.each( fn( MAP_POLYGON* p ) { map->polygons.push( *p ); } ); } break; case EDITOR_UNDO_CREATE_SPRITES: { if( !action.sprites.size ) return 0; action.sprites.each( fn( MAP_SPRITE* s ) { map->sprites.push( *s ); } ); } break; case EDITOR_UNDO_CREATE_ENTITIES: { if( !action.entities.size ) return 0; action.entities.each( fn( MAP_ENTITY* ent ) { map->entities.push( *ent ); } ); } break; default: return 0; } editor_refresh_after_map_change( e ); return 1; } U8 editor_undo( GAME_EDITOR* e ) { if( !e || !e->map || !e->undo_actions.size ) return 0; GAME_EDITOR::EDITOR_UNDO_ACTION action = e->undo_actions.pop(); if( !editor_apply_undo_action_reverse( e, action ) ) { e->undo_actions.push( action ); return 0; } e->redo_actions.push( action ); return 1; } U8 editor_redo( GAME_EDITOR* e ) { if( !e || !e->map || !e->redo_actions.size ) return 0; GAME_EDITOR::EDITOR_UNDO_ACTION action = e->redo_actions.pop(); if( !editor_apply_undo_action_forward( e, action ) ) { e->redo_actions.push( action ); return 0; } e->undo_actions.push( action ); return 1; } void editor_register_grid_dependency( GAME_EDITOR* e, void* tag, EDITOR_GRID_DEP_CALLBACK cb ) { if( !e || !tag || !cb ) return; I32 idx = e->grid_dependencies.idx_where( fn( GAME_EDITOR::GRID_DEPENDENCY* dep ) { return dep->tag == tag; } ); if( idx != -1 ) { e->grid_dependencies[idx].cb = cb; return; } GAME_EDITOR::GRID_DEPENDENCY dep; dep.tag = tag; dep.cb = cb; e->grid_dependencies.push( dep ); } void editor_notify_grid_change( GAME_EDITOR* e ) { if( !e ) return; e->grid_dependencies.each( fn( GAME_EDITOR::GRID_DEPENDENCY* dep ) { if( dep->cb ) { dep->cb( e ); } } ); } GAME_EDITOR* editor_create( GAME_DATA* game ) { if( editor ) { dlog( "editor_create() : attempted to create editor when one already exists\n" ); return 0; } GAME_EDITOR* e = new GAME_EDITOR(); editor = e; e->grid = 1.f; e->spritesize = EDITOR_DEFAULT_SPRITE_SIZE; e->tool.wallshape = EDITOR_WALLSHAPE_LINE; e->tool.polysides = EDITOR_DEFAULT_POLY_SIDES; e->tool.wallheight = EDITOR_DEFAULT_WALL_HEIGHT; e->tool.placementheight = EDITOR_DEFAULT_PLACEMENT_HEIGHT; e->tool.entclass = OBJCLASS_TRIGGER; e->tool.objecttype = EDITOR_OBJECT_SPRITE; e->game = game; gui_init( game ); e->wnd = gui_editorwindow( 800, 600 ); gui_end(); return e; } STAT editor_close( GAME_EDITOR* e ) { editor_undo_clear( e ); game_unload_map( e->game ); e->map = 0; gui_push_callback( e, pfn( void* data ) { GAME_EDITOR* e = (GAME_EDITOR*)data; I32 w = e->wnd->w, h = e->wnd->h; gui_free( e->wnd ); editor_clear_gui_state_refs( e ); e->wnd = gui_editorwindow( w, h ); } ); return STAT_OK; } STAT editor_load_map( GAME_EDITOR* e, const char* mapname ) { if( e->map ) { dlog( "editor_load() : map already loaded\n" ); return STAT_ERR; } dlog( "editor_load() : loading map %s\n", mapname ); WORLD_MAP* m = game_load_map( e->game, mapname ); if( !m ) return STAT_ERR; e->game->state.map = e->map = m; editor_undo_clear( e ); gui_push_callback( e, pfn( void* d ) { editor_create_map_view( (GAME_EDITOR*)d ); } ); return STAT_OK; } STAT editor_new_map( GAME_EDITOR* e, const char* mapname ) { WORLD_MAP* m = new WORLD_MAP; defer( map_free( e->game, m ) ); m->name = mapname; m->name += ".hmap"; m->startpos = { 10.f, 10.f, 0.f }; m->props.push( { .clr = { 1.f, 1.f, 1.f, 1.f } } ); m->walls.push( { .start = { 0 , 0, -40.f }, .end = { 10.f, 0, 80.f }, .propid = 0 } ); CFG_SECTION* map_cfg = map_serialize( m ); defer( cfg_free( map_cfg ) ); char full_path[256]; sprintf( full_path, "../assets/maps/%s.hmap", mapname ); if( !OK( cfg_save( map_cfg, full_path ) ) ) { dlog( "editor_new_map() : error saving file %s\n", full_path ); return STAT_ERR; } GUI_LIST_ENTRY ne; ne.val = e->map_list.size; strcpy( ne.title, mapname ); strcat( ne.title, ".hmap" ); e->map_list.push( ne ); return STAT_OK; } STAT editor_save_map( GAME_EDITOR* e ) { if( !e->map ) return STAT_ERR; CFG_SECTION* serialized = map_serialize( e->map ); if( !serialized ) return STAT_ERR; defer( cfg_free( serialized ) ); STR full_path = { "../assets/maps/%s", e->map->name.data }; if( !OK( cfg_save( serialized, full_path ) ) ) { dlog( "failed to save map %s", full_path.data ); return STAT_ERR; } return STAT_OK; } LIST* editor_get_map_list( GAME_EDITOR* ge ) { const char* mapsdir = "../assets/maps"; const char* ext[] = { "hmap", 0 }; LIST files = assets_get_files_by_ext_dir( ext, mapsdir ); LIST list; I32 i = 0; files.each( fn( FILE_ENTRY* e ) { if( e->dir ) return; GUI_LIST_ENTRY ne; ne.val = i++; strcpy( ne.title, file_path_last_of( e->name ) ); list.push( ne ); } ); ge->map_list = list; return &ge->map_list; } void editor_destroy( GAME_EDITOR* e ) { delete e; editor = 0; }