diff --git a/GLSMAC_data/default/bases.gls.js b/GLSMAC_data/default/bases.gls.js new file mode 100644 index 00000000..6d74abd5 --- /dev/null +++ b/GLSMAC_data/default/bases.gls.js @@ -0,0 +1,15 @@ +const pops = #include('bases/pops'); + +const result = { + + init: () => { + //pops.init(); + }, + + define: () => { + pops.define(); + }, + +}; + +return result; diff --git a/GLSMAC_data/default/bases/pops.gls.js b/GLSMAC_data/default/bases/pops.gls.js new file mode 100644 index 00000000..54865822 --- /dev/null +++ b/GLSMAC_data/default/bases/pops.gls.js @@ -0,0 +1,46 @@ +const define = (id, name, renders_human_x, renders_progenitor_x, properties) => { + // TODO: if (!properties) { ... } + let rh = []; + for ( x of renders_human_x ) { + rh []= { + type: 'sprite', + file: 'newicons.pcx', + x: x, y: 501, + w: 38, h: 48, + }; + } + let rp = []; + for ( x of renders_progenitor_x ) { + rp []= { + type: 'sprite', + file: 'aliencit.pcx', + x: x, y: 41, + w: 38, h: 48, + }; + } + #game.bases.define_pop(id, { + name: name, + renders_human: rh, + renders_progenitor: rp, + } + properties); +}; + +const result = { + define: () => { + + define('Worker', 'Worker', [79, 118], [40], {tile_worker: true}); + define('Talent', 'Talent', [1, 40], [1], {tile_worker: true}); + define('Doctor', 'Doctor', [352], [196], {}); + define('Technician', 'Technician', [313], [157], {}); + define('Librarian', 'Librarian', [391], [235], {}); + define('Engineer', 'Engineer', [430], [274], {}); + define('Empath', 'Empath', [469], [313], {}); + define('Thinker', 'Thinker', [508], [352], {}); + define('Transcend', 'Transcend', [547], [391], {}); + define('Drone', 'Drone', [157, 196], [79], {}); + define('DronePlus', 'Drone', [235, 274], [118], {}); + + }, +}; + +return result; diff --git a/GLSMAC_data/default/main.gls.js b/GLSMAC_data/default/main.gls.js index 55b00d44..38827ec4 100644 --- a/GLSMAC_data/default/main.gls.js +++ b/GLSMAC_data/default/main.gls.js @@ -1,5 +1,8 @@ -const units = #include('units'); const factions = #include('factions'); +const resources = #include('resources'); +const map = #include('map'); +const units = #include('units'); +const bases = #include('bases'); #game.on.configure((e) => { @@ -25,12 +28,33 @@ let random_health = () => { return #game.random.get_float(#to_float(0.1), #to_float(1)); }; +const pop_types = [ + 'Worker', + 'Talent', + 'Doctor', + 'Librarian', +]; + +let add_pops = ( base, count ) => { + for (let i = 0; i < count; i++) { + const type = pop_types[(#game.random.get_int(0, #size(pop_types) - 1))]; + base.create_pop({ + type: type, + }); + } +}; + units.init(); +let all_bases = []; + #game.on.start((e) => { - // init units + // init game data + resources.define(); + map.define(); units.define(); + bases.define(); // init players players = #game.players.get_all(); @@ -92,14 +116,15 @@ units.init(); } } if (!has_adjactent_bases) { - #game.bases.spawn( + let base = #game.bases.spawn( owner, tile, { // name: 'base name', - population: #game.random.get_int(0, 4) * #game.random.get_int(0, 4) + #game.random.get_int(1, 3), } ); + add_pops(base, #game.random.get_int(1, 7)); + all_bases []= base; bases_spawned++; } } @@ -112,6 +137,9 @@ units.init(); }); #game.on.turn((e) => { + for ( base of all_bases ) { + add_pops(base, 1); + } // }); diff --git a/GLSMAC_data/default/map.gls.js b/GLSMAC_data/default/map.gls.js new file mode 100644 index 00000000..b31d7413 --- /dev/null +++ b/GLSMAC_data/default/map.gls.js @@ -0,0 +1,46 @@ +const result = { + + define: () => { + + #game.on.get_tile_yields((e) => { + + let result = { + Nutrients: 0, + Minerals: 0, + Energy: 0, + }; + + // TODO: fix increments of properties in GSE + + if (e.tile.has_fungus) { + if (e.player.faction.is_progenitor) { // tmp + //result.Energy += 2; + result.Energy = result.Energy + 2; + } + } else { + if (e.tile.is_land) { + //result.Nutrients += (e.tile.moisture - 1); + result.Nutrients = result.Nutrients + (e.tile.moisture - 1); + if (e.tile.rockiness > 1) { + //result.Minerals++; + result.Minerals = result.Minerals + 1; + } + if (e.tile.has_river) { + result.Energy = result.Energy + 1; + } + } else { + //result.Nutrients++; + result.Nutrients = result.Energy + 1; + if (e.player.faction.id == 'PIRATES') { // tmp + //result.Minerals++; + result.Minerals = result.Minerals + 1; + } + } + } + return result; + }); + }, + +}; + +return result; diff --git a/GLSMAC_data/default/resources.gls.js b/GLSMAC_data/default/resources.gls.js new file mode 100644 index 00000000..c83f4680 --- /dev/null +++ b/GLSMAC_data/default/resources.gls.js @@ -0,0 +1,33 @@ +const define = (id, levels_y, minusplus_y) => { + + #game.resources.define(id, { + name: id, + render: { + type: 'sprite_map', + file: 'newicons.pcx', + yields: { + grid_x: 174, grid_y: levels_y, grid_margin: 1, + cell_width: 40, cell_height: 40, cells_count: 8, + }, + plus: { + x: 24, y: minusplus_y, + width: 22, height: 22, + }, + minus: { + x: 47, y: minusplus_y, + width: 22, height: 22, + }, + } + }); + +}; + +const result = { + define: () => { + define('Nutrients', 304, 13); + define('Minerals', 345, 36); + define('Energy', 386, 59); + }, +}; + +return result; diff --git a/README.md b/README.md index 01802b01..75182268 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ Or copy `GLSMAC` file and `GLSMAC_data` directory into your SMAC directory and r Run ./bin/GLSMAC --help to see more options. Debug builds have extra options that aren't available for release builds. -Supported SMAC releases: GOG, Loki, Planetary Pack (if you have something else and it doesn't work - double-check that you have SMACX expansion and then create issue) +Supported SMAC releases: GOG, Steam, Loki, Legacy (if you have something else and it doesn't work - double-check that you have SMACX expansion and then create issue) ### Reporting problems diff --git a/src/config/Config.cpp b/src/config/Config.cpp index 2e3fb8e8..f9686d27 100644 --- a/src/config/Config.cpp +++ b/src/config/Config.cpp @@ -88,11 +88,12 @@ Config::Config( const int argc, const char* argv[] ) } ); m_parser->AddRule( - "smactype", "SMAC_TYPE", "Specify type of SMAC installation: gog, loki, pp (default: autodetect)", AH( this ) { + "smactype", "SMAC_TYPE", "Specify type of SMAC installation: gog, loki, steam, legacy (default: autodetect)", AH( this ) { const std::unordered_map< std::string, smac_type_t > values = { - { "gog", ST_GOG }, - { "loki", ST_LOKI }, - { "pp", ST_PP }, + { "gog", ST_GOG }, + { "loki", ST_LOKI }, + { "steam", ST_STEAM }, + { "legacy", ST_LEGACY }, }; const auto& it = values.find( value ); if ( it != values.end() ) { @@ -133,36 +134,15 @@ Config::Config( const int argc, const char* argv[] ) } ); -#ifdef DEBUG - m_parser->AddRule( - "gdb", "Try to start within gdb (on supported platforms)", AH( this ) { - m_debug_flags |= DF_GDB; - } - ); - m_parser->AddRule( - "mapdump", "Save map dump upon loading map", AH( this ) { - m_debug_flags |= DF_MAPDUMP; - } - ); - m_parser->AddRule( - "memorydebug", "Add extra memory checks and tracking (slow!)", AH( this ) { - m_debug_flags |= DF_MEMORYDEBUG; - } - ); - m_parser->AddRule( - "nopings", "Omit pings and timeouts during multiplayer games", AH( this ) { - m_debug_flags |= DF_NOPINGS; - } - ); m_parser->AddRule( "quickstart", "Skip intro and main menu and generate/load map directly", AH( this ) { - m_debug_flags |= DF_QUICKSTART; + m_launch_flags |= LF_QUICKSTART; } ); const std::string s_quickstart_argument_missing = "Quickstart-related options can only be used after --quickstart argument!"; m_parser->AddRule( "quickstart-seed", "SEED", "Generate map with specific seed (A:B:C:D)", AH( this, s_quickstart_argument_missing ) { - if ( !HasDebugFlag( DF_QUICKSTART ) ) { + if ( !HasLaunchFlag( LF_QUICKSTART ) ) { Error( s_quickstart_argument_missing ); } try { @@ -171,50 +151,38 @@ Config::Config( const int argc, const char* argv[] ) catch ( std::runtime_error& e ) { Error( "Invalid seed format! Seed must contain four numbers separated by colon, for example: 1651011033:1377505029:3019448108:3247278135" ); } - m_debug_flags |= DF_QUICKSTART_SEED; - } - ); - m_parser->AddRule( - "quickstart-mapdump", "MAP_DUMP_FILE", "Load from existing map dump file (*.gsmd)", AH( this, s_quickstart_argument_missing ) { - if ( !HasDebugFlag( DF_QUICKSTART ) ) { - Error( s_quickstart_argument_missing ); - } - if ( !util::FS::FileExists( value ) ) { - Error( "Map dump file \"" + value + "\" not found!" ); - } - m_quickstart_mapdump = value; - m_debug_flags |= DF_QUICKSTART_MAP_DUMP; + m_launch_flags |= LF_QUICKSTART_SEED; } ); m_parser->AddRule( "quickstart-mapfile", "MAP_FILE", "Load from existing map file (*.gsm)", AH( this, s_quickstart_argument_missing ) { - if ( !HasDebugFlag( DF_QUICKSTART ) ) { + if ( !HasLaunchFlag( LF_QUICKSTART ) ) { Error( s_quickstart_argument_missing ); } if ( !util::FS::FileExists( value ) ) { Error( "Map file \"" + value + "\" not found!" ); } m_quickstart_mapfile = value; - m_debug_flags |= DF_QUICKSTART_MAP_FILE; + m_launch_flags |= LF_QUICKSTART_MAP_FILE; } ); m_parser->AddRule( "quickstart-mapsize", "MAP_SIZE", "Generate map of specific size (WxH)", AH( this, s_quickstart_argument_missing ) { - if ( !HasDebugFlag( DF_QUICKSTART ) ) { + if ( !HasLaunchFlag( LF_QUICKSTART ) ) { Error( s_quickstart_argument_missing ); } m_quickstart_mapsize = ParseSize( value ); - m_debug_flags |= DF_QUICKSTART_MAP_SIZE; + m_launch_flags |= LF_QUICKSTART_MAP_SIZE; } ); const auto f_add_map_parameter_option = - [ this, s_quickstart_argument_missing ]( const std::string& name, const std::vector< std::string >& values, const std::string& desc, debug_flag_t flag, game::backend::settings::map_config_value_t* out_param ) + [ this, s_quickstart_argument_missing ]( const std::string& name, const std::vector< std::string >& values, const std::string& desc, launch_flag_t flag, game::backend::settings::map_config_value_t* out_param ) -> void { ASSERT( values.size() == 3, "values size mismatch" ); m_parser->AddRule( name, values[ 0 ] + "|" + values[ 1 ] + "|" + values[ 2 ], "Generate map with specific " + desc + " setting", AH( this, name, values, s_quickstart_argument_missing, out_param, &desc, flag ) { - if ( !HasDebugFlag( DF_QUICKSTART ) ) { + if ( !HasLaunchFlag( LF_QUICKSTART ) ) { Error( s_quickstart_argument_missing ); } if ( value == values[ 0 ] ) { @@ -229,7 +197,7 @@ Config::Config( const int argc, const char* argv[] ) else { Error( "Invalid --" + name + " value specified! Possible choices: " + values[ 0 ] + " " + values[ 1 ] + " " + values[ 2 ] ); } - m_debug_flags |= flag; + m_launch_flags |= flag; } ); }; @@ -239,7 +207,7 @@ Config::Config( const int argc, const char* argv[] ) "medium", "high" }, "ocean coverage", - DF_QUICKSTART_MAP_OCEAN, &m_quickstart_map_ocean + LF_QUICKSTART_MAP_OCEAN, &m_quickstart_map_ocean ); f_add_map_parameter_option( "quickstart-map-erosive", { @@ -247,7 +215,7 @@ Config::Config( const int argc, const char* argv[] ) "average", "weak" }, "erosive forces", - DF_QUICKSTART_MAP_EROSIVE, &m_quickstart_map_erosive + LF_QUICKSTART_MAP_EROSIVE, &m_quickstart_map_erosive ); f_add_map_parameter_option( "quickstart-map-lifeforms", { @@ -255,7 +223,7 @@ Config::Config( const int argc, const char* argv[] ) "average", "abundant" }, "native lifeforms", - DF_QUICKSTART_MAP_LIFEFORMS, &m_quickstart_map_lifeforms + LF_QUICKSTART_MAP_LIFEFORMS, &m_quickstart_map_lifeforms ); f_add_map_parameter_option( "quickstart-map-clouds", { @@ -263,15 +231,49 @@ Config::Config( const int argc, const char* argv[] ) "average", "dense" }, "cloud cover", - DF_QUICKSTART_MAP_CLOUDS, &m_quickstart_map_clouds + LF_QUICKSTART_MAP_CLOUDS, &m_quickstart_map_clouds ); m_parser->AddRule( "quickstart-faction", "FACTION", "Play as specific faction", AH( this, s_quickstart_argument_missing ) { - if ( !HasDebugFlag( DF_QUICKSTART ) ) { + if ( !HasLaunchFlag( LF_QUICKSTART ) ) { Error( s_quickstart_argument_missing ); } m_quickstart_faction = value; - m_debug_flags |= DF_QUICKSTART_FACTION; + m_launch_flags |= LF_QUICKSTART_FACTION; + } + ); + +#ifdef DEBUG + m_parser->AddRule( + "gdb", "Try to start within gdb (on supported platforms)", AH( this ) { + m_debug_flags |= DF_GDB; + } + ); + m_parser->AddRule( + "mapdump", "Save map dump upon loading map", AH( this ) { + m_debug_flags |= DF_MAPDUMP; + } + ); + m_parser->AddRule( + "memorydebug", "Add extra memory checks and tracking (slow!)", AH( this ) { + m_debug_flags |= DF_MEMORYDEBUG; + } + ); + m_parser->AddRule( + "nopings", "Omit pings and timeouts during multiplayer games", AH( this ) { + m_debug_flags |= DF_NOPINGS; + } + ); + m_parser->AddRule( + "quickstart-mapdump", "MAP_DUMP_FILE", "Load from existing map dump file (*.gsmd)", AH( this, s_quickstart_argument_missing ) { + if ( !HasLaunchFlag( LF_QUICKSTART ) ) { + Error( s_quickstart_argument_missing ); + } + if ( !util::FS::FileExists( value ) ) { + Error( "Map dump file \"" + value + "\" not found!" ); + } + m_quickstart_mapdump = value; + m_debug_flags |= DF_QUICKSTART_MAP_DUMP; } ); m_parser->AddRule( @@ -371,20 +373,10 @@ const types::Vec2< size_t >& Config::GetWindowSize() const { return m_window_size; } -#ifdef DEBUG - -const bool Config::HasDebugFlag( const debug_flag_t flag ) const { - return m_debug_flags & flag; -} - const util::random::state_t& Config::GetQuickstartSeed() const { return m_quickstart_seed; } -const std::string& Config::GetQuickstartMapDump() const { - return m_quickstart_mapdump; -} - const std::string& Config::GetQuickstartMapFile() const { return m_quickstart_mapfile; } @@ -413,6 +405,16 @@ const std::string& Config::GetQuickstartFaction() const { return m_quickstart_faction; } +#ifdef DEBUG + +const bool Config::HasDebugFlag( const debug_flag_t flag ) const { + return m_debug_flags & flag; +} + +const std::string& Config::GetQuickstartMapDump() const { + return m_quickstart_mapdump; +} + const std::string& Config::GetGSETestsScript() const { return m_gse_tests_script; } diff --git a/src/config/Config.h b/src/config/Config.h index ce8bf111..6536d57f 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -22,38 +22,38 @@ CLASS( Config, common::Module ) void Init(); - enum launch_flag_t : uint8_t { + enum launch_flag_t : uint16_t { LF_NONE = 0, LF_BENCHMARK = 1 << 0, LF_SHOWFPS = 1 << 1, LF_NOSOUND = 1 << 2, LF_SKIPINTRO = 1 << 3, LF_WINDOWED = 1 << 4, - LF_WINDOW_SIZE = 1 << 5 + LF_WINDOW_SIZE = 1 << 5, + LF_QUICKSTART = 1 << 6, + LF_QUICKSTART_SEED = 1 << 7, + LF_QUICKSTART_MAP_FILE = 1 << 8, + LF_QUICKSTART_MAP_SIZE = 1 << 9, + LF_QUICKSTART_MAP_OCEAN = 1 << 10, + LF_QUICKSTART_MAP_EROSIVE = 1 << 11, + LF_QUICKSTART_MAP_LIFEFORMS = 1 << 12, + LF_QUICKSTART_MAP_CLOUDS = 1 << 13, + LF_QUICKSTART_FACTION = 1 << 14, }; #ifdef DEBUG - enum debug_flag_t : uint32_t { + enum debug_flag_t : uint16_t { DF_NONE = 0, DF_GDB = 1 << 0, DF_MAPDUMP = 1 << 1, DF_MEMORYDEBUG = 1 << 2, - DF_QUICKSTART = 1 << 3, - DF_QUICKSTART_SEED = 1 << 4, - DF_QUICKSTART_MAP_DUMP = 1 << 5, - DF_QUICKSTART_MAP_FILE = 1 << 6, - DF_QUICKSTART_MAP_SIZE = 1 << 7, - DF_QUICKSTART_MAP_OCEAN = 1 << 8, - DF_QUICKSTART_MAP_EROSIVE = 1 << 9, - DF_QUICKSTART_MAP_LIFEFORMS = 1 << 10, - DF_QUICKSTART_MAP_CLOUDS = 1 << 11, - DF_QUICKSTART_FACTION = 1 << 12, - DF_QUIET = 1 << 13, - DF_GSE_ONLY = 1 << 14, - DF_GSE_TESTS = 1 << 15, - DF_GSE_TESTS_SCRIPT = 1 << 16, - DF_GSE_PROMPT_JS = 1 << 17, - DF_NOPINGS = 1 << 18, + DF_QUIET = 1 << 3, + DF_GSE_ONLY = 1 << 4, + DF_GSE_TESTS = 1 << 5, + DF_GSE_TESTS_SCRIPT = 1 << 6, + DF_GSE_PROMPT_JS = 1 << 7, + DF_NOPINGS = 1 << 8, + DF_QUICKSTART_MAP_DUMP = 1 << 9, }; #endif @@ -72,11 +72,7 @@ CLASS( Config, common::Module ) const bool HasLaunchFlag( const launch_flag_t flag ) const; const types::Vec2< size_t >& GetWindowSize() const; -#ifdef DEBUG - - const bool HasDebugFlag( const debug_flag_t flag ) const; const util::random::state_t& GetQuickstartSeed() const; - const std::string& GetQuickstartMapDump() const; const std::string& GetQuickstartMapFile() const; const types::Vec2< size_t >& GetQuickstartMapSize() const; const game::backend::settings::map_config_value_t GetQuickstartMapOcean() const; @@ -84,6 +80,11 @@ CLASS( Config, common::Module ) const game::backend::settings::map_config_value_t GetQuickstartMapLifeforms() const; const game::backend::settings::map_config_value_t GetQuickstartMapClouds() const; const std::string& GetQuickstartFaction() const; + +#ifdef DEBUG + + const bool HasDebugFlag( const debug_flag_t flag ) const; + const std::string& GetQuickstartMapDump() const; const std::string& GetGSETestsScript() const; #endif @@ -94,7 +95,6 @@ CLASS( Config, common::Module ) void Error( const std::string& error ); const types::Vec2< size_t > ParseSize( const std::string& value ); - void CheckAndSetSMACPath( const std::string& path ); const std::string DEFAULT_GLSMAC_PREFIX = #ifdef _WIN32 @@ -109,14 +109,10 @@ CLASS( Config, common::Module ) std::string m_smac_path; smac_type_t m_smac_type = ST_AUTO; - uint8_t m_launch_flags = LF_NONE; + uint16_t m_launch_flags = LF_NONE; types::Vec2< size_t > m_window_size = {}; -#ifdef DEBUG - - uint32_t m_debug_flags = DF_NONE; util::random::state_t m_quickstart_seed = {}; - std::string m_quickstart_mapdump = ""; std::string m_quickstart_mapfile = ""; types::Vec2< size_t > m_quickstart_mapsize = {}; game::backend::settings::map_config_value_t m_quickstart_map_ocean = game::backend::settings::MAP_CONFIG_OCEAN_MEDIUM; @@ -125,6 +121,11 @@ CLASS( Config, common::Module ) game::backend::settings::map_config_value_t m_quickstart_map_clouds = game::backend::settings::MAP_CONFIG_CLOUDS_AVERAGE; std::string m_quickstart_faction = ""; +#ifdef DEBUG + + uint16_t m_debug_flags = DF_NONE; + std::string m_quickstart_mapdump = ""; + std::string m_gse_tests_script = ""; #endif diff --git a/src/config/Types.h b/src/config/Types.h index 737d46f1..8b426048 100644 --- a/src/config/Types.h +++ b/src/config/Types.h @@ -7,7 +7,8 @@ enum smac_type_t : uint8_t { ST_AUTO, ST_GOG, ST_LOKI, - ST_PP, + ST_STEAM, + ST_LEGACY, }; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 3d6777b4..7490e9f8 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -80,7 +80,7 @@ Engine::Engine( if ( !m_config->HasDebugFlag( config::Config::DF_GSE_ONLY ) ) #endif { - m_resource_manager->Init( m_config->GetPossibleSMACPaths(), m_config->GetSMACType() ); + m_resource_manager->Init( m_config->GetPossibleSMACPaths(), m_config->GetSMACType(), m_config->GetDataPath() ); t_main->AddModule( m_resource_manager ); } t_main->AddModule( m_input ); diff --git a/src/game/BackendRequest.h b/src/game/BackendRequest.h index c164faa0..6db1e365 100644 --- a/src/game/BackendRequest.h +++ b/src/game/BackendRequest.h @@ -10,6 +10,7 @@ class BackendRequest { enum request_type_t { BR_NONE, + BR_GET_TILE_DATA, BR_ANIMATION_FINISHED, }; BackendRequest( const request_type_t type ); @@ -19,6 +20,10 @@ class BackendRequest { const request_type_t type = BR_NONE; union { + struct { + size_t tile_x; + size_t tile_y; + } get_tile_data; struct { size_t animation_id; } animation_finished; diff --git a/src/game/FrontendRequest.cpp b/src/game/FrontendRequest.cpp index 28adba44..be7fd9e1 100644 --- a/src/game/FrontendRequest.cpp +++ b/src/game/FrontendRequest.cpp @@ -23,7 +23,9 @@ FrontendRequest::FrontendRequest( const FrontendRequest& other ) } case FR_ERROR: { NEW( data.error.what, std::string, *other.data.error.what ); - NEW( data.error.stacktrace, std::string, *other.data.error.stacktrace ); + if ( other.data.error.stacktrace ) { + NEW( data.error.stacktrace, std::string, *other.data.error.stacktrace ); + } break; } case FR_GLOBAL_MESSAGE: { @@ -34,6 +36,10 @@ FrontendRequest::FrontendRequest( const FrontendRequest& other ) NEW( data.update_tiles.tile_updates, tile_updates_t, *other.data.update_tiles.tile_updates ); break; } + case FR_TILE_DATA: { + NEW( data.tile_data.tile_yields, tile_yields_t, *other.data.tile_data.tile_yields ); + break; + } case FR_FACTION_DEFINE: { NEW( data.faction_define.factiondefs, faction_defines_t, *other.data.faction_define.factiondefs ); break; @@ -59,8 +65,18 @@ FrontendRequest::FrontendRequest( const FrontendRequest& other ) NEW( data.unit_spawn.morale_string, std::string, *other.data.unit_spawn.morale_string ); break; } + case FR_BASE_POP_DEFINE: { + NEW( data.base_pop_define.serialized_popdef, std::string, *other.data.base_pop_define.serialized_popdef ); + break; + } case FR_BASE_SPAWN: { - NEW( data.base_spawn.base_info.name, std::string, *other.data.base_spawn.base_info.name ); + NEW( data.base_spawn.name, std::string, *other.data.base_spawn.name ); + break; + } + case FR_BASE_UPDATE: { + NEW( data.base_update.name, std::string, *other.data.base_update.name ); + NEW( data.base_update.faction_id, std::string, *other.data.base_update.faction_id ); + NEW( data.base_update.pops, base_pops_t, *other.data.base_update.pops ); break; } @@ -78,7 +94,9 @@ FrontendRequest::~FrontendRequest() { } case FR_ERROR: { DELETE( data.error.what ); - DELETE( data.error.stacktrace ); + if ( data.error.stacktrace ) { + DELETE( data.error.stacktrace ); + } break; } case FR_GLOBAL_MESSAGE: { @@ -89,6 +107,10 @@ FrontendRequest::~FrontendRequest() { DELETE( data.update_tiles.tile_updates ); break; } + case FR_TILE_DATA: { + DELETE( data.tile_data.tile_yields ); + break; + } case FR_FACTION_DEFINE: { DELETE( data.faction_define.factiondefs ); break; @@ -114,8 +136,18 @@ FrontendRequest::~FrontendRequest() { DELETE( data.unit_spawn.morale_string ); break; } + case FR_BASE_POP_DEFINE: { + DELETE( data.base_pop_define.serialized_popdef ); + break; + } case FR_BASE_SPAWN: { - DELETE( data.base_spawn.base_info.name ); + DELETE( data.base_spawn.name ); + break; + } + case FR_BASE_UPDATE: { + DELETE( data.base_update.name ); + DELETE( data.base_update.faction_id ); + DELETE( data.base_update.pops ); break; } default: { diff --git a/src/game/FrontendRequest.h b/src/game/FrontendRequest.h index 033ff746..427f109e 100644 --- a/src/game/FrontendRequest.h +++ b/src/game/FrontendRequest.h @@ -2,6 +2,7 @@ #include #include +#include #include "backend/unit/Types.h" #include "backend/turn/Types.h" @@ -27,6 +28,7 @@ class FrontendRequest { FR_ERROR, FR_GLOBAL_MESSAGE, FR_UPDATE_TILES, + FR_TILE_DATA, FR_TURN_STATUS, FR_TURN_ADVANCE, FR_FACTION_DEFINE, @@ -38,7 +40,9 @@ class FrontendRequest { FR_UNIT_DESPAWN, FR_UNIT_UPDATE, FR_UNIT_MOVE, + FR_BASE_POP_DEFINE, FR_BASE_SPAWN, + FR_BASE_UPDATE }; FrontendRequest( const request_type_t type ); FrontendRequest( const FrontendRequest& other ); @@ -56,6 +60,14 @@ class FrontendRequest { typedef std::vector< std::pair< backend::map::tile::Tile*, backend::map::tile::TileState* > > tile_updates_t; + struct base_pop_t { + std::string type; + uint8_t variant; + }; + typedef std::vector< base_pop_t > base_pops_t; + + typedef std::vector< std::pair< std::string, size_t > > tile_yields_t; + union { struct { const std::string* reason; @@ -70,6 +82,11 @@ class FrontendRequest { struct { const tile_updates_t* tile_updates; } update_tiles; + struct { + size_t tile_x; + size_t tile_y; + const tile_yields_t* tile_yields; + } tile_data; struct { backend::turn::turn_status_t status; } turn_status; @@ -140,6 +157,9 @@ class FrontendRequest { size_t y; } dst_tile_coords; } unit_move; + struct { + const std::string* serialized_popdef; + } base_pop_define; struct { size_t base_id; size_t slot_index; @@ -152,11 +172,15 @@ class FrontendRequest { float y; float z; } render_coords; - struct { - const std::string* name; - size_t population; - } base_info; + const std::string* name; } base_spawn; + struct { + size_t base_id; + size_t slot_index; + const std::string* faction_id; + const std::string* name; + base_pops_t* pops; + } base_update; } data; }; diff --git a/src/game/backend/CMakeLists.txt b/src/game/backend/CMakeLists.txt index 593d4e6c..34974441 100644 --- a/src/game/backend/CMakeLists.txt +++ b/src/game/backend/CMakeLists.txt @@ -19,5 +19,6 @@ SET( SRC ${SRC} ${PWD}/Player.cpp ${PWD}/MapObject.cpp ${PWD}/TileLock.cpp + ${PWD}/Resource.cpp PARENT_SCOPE ) diff --git a/src/game/backend/Game.cpp b/src/game/backend/Game.cpp index 659f45fb..edde47aa 100644 --- a/src/game/backend/Game.cpp +++ b/src/game/backend/Game.cpp @@ -8,35 +8,40 @@ #include "util/FS.h" #include "types/Buffer.h" #include "State.h" -#include "game/backend/map_editor/MapEditor.h" -#include "game/backend/event/FinalizeTurn.h" -#include "game/backend/event/TurnFinalized.h" -#include "game/backend/event/AdvanceTurn.h" -#include "game/backend/event/DespawnUnit.h" -#include "game/backend/event/RequestTileLocks.h" -#include "game/backend/event/LockTiles.h" -#include "game/backend/event/RequestTileUnlocks.h" -#include "game/backend/event/UnlockTiles.h" +#include "map_editor/MapEditor.h" +#include "event/FinalizeTurn.h" +#include "event/TurnFinalized.h" +#include "event/AdvanceTurn.h" +#include "event/DespawnUnit.h" +#include "event/RequestTileLocks.h" +#include "event/LockTiles.h" +#include "event/RequestTileUnlocks.h" +#include "event/UnlockTiles.h" #include "util/random/Random.h" #include "config/Config.h" -#include "game/backend/slot/Slots.h" +#include "slot/Slots.h" #include "Player.h" -#include "game/backend/connection/Connection.h" -#include "game/backend/connection/Server.h" -#include "game/backend/connection/Client.h" -#include "game/backend/map/Map.h" -#include "game/backend/map/Consts.h" +#include "connection/Connection.h" +#include "connection/Server.h" +#include "connection/Client.h" +#include "map/Map.h" +#include "map/Consts.h" #include "gse/type/String.h" +#include "gse/type/Int.h" +#include "gse/type/Undefined.h" +#include "gse/type/Array.h" #include "ui/UI.h" -#include "game/backend/map/tile/Tiles.h" -#include "game/backend/map/MapState.h" -#include "game/backend/bindings/Bindings.h" +#include "map/tile/Tiles.h" +#include "map/MapState.h" +#include "bindings/Bindings.h" #include "graphics/Graphics.h" -#include "game/backend/animation/Def.h" -#include "game/backend/unit/Def.h" -#include "game/backend/unit/Unit.h" -#include "game/backend/unit/MoraleSet.h" -#include "game/backend/base/Base.h" +#include "Resource.h" +#include "animation/Def.h" +#include "unit/Def.h" +#include "unit/Unit.h" +#include "unit/MoraleSet.h" +#include "base/PopDef.h" +#include "base/Base.h" namespace game { namespace backend { @@ -165,12 +170,10 @@ void Game::Start() { NEW( m_random, util::random::Random ); -#ifdef DEBUG const auto* config = g_engine->GetConfig(); - if ( config->HasDebugFlag( config::Config::DF_QUICKSTART_SEED ) ) { + if ( config->HasLaunchFlag( config::Config::LF_QUICKSTART_SEED ) ) { m_random->SetState( config->GetQuickstartSeed() ); } -#endif // init map editor NEW( m_map_editor, map_editor::MapEditor, this ); @@ -588,21 +591,80 @@ const MT_Response Game::ProcessRequest( const MT_Request& request, MT_CANCELABLE break; } case OP_SEND_BACKEND_REQUESTS: { - for ( const auto& r : *request.data.send_backend_requests.requests ) { - switch ( r.type ) { - case BackendRequest::BR_ANIMATION_FINISHED: { - const auto& it = m_running_animations_callbacks.find( r.data.animation_finished.animation_id ); - ASSERT( it != m_running_animations_callbacks.end(), "animation " + std::to_string( r.data.animation_finished.animation_id ) + " is not running" ); - const auto on_complete = it->second; - m_running_animations_callbacks.erase( it ); - on_complete(); - break; + try { + for ( const auto& r : *request.data.send_backend_requests.requests ) { + switch ( r.type ) { + case BackendRequest::BR_GET_TILE_DATA: { + const auto& tile = m_map->GetTile( r.data.get_tile_data.tile_x, r.data.get_tile_data.tile_y ); + const auto result = m_state->m_bindings->Call( + bindings::Bindings::CS_ON_GET_TILE_YIELDS, { + { + "tile", + tile->Wrap() + }, + { + "player", + m_slot->Wrap() + }, + } + ); + if ( result.Get()->type != gse::type::Type::T_OBJECT ) { + THROW( "unexpected return type: expected Object, got " + result.GetTypeString() ); + } + const auto& values = ( (gse::type::Object*)result.Get() )->value; + for ( const auto& v : values ) { + if ( m_resources.find( v.first ) == m_resources.end() ) { + THROW( "unknown resource type: " + v.first ); + } + } + NEWV( yields, FrontendRequest::tile_yields_t, {} ); + yields->reserve( m_resource_idx.size() ); + for ( const auto& idx : m_resource_idx ) { + const auto& v = values.find( idx ); + if ( v == values.end() ) { + DELETE( yields ); + THROW( "missing yields for resource: " + idx ); + } + if ( v->second.Get()->type != gse::type::Type::T_INT ) { + DELETE( yields ); + THROW( "invalid resource value, expected Int, got " + v->second.GetTypeString() + ": " + v->second.ToString() ); + } + yields->push_back( + { + idx, + ( (gse::type::Int*)v->second.Get() )->value + } + ); + } + auto fr = FrontendRequest( FrontendRequest::FR_TILE_DATA ); + fr.data.tile_data.tile_x = tile->coord.x; + fr.data.tile_data.tile_y = tile->coord.y; + fr.data.tile_data.tile_yields = yields; + AddFrontendRequest( fr ); + break; + } + case BackendRequest::BR_ANIMATION_FINISHED: { + const auto& it = m_running_animations_callbacks.find( r.data.animation_finished.animation_id ); + ASSERT( it != m_running_animations_callbacks.end(), "animation " + std::to_string( r.data.animation_finished.animation_id ) + " is not running" ); + const auto on_complete = it->second; + m_running_animations_callbacks.erase( it ); + on_complete(); + break; + } + default: + THROW( "unknown backend request type: " + std::to_string( r.type ) ); } - default: - THROW( "unknown backend request type: " + std::to_string( r.type ) ); } + response.result = R_SUCCESS; + } + catch ( gse::Exception& e ) { + OnGSEError( e ); + response.result = R_ERROR; + } + catch ( std::runtime_error& e ) { + OnError( e ); + response.result = R_ERROR; } - response.result = R_SUCCESS; break; } case OP_ADD_EVENT: { @@ -716,6 +778,13 @@ void Game::Quit( const std::string& reason ) { AddFrontendRequest( fr ); } +void Game::OnError( std::runtime_error& err ) { + auto fr = FrontendRequest( FrontendRequest::FR_ERROR ); + NEW( fr.data.error.what, std::string, (std::string)"Script error: " + err.what() ); + fr.data.error.stacktrace = nullptr; + AddFrontendRequest( fr ); +} + void Game::OnGSEError( gse::Exception& err ) { auto fr = FrontendRequest( FrontendRequest::FR_ERROR ); NEW( fr.data.error.what, std::string, (std::string)"Script error: " + err.what() ); @@ -749,14 +818,36 @@ unit::Def* Game::GetUnitDef( const std::string& name ) const { } } -void Game::AddEvent( event::Event* event ) { +base::PopDef* Game::GetPopDef( const std::string& id ) const { + const auto& it = m_base_popdefs.find( id ); + if ( it != m_base_popdefs.end() ) { + return it->second; + } + else { + return nullptr; + } +} + +base::Base* Game::GetBase( const size_t id ) const { + const auto& it = m_bases.find( id ); + if ( it != m_bases.end() ) { + return it->second; + } + else { + return nullptr; + } +} + +const gse::Value Game::AddEvent( event::Event* event ) { ASSERT( event->m_initiator_slot == m_slot_num, "initiator slot mismatch" ); if ( m_connection ) { m_connection->SendGameEvent( event ); } if ( m_state->IsMaster() ) { - ProcessEvent( event ); + // note that this will work only on master, do slaves need return values too? i.e. for callbacks + return ProcessEvent( event ); } + return VALUE( gse::type::Undefined ); } void Game::RefreshUnit( const unit::Unit* unit ) { @@ -771,7 +862,7 @@ void Game::RefreshUnit( const unit::Unit* unit ) { } void Game::RefreshBase( const base::Base* base ) { - // TODO + QueueBaseUpdate( base, BUO_REFRESH ); } void Game::DefineAnimation( animation::Def* def ) { @@ -819,6 +910,26 @@ const std::string* Game::ShowAnimationOnTile( const std::string& animation_id, m return nullptr; // no error } +void Game::DefineResource( Resource* resource ) { + Log( "Defining resource ('" + resource->m_id + "')" ); + + ASSERT( m_resources.find( resource->m_id ) == m_resources.end(), "Resource '" + resource->m_id + "' already exists" ); + + m_resources.insert( + { + resource->m_id, + resource + } + ); + m_resource_idx_map.insert( + { + resource->m_id, + m_resource_idx.size() + } + ); + m_resource_idx.push_back( resource->m_id ); +} + void Game::DefineMoraleSet( unit::MoraleSet* moraleset ) { Log( "Defining unit moraleset ('" + moraleset->m_id + "')" ); @@ -920,6 +1031,24 @@ void Game::DespawnUnit( const size_t unit_id ) { delete unit; } +void Game::DefinePop( base::PopDef* pop_def ) { + Log( "Defining base pop ('" + pop_def->m_id + "')" ); + + ASSERT( m_base_popdefs.find( pop_def->m_id ) == m_base_popdefs.end(), "Base pop def '" + pop_def->m_id + "' already exists" ); + + m_base_popdefs.insert( + { + pop_def->m_id, + pop_def + } + ); + + auto fr = FrontendRequest( FrontendRequest::FR_BASE_POP_DEFINE ); + NEW( fr.data.base_pop_define.serialized_popdef, std::string, base::PopDef::Serialize( pop_def ).ToString() ); + AddFrontendRequest( fr ); + +} + void Game::SpawnBase( base::Base* base ) { if ( m_game_state != GS_RUNNING ) { m_unprocessed_bases.push_back( base ); @@ -930,14 +1059,14 @@ void Game::SpawnBase( base::Base* base ) { // validate and fix name if needed (or assign if empty) std::vector< std::string > names_to_try = {}; - if ( base->m_data.name.empty() ) { + if ( base->m_name.empty() ) { const auto& names = base->m_owner->GetPlayer()->GetFaction()->m_base_names; names_to_try = tile->is_water_tile ? names.water : names.land; } - else if ( m_registered_base_names.find( base->m_data.name ) != m_registered_base_names.end() ) { - names_to_try = { base->m_data.name }; + else if ( m_registered_base_names.find( base->m_name ) != m_registered_base_names.end() ) { + names_to_try = { base->m_name }; } if ( !names_to_try.empty() ) { size_t cycle = 0; @@ -945,19 +1074,19 @@ void Game::SpawnBase( base::Base* base ) { while ( !found ) { cycle++; for ( const auto& name_to_try : names_to_try ) { - base->m_data.name = cycle == 1 + base->m_name = cycle == 1 ? name_to_try : name_to_try + " " + std::to_string( cycle ); - if ( m_registered_base_names.find( base->m_data.name ) == m_registered_base_names.end() ) { + if ( m_registered_base_names.find( base->m_name ) == m_registered_base_names.end() ) { found = true; break; } } } } - m_registered_base_names.insert( base->m_data.name ); + m_registered_base_names.insert( base->m_name ); - Log( "Spawning base #" + std::to_string( base->m_id ) + " ( " + base->m_data.name + ", " + std::to_string( base->m_data.population ) + " ) at " + base->GetTile()->ToString() ); + Log( "Spawning base #" + std::to_string( base->m_id ) + " ( " + base->m_name + " ) at " + base->GetTile()->ToString() ); ASSERT( m_bases.find( base->m_id ) == m_bases.end(), "duplicate base id" ); m_bases.insert_or_assign( base->m_id, base ); @@ -1401,6 +1530,12 @@ void Game::UnlockTiles( const size_t initiator_slot, const map::tile::positions_ } } +rules::Faction* Game::GetFaction( const std::string& id ) const { + const auto& it = m_state->m_settings.global.game_rules.m_factions.find( id ); // TODO: store factions in Game itself? + ASSERT( it != m_state->m_settings.global.game_rules.m_factions.end(), "faction not found: " + id ); + return &it->second; +} + void Game::ValidateEvent( event::Event* event ) { if ( !event->m_is_validated ) { const auto* errmsg = event->Validate( this ); @@ -1445,6 +1580,31 @@ const types::Vec3 Game::GetTileRenderCoords( const map::tile::Tile* tile ) { }; } +void Game::SerializeResources( types::Buffer& buf ) const { + Log( "Serializing " + std::to_string( m_resources.size() ) + " resources" ); + buf.WriteInt( m_resources.size() ); + for ( const auto& it : m_resource_idx ) { + ASSERT( m_resources.find( it ) != m_resources.end(), "invalid resource idx" ); + const auto& res = m_resources.at( it ); + buf.WriteString( res->m_id ); + buf.WriteString( Resource::Serialize( res ).ToString() ); + } +} + +void Game::UnserializeResources( types::Buffer& buf ) { + ASSERT( m_resources.empty(), "resources not empty" ); + size_t sz = buf.ReadInt(); + Log( "Unserializing " + std::to_string( sz ) + " resources" ); + m_resources.reserve( sz ); + m_resource_idx.reserve( sz ); + m_resource_idx_map.reserve( sz ); + for ( size_t i = 0 ; i < sz ; i++ ) { + const auto name = buf.ReadString(); + auto b = types::Buffer( buf.ReadString() ); + DefineResource( Resource::Unserialize( b ) ); + } +} + void Game::SerializeUnits( types::Buffer& buf ) const { Log( "Serializing " + std::to_string( m_unit_moralesets.size() ) + " unit moralesets" ); @@ -1476,9 +1636,11 @@ void Game::UnserializeUnits( types::Buffer& buf ) { ASSERT( m_unit_moralesets.empty(), "unit moralesets not empty" ); ASSERT( m_unit_defs.empty(), "unit defs not empty" ); ASSERT( m_units.empty(), "units not empty" ); + ASSERT( m_unprocessed_units.empty(), "unprocessed units not empty" ); size_t sz = buf.ReadInt(); Log( "Unserializing " + std::to_string( sz ) + " unit moralesets" ); + m_unit_moralesets.reserve( sz ); for ( size_t i = 0 ; i < sz ; i++ ) { const auto name = buf.ReadString(); auto b = types::Buffer( buf.ReadString() ); @@ -1487,6 +1649,7 @@ void Game::UnserializeUnits( types::Buffer& buf ) { sz = buf.ReadInt(); Log( "Unserializing " + std::to_string( sz ) + " unit defs" ); + m_unit_defs.reserve( sz ); for ( size_t i = 0 ; i < sz ; i++ ) { const auto name = buf.ReadString(); auto b = types::Buffer( buf.ReadString() ); @@ -1495,7 +1658,9 @@ void Game::UnserializeUnits( types::Buffer& buf ) { sz = buf.ReadInt(); Log( "Unserializing " + std::to_string( sz ) + " units" ); - ASSERT( m_unprocessed_units.empty(), "unprocessed units not empty" ); + if ( m_game_state != GS_RUNNING ) { + m_unprocessed_units.reserve( sz ); + } for ( size_t i = 0 ; i < sz ; i++ ) { const auto unit_id = buf.ReadInt(); auto b = types::Buffer( buf.ReadString() ); @@ -1508,6 +1673,13 @@ void Game::UnserializeUnits( types::Buffer& buf ) { void Game::SerializeBases( types::Buffer& buf ) const { + Log( "Serializing " + std::to_string( m_base_popdefs.size() ) + " base pop defs" ); + buf.WriteInt( m_base_popdefs.size() ); + for ( const auto& it : m_base_popdefs ) { + buf.WriteString( it.first ); + buf.WriteString( base::PopDef::Serialize( it.second ).ToString() ); + } + Log( "Serializing " + std::to_string( m_bases.size() ) + " bases" ); buf.WriteInt( m_bases.size() ); for ( const auto& it : m_bases ) { @@ -1520,11 +1692,24 @@ void Game::SerializeBases( types::Buffer& buf ) const { } void Game::UnserializeBases( types::Buffer& buf ) { + ASSERT( m_base_popdefs.empty(), "base pop defs not empty" ); ASSERT( m_bases.empty(), "bases not empty" ); + ASSERT( m_unprocessed_bases.empty(), "unprocessed bases not empty" ); - const size_t sz = buf.ReadInt(); + size_t sz = buf.ReadInt(); + m_base_popdefs.reserve( sz ); + Log( "Unserializing " + std::to_string( sz ) + " base pop defs" ); + for ( size_t i = 0 ; i < sz ; i++ ) { + const auto name = buf.ReadString(); + auto b = types::Buffer( buf.ReadString() ); + DefinePop( base::PopDef::Unserialize( b ) ); + } + + sz = buf.ReadInt(); Log( "Unserializing " + std::to_string( sz ) + " bases" ); - ASSERT( m_unprocessed_bases.empty(), "unprocessed bases not empty" ); + if ( m_game_state != GS_RUNNING ) { + m_unprocessed_bases.reserve( sz ); + } for ( size_t i = 0 ; i < sz ; i++ ) { const auto base_id = buf.ReadInt(); auto b = types::Buffer( buf.ReadString() ); @@ -1545,8 +1730,10 @@ void Game::SerializeAnimations( types::Buffer& buf ) const { } void Game::UnserializeAnimations( types::Buffer& buf ) { + ASSERT( m_animation_defs.empty(), "animation defs not empty" ); size_t sz = buf.ReadInt(); Log( "Unserializing " + std::to_string( sz ) + " animation defs" ); + m_animation_defs.reserve( sz ); for ( size_t i = 0 ; i < sz ; i++ ) { const auto name = buf.ReadString(); auto b = types::Buffer( buf.ReadString() ); @@ -1647,6 +1834,13 @@ void Game::InitGame( MT_Response& response, MT_CANCELABLE ) { // map m_map->SaveToBuffer( buf ); + // resources + { + types::Buffer b; + SerializeResources( b ); + buf.WriteString( b.ToString() ); + } + // units { types::Buffer b; @@ -1749,9 +1943,9 @@ void Game::InitGame( MT_Response& response, MT_CANCELABLE ) { } NEW( m_map, map::Map, this ); -#ifdef DEBUG const auto* config = g_engine->GetConfig(); +#ifdef DEBUG // if crash happens - it's handy to have a seed to reproduce it util::FS::WriteFile( config->GetDebugPath() + map::s_consts.debug.lastseed_filename, m_random->GetStateString() ); #endif @@ -1770,14 +1964,11 @@ void Game::InitGame( MT_Response& response, MT_CANCELABLE ) { else #endif { -#ifdef DEBUG - if ( !m_connection && config->HasDebugFlag( config::Config::DF_QUICKSTART_MAP_FILE ) ) { + if ( !m_connection && config->HasLaunchFlag( config::Config::LF_QUICKSTART_MAP_FILE ) ) { const std::string& filename = config->GetQuickstartMapFile(); ec = m_map->LoadFromFile( filename ); } - else -#endif - { + else { auto& map_settings = m_state->m_settings.global.map; if ( map_settings.type == settings::MapSettings::MT_MAPFILE ) { ASSERT( !map_settings.filename.empty(), "loading map requested but map file not specified" ); @@ -1837,6 +2028,12 @@ void Game::InitGame( MT_Response& response, MT_CANCELABLE ) { const auto ec = m_map->LoadFromBuffer( b ); if ( ec == map::Map::EC_NONE ) { + // resources + { + auto ub = types::Buffer( buf.ReadString() ); + UnserializeResources( ub ); + } + // units { auto ub = types::Buffer( buf.ReadString() ); @@ -1916,6 +2113,13 @@ void Game::ResetGame() { m_running_animations_callbacks.clear(); m_next_running_animation_id = 1; + for ( auto& it : m_resources ) { + delete it.second; + } + m_resources.clear(); + m_resource_idx.clear(); + m_resource_idx_map.clear(); + for ( auto& it : m_unit_moralesets ) { delete it.second; } @@ -1931,6 +2135,10 @@ void Game::ResetGame() { } m_unit_defs.clear(); + for ( auto& it : m_base_popdefs ) { + delete it.second; + } + m_base_popdefs.clear(); for ( auto& it : m_bases ) { delete it.second; } @@ -2117,7 +2325,7 @@ void Game::PushBaseUpdates() { const auto& base = bu.base; if ( bu.ops & BUO_SPAWN ) { auto fr = FrontendRequest( FrontendRequest::FR_BASE_SPAWN ); - fr.data.unit_spawn.unit_id = base->m_id; + fr.data.base_spawn.base_id = base->m_id; fr.data.base_spawn.slot_index = base->m_owner->GetIndex(); const auto* tile = base->GetTile(); fr.data.base_spawn.tile_coords = { @@ -2130,12 +2338,25 @@ void Game::PushBaseUpdates() { c.y, c.z }; - NEW( fr.data.base_spawn.base_info.name, std::string, base->m_data.name ); - fr.data.base_spawn.base_info.population = base->m_data.population; + NEW( fr.data.base_spawn.name, std::string, base->m_name ); AddFrontendRequest( fr ); } if ( bu.ops & BUO_REFRESH ) { - THROW( "TODO: BASE REFRESH" ); + auto fr = FrontendRequest( FrontendRequest::FR_BASE_UPDATE ); + fr.data.base_update.base_id = base->m_id; + fr.data.base_update.slot_index = base->m_owner->GetIndex(); + NEW( fr.data.base_update.name, std::string, base->m_name ); + NEW( fr.data.base_update.faction_id, std::string, base->m_faction->m_id ); + NEW( fr.data.base_update.pops, FrontendRequest::base_pops_t, {} ); + for ( const auto& pop : base->m_pops ) { + fr.data.base_update.pops->push_back( + { + pop.m_def->m_id, + pop.m_variant + } + ); + } + AddFrontendRequest( fr ); } if ( bu.ops & UUO_DESPAWN ) { THROW( "TODO: BASE DESPAWN" ); diff --git a/src/game/backend/Game.h b/src/game/backend/Game.h index 635eac89..3581f8b0 100644 --- a/src/game/backend/Game.h +++ b/src/game/backend/Game.h @@ -51,9 +51,12 @@ class Unit; } namespace base { +class PopDef; class Base; } +class Resource; + namespace event { class Event; } @@ -320,21 +323,25 @@ CLASS( Game, MTModule ) // for bindings etc void Message( const std::string& text ); void Quit( const std::string& reason ); + void OnError( std::runtime_error& err ); void OnGSEError( gse::Exception& e ); unit::MoraleSet* GetMoraleSet( const std::string& name ) const; unit::Unit* GetUnit( const size_t id ) const; unit::Def* GetUnitDef( const std::string& name ) const; - void AddEvent( event::Event* event ); + base::PopDef* GetPopDef( const std::string& id ) const; + base::Base* GetBase( const size_t id ) const; + const gse::Value AddEvent( event::Event* event ); void RefreshUnit( const unit::Unit* unit ); void RefreshBase( const base::Base* base ); void DefineAnimation( animation::Def* def ); const std::string* ShowAnimationOnTile( const std::string& animation_id, map::tile::Tile* tile, const cb_oncomplete& on_complete ); + void DefineResource( Resource* resource ); void DefineMoraleSet( unit::MoraleSet* moraleset ); void DefineUnit( unit::Def* def ); void SpawnUnit( unit::Unit* unit ); void SkipUnitTurn( const size_t unit_id ); void DespawnUnit( const size_t unit_id ); - std::string RegisterBaseName( const std::string& requested_name ); + void DefinePop( base::PopDef* pop_def ); void SpawnBase( base::Base* base ); const std::string* MoveUnitValidate( unit::Unit* unit, map::tile::Tile* dst_tile ); const gse::Value MoveUnitResolve( unit::Unit* unit, map::tile::Tile* dst_tile ); @@ -364,6 +371,8 @@ CLASS( Game, MTModule ) void RequestTileUnlocks( const size_t initiator_slot, const map::tile::positions_t& tile_positions ); void UnlockTiles( const size_t initiator_slot, const map::tile::positions_t& tile_positions ); + rules::Faction* GetFaction( const std::string& id ) const; + private: void ValidateEvent( event::Event* event ); @@ -371,12 +380,19 @@ CLASS( Game, MTModule ) const types::Vec3 GetTileRenderCoords( const map::tile::Tile* tile ); + std::unordered_map< std::string, Resource* > m_resources = {}; + std::vector< std::string > m_resource_idx = {}; + std::unordered_map< std::string, size_t > m_resource_idx_map = {}; + void SerializeResources( types::Buffer& buf ) const; + void UnserializeResources( types::Buffer& buf ); + std::unordered_map< std::string, unit::MoraleSet* > m_unit_moralesets = {}; std::unordered_map< std::string, unit::Def* > m_unit_defs = {}; std::map< size_t, unit::Unit* > m_units = {}; void SerializeUnits( types::Buffer& buf ) const; void UnserializeUnits( types::Buffer& buf ); + std::unordered_map< std::string, base::PopDef* > m_base_popdefs = {}; std::map< size_t, base::Base* > m_bases = {}; void SerializeBases( types::Buffer& buf ) const; void UnserializeBases( types::Buffer& buf ); diff --git a/src/game/backend/Resource.cpp b/src/game/backend/Resource.cpp new file mode 100644 index 00000000..449ca3b3 --- /dev/null +++ b/src/game/backend/Resource.cpp @@ -0,0 +1,98 @@ +#include "Resource.h" + +namespace game { +namespace backend { + +Resource::Resource( const std::string& id, const std::string& name, const render_info_t& render_info ) + : m_id( id ) + , m_name( name ) + , m_render_info( render_info ) { + // +} + +const std::string Resource::ToString( const std::string& prefix ) const { + return (std::string) + TS_OBJ_BEGIN( "Resource" ) + + TS_OBJ_PROP_STR( "id", m_id ) + + TS_OBJ_PROP_STR( "name", m_name ) + + TS_OBJ_BEGIN( "renders" ) + + TS_OBJ_PROP_STR( "file", m_render_info.file ) + + TS_OBJ_BEGIN( "yields" ) + + TS_OBJ_PROP_NUM( "grid_x", m_render_info.yields.grid_x ) + + TS_OBJ_PROP_NUM( "grid_y", m_render_info.yields.grid_y ) + + TS_OBJ_PROP_NUM( "grid_margin", m_render_info.yields.grid_margin ) + + TS_OBJ_PROP_NUM( "cell_width", m_render_info.yields.cell_width ) + + TS_OBJ_PROP_NUM( "cell_height", m_render_info.yields.cell_height ) + + TS_OBJ_PROP_NUM( "cells_count", m_render_info.yields.cells_count ) + + TS_OBJ_END() + +#define X( _r ) \ + TS_OBJ_BEGIN( #_r ) + \ + TS_OBJ_PROP_NUM( "x", m_render_info._r.x ) + \ + TS_OBJ_PROP_NUM( "y", m_render_info._r.y ) + \ + TS_OBJ_PROP_NUM( "width", m_render_info._r.width ) + \ + TS_OBJ_PROP_NUM( "height", m_render_info._r.height ) + \ + TS_OBJ_END() + + X( plus ) + X( minus ) +#undef X + TS_OBJ_END(); +} + +const types::Buffer Resource::Serialize( const Resource* resource ) { + types::Buffer buf; + buf.WriteString( resource->m_id ); + buf.WriteString( resource->m_name ); + buf.WriteString( resource->m_render_info.file ); + { + const auto& r = resource->m_render_info.yields; + buf.WriteInt( r.grid_x ); + buf.WriteInt( r.grid_y ); + buf.WriteInt( r.grid_margin ); + buf.WriteInt( r.cell_width ); + buf.WriteInt( r.cell_height ); + buf.WriteInt( r.cells_count ); + }; +#define X( _n ) \ + { \ + const auto& r = resource->m_render_info._n; \ + buf.WriteInt( r.x ); \ + buf.WriteInt( r.y ); \ + buf.WriteInt( r.width ); \ + buf.WriteInt( r.height ); \ + }; + X( plus ) + X( minus ) +#undef X + return buf; +} + +Resource* Resource::Unserialize( types::Buffer& buf ) { + const auto id = buf.ReadString(); + const auto name = buf.ReadString(); + render_info_t render_info = {}; + render_info.file = buf.ReadString(); + { + auto& r = render_info.yields; + r.grid_x = buf.ReadInt(); + r.grid_y = buf.ReadInt(); + r.grid_margin = buf.ReadInt(); + r.cell_width = buf.ReadInt(); + r.cell_height = buf.ReadInt(); + r.cells_count = buf.ReadInt(); + }; +#define X( _n ) \ + { \ + auto& r = render_info._n; \ + r.x = buf.ReadInt(); \ + r.y = buf.ReadInt(); \ + r.width = buf.ReadInt(); \ + r.height = buf.ReadInt(); \ + }; + X( plus ) + X( minus ) +#undef X + return new Resource( id, name, render_info ); +} + +} +} diff --git a/src/game/backend/Resource.h b/src/game/backend/Resource.h new file mode 100644 index 00000000..176ef2de --- /dev/null +++ b/src/game/backend/Resource.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include "types/Buffer.h" + +namespace game { +namespace backend { + +class Resource { +public: + + struct render_info_t { + std::string file; + struct { + uint16_t grid_x; + uint16_t grid_y; + uint8_t grid_margin; + uint16_t cell_width; + uint16_t cell_height; + uint8_t cells_count; + } yields; +#define X( _n ) \ + struct { \ + uint16_t x; \ + uint16_t y; \ + uint16_t width; \ + uint16_t height; \ + } _n; + X( plus ) + X( minus ) +#undef X + }; + + Resource( const std::string& id, const std::string& name, const render_info_t& render_info ); + + const std::string m_id; + const std::string m_name; + const render_info_t m_render_info; + + const std::string ToString( const std::string& prefix = "" ) const; + + static const types::Buffer Serialize( const Resource* resource ); + static Resource* Unserialize( types::Buffer& buf ); + +}; + +} +} diff --git a/src/game/backend/base/Base.cpp b/src/game/backend/base/Base.cpp index 63f2759d..54b8a304 100644 --- a/src/game/backend/base/Base.cpp +++ b/src/game/backend/base/Base.cpp @@ -3,12 +3,18 @@ #include "gse/context/Context.h" #include "gse/type/Object.h" #include "gse/type/Int.h" +#include "gse/type/String.h" +#include "gse/type/Undefined.h" #include "gse/callable/Native.h" #include "game/backend/Game.h" #include "game/backend/State.h" #include "game/backend/slot/Slot.h" #include "game/backend/slot/Slots.h" #include "game/backend/map/Map.h" +#include "Pop.h" +#include "game/backend/bindings/Binding.h" +#include "PopDef.h" +#include "util/random/Random.h" namespace game { namespace backend { @@ -26,14 +32,18 @@ Base::Base( Game* game, const size_t id, slot::Slot* owner, + rules::Faction* faction, map::tile::Tile* tile, - const BaseData& data + const std::string& name, + const pops_t& pops ) : MapObject( game->GetMap(), tile ) , m_game( game ) , m_id( id ) , m_owner( owner ) - , m_data( data ) { + , m_faction( faction ) + , m_name( name ) + , m_pops( pops ) { if ( next_id <= id ) { next_id = id + 1; } @@ -42,13 +52,23 @@ Base::Base( m_tile = tile; } +void Base::AddPop( const Pop& pop ) { + m_pops.push_back( pop ); + m_game->RefreshBase( this ); +} + const types::Buffer Base::Serialize( const Base* base ) { types::Buffer buf; buf.WriteInt( base->m_id ); buf.WriteInt( base->m_owner->GetIndex() ); + buf.WriteString( base->m_faction->m_id ); buf.WriteInt( base->m_tile->coord.x ); buf.WriteInt( base->m_tile->coord.y ); - base->m_data.Serialize( buf ); + buf.WriteString( base->m_name ); + buf.WriteInt( base->m_pops.size() ); + for ( const auto& pop : base->m_pops ) { + pop.Serialize( buf ); + } return buf; } @@ -57,19 +77,45 @@ Base* Base::Unserialize( types::Buffer& buf, Game* game ) { auto* slot = game ? &game->GetState()->m_slots->GetSlot( buf.ReadInt() ) : nullptr; + const auto faction_id = buf.ReadString(); + auto* faction = game->GetFaction( faction_id ); const auto pos_x = buf.ReadInt(); const auto pos_y = buf.ReadInt(); auto* tile = game ? game->GetMap()->GetTile( pos_x, pos_y ) : nullptr; - const auto data = BaseData( buf ); - return new Base( game, id, slot, tile, data ); + const auto name = buf.ReadString(); + pops_t pops = {}; + const auto pops_count = buf.ReadInt(); + pops.resize( pops_count ); + for ( auto& pop : pops ) { + pop.Unserialize( buf, game ); + } + return new Base( game, id, slot, faction, tile, name, pops ); } WRAPIMPL_DYNAMIC_GETTERS( Base, CLASS_BASE ) - WRAPIMPL_GET( "id", Int, m_id ) - WRAPIMPL_LINK( "get_owner", m_owner ) - WRAPIMPL_LINK( "get_tile", m_tile ) + WRAPIMPL_GET( "id", Int, m_id ) + WRAPIMPL_LINK( "get_owner", m_owner ) + WRAPIMPL_LINK( "get_tile", m_tile ) + { + "create_pop", + NATIVE_CALL( this ) { + N_EXPECT_ARGS( 1 ); + N_GETVALUE( data, 0, Object ); + N_GETPROP( poptype, data, "type", String ); + auto* def = m_game->GetPopDef( poptype ); + if ( !def ) { + ERROR( gse::EC.INVALID_DEFINITION, "Unknown pop type: " + poptype ); + } + const auto max_variants = (m_faction->m_flags & rules::Faction::FF_PROGENITOR) + ? 1 // aliens have 1 gender + : 2; // humans have 2 + ASSERT_NOLOG( max_variants > 0, "no variants found for pop type: " + poptype ); + AddPop( Pop( this, def, m_game->GetRandom()->GetUInt(0, max_variants - 1) ) ); + return VALUE( gse::type::Undefined ); + } ) + }, WRAPIMPL_DYNAMIC_SETTERS( Base ) WRAPIMPL_DYNAMIC_ON_SET( Base ) WRAPIMPL_DYNAMIC_END() diff --git a/src/game/backend/base/Base.h b/src/game/backend/base/Base.h index ec21bb96..1829db45 100644 --- a/src/game/backend/base/Base.h +++ b/src/game/backend/base/Base.h @@ -1,14 +1,15 @@ #pragma once +#include #include -#include "BaseData.h" - #include "gse/Wrappable.h" #include "game/backend/MapObject.h" #include "types/Buffer.h" +#include "Pop.h" + namespace game { namespace backend { @@ -16,6 +17,9 @@ class Game; namespace slot { class Slot; } +namespace rules { +class Faction; +} namespace map::tile { class Tile; } @@ -28,19 +32,26 @@ class Base : public gse::Wrappable, public MapObject { static const size_t GetNextId(); static const void SetNextId( const size_t id ); + typedef std::vector< Pop > pops_t; + Base( Game* game, const size_t id, slot::Slot* owner, + rules::Faction* faction, // faction may differ from owner's faction in some cases, i.e. after being conquered map::tile::Tile* tile, - const BaseData& data + const std::string& name, + const pops_t& pops ); virtual ~Base() = default; + void AddPop( const Pop& pop ); + const size_t m_id; slot::Slot* m_owner; - - BaseData m_data; + rules::Faction* m_faction; + std::string m_name; + pops_t m_pops; static const types::Buffer Serialize( const Base* base ); static Base* Unserialize( types::Buffer& buf, Game* game ); diff --git a/src/game/backend/base/BaseData.cpp b/src/game/backend/base/BaseData.cpp deleted file mode 100644 index cac23c10..00000000 --- a/src/game/backend/base/BaseData.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "BaseData.h" - -namespace game { -namespace backend { -namespace base { - -BaseData::BaseData( const std::string& name, const size_t population ) - : name( name ) - , population( population ) { - // -} - -BaseData::BaseData( types::Buffer& buf ) { - Unserialize( buf ); -} - -void BaseData::Serialize( types::Buffer& buf ) const { - buf.WriteString( name ); - buf.WriteInt( population ); -} - -void BaseData::Unserialize( types::Buffer& buf ) { - name = buf.ReadString(); - population = buf.ReadInt(); -} - -TS_BEGIN( BaseData ) - TS_FUNC_BEGIN( "BaseData" ) + - TS_OBJ_PROP_STR( "name", name ) + - TS_OBJ_PROP_NUM( "population", population ) + - TS_FUNC_END() -TS_END() - -} -} -} diff --git a/src/game/backend/base/BaseData.h b/src/game/backend/base/BaseData.h deleted file mode 100644 index f61266e7..00000000 --- a/src/game/backend/base/BaseData.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "gse/Wrappable.h" -#include "gse/Value.h" - -#include "types/Buffer.h" - -namespace game { -namespace backend { -namespace base { - -class BaseData : public gse::Wrappable { -public: - - BaseData( const std::string& name, const size_t population ); - BaseData( types::Buffer& buf ); - - std::string name; - size_t population; - - void Serialize( types::Buffer& buf ) const; - void Unserialize( types::Buffer& buf ); - - const std::string ToString( const std::string& prefix = "" ) const; - - WRAPDEFS_DYNAMIC( BaseData ); - -}; - -} -} -} diff --git a/src/game/backend/base/CMakeLists.txt b/src/game/backend/base/CMakeLists.txt index 74476b9c..e5d9c85a 100644 --- a/src/game/backend/base/CMakeLists.txt +++ b/src/game/backend/base/CMakeLists.txt @@ -1,6 +1,7 @@ SET( SRC ${SRC} ${PWD}/Base.cpp - ${PWD}/BaseData.cpp + ${PWD}/PopDef.cpp + ${PWD}/Pop.cpp PARENT_SCOPE ) diff --git a/src/game/backend/base/Pop.cpp b/src/game/backend/base/Pop.cpp new file mode 100644 index 00000000..aab6094d --- /dev/null +++ b/src/game/backend/base/Pop.cpp @@ -0,0 +1,67 @@ +#include "Pop.h" + +#include "game/backend/Game.h" +#include "game/backend/base/Base.h" +#include "game/backend/base/PopDef.h" +#include "gse/type/String.h" +#include "gse/type/Int.h" +#include "gse/type/Undefined.h" +#include "gse/callable/Native.h" + +namespace game { +namespace backend { +namespace base { + +Pop::Pop( Base* const base, PopDef* def, const uint8_t variant ) + : m_base( base ) + , m_def( def ) + , m_variant( variant ) { + // +} + +void Pop::Serialize( types::Buffer& buf ) const { + ASSERT_NOLOG( m_base, "pop base is null" ); + ASSERT_NOLOG( m_def, "pop def is null" ); + + buf.WriteInt( m_base->m_id ); + buf.WriteString( m_def->m_name ); + buf.WriteInt( m_variant ); +} + +void Pop::Unserialize( types::Buffer& buf, Game* game ) { + ASSERT_NOLOG( !m_base, "pop base is not null" ); + ASSERT_NOLOG( !m_def, "pop def is not null" ); + + m_base = game->GetBase( buf.ReadInt() ); + ASSERT_NOLOG( m_base, "base not found" ); + m_def = game->GetPopDef( buf.ReadString() ); + ASSERT_NOLOG( m_def, "pop def not found" ); + m_variant = buf.ReadInt(); +} + +WRAPIMPL_BEGIN( Pop, CLASS_BASE_POP ) + WRAPIMPL_PROPS { + { + "type", + VALUE( gse::type::String, m_def->m_id ) + }, + { + "variant", + VALUE( gse::type::Int, m_variant ) + }, + { + "get_base", + NATIVE_CALL( this ) { + N_EXPECT_ARGS( 0 ); + ASSERT_NOLOG( m_base, "pop has no base" ); + return m_base->Wrap(); + } ) + }, + }; +WRAPIMPL_END_PTR( Pop ) + +UNWRAPIMPL_PTR( Pop ) + +} +} +} diff --git a/src/game/backend/base/Pop.h b/src/game/backend/base/Pop.h new file mode 100644 index 00000000..8cf9c4c1 --- /dev/null +++ b/src/game/backend/base/Pop.h @@ -0,0 +1,35 @@ +#pragma once + +#include "types/Buffer.h" + +#include "gse/Wrappable.h" + +namespace game { +namespace backend { + +class Game; + +namespace base { + +class Base; +class PopDef; + +class Pop : public gse::Wrappable { +public: + + Pop( Base* base = nullptr, PopDef* def = nullptr, const uint8_t variant = 0 ); + + void Serialize( types::Buffer& buf ) const; + void Unserialize( types::Buffer& buf, Game* game ); + + WRAPDEFS_PTR( Pop ); + + Base* m_base; + PopDef* m_def; + uint8_t m_variant = 0; + +}; + +} +} +} \ No newline at end of file diff --git a/src/game/backend/base/PopDef.cpp b/src/game/backend/base/PopDef.cpp new file mode 100644 index 00000000..37f32758 --- /dev/null +++ b/src/game/backend/base/PopDef.cpp @@ -0,0 +1,95 @@ +#include "PopDef.h" + +namespace game { +namespace backend { +namespace base { + +PopDef::PopDef( + const std::string& id, + const std::string& name, + const pop_render_infos_t& renders_human, + const pop_render_infos_t& renders_progenitor, + const pop_flags_t flags +) + : m_id( id ) + , m_name( name ) + , m_renders_human( renders_human ) + , m_renders_progenitor( renders_progenitor ) + , m_flags( flags ) { + // +} + +const std::string PopDef::ToString( const std::string& prefix ) const { + return (std::string) + TS_OBJ_BEGIN( "PopDef" ) + + TS_OBJ_PROP_STR( "id", m_id ) + + TS_OBJ_PROP_STR( "name", m_name ) + + InfosToString( prefix, "renders_human", m_renders_human ) + + InfosToString( prefix, "renders_progenitor", m_renders_human ) + + TS_OBJ_PROP_STR( "flags", ( + ( m_flags & PF_TILE_WORKER ) + ? "tile_worker" + : "" + ) ) + + TS_OBJ_END(); +} + +const types::Buffer PopDef::Serialize( const PopDef* def ) { + types::Buffer buf; + buf.WriteString( def->m_id ); + buf.WriteString( def->m_name ); +#define X( _r ) \ + buf.WriteInt( def->_r.size() ); \ + for ( const auto& r : def->_r ) { \ + buf.WriteString( r.file ); \ + buf.WriteInt( r.x ); \ + buf.WriteInt( r.y ); \ + buf.WriteInt( r.width ); \ + buf.WriteInt( r.height ); \ + } + X( m_renders_human ) + X( m_renders_progenitor ) +#undef X + buf.WriteInt( def->m_flags ); + return buf; +} + +PopDef* PopDef::Unserialize( types::Buffer& buf ) { + const auto id = buf.ReadString(); + const auto name = buf.ReadString(); +#define X( _r ) \ + pop_render_infos_t _r = {}; \ + _r.resize( buf.ReadInt() ); \ + for ( auto& r : _r ) { \ + r.file = buf.ReadString(); \ + r.x = buf.ReadInt(); \ + r.y = buf.ReadInt(); \ + r.width = buf.ReadInt(); \ + r.height = buf.ReadInt(); \ + } + X( renders_human ) + X( renders_progenitor ) +#undef X + const auto flags = buf.ReadInt(); + return new PopDef( id, name, renders_human, renders_progenitor, flags ); +} + +const std::string PopDef::InfosToString( const std::string& prefix, const std::string& name, const pop_render_infos_t& infos ) const { + std::string result = TS_ARR_BEGIN( name ); + for ( size_t i = 0 ; i < infos.size() ; i++ ) { + const auto& info = infos.at( i ); + result += TS_OBJ_BEGIN( std::to_string( i ) ) + + TS_OBJ_PROP_STR( "file", info.file ) + + TS_OBJ_PROP_NUM( "x", info.x ) + + TS_OBJ_PROP_NUM( "y", info.y ) + + TS_OBJ_PROP_NUM( "width", info.width ) + + TS_OBJ_PROP_NUM( "height", info.height ) + + TS_OBJ_END(); + } + result += TS_ARR_END(); + return result; +} + +} +} +} diff --git a/src/game/backend/base/PopDef.h b/src/game/backend/base/PopDef.h new file mode 100644 index 00000000..2be3435d --- /dev/null +++ b/src/game/backend/base/PopDef.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "Types.h" +#include "types/Buffer.h" + +namespace game { +namespace backend { +namespace base { + +class PopDef { +public: + + typedef uint8_t pop_flags_t; + static constexpr pop_flags_t PF_NONE = 0; + static constexpr pop_flags_t PF_TILE_WORKER = 1 << 0; + + PopDef( + const std::string& id, + const std::string& name, + const pop_render_infos_t& renders_human, + const pop_render_infos_t& renders_progenitor, + const pop_flags_t flags + ); + virtual ~PopDef() = default; + + const std::string m_id; + const std::string m_name; + const pop_render_infos_t m_renders_human; + const pop_render_infos_t m_renders_progenitor; + const pop_flags_t m_flags; + + const std::string ToString( const std::string& prefix = "" ) const; + + static const types::Buffer Serialize( const PopDef* def ); + static PopDef* Unserialize( types::Buffer& buf ); + +private: + const std::string InfosToString( const std::string& prefix, const std::string& name, const pop_render_infos_t& infos ) const; + +}; + +} +} +} diff --git a/src/game/backend/base/Types.h b/src/game/backend/base/Types.h new file mode 100644 index 00000000..277f9810 --- /dev/null +++ b/src/game/backend/base/Types.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace game { +namespace backend { +namespace base { + +struct pop_render_info_t { + std::string file; + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; +}; +typedef std::vector< pop_render_info_t > pop_render_infos_t; + +} +} +} diff --git a/src/game/backend/bindings/Animations.cpp b/src/game/backend/bindings/Animations.cpp index 196eb289..bac3f93a 100644 --- a/src/game/backend/bindings/Animations.cpp +++ b/src/game/backend/bindings/Animations.cpp @@ -65,13 +65,11 @@ BINDING_IMPL( animations ) { sound ); auto* game = GAME; - game->AddEvent( new event::DefineAnimation( game->GetSlotNum(), def ) ); - return VALUE( gse::type::Undefined ); + return game->AddEvent( new event::DefineAnimation( game->GetSlotNum(), def ) ); } else { ERROR( gse::EC.GAME_ERROR, "Unsupported animation type: " + type ); } - return VALUE( gse::type::Undefined ); }) }, { diff --git a/src/game/backend/bindings/Bases.cpp b/src/game/backend/bindings/Bases.cpp index 3905f57e..609b8bad 100644 --- a/src/game/backend/bindings/Bases.cpp +++ b/src/game/backend/bindings/Bases.cpp @@ -8,9 +8,14 @@ #include "gse/type/Object.h" #include "gse/type/Int.h" #include "gse/type/String.h" +#include "gse/type/Array.h" +#include "gse/type/Bool.h" +#include "gse/context/Context.h" #include "game/backend/Game.h" #include "game/backend/slot/Slot.h" #include "game/backend/map/tile/Tile.h" +#include "game/backend/base/PopDef.h" +#include "game/backend/event/DefinePop.h" #include "game/backend/event/SpawnBase.h" namespace game { @@ -19,26 +24,87 @@ namespace bindings { BINDING_IMPL( bases ) { const gse::type::object_properties_t properties = { + { + "define_pop", + NATIVE_CALL( this ) { + N_EXPECT_ARGS( 2 ); + N_GETVALUE( id, 0, String ); + N_GETVALUE( def, 1, Object ); + + N_GETPROP( name, def, "name", String ); + + base::pop_render_infos_t rh = {}; + base::pop_render_infos_t rp = {}; + const auto& f_read_renders = [ &def, &arg, &call_si, &ctx, &getprop_val, &obj_it ]( const std::string& key, base::pop_render_infos_t& out ) { + N_GETPROP( renders, def, key, Array ); + out.reserve( renders.size() ); + for ( const auto& v : renders ) { + if ( v.Get()->type != gse::type::Type::T_OBJECT ) { + ERROR( gse::EC.INVALID_CALL, "Pop render elements must be objects" ); + } + const auto* obj = (gse::type::Object*)v.Get(); + const auto& ov = obj->value; + N_GETPROP( type, ov, "type", String ); + if ( type == "sprite" ) { + N_GETPROP( file, ov, "file", String ); + N_GETPROP( x, ov, "x", Int ); + N_GETPROP( y, ov, "y", Int ); + N_GETPROP( w, ov, "w", Int ); + N_GETPROP( h, ov, "h", Int ); + out.push_back( + base::pop_render_info_t{ + file, + (uint16_t)x, + (uint16_t)y, + (uint16_t)w, + (uint16_t)h + } + ); + } + else { + ERROR( gse::EC.INVALID_CALL, "Only sprite pops are supported for now" ); + } + + } + }; + f_read_renders( "renders_human", rh ); + f_read_renders( "renders_progenitor", rp ); + + base::PopDef::pop_flags_t flags = base::PopDef::PF_NONE; + N_GETPROP_OPT( bool, can_work_tiles, def, "tile_worker", Bool, false ); + if ( can_work_tiles ) { + flags |= base::PopDef::PF_TILE_WORKER; + } + + auto* game = GAME; + return game->AddEvent( new event::DefinePop( + game->GetSlotNum(), + new base::PopDef( id, name, rh, rp, flags ) + ) ); + } ) + }, { "spawn", NATIVE_CALL( this ) { - N_EXPECT_ARGS( 3 ); + N_EXPECT_ARGS_MIN_MAX( 3, 4 ); N_GETVALUE_UNWRAP( owner, 0, slot::Slot ); N_GETVALUE_UNWRAP( tile, 1, map::tile::Tile ); N_GETVALUE( info, 2, Object ); N_GETPROP_OPT( std::string, name, info, "name", String, "" ); - N_GETPROP_OPT( size_t, population, info, "population", Int, 1 ); + + if ( arguments.size() > 3 ) { + N_PERSIST_CALLABLE( on_spawn, 3 ); + } auto* game = GAME; - game->AddEvent( new event::SpawnBase( + return game->AddEvent( new event::SpawnBase( game->GetSlotNum(), owner->GetIndex(), tile->coord.x, tile->coord.y, - base::BaseData( name, population ) + name ) ); - return VALUE( gse::type::Undefined ); }) }, }; diff --git a/src/game/backend/bindings/Binding.h b/src/game/backend/bindings/Binding.h index 5febb811..5af7be5d 100644 --- a/src/game/backend/bindings/Binding.h +++ b/src/game/backend/bindings/Binding.h @@ -88,6 +88,8 @@ BINDING_DEF( animations ) BINDING_DEF( map ) +BINDING_DEF( resources ) + } } } diff --git a/src/game/backend/bindings/Bindings.cpp b/src/game/backend/bindings/Bindings.cpp index ad47e24a..4b804600 100644 --- a/src/game/backend/bindings/Bindings.cpp +++ b/src/game/backend/bindings/Bindings.cpp @@ -48,6 +48,7 @@ Bindings::Bindings( State* state ) B( bases ), B( animations ), B( map ), + B( resources ), }; #undef B } diff --git a/src/game/backend/bindings/Bindings.h b/src/game/backend/bindings/Bindings.h index 797cd8bb..60abc8fd 100644 --- a/src/game/backend/bindings/Bindings.h +++ b/src/game/backend/bindings/Bindings.h @@ -42,6 +42,7 @@ class Bindings : public gse::Bindings { CS_ON_CONFIGURE, CS_ON_START, CS_ON_TURN, + CS_ON_GET_TILE_YIELDS, CS_ON_UNIT_SPAWN, CS_ON_UNIT_DESPAWN, CS_ON_UNIT_MOVE_VALIDATE, diff --git a/src/game/backend/bindings/CMakeLists.txt b/src/game/backend/bindings/CMakeLists.txt index 96418de7..51987b0b 100644 --- a/src/game/backend/bindings/CMakeLists.txt +++ b/src/game/backend/bindings/CMakeLists.txt @@ -13,5 +13,6 @@ SET( SRC ${SRC} ${PWD}/Bases.cpp ${PWD}/Animations.cpp ${PWD}/Map.cpp + ${PWD}/Resources.cpp PARENT_SCOPE ) diff --git a/src/game/backend/bindings/On.cpp b/src/game/backend/bindings/On.cpp index 4804c406..5203622e 100644 --- a/src/game/backend/bindings/On.cpp +++ b/src/game/backend/bindings/On.cpp @@ -17,6 +17,7 @@ BINDING_IMPL( on ) { ON( "configure", CS_ON_CONFIGURE ), ON( "start", CS_ON_START ), ON( "turn", CS_ON_TURN ), + ON( "get_tile_yields", CS_ON_GET_TILE_YIELDS ), ON( "unit_spawn", CS_ON_UNIT_SPAWN ), ON( "unit_despawn", CS_ON_UNIT_DESPAWN ), ON( "unit_move_validate", CS_ON_UNIT_MOVE_VALIDATE ), diff --git a/src/game/backend/bindings/Resources.cpp b/src/game/backend/bindings/Resources.cpp new file mode 100644 index 00000000..1f1c6d6b --- /dev/null +++ b/src/game/backend/bindings/Resources.cpp @@ -0,0 +1,89 @@ +#include "Binding.h" + +#include "game/backend/Game.h" +#include "Bindings.h" + +#include "gse/type/String.h" +#include "gse/type/Int.h" +#include "gse/callable/Native.h" +#include "gse/Exception.h" +#include "gse/type/Undefined.h" + +#include "game/backend/Resource.h" +#include "game/backend/event/DefineResource.h" + +namespace game { +namespace backend { +namespace bindings { + +BINDING_IMPL( resources ) { + const gse::type::object_properties_t properties = { + { + "define", + NATIVE_CALL( this ) { + N_EXPECT_ARGS( 2 ); + N_GETVALUE( id, 0, String ); + N_GETVALUE( def, 1, Object ); + N_GETPROP( name, def, "name", String ); + N_GETPROP( render, def, "render", Object ); + N_GETPROP( type, render, "type", String ); + + if ( type == "sprite_map" ) { + N_GETPROP( file, render, "file", String ); + + N_GETPROP( yields, render, "yields", Object ); + N_GETPROP( grid_x, yields, "grid_x", Int ); + N_GETPROP( grid_y, yields, "grid_y", Int ); + N_GETPROP( grid_margin, yields, "grid_margin", Int ); + N_GETPROP( cell_width, yields, "cell_width", Int ); + N_GETPROP( cell_height, yields, "cell_height", Int ); + N_GETPROP( cells_count, yields, "cells_count", Int ); + +#define X( _n ) \ + N_GETPROP( _n, render, "plus", Object ); \ + N_GETPROP( _n ## _x, _n, "x", Int ); \ + N_GETPROP( _n ## _y, _n, "y", Int ); \ + N_GETPROP( _n ## _width, _n, "width", Int ); \ + N_GETPROP( _n ## _height, _n, "height", Int ); + X( plus ) + X( minus ) + + auto* resource = new Resource( + id, + name, + { + file, + { + (uint16_t)grid_x, (uint16_t)grid_y, (uint8_t)grid_margin, + (uint16_t)cell_width, (uint16_t)cell_height, (uint8_t)cells_count, + }, + { + (uint16_t)plus_x, (uint16_t)plus_y, + (uint16_t)plus_width, (uint16_t)plus_height, + }, + { + (uint16_t)minus_x, (uint16_t)minus_y, + (uint16_t)minus_width, (uint16_t)minus_height, + }, + } + ); + auto* game = GAME; + return game->AddEvent( new event::DefineResource( game->GetSlotNum(), resource ) ); + } + else { + ERROR( gse::EC.GAME_ERROR, "Unsupported resource type: " + type ); + } + + return VALUE( gse::type::Undefined ); + } ) + }, + }; + return VALUE( gse::type::Object, properties ); + +} + +} +} +} + +#undef X \ No newline at end of file diff --git a/src/game/backend/bindings/Units.cpp b/src/game/backend/bindings/Units.cpp index 1bdab30f..326f2eb0 100644 --- a/src/game/backend/bindings/Units.cpp +++ b/src/game/backend/bindings/Units.cpp @@ -63,8 +63,7 @@ BINDING_IMPL( units ) { values.push_back( unit::Morale{ name } ); } auto* game = GAME; - game->AddEvent( new event::DefineMorales( game->GetSlotNum(), new unit::MoraleSet( id, values ) ) ); - return VALUE( gse::type::Undefined ); + return game->AddEvent( new event::DefineMorales( game->GetSlotNum(), new unit::MoraleSet( id, values ) ) ); } ) }, { @@ -128,8 +127,7 @@ BINDING_IMPL( units ) { sprite_morale_based_xshift ) ); - game->AddEvent( new event::DefineUnit( game->GetSlotNum(), def ) ); - return VALUE( gse::type::Undefined ); + return game->AddEvent( new event::DefineUnit( game->GetSlotNum(), def ) ); } else { ERROR( gse::EC.GAME_ERROR, "Unsupported render type: " + render_type ); @@ -138,7 +136,6 @@ BINDING_IMPL( units ) { else { ERROR( gse::EC.GAME_ERROR, "Unsupported unit type: " + unit_type ); } - return VALUE( gse::type::Undefined ); }) }, { @@ -151,7 +148,7 @@ BINDING_IMPL( units ) { N_GETVALUE( morale, 3, Int ); N_GETVALUE( health, 4, Float ); auto* game = GAME; - game->AddEvent( new event::SpawnUnit( + return game->AddEvent( new event::SpawnUnit( game->GetSlotNum(), def_name, owner->GetIndex(), @@ -160,7 +157,6 @@ BINDING_IMPL( units ) { GetMorale( morale, ctx, call_si ), GetHealth( health, ctx, call_si ) ) ); - return VALUE( gse::type::Undefined ); }) }, { @@ -169,8 +165,7 @@ BINDING_IMPL( units ) { N_EXPECT_ARGS( 1 ); N_GETVALUE_UNWRAP( unit, 0, unit::Unit ); auto* game = GAME; - game->AddEvent( new event::DespawnUnit( game->GetSlotNum(), unit->m_id ) ); - return VALUE( gse::type::Undefined ); + return game->AddEvent( new event::DespawnUnit( game->GetSlotNum(), unit->m_id ) ); }) }, }; diff --git a/src/game/backend/event/CMakeLists.txt b/src/game/backend/event/CMakeLists.txt index 3776ace9..5e238732 100644 --- a/src/game/backend/event/CMakeLists.txt +++ b/src/game/backend/event/CMakeLists.txt @@ -1,6 +1,7 @@ SET( SRC ${SRC} ${PWD}/Event.cpp + ${PWD}/DefineResource.cpp ${PWD}/DefineAnimation.cpp ${PWD}/DefineMorales.cpp ${PWD}/DefineUnit.cpp @@ -9,6 +10,7 @@ SET( SRC ${SRC} ${PWD}/MoveUnit.cpp ${PWD}/AttackUnit.cpp ${PWD}/SkipUnitTurn.cpp + ${PWD}/DefinePop.cpp ${PWD}/SpawnBase.cpp ${PWD}/CompleteTurn.cpp ${PWD}/UncompleteTurn.cpp diff --git a/src/game/backend/event/DefinePop.cpp b/src/game/backend/event/DefinePop.cpp new file mode 100644 index 00000000..cb903f9f --- /dev/null +++ b/src/game/backend/event/DefinePop.cpp @@ -0,0 +1,47 @@ +#include "DefinePop.h" + +#include "game/backend/Game.h" + +#include "game/backend/base/PopDef.h" +#include "gse/type/Undefined.h" + +namespace game { +namespace backend { +namespace event { + +DefinePop::DefinePop( const size_t initiator_slot, base::PopDef* pop_def ) + : Event( initiator_slot, ET_BASE_DEFINE_POP ) + , m_pop_def( pop_def ) { + // +} + +const std::string* DefinePop::Validate( Game* game ) const { + if ( m_initiator_slot != 0 ) { + return Error( "Only master is allowed to define units" ); + } + return Ok(); +} + +const gse::Value DefinePop::Apply( Game* game ) const { + game->DefinePop( m_pop_def ); + return VALUE( gse::type::Undefined ); +} + +TS_BEGIN( DefinePop ) + TS_FUNC_BEGIN( "DefinePop" ) + + TS_FUNC_ARG( "def", TS_OF( m_pop_def ) ) + + TS_FUNC_END() +TS_END() + +void DefinePop::Serialize( types::Buffer& buf, const DefinePop* event ) { + buf.WriteString( base::PopDef::Serialize( event->m_pop_def ).ToString() ); +} + +DefinePop* DefinePop::Unserialize( types::Buffer& buf, const size_t initiator_slot ) { + auto b = types::Buffer( buf.ReadString() ); + return new DefinePop( initiator_slot, base::PopDef::Unserialize( b ) ); +} + +} +} +} diff --git a/src/game/backend/event/DefinePop.h b/src/game/backend/event/DefinePop.h new file mode 100644 index 00000000..65323005 --- /dev/null +++ b/src/game/backend/event/DefinePop.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Event.h" + +namespace game { +namespace backend { + +namespace base { +class PopDef; +} + +namespace event { + +class DefinePop : public Event { +public: + DefinePop( const size_t initiator_slot, base::PopDef* pop_def ); + + const std::string* Validate( Game* game ) const override; + const gse::Value Apply( Game* game ) const override; + TS_DEF() + +private: + friend class Event; + + static void Serialize( types::Buffer& buf, const DefinePop* event ); + static DefinePop* Unserialize( types::Buffer& buf, const size_t initiator_slot ); + +private: + base::PopDef* m_pop_def; +}; + +} +} +} diff --git a/src/game/backend/event/DefineResource.cpp b/src/game/backend/event/DefineResource.cpp new file mode 100644 index 00000000..b4179711 --- /dev/null +++ b/src/game/backend/event/DefineResource.cpp @@ -0,0 +1,47 @@ +#include "DefineResource.h" + +#include "game/backend/Game.h" + +#include "game/backend/Resource.h" +#include "gse/type/Undefined.h" + +namespace game { +namespace backend { +namespace event { + +DefineResource::DefineResource( const size_t initiator_slot, Resource* resource ) + : Event( initiator_slot, ET_RESOURCE_DEFINE ) + , m_resource( resource ) { + // +} + +const std::string* DefineResource::Validate( Game* game ) const { + if ( m_initiator_slot != 0 ) { + return Error( "Only master is allowed to define resources" ); + } + return Ok(); +} + +const gse::Value DefineResource::Apply( Game* game ) const { + game->DefineResource( m_resource ); + return VALUE( gse::type::Undefined ); +} + +TS_BEGIN( DefineResource ) + TS_FUNC_BEGIN( "DefineResource" ) + + TS_FUNC_ARG( "resource", TS_OF( m_resource ) ) + + TS_FUNC_END() +TS_END() + +void DefineResource::Serialize( types::Buffer& buf, const DefineResource* event ) { + buf.WriteString( Resource::Serialize( event->m_resource ).ToString() ); +} + +DefineResource* DefineResource::Unserialize( types::Buffer& buf, const size_t initiator_slot ) { + auto b = types::Buffer( buf.ReadString() ); + return new DefineResource( initiator_slot, Resource::Unserialize( b ) ); +} + +} +} +} diff --git a/src/game/backend/event/DefineResource.h b/src/game/backend/event/DefineResource.h new file mode 100644 index 00000000..4b7cb968 --- /dev/null +++ b/src/game/backend/event/DefineResource.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Event.h" + +namespace game { +namespace backend { + +class Resource; + +namespace event { + +class DefineResource : public Event { +public: + DefineResource( const size_t initiator_slot, Resource* resource ); + + const std::string* Validate( Game* game ) const override; + const gse::Value Apply( Game* game ) const override; + TS_DEF() + +private: + friend class Event; + + static void Serialize( types::Buffer& buf, const DefineResource* event ); + static DefineResource* Unserialize( types::Buffer& buf, const size_t initiator_slot ); + +private: + Resource* m_resource; +}; + +} +} +} diff --git a/src/game/backend/event/Event.cpp b/src/game/backend/event/Event.cpp index cf6d5e57..30a4a62f 100644 --- a/src/game/backend/event/Event.cpp +++ b/src/game/backend/event/Event.cpp @@ -1,5 +1,6 @@ #include "Event.h" +#include "DefineResource.h" #include "DefineAnimation.h" #include "DefineMorales.h" #include "DefineUnit.h" @@ -8,6 +9,7 @@ #include "MoveUnit.h" #include "AttackUnit.h" #include "SkipUnitTurn.h" +#include "DefinePop.h" #include "SpawnBase.h" #include "CompleteTurn.h" #include "UncompleteTurn.h" @@ -39,6 +41,7 @@ const types::Buffer Event::Serialize( const Event* event ) { break; \ } switch ( event->m_type ) { + SERIALIZE( ET_RESOURCE_DEFINE, DefineResource ) SERIALIZE( ET_ANIMATION_DEFINE, DefineAnimation ) SERIALIZE( ET_UNIT_DEFINE_MORALES, DefineMorales ) SERIALIZE( ET_UNIT_DEFINE, DefineUnit ) @@ -47,6 +50,7 @@ const types::Buffer Event::Serialize( const Event* event ) { SERIALIZE( ET_UNIT_MOVE, MoveUnit ) SERIALIZE( ET_UNIT_ATTACK, AttackUnit ) SERIALIZE( ET_UNIT_SKIP_TURN, SkipUnitTurn ) + SERIALIZE( ET_BASE_DEFINE_POP, DefinePop ) SERIALIZE( ET_BASE_SPAWN, SpawnBase ) SERIALIZE( ET_COMPLETE_TURN, CompleteTurn ) SERIALIZE( ET_UNCOMPLETE_TURN, UncompleteTurn ) @@ -74,6 +78,7 @@ Event* Event::Unserialize( types::Buffer& buf ) { break; \ } switch ( type ) { + UNSERIALIZE( ET_RESOURCE_DEFINE, DefineResource ) UNSERIALIZE( ET_ANIMATION_DEFINE, DefineAnimation ) UNSERIALIZE( ET_UNIT_DEFINE_MORALES, DefineMorales ) UNSERIALIZE( ET_UNIT_DEFINE, DefineUnit ) @@ -82,6 +87,7 @@ Event* Event::Unserialize( types::Buffer& buf ) { UNSERIALIZE( ET_UNIT_MOVE, MoveUnit ) UNSERIALIZE( ET_UNIT_ATTACK, AttackUnit ) UNSERIALIZE( ET_UNIT_SKIP_TURN, SkipUnitTurn ) + UNSERIALIZE( ET_BASE_DEFINE_POP, DefinePop ) UNSERIALIZE( ET_BASE_SPAWN, SpawnBase ) UNSERIALIZE( ET_COMPLETE_TURN, CompleteTurn ) UNSERIALIZE( ET_UNCOMPLETE_TURN, UncompleteTurn ) diff --git a/src/game/backend/event/Event.h b/src/game/backend/event/Event.h index 00582be9..60f83792 100644 --- a/src/game/backend/event/Event.h +++ b/src/game/backend/event/Event.h @@ -16,6 +16,7 @@ class Event { public: enum event_type_t { ET_NONE, + ET_RESOURCE_DEFINE, ET_ANIMATION_DEFINE, ET_UNIT_DEFINE_MORALES, ET_UNIT_DEFINE, @@ -24,6 +25,7 @@ class Event { ET_UNIT_MOVE, ET_UNIT_ATTACK, ET_UNIT_SKIP_TURN, + ET_BASE_DEFINE_POP, ET_BASE_SPAWN, ET_COMPLETE_TURN, ET_UNCOMPLETE_TURN, diff --git a/src/game/backend/event/SpawnBase.cpp b/src/game/backend/event/SpawnBase.cpp index 90fa2ea1..73b3ad7a 100644 --- a/src/game/backend/event/SpawnBase.cpp +++ b/src/game/backend/event/SpawnBase.cpp @@ -7,7 +7,9 @@ #include "game/backend/unit/StaticDef.h" #include "game/backend/unit/Unit.h" #include "game/backend/base/Base.h" +#include "game/backend/base/Pop.h" #include "gse/type/Undefined.h" +#include "game/backend/Player.h" namespace game { namespace backend { @@ -18,13 +20,13 @@ SpawnBase::SpawnBase( const size_t owner_slot, const size_t pos_x, const size_t pos_y, - const base::BaseData& data + const std::string& name ) : Event( initiator_slot, ET_BASE_SPAWN ) , m_owner_slot( owner_slot ) , m_pos_x( pos_x ) , m_pos_y( pos_y ) - , m_data( data ) { + , m_name( name ) { // } @@ -44,8 +46,10 @@ const gse::Value SpawnBase::Apply( Game* game ) const { game, base::Base::GetNextId(), &owner, + &owner.GetPlayer()->GetFaction().value(), tile, - m_data + m_name, + {} // will be added later ); game->SpawnBase( base ); return base->Wrap(); @@ -56,7 +60,7 @@ TS_BEGIN( SpawnBase ) TS_FUNC_ARG_NUM( "owner_slot", m_owner_slot ) + TS_FUNC_ARG_NUM( "pos_x", m_pos_x ) + TS_FUNC_ARG_NUM( "pos_y", m_pos_y ) + - TS_FUNC_ARG_STR( "data", m_data.ToString( TS_PREFIX_NEXT ) ) + + TS_FUNC_ARG_STR( "name", m_name ) + TS_FUNC_END() TS_END() @@ -64,15 +68,15 @@ void SpawnBase::Serialize( types::Buffer& buf, const SpawnBase* event ) { buf.WriteInt( event->m_owner_slot ); buf.WriteInt( event->m_pos_x ); buf.WriteInt( event->m_pos_y ); - event->m_data.Serialize( buf ); + buf.WriteString( event->m_name ); } SpawnBase* SpawnBase::Unserialize( types::Buffer& buf, const size_t initiator_slot ) { const auto owner_slot = buf.ReadInt(); const auto pos_x = buf.ReadInt(); const auto pos_y = buf.ReadInt(); - const auto data = base::BaseData( buf ); - return new SpawnBase( initiator_slot, owner_slot, pos_x, pos_y, data ); + const auto name = buf.ReadString(); + return new SpawnBase( initiator_slot, owner_slot, pos_x, pos_y, name ); } } diff --git a/src/game/backend/event/SpawnBase.h b/src/game/backend/event/SpawnBase.h index 491b74a2..050a3f1d 100644 --- a/src/game/backend/event/SpawnBase.h +++ b/src/game/backend/event/SpawnBase.h @@ -4,8 +4,6 @@ #include "Event.h" -#include "game/backend/base/BaseData.h" - namespace game { namespace backend { namespace event { @@ -17,7 +15,7 @@ class SpawnBase : public Event { const size_t owner_slot, const size_t pos_x, const size_t pos_y, - const base::BaseData& data + const std::string& name ); const std::string* Validate( Game* game ) const override; @@ -34,7 +32,7 @@ class SpawnBase : public Event { const size_t m_owner_slot; const size_t m_pos_x; const size_t m_pos_y; - const base::BaseData m_data; + const std::string m_name; }; } diff --git a/src/game/backend/map/Map.cpp b/src/game/backend/map/Map.cpp index 2ba378bc..65cc8724 100644 --- a/src/game/backend/map/Map.cpp +++ b/src/game/backend/map/Map.cpp @@ -160,6 +160,7 @@ Map::Map( Game* game ) module_pass.push_back( m ); m_modules_deferred.push_back( module_pass ); } + } Map::~Map() { @@ -616,25 +617,25 @@ const Map::error_code_t Map::Generate( settings::MapSettings* map_settings, MT_C #ifdef DEBUG util::Timer timer; timer.Start(); +#endif const auto* c = g_engine->GetConfig(); - if ( c->HasDebugFlag( config::Config::DF_QUICKSTART ) ) { - if ( c->HasDebugFlag( config::Config::DF_QUICKSTART_MAP_SIZE ) ) { + if ( c->HasLaunchFlag( config::Config::LF_QUICKSTART ) ) { + if ( c->HasLaunchFlag( config::Config::LF_QUICKSTART_MAP_SIZE ) ) { size = c->GetQuickstartMapSize(); } - map_settings->ocean = c->HasDebugFlag( config::Config::DF_QUICKSTART_MAP_OCEAN ) + map_settings->ocean = c->HasLaunchFlag( config::Config::LF_QUICKSTART_MAP_OCEAN ) ? map_settings->ocean = c->GetQuickstartMapOcean() : map_settings->ocean = random->GetUInt( 1, 3 ); - map_settings->erosive = c->HasDebugFlag( config::Config::DF_QUICKSTART_MAP_EROSIVE ) + map_settings->erosive = c->HasLaunchFlag( config::Config::LF_QUICKSTART_MAP_EROSIVE ) ? map_settings->erosive = c->GetQuickstartMapErosive() : map_settings->erosive = random->GetUInt( 1, 3 ); - map_settings->lifeforms = c->HasDebugFlag( config::Config::DF_QUICKSTART_MAP_LIFEFORMS ) + map_settings->lifeforms = c->HasLaunchFlag( config::Config::LF_QUICKSTART_MAP_LIFEFORMS ) ? map_settings->lifeforms = c->GetQuickstartMapLifeforms() : map_settings->lifeforms = random->GetUInt( 1, 3 ); - map_settings->clouds = c->HasDebugFlag( config::Config::DF_QUICKSTART_MAP_CLOUDS ) + map_settings->clouds = c->HasLaunchFlag( config::Config::LF_QUICKSTART_MAP_CLOUDS ) ? map_settings->clouds = c->GetQuickstartMapClouds() : map_settings->clouds = random->GetUInt( 1, 3 ); } -#endif Log( "Generating map of size " + size.ToString() ); ASSERT( !m_tiles, "tiles already set" ); NEW( m_tiles, tile::Tiles, size.x, size.y ); @@ -648,7 +649,7 @@ const Map::error_code_t Map::Generate( settings::MapSettings* map_settings, MT_C #ifdef DEBUG Log( "Map generation took " + std::to_string( timer.GetElapsed().count() ) + "ms" ); // if crash happens - it's handy to have a map file to reproduce it - if ( !c->HasDebugFlag( config::Config::DF_QUICKSTART_MAP_FILE ) ) { // no point saving if we just loaded it + if ( !c->HasLaunchFlag( config::Config::LF_QUICKSTART_MAP_FILE ) ) { // no point saving if we just loaded it Log( (std::string)"Saving map to " + c->GetDebugPath() + s_consts.debug.lastmap_filename ); util::FS::WriteFile( c->GetDebugPath() + s_consts.debug.lastmap_filename, m_tiles->Serialize().ToString() ); } diff --git a/src/game/backend/map/tile/Tile.cpp b/src/game/backend/map/tile/Tile.cpp index b328c9e3..a91cfa7d 100644 --- a/src/game/backend/map/tile/Tile.cpp +++ b/src/game/backend/map/tile/Tile.cpp @@ -120,6 +120,11 @@ const types::Buffer Tile::Serialize() const { buf.WriteInt( features ); buf.WriteInt( terraforming ); + buf.WriteInt( yields.size() ); + for ( const auto& y : yields ) { + buf.WriteInt( y ); + } + return buf; } @@ -141,6 +146,13 @@ void Tile::Unserialize( types::Buffer buf ) { features = buf.ReadInt(); terraforming = buf.ReadInt(); + ASSERT_NOLOG( yields.empty(), "yields not empty" ); + const auto yields_size = buf.ReadInt(); + yields.reserve( yields_size ); + for ( size_t i = 0 ; i < yields_size ; i++ ) { + yields.push_back( buf.ReadInt() ); + } + Update(); } @@ -172,6 +184,18 @@ WRAPIMPL_BEGIN( Tile, CLASS_TILE ) "is_land", VALUE( gse::type::Bool, !is_water_tile ) }, + { + "moisture", + VALUE( gse::type::Int, moisture ) + }, + { + "rockiness", + VALUE( gse::type::Int, rockiness ) + }, + { + "elevation", + VALUE( gse::type::Int, *elevation.center ) + }, { "is_rocky", VALUE( gse::type::Bool, rockiness == ROCKINESS_ROCKY ) diff --git a/src/game/backend/map/tile/Tile.h b/src/game/backend/map/tile/Tile.h index aa9800fd..c91e2539 100644 --- a/src/game/backend/map/tile/Tile.h +++ b/src/game/backend/map/tile/Tile.h @@ -78,6 +78,8 @@ class Tile : public gse::Wrappable { feature_t features; terraforming_t terraforming; + std::vector< uint16_t > yields = {}; + // units (id -> unit) std::map< size_t, unit::Unit* > units = {}; diff --git a/src/game/backend/map/tile/Tiles.cpp b/src/game/backend/map/tile/Tiles.cpp index a245c66b..3ac73602 100644 --- a/src/game/backend/map/tile/Tiles.cpp +++ b/src/game/backend/map/tile/Tiles.cpp @@ -198,7 +198,7 @@ const Tile& Tiles::AtConst( const size_t x, const size_t y ) const { return m_data.at( y * m_width + x / 2 ); } -const std::vector< Tile >* Tiles::GetTilesPtr() const { +std::vector< Tile >* Tiles::GetTilesPtr() { return &m_data; } @@ -275,7 +275,7 @@ void Tiles::FixTopBottomRows( util::random::Random* random ) { } const std::vector< Tile* > Tiles::GetVector( MT_CANCELABLE ) { - std::vector< Tile* > tiles; + std::vector< Tile* > tiles = {}; const size_t tiles_count = GetDataCount() / 2; // / 2 because SMAC coordinate system tiles.reserve( tiles_count ); for ( size_t y = 0 ; y < m_height ; y++ ) { diff --git a/src/game/backend/map/tile/Tiles.h b/src/game/backend/map/tile/Tiles.h index 4e56c88e..25000ddb 100644 --- a/src/game/backend/map/tile/Tiles.h +++ b/src/game/backend/map/tile/Tiles.h @@ -32,7 +32,7 @@ CLASS( Tiles, types::Serializable ) Tile& At( const size_t x, const size_t y ); const Tile& AtConst( const size_t x, const size_t y ) const; - const std::vector< Tile >* GetTilesPtr() const; + std::vector< Tile >* GetTilesPtr(); elevation_t* TopVertexAt( const size_t x, const size_t y ); elevation_t* TopRightVertexAt( const size_t x ); diff --git a/src/game/backend/rules/Faction.cpp b/src/game/backend/rules/Faction.cpp index d1979a67..9205676a 100644 --- a/src/game/backend/rules/Faction.cpp +++ b/src/game/backend/rules/Faction.cpp @@ -2,6 +2,7 @@ #include "types/texture/Texture.h" #include "gse/type/String.h" +#include "gse/type/Bool.h" #include "engine/Engine.h" #include "loader/texture/TextureLoader.h" #include "resource/ResourceManager.h" @@ -76,6 +77,10 @@ WRAPIMPL_BEGIN( Faction, CLASS_FACTION ) "name", VALUE( gse::type::String, m_name ) }, + { + "is_progenitor", + VALUE( gse::type::Bool, ( m_flags & rules::Faction::FF_PROGENITOR ) == rules::Faction::FF_PROGENITOR ) + }, }; WRAPIMPL_END_PTR( Faction ) diff --git a/src/game/backend/unit/StaticDef.h b/src/game/backend/unit/StaticDef.h index 1624ba16..2f8ad905 100644 --- a/src/game/backend/unit/StaticDef.h +++ b/src/game/backend/unit/StaticDef.h @@ -35,7 +35,7 @@ class StaticDef : public Def { const std::string ToString( const std::string& prefix ) const override; - WRAPDEFS_PTR( StaticDef, override ); + WRAPDEFS_PTR( StaticDef ); private: static const std::unordered_map< movement_type_t, std::string > s_movement_type_str; diff --git a/src/game/frontend/Game.cpp b/src/game/frontend/Game.cpp index 05724957..37d8582c 100644 --- a/src/game/frontend/Game.cpp +++ b/src/game/frontend/Game.cpp @@ -10,6 +10,7 @@ #include "../../ui/UI.h" // TODO: fix path #include "game/backend/Game.h" #include "game/backend/unit/Def.h" +#include "game/backend/base/PopDef.h" #include "game/backend/animation/Def.h" #include "game/backend/connection/Connection.h" #include "task/mainmenu/MainMenu.h" @@ -30,6 +31,7 @@ #include "game/frontend/base/BaseManager.h" #include "game/frontend/base/Base.h" #include "game/frontend/faction/FactionManager.h" +#include "game/frontend/faction/Faction.h" #include "game/frontend/sprite/InstancedSpriteManager.h" #include "game/frontend/text/InstancedTextManager.h" #include "Animation.h" @@ -86,13 +88,10 @@ void Game::Start() { auto* config = g_engine->GetConfig(); if ( m_state->IsMaster() ) { -#ifdef DEBUG - if ( config->HasDebugFlag( config::Config::DF_QUICKSTART_MAP_FILE ) ) { + if ( config->HasLaunchFlag( config::Config::LF_QUICKSTART_MAP_FILE ) ) { m_state->m_settings.global.map.type = backend::settings::MapSettings::MT_MAPFILE; m_state->m_settings.global.map.filename = config->GetQuickstartMapFile(); } -#endif - if ( m_state->m_settings.global.map.type == backend::settings::MapSettings::MT_MAPFILE ) { m_map_data.filename = util::FS::GetBaseName( m_state->m_settings.global.map.filename ); m_map_data.last_directory = util::FS::GetDirName( m_state->m_settings.global.map.filename ); @@ -337,13 +336,13 @@ void Game::Iterate() { } } - if ( m_is_initialized ) { + if ( m_is_initialized ) { // do not check anything else if error // check if previous backend requests were sent successfully if ( m_mt_ids.send_backend_requests ) { auto response = game->MT_GetResponse( m_mt_ids.send_backend_requests ); if ( response.result != backend::R_NONE ) { - ASSERT( response.result == backend::R_SUCCESS, "backend requests result not successful" ); + //ASSERT( response.result == backend::R_SUCCESS, "backend requests result not successful" ); m_mt_ids.send_backend_requests = 0; } } @@ -777,12 +776,10 @@ void Game::SaveMap( const std::string& path ) { } void Game::ConfirmExit( ::ui::ui_handler_t on_confirm ) { -#ifdef DEBUG - if ( g_engine->GetConfig()->HasDebugFlag( config::Config::DF_QUICKSTART ) ) { + if ( g_engine->GetConfig()->HasLaunchFlag( config::Config::LF_QUICKSTART ) ) { on_confirm(); return; } -#endif NEWV( popup, ui::popup::PleaseDontGo, this, on_confirm ); m_map_control.edge_scrolling.timer.Stop(); popup->Open(); @@ -884,36 +881,44 @@ void Game::DefineAnimation( const backend::animation::Def* def ) { void Game::ProcessRequest( const FrontendRequest* request ) { //Log( "Received frontend request (type=" + std::to_string( request->type ) + ")" ); // spammy - const auto f_exit = [ this, request ]() -> void { - const auto quit_reason = request->data.quit.reason - ? *request->data.quit.reason - : ""; + const auto f_exit = [ this ]( const std::string& quit_reason ) -> void { ExitGame( [ this, quit_reason ]() -> void { -#ifdef DEBUG - if ( g_engine->GetConfig()->HasDebugFlag( config::Config::DF_QUICKSTART ) ) { + if ( g_engine->GetConfig()->HasLaunchFlag( config::Config::LF_QUICKSTART ) ) { g_engine->ShutDown(); } - else -#endif - { + else { ReturnToMainMenu( quit_reason ); } } ); }; - switch ( request->type ) { + switch ( request + ? request->type + : FrontendRequest::FR_QUIT ) { case FrontendRequest::FR_QUIT: { - f_exit(); + f_exit( + request && request->data.quit.reason + ? *request->data.quit.reason + : "" + ); break; } case FrontendRequest::FR_ERROR: { - Log( *request->data.error.stacktrace ); - g_engine->GetUI()->ShowError( - *request->data.error.what, UH( f_exit ) { - f_exit(); + if ( !g_engine->GetUI()->HasErrorPopup() ) { // show first error only + const auto& errmsg = *request->data.error.what; + if ( request->data.error.stacktrace ) { + Log( *request->data.error.stacktrace ); } - ); + else { + Log( errmsg ); + } + g_engine->GetUI()->ShowError( + errmsg, UH( f_exit, errmsg ) { + f_exit( errmsg ); + } + ); + } } case FrontendRequest::FR_GLOBAL_MESSAGE: { AddMessage( *request->data.global_message.message ); @@ -934,6 +939,16 @@ void Game::ProcessRequest( const FrontendRequest* request ) { } break; } + case FrontendRequest::FR_TILE_DATA: { + const auto& d = request->data.tile_data; + if ( m_ui.bottom_bar ) { + const auto& sc = m_tm->GetSelectedTile()->m_coords; + if ( d.tile_x == sc.x && d.tile_y == sc.y ) { + m_ui.bottom_bar->SetTileYields( *d.tile_yields ); + } + } + break; + } case FrontendRequest::FR_TURN_STATUS: { m_turn_status = request->data.turn_status.status; ASSERT( m_ui.bottom_bar, "bottom bar not initialized" ); @@ -1074,6 +1089,13 @@ void Game::ProcessRequest( const FrontendRequest* request ) { m_um->MoveUnit( unit, dst_tile, d.running_animation_id ); break; } + case FrontendRequest::FR_BASE_POP_DEFINE: { + types::Buffer buf( *request->data.base_pop_define.serialized_popdef ); + const auto* popdef = backend::base::PopDef::Unserialize( buf ); + m_bm->DefinePop( popdef ); + delete popdef; + break; + } case FrontendRequest::FR_BASE_SPAWN: { const auto& d = request->data.base_spawn; const auto& tc = d.tile_coords; @@ -1090,13 +1112,33 @@ void Game::ProcessRequest( const FrontendRequest* request ) { rc.y, rc.z }, - { - *d.base_info.name, - d.base_info.population - } + *d.name ); break; } + case FrontendRequest::FR_BASE_UPDATE: { + const auto& d = request->data.base_update; + auto* base = m_bm->GetBaseById( d.base_id ); + base->SetName( *d.name ); + // TODO: update slot index + if ( base->GetFaction()->m_id != *d.faction_id ) { + THROW( "TODO: UPDATE FACTION" ); + } + base::Base::pops_t pops = {}; + pops.reserve( d.pops->size() ); + for ( const auto& pop : *d.pops ) { + pops.push_back( + base::Pop{ + base, + m_bm->GetPopDef( pop.type ), + pop.variant + } + ); + } + base->SetPops( pops ); + m_bm->RefreshBase( base ); + break; + } default: { THROW( "unexpected frontend request type: " + std::to_string( request->type ) ); } @@ -1248,7 +1290,7 @@ void Game::Initialize( for ( size_t y = 0 ; y < m_map_data.height ; y++ ) { for ( size_t x = y & 1 ; x < m_map_data.width ; x += 2 ) { auto* tile = m_tm->GetTile( x, y ); - Log( "Initializing tile: " + tile->GetCoords().ToString() ); + //Log( "Initializing tile: " + tile->GetCoords().ToString() ); tile->Update( tiles->at( y * m_map_data.width + x / 2 ), tile_states->at( y * m_map_data.width + x / 2 ) ); } } @@ -2220,10 +2262,6 @@ void Game::CancelRequests() { game->MT_Cancel( m_mt_ids.reset ); m_mt_ids.reset = 0; } - for ( const auto& mt_id : m_mt_ids.select_tile ) { - game->MT_Cancel( mt_id ); - } - m_mt_ids.select_tile.clear(); if ( m_mt_ids.get_frontend_requests ) { game->MT_Cancel( m_mt_ids.get_frontend_requests ); m_mt_ids.get_frontend_requests = 0; @@ -2311,6 +2349,7 @@ void Game::RenderTile( tile::Tile* tile, const unit::Unit* selected_unit ) { ? selected_unit->GetId() : 0 ); + RefreshSelectedTileIf( tile, selected_unit ); /* if ( m_selected_unit && m_selected_unit->IsActive() ) { m_selected_unit->StartBadgeBlink(); @@ -2323,6 +2362,13 @@ void Game::SendAnimationFinished( const size_t animation_id ) { SendBackendRequest( &br ); } +void Game::SendGetTileData( const tile::Tile* tile ) { + auto br = BackendRequest( BackendRequest::BR_GET_TILE_DATA ); + br.data.get_tile_data.tile_x = tile->m_coords.x; + br.data.get_tile_data.tile_y = tile->m_coords.y; + SendBackendRequest( &br ); +} + const bool Game::IsTurnActive() const { return m_is_turn_active; } @@ -2344,12 +2390,18 @@ void Game::RefreshSelectedTile( unit::Unit* selected_unit ) { : 0 ); } + SendGetTileData( selected_tile ); } -void Game::RefreshSelectedTileIf( tile::Tile* if_tile, unit::Unit* selected_unit ) { +void Game::RefreshSelectedTileIf( tile::Tile* if_tile, const unit::Unit* selected_unit ) { auto* selected_tile = m_tm->GetSelectedTile(); if ( selected_tile && selected_tile == if_tile ) { - RefreshSelectedTile( selected_unit ); + m_ui.bottom_bar->PreviewTile( + selected_tile, selected_unit + ? selected_unit->GetId() + : 0 + ); + SendGetTileData( selected_tile ); } } diff --git a/src/game/frontend/Game.h b/src/game/frontend/Game.h index 6a4ffe27..02c28939 100644 --- a/src/game/frontend/Game.h +++ b/src/game/frontend/Game.h @@ -440,7 +440,6 @@ CLASS( Game, common::Module ) common::mt_id_t init = 0; common::mt_id_t get_map_data = 0; common::mt_id_t reset = 0; - std::unordered_set< common::mt_id_t > select_tile = {}; common::mt_id_t save_map = 0; common::mt_id_t edit_map = 0; common::mt_id_t chat = 0; @@ -474,6 +473,10 @@ CLASS( Game, common::Module ) unit::Unit* m_unit_selected_before_base_popup = nullptr; tile::Tile* m_tile_selected_before_base_popup = nullptr; +private: + friend class tile::TileManager; + void SendGetTileData( const tile::Tile* tile ); + private: friend class unit::UnitManager; friend class base::BaseManager; @@ -484,7 +487,7 @@ CLASS( Game, common::Module ) void SetSelectedTile( tile::Tile* tile ); void RefreshSelectedTile( unit::Unit* selected_unit ); - void RefreshSelectedTileIf( tile::Tile* if_tile, unit::Unit* selected_unit ); + void RefreshSelectedTileIf( tile::Tile* if_tile, const unit::Unit* selected_unit ); void ScrollToSelectedTile( const bool center_on_tile ); void SelectAnyUnitAtTile( tile::Tile* tile ); void SelectUnitOrSelectedTile( unit::Unit* selected_unit ); diff --git a/src/game/frontend/base/Base.cpp b/src/game/frontend/base/Base.cpp index 6ecd7783..12bf7672 100644 --- a/src/game/frontend/base/Base.cpp +++ b/src/game/frontend/base/Base.cpp @@ -38,8 +38,7 @@ Base::Base( tile::Tile* tile, const bool is_owned, const types::Vec3& render_coords, - text::InstancedText* render_name_sprite, - size_t population + text::InstancedText* render_name_sprite ) : TileObject( TOT_BASE, tile ) , m_bm( bm ) @@ -56,11 +55,10 @@ Base::Base( } ) , m_is_owned( is_owned ) - , m_population( population ) , m_is_guarded( !m_tile->GetUnits().empty() ) , m_slot_badges( m_bm->GetSlotBadges( slot->GetIndex() ) ) { m_render_data.base = GetMeshTex( GetSprite()->instanced_sprite ); - m_render.badge.def = m_slot_badges->GetBaseBadgeSprite( m_population, m_is_guarded ); + m_render.badge.def = m_slot_badges->GetBaseBadgeSprite( m_render.badge.pops_count, m_is_guarded ); m_render_data.badge = GetMeshTex( m_render.badge.def->instanced_sprite ); m_tile->SetBase( this ); } @@ -79,6 +77,12 @@ const std::string& Base::GetName() const { return m_name; } +void Base::SetName( const std::string& name ) { + if ( m_name != name ) { + m_name = name; + } +} + faction::Faction* const Base::GetFaction() const { return m_faction; } @@ -91,14 +95,11 @@ tile::Tile* Base::GetTile() const { return m_tile; } -const size_t Base::GetPopulation() const { - return m_population; -} - sprite::Sprite* Base::GetSprite() const { uint8_t size = 0; + const auto population = m_pops.size(); for ( uint8_t i = 0 ; i < s_base_render_population_thresholds.size() ; i++ ) { - if ( s_base_render_population_thresholds.at( i ) > m_population ) { + if ( s_base_render_population_thresholds.at( i ) > population ) { break; } size = i; @@ -109,6 +110,13 @@ sprite::Sprite* Base::GetSprite() const { void Base::Show() { if ( !m_render.is_rendered ) { + if ( m_render.badge.pops_count != m_pops.size() ) { + m_render.badge.pops_count = m_pops.size(); + m_render_data.base = GetMeshTex( GetSprite()->instanced_sprite ); + m_render.badge.def = m_slot_badges->GetBaseBadgeSprite( m_render.badge.pops_count, m_is_guarded ); + m_render_data.badge = GetMeshTex( m_render.badge.def->instanced_sprite ); + } + const auto& c = m_render.coords; m_render.sprite = GetSprite(); @@ -155,7 +163,7 @@ void Base::Update() { m_render.badge.instance_id = 0; } m_is_guarded = is_guarded; - m_render.badge.def = m_slot_badges->GetBaseBadgeSprite( m_population, m_is_guarded ); + m_render.badge.def = m_slot_badges->GetBaseBadgeSprite( m_pops.size(), m_is_guarded ); m_render_data.badge = GetMeshTex( m_render.badge.def->instanced_sprite ); if ( m_render.is_rendered ) { ShowBadge(); @@ -185,7 +193,7 @@ void* Base::CreateOnBottomBarList( ui::ObjectsListItem* element ) const { // order is important X( base, "Base" ); - X( badge, m_population >= 10 + X( badge, m_pops.size() >= 10 ? "BaseBadge2" : "BaseBadge1" ); @@ -261,6 +269,14 @@ const bool Base::OnBottomBarListActivate( Game* game ) { return false; // because previously active unit should stay active, base popup will have it's own bottombar } +const Base::pops_t& Base::GetPops() const { + return m_pops; +} + +void Base::SetPops( const pops_t& pops ) { + m_pops = pops; // can be optimized if needed +} + void Base::SetRenderCoords( const types::Vec3& coords ) { Hide(); m_render.coords = coords; diff --git a/src/game/frontend/base/Base.h b/src/game/frontend/base/Base.h index 3d7dfac3..5aef38be 100644 --- a/src/game/frontend/base/Base.h +++ b/src/game/frontend/base/Base.h @@ -6,6 +6,8 @@ #include "game/backend/unit/Types.h" +#include "Pop.h" + #include "util/Timer.h" #include "util/Scroller.h" #include "types/Vec3.h" @@ -58,17 +60,18 @@ class Base : public TileObject { tile::Tile* tile, const bool is_owned, const types::Vec3& render_coords, - text::InstancedText* render_name_sprite, - size_t population + text::InstancedText* render_name_sprite ); ~Base(); const size_t GetId() const; + const std::string& GetName() const; + void SetName( const std::string& name ); + faction::Faction* const GetFaction() const; const bool IsOwned() const; tile::Tile* GetTile() const; - const size_t GetPopulation() const; sprite::Sprite* GetSprite() const; @@ -92,6 +95,10 @@ class Base : public TileObject { void DestroyOnBottomBarPreview( ui::ObjectPreview* element, void* state ) const override; const bool OnBottomBarListActivate( Game* game ) override; + typedef std::vector< Pop > pops_t; + const pops_t& GetPops() const; + void SetPops( const pops_t& pops ); + protected: void SetRenderCoords( const types::Vec3& coords ) override; @@ -112,19 +119,21 @@ class Base : public TileObject { bool is_rendered = false; size_t instance_id = 0; struct { + size_t pops_count = 0; sprite::Sprite* def = nullptr; size_t instance_id = 0; text::InstancedText* label = nullptr; } badge; } m_render; - size_t m_population = 0; bool m_is_guarded = false; const bool m_is_owned = false; render_data_t m_render_data = {}; + pops_t m_pops = {}; + void ShowBadge(); void HideBadge(); diff --git a/src/game/frontend/base/BaseManager.cpp b/src/game/frontend/base/BaseManager.cpp index 2475e654..f4cca304 100644 --- a/src/game/frontend/base/BaseManager.cpp +++ b/src/game/frontend/base/BaseManager.cpp @@ -10,6 +10,8 @@ #include "game/frontend/text/InstancedTextManager.h" #include "game/frontend/text/InstancedText.h" #include "game/frontend/faction/Faction.h" +#include "game/backend/base/PopDef.h" +#include "PopDef.h" #include "types/mesh/Rectangle.h" #include "engine/Engine.h" #include "loader/font/FontLoader.h" @@ -31,6 +33,9 @@ BaseManager::~BaseManager() { for ( const auto& it : m_bases ) { delete it.second; } + for ( const auto& it : m_popdefs ) { + delete it.second; + } } base::Base* BaseManager::GetBaseById( const size_t id ) const { @@ -38,12 +43,36 @@ base::Base* BaseManager::GetBaseById( const size_t id ) const { return m_bases.at( id ); } +void BaseManager::DefinePop( const backend::base::PopDef* def ) { + ASSERT( m_popdefs.find( def->m_id ) == m_popdefs.end(), "popdef already defined: " + def->m_id ); + m_popdefs_order.push_back( def->m_id ); + m_popdefs.insert( + { + def->m_id, + new PopDef( + def->m_name, + def->m_renders_human, + def->m_renders_progenitor + ) + } + ); +} + +const std::vector< std::string >& BaseManager::GetPopDefOrder() const { + return m_popdefs_order; +} + +PopDef* BaseManager::GetPopDef( const std::string& id ) const { + ASSERT( m_popdefs.find( id ) != m_popdefs.end(), "popdef not defined: " + id ); + return m_popdefs.at( id ); +} + void BaseManager::SpawnBase( const size_t base_id, const size_t slot_index, const types::Vec2< size_t >& tile_coords, const types::Vec3& render_coords, - const backend::base::BaseData& data + const std::string& name ) { ASSERT( m_bases.find( base_id ) == m_bases.end(), "base id already exists" ); @@ -52,25 +81,26 @@ void BaseManager::SpawnBase( auto* slot = m_game->GetSlot( slot_index ); auto* faction = slot->GetFaction(); + auto* base = new base::Base( + this, + base_id, + name, + slot, + tile, + slot_index == m_game->GetMySlotIndex(), + render_coords, + m_game->GetITM()->CreateInstancedText( + name, + m_name_font, + faction->m_colors.text, + faction->m_colors.text_shadow + ) + ); + m_bases.insert( { base_id, - new base::Base( - this, - base_id, - data.name, - slot, - tile, - slot_index == m_game->GetMySlotIndex(), - render_coords, - m_game->GetITM()->CreateInstancedText( - data.name, - m_name_font, - faction->m_colors.text, - faction->m_colors.text_shadow - ), - data.population - ) + base } ); auto it = m_faction_base_ids.find( faction ); @@ -85,8 +115,11 @@ void BaseManager::SpawnBase( ASSERT( it->second.find( base_id ) == it->second.end(), "faction base id already exists" ); it->second.insert( base_id ); - m_game->RenderTile( tile, m_game->GetUM()->GetSelectedUnit() ); + RefreshBase( base ); +} +void BaseManager::RefreshBase( Base* base ) { + m_game->RenderTile( base->GetTile(), m_game->GetUM()->GetSelectedUnit() ); } /* TODO void BaseManager::DespawnBase( const size_t base_id ) { diff --git a/src/game/frontend/base/BaseManager.h b/src/game/frontend/base/BaseManager.h index 52e6ecc0..23c1675c 100644 --- a/src/game/frontend/base/BaseManager.h +++ b/src/game/frontend/base/BaseManager.h @@ -1,20 +1,24 @@ #pragma once #include +#include #include #include "common/Common.h" -#include "game/backend/base/BaseData.h" - #include "game/backend/unit/Types.h" #include "types/Vec2.h" #include "types/Vec3.h" -namespace game::backend::unit { +namespace game::backend { +namespace unit { class Def; } +namespace base { +class PopDef; +} +} namespace game { namespace frontend { @@ -42,6 +46,7 @@ class Faction; namespace base { +class PopDef; class Base; class SlotBadges; @@ -52,14 +57,20 @@ CLASS( BaseManager, common::Class ) base::Base* GetBaseById( const size_t id ) const; + void DefinePop( const backend::base::PopDef* def ); + + const std::vector< std::string >& GetPopDefOrder() const; + PopDef* GetPopDef( const std::string& id ) const; + void SpawnBase( const size_t base_id, const size_t slot_index, const ::types::Vec2< size_t >& tile_coords, const ::types::Vec3& render_coords, - const backend::base::BaseData& data + const std::string& name ); // TODO void DespawnBase( const size_t base_id ); + void RefreshBase( Base* base ); SlotBadges* GetSlotBadges( const size_t slot_index ) const; void DefineSlotBadges( const size_t slot_index, const faction::Faction* faction ); @@ -85,6 +96,9 @@ CLASS( BaseManager, common::Class ) std::unordered_map< size_t, SlotBadges* > m_slot_badges = {}; + std::vector< std::string > m_popdefs_order = {}; + std::unordered_map< std::string, PopDef* > m_popdefs = {}; + std::unordered_map< size_t, base::Base* > m_bases = {}; typedef std::set< size_t > ordered_base_ids_t; std::unordered_map< faction::Faction*, ordered_base_ids_t > m_faction_base_ids = {}; diff --git a/src/game/frontend/base/CMakeLists.txt b/src/game/frontend/base/CMakeLists.txt index 24de5998..ecdf42f1 100644 --- a/src/game/frontend/base/CMakeLists.txt +++ b/src/game/frontend/base/CMakeLists.txt @@ -1,6 +1,8 @@ SET( SRC ${SRC} ${PWD}/BaseManager.cpp + ${PWD}/PopDef.cpp + ${PWD}/Pop.cpp ${PWD}/Base.cpp ${PWD}/SlotBadges.cpp diff --git a/src/game/frontend/base/Pop.cpp b/src/game/frontend/base/Pop.cpp new file mode 100644 index 00000000..9ff859bb --- /dev/null +++ b/src/game/frontend/base/Pop.cpp @@ -0,0 +1,24 @@ +#include "Pop.h" + +#include "PopDef.h" +#include "Base.h" +#include "game/frontend/faction/Faction.h" + +namespace game { +namespace frontend { +namespace base { + +Pop::Pop( const Base* base, const PopDef* def, const uint8_t variant ) + : m_base( base ) + , m_def( def ) + , m_variant( variant ) { + // +} + +types::texture::Texture* Pop::GetTexture() const { + return m_def->GetTexture( m_variant, m_base->GetFaction()->m_is_progenitor ); +} + +} +} +} \ No newline at end of file diff --git a/src/game/frontend/base/Pop.h b/src/game/frontend/base/Pop.h new file mode 100644 index 00000000..b84270ef --- /dev/null +++ b/src/game/frontend/base/Pop.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace types::texture { +class Texture; +} + +namespace game { +namespace frontend { +namespace base { + +class Base; +class PopDef; + +class Pop { +public: + Pop( const Base* base, const PopDef* def, const uint8_t variant ); + + types::texture::Texture* GetTexture() const; + + const Base* m_base; + const PopDef* m_def; + uint8_t m_variant; +}; + +} +} +} diff --git a/src/game/frontend/base/PopDef.cpp b/src/game/frontend/base/PopDef.cpp new file mode 100644 index 00000000..2908d0e0 --- /dev/null +++ b/src/game/frontend/base/PopDef.cpp @@ -0,0 +1,37 @@ +#include "PopDef.h" + +#include "engine/Engine.h" +#include "loader/texture/TextureLoader.h" +#include "resource/ResourceManager.h" + +namespace game { +namespace frontend { +namespace base { + +PopDef::PopDef( + const std::string& name, + const backend::base::pop_render_infos_t& renders_human, + const backend::base::pop_render_infos_t& renders_progenitor +) + : m_name( name ) { + auto* tl = g_engine->GetTextureLoader(); + auto* rm = g_engine->GetResourceManager(); +#define X( _r ) \ + for ( const auto& r : renders_##_r ) { \ + m_textures_##_r.push_back( tl->LoadTexture( rm->GetResource( r.file ), r.x, r.y, r.x + r.width, r.y + r.height ) ); \ + } + X( human ) + X( progenitor ) +#undef X +} + +types::texture::Texture* PopDef::GetTexture( const uint8_t variant, const bool is_progenitor ) const { + const auto& textures = is_progenitor + ? m_textures_progenitor + : m_textures_human; + return textures.at( std::min( (size_t)variant, textures.size() - 1 ) ); +} + +} +} +} \ No newline at end of file diff --git a/src/game/frontend/base/PopDef.h b/src/game/frontend/base/PopDef.h new file mode 100644 index 00000000..1eb2ebda --- /dev/null +++ b/src/game/frontend/base/PopDef.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "game/backend/base/Types.h" + +namespace types::texture { +class Texture; +} + +namespace game { +namespace frontend { +namespace base { + +class PopDef { +public: + PopDef( + const std::string& name, + const backend::base::pop_render_infos_t& renders_human, + const backend::base::pop_render_infos_t& renders_progenitor + ); + + const std::string m_name; + + types::texture::Texture* GetTexture( const uint8_t variant, const bool is_progenitor ) const; + +private: + std::vector< types::texture::Texture* > m_textures_human = {}; + std::vector< types::texture::Texture* > m_textures_progenitor = {}; + +}; + +} +} +} \ No newline at end of file diff --git a/src/game/frontend/tile/TileManager.cpp b/src/game/frontend/tile/TileManager.cpp index ec366013..ff9462be 100644 --- a/src/game/frontend/tile/TileManager.cpp +++ b/src/game/frontend/tile/TileManager.cpp @@ -1,5 +1,6 @@ #include "TileManager.h" +#include "game/frontend/Game.h" #include "game/backend/map/Consts.h" #include "util/FS.h" @@ -99,7 +100,10 @@ Tile* TileManager::GetSelectedTile() const { } void TileManager::SelectTile( Tile* tile ) { - m_selected_tile = tile; + if ( tile != m_selected_tile ) { + m_selected_tile = tile; + m_game->SendGetTileData( tile ); + } } void TileManager::DeselectTile() { diff --git a/src/game/frontend/ui/bottom_bar/BottomBar.cpp b/src/game/frontend/ui/bottom_bar/BottomBar.cpp index 8ea36986..0efe29df 100644 --- a/src/game/frontend/ui/bottom_bar/BottomBar.cpp +++ b/src/game/frontend/ui/bottom_bar/BottomBar.cpp @@ -179,6 +179,10 @@ void BottomBar::HideTilePreview() { m_sections.tile_preview->HideTilePreview(); } +void BottomBar::SetTileYields( const std::vector< std::pair< std::string, size_t > >& yields ) const { + m_sections.tile_preview->SetTileYields( yields ); +} + void BottomBar::PreviewObject( const TileObject* object ) { m_sections.unit_preview->PreviewObject( object ); } diff --git a/src/game/frontend/ui/bottom_bar/BottomBar.h b/src/game/frontend/ui/bottom_bar/BottomBar.h index ee8b59f2..aa874b62 100644 --- a/src/game/frontend/ui/bottom_bar/BottomBar.h +++ b/src/game/frontend/ui/bottom_bar/BottomBar.h @@ -49,6 +49,8 @@ CLASS( BottomBar, BottomBarBase ) void PreviewTile( tile::Tile* tile, const size_t selected_unit_id ); void HideTilePreview(); + void SetTileYields( const std::vector< std::pair< std::string, size_t > >& yields ) const; + void PreviewObject( const TileObject* object ); void HideObjectPreview(); diff --git a/src/game/frontend/ui/bottom_bar/TilePreview.cpp b/src/game/frontend/ui/bottom_bar/TilePreview.cpp index e6c3faad..306d4620 100644 --- a/src/game/frontend/ui/bottom_bar/TilePreview.cpp +++ b/src/game/frontend/ui/bottom_bar/TilePreview.cpp @@ -50,21 +50,34 @@ void TilePreview::PreviewTile( const tile::Tile* tile ) { ASSERT( !m_preview_layers.empty(), "no preview layers defined" ); + NEW( m_pages.normal, ::ui::object::UIContainer, "BBTilePreviewPage" ); + if ( show_yields_page ) { + m_pages.normal->Hide(); + } + AddChild( m_pages.normal ); + // print lines - size_t label_top = m_preview_layers.front().object->GetHeight() + 6; + size_t label_top = 2; for ( auto& line : render.preview_lines ) { NEWV( label, ::ui::object::Label, "BBTilePreviewTextLine" ); label->SetText( line ); label->SetTop( label_top ); - m_info_lines.push_back( label ); - AddChild( label ); + m_texts.normal.push_back( label ); + m_pages.normal->AddChild( label ); label_top += label->GetHeight(); } + // yields page + NEW( m_pages.yields, ::ui::object::UIContainer, "BBTilePreviewPage" ); + if ( !show_yields_page ) { + m_pages.yields->Hide(); + } + AddChild( m_pages.yields ); + // print tile coordinates NEWV( label, ::ui::object::Label, "BBTilePreviewTextFooter" ); label->SetText( "(" + std::to_string( tile->GetCoords().x ) + "," + std::to_string( tile->GetCoords().y ) + ")" ); - m_info_lines.push_back( label ); + m_texts.footer = label; AddChild( label ); // copy sprites from tile @@ -91,19 +104,88 @@ void TilePreview::PreviewTile( const tile::Tile* tile ) { ); AddChild( sprite_preview ); } + + On( + ::ui::event::EV_MOUSE_DOWN, EH( this ) { + if ( !show_yields_page ) { + show_yields_page = true; + m_pages.normal->Hide(); + m_pages.yields->Show(); + } + else { + show_yields_page = false; + m_pages.yields->Hide(); + m_pages.normal->Show(); + } + return true; + } + ); + } void TilePreview::HideTilePreview() { - for ( auto& label : m_info_lines ) { - RemoveChild( label ); + if ( m_pages.normal ) { + for ( auto& label : m_texts.normal ) { + m_pages.normal->RemoveChild( label ); + } + m_texts.normal.clear(); + RemoveChild( m_pages.normal ); + m_pages.normal = nullptr; + } + + if ( m_pages.yields ) { + HideYields(); + RemoveChild( m_pages.yields ); + m_pages.yields = nullptr; + } + + if ( m_texts.footer ) { + RemoveChild( m_texts.footer ); + m_texts.footer = nullptr; } - m_info_lines.clear(); + for ( auto& preview_layer : m_preview_layers ) { RemoveChild( preview_layer.object ); } m_preview_layers.clear(); } +void TilePreview::SetTileYields( const std::vector< std::pair< std::string, size_t > >& yields ) { + if ( m_pages.yields ) { + const bool need_to_add = m_texts.yields.empty(); + if ( !need_to_add ) { + ASSERT_NOLOG( m_texts.yields.size() == yields.size(), "yields lines count mismatch" ); + } + size_t label_top = 2; + for ( size_t i = 0 ; i < yields.size() ; i++ ) { + const auto& y = yields.at( i ); + const auto text = y.first + ": " + std::to_string( y.second ); + ::ui::object::Label* label; + if ( need_to_add ) { + NEW( label, ::ui::object::Label, "BBTilePreviewTextLine" ); + } + else { + label = m_texts.yields.at( i ); + } + label->SetText( text ); + if ( need_to_add ) { + label->SetTop( label_top ); + m_texts.yields.push_back( label ); + m_pages.yields->AddChild( label ); + label_top += label->GetHeight() * 2; // TODO: 'with farm:' etc + } + } + } +} + +void TilePreview::HideYields() { + ASSERT_NOLOG( m_pages.yields, "yields page not initialized" ); + for ( auto& label : m_texts.yields ) { + m_pages.yields->RemoveChild( label ); + } + m_texts.yields.clear(); +} + } } } diff --git a/src/game/frontend/ui/bottom_bar/TilePreview.h b/src/game/frontend/ui/bottom_bar/TilePreview.h index 07f2ffda..d2f5a7c3 100644 --- a/src/game/frontend/ui/bottom_bar/TilePreview.h +++ b/src/game/frontend/ui/bottom_bar/TilePreview.h @@ -8,6 +8,7 @@ class Texture; namespace ui::object { class Mesh; +class UIContainer; class Label; } @@ -30,6 +31,7 @@ CLASS( TilePreview, BBSection ) void PreviewTile( const tile::Tile* tile ); void HideTilePreview(); + void SetTileYields( const std::vector< std::pair< std::string, size_t > >& yields ); private: @@ -39,8 +41,20 @@ CLASS( TilePreview, BBSection ) }; std::vector< preview_layer_t > m_preview_layers = {}; // multiple layers of textures + bool show_yields_page = false; + struct { + ::ui::object::UIContainer* normal = nullptr; + ::ui::object::UIContainer* yields = nullptr; + } m_pages = {}; + // TODO: multiline labels? - std::vector< ::ui::object::Label* > m_info_lines = {}; + struct { + std::vector< ::ui::object::Label* > normal = {}; + std::vector< ::ui::object::Label* > yields = {}; + ::ui::object::Label* footer = nullptr; + } m_texts = {}; + + void HideYields(); }; diff --git a/src/game/frontend/ui/bottom_bar/left_menu/GameMenu.cpp b/src/game/frontend/ui/bottom_bar/left_menu/GameMenu.cpp index b1673479..d6d3be84 100644 --- a/src/game/frontend/ui/bottom_bar/left_menu/GameMenu.cpp +++ b/src/game/frontend/ui/bottom_bar/left_menu/GameMenu.cpp @@ -12,10 +12,7 @@ namespace menu { GameMenu::GameMenu( Game* game ) : Menu( game, "BBLeftMenu" ) { -#ifdef DEBUG - if ( !g_engine->GetConfig()->HasDebugFlag( config::Config::DF_QUICKSTART ) ) -#endif - { + if ( !g_engine->GetConfig()->HasLaunchFlag( config::Config::LF_QUICKSTART ) ) { AddItem( "Start New Game", MH( this ) { m_game->ConfirmExit( @@ -27,11 +24,9 @@ GameMenu::GameMenu( Game* game ) ); } ); -#ifdef DEBUG - if ( g_engine->GetConfig()->HasDebugFlag( config::Config::DF_QUICKSTART ) ) { + if ( g_engine->GetConfig()->HasLaunchFlag( config::Config::LF_QUICKSTART ) ) { return false; } // menu is already destroyed by now, can't do animation -#endif return true; } ); @@ -44,11 +39,9 @@ GameMenu::GameMenu( Game* game ) g_engine->ShutDown(); } ); -#ifdef DEBUG - if ( g_engine->GetConfig()->HasDebugFlag( config::Config::DF_QUICKSTART ) ) { + if ( g_engine->GetConfig()->HasLaunchFlag( config::Config::LF_QUICKSTART ) ) { return false; } // menu is already destroyed by now, can't do animation -#endif return true; } ); diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/Population.cpp b/src/game/frontend/ui/popup/base_popup/bottom_bar/Population.cpp index 2f812a5f..fa8cb533 100644 --- a/src/game/frontend/ui/popup/base_popup/bottom_bar/Population.cpp +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/Population.cpp @@ -1,7 +1,10 @@ #include "Population.h" #include "ui/object/Surface.h" +#include "game/frontend/Game.h" #include "game/frontend/base/Base.h" +#include "game/frontend/base/PopDef.h" +#include "game/frontend/base/BaseManager.h" #include "game/frontend/faction/Faction.h" namespace game { @@ -30,30 +33,59 @@ void Population::Destroy() { void Population::Update( base::Base* base ) { // TODO: use actual pop objects HideIcons(); - auto icon_class = SubClass( - base->GetFaction()->m_is_progenitor - ? "IconProgenitor" - : "IconHuman" - ); - const auto population = base->GetPopulation(); + const auto& pops = base->GetPops(); + const size_t pops_count = pops.size(); float w = 40.0f; - if ( w * ( population - 1 ) > 390.0f ) { - w = 390.0f / ( population - 1 ); + if ( w * ( pops_count - 1 ) > 390.0f ) { + w = 390.0f / ( pops_count - 1 ); } - m_icons.reserve( population ); - for ( size_t i = 0 ; i < population ; i++ ) { - NEWV( icon, ::ui::object::Surface, icon_class ); - icon->SetLeft( i * w ); - AddChild( icon ); - m_icons.push_back( icon ); + + m_pops.clear(); + m_pops.reserve( pops_count ); + + std::unordered_map< std::string, std::vector< size_t > > pops_by_def = {}; + for ( size_t i = 0 ; i < pops_count ; i++ ) { + const auto& pop = pops.at( i ); + auto it = pops_by_def.find( pop.m_def->m_name ); + if ( it == pops_by_def.end() ) { + it = pops_by_def.insert( + { + pop.m_def->m_name, + {} + } + ).first; + } + it->second.push_back( i ); + } + const auto& order = m_game->GetBM()->GetPopDefOrder(); + size_t left = 0; + for ( const auto& id : order ) { + const auto& it = pops_by_def.find( id ); + if ( it != pops_by_def.end() ) { + for ( const auto& i : it->second ) { + const auto& pop = pops.at( i ); + NEWV( icon, ::ui::object::Surface, SubClass( "Icon" ) ); + icon->SetLeft( ( left++ ) * w ); + // TODO: SetTexture doesn't work if used before AddChild, fix + AddChild( icon ); + icon->SetTexture( pop.GetTexture() ); + m_pops.push_back( + { + i, + icon + } + ); + } + } } + ASSERT_NOLOG( m_pops.size() == pops_count, "resulting pops size mismatch" ); } void Population::HideIcons() { - for ( const auto& icon : m_icons ) { - RemoveChild( icon ); + for ( const auto& pop : m_pops ) { + RemoveChild( pop.icon ); } - m_icons.clear(); + m_pops.clear(); } } diff --git a/src/game/frontend/ui/popup/base_popup/bottom_bar/Population.h b/src/game/frontend/ui/popup/base_popup/bottom_bar/Population.h index a254b590..58fb8b5f 100644 --- a/src/game/frontend/ui/popup/base_popup/bottom_bar/Population.h +++ b/src/game/frontend/ui/popup/base_popup/bottom_bar/Population.h @@ -30,7 +30,11 @@ CLASS( Population, BBSection ) void Update( base::Base* base ); private: - std::vector< ::ui::object::Surface* > m_icons = {}; + struct pop_info_t { + size_t pop_id; + ::ui::object::Surface* icon; + }; + std::vector< pop_info_t > m_pops = {}; void HideIcons(); diff --git a/src/game/frontend/ui/style/BottomBar.cpp b/src/game/frontend/ui/style/BottomBar.cpp index 54b108fd..21b6e45e 100644 --- a/src/game/frontend/ui/style/BottomBar.cpp +++ b/src/game/frontend/ui/style/BottomBar.cpp @@ -242,6 +242,12 @@ void BottomBar::AddStyles() { } ); + AddStyle( + "TilePreviewPage", SH() { + s->Set( ::ui::A_TOP, 56 ); + } + ); + AddStyle( "TilePreviewText", SH() { s->SetFont( ::ui::A_FONT, resource::TTF_ARIALN, 14 ); @@ -952,16 +958,6 @@ void BottomBar::AddStyles() { s->Set( ::ui::A_HEIGHT, 48 ); } ); - AddStyle( - "PopulationIconHuman", { "PopulationIcon" }, SH() { - s->SetTexture( ::ui::A_TEXTURE, resource::PCX_NEWICONS, 79, 501, 116, 548 ); - } - ); - AddStyle( - "PopulationIconProgenitor", { "PopulationIcon" }, SH() { - s->SetTexture( ::ui::A_TEXTURE, resource::PCX_ALIENCIT, 40, 41, 77, 88 ); - } - ); AddStyle( "SupportedUnits", SH() { diff --git a/src/game/frontend/unit/UnitManager.cpp b/src/game/frontend/unit/UnitManager.cpp index 954aa508..6e7467e6 100644 --- a/src/game/frontend/unit/UnitManager.cpp +++ b/src/game/frontend/unit/UnitManager.cpp @@ -153,7 +153,7 @@ void UnitManager::DespawnUnit( const size_t unit_id ) { } ASSERT( m_selected_unit != unit, "unit still selected" ); - + delete unit; m_game->RefreshSelectedTile( m_selected_unit ); @@ -165,7 +165,6 @@ void UnitManager::RefreshUnit( Unit* unit ) { unit->Refresh(); UpdateSelectable( unit ); m_game->RenderTile( unit->GetTile(), m_selected_unit ); - m_game->RefreshSelectedTileIf( unit->GetTile(), m_selected_unit ); if ( m_selected_unit == unit && was_active ) { if ( !unit->IsActive() ) { SelectNextUnitOrSwitchToTileSelection(); @@ -216,7 +215,6 @@ void UnitManager::MoveUnit_deprecated( Unit* unit, tile::Tile* dst_tile, const t m_game->RenderTile( dst_tile, m_selected_unit ); if ( m_selected_unit == unit ) { - m_game->RefreshSelectedTile( m_selected_unit ); m_game->ScrollToSelectedTile( false ); } diff --git a/src/graphics/opengl/actor/Mesh.cpp b/src/graphics/opengl/actor/Mesh.cpp index 9a3abc0a..b47a6fef 100644 --- a/src/graphics/opengl/actor/Mesh.cpp +++ b/src/graphics/opengl/actor/Mesh.cpp @@ -268,7 +268,7 @@ void Mesh::Draw( shader_program::ShaderProgram* shader_program, scene::Camera* c auto* sp = (shader_program::Simple2D*)shader_program; glUniform1ui( sp->uniforms.flags, flags ); if ( flags & scene::actor::Actor::RF_USE_TINT ) { - glUniform4fv( sp->uniforms.tint_color, 1, (const GLfloat*)&mesh_actor->GetTintColor() ); + glUniform4fv( sp->uniforms.tint_color, 1, (const GLfloat*)&mesh_actor->GetTintColor().value ); } if ( flags & scene::actor::Actor::RF_USE_AREA_LIMITS ) { const auto& limits = mesh_actor->GetAreaLimits(); @@ -295,7 +295,7 @@ void Mesh::Draw( shader_program::ShaderProgram* shader_program, scene::Camera* c auto* lights = m_actor->GetScene()->GetLights(); if ( !( flags & scene::actor::Actor::RF_IGNORE_LIGHTING ) && !lights->empty() ) { types::Vec3 light_pos[lights->size()]; - types::Color light_color[lights->size()]; + types::Color::color_t light_color[lights->size()]; size_t i = 0; for ( auto& light : *lights ) { light_pos[ i ] = light->GetPosition(); @@ -306,7 +306,7 @@ void Mesh::Draw( shader_program::ShaderProgram* shader_program, scene::Camera* c glUniform4fv( sp->uniforms.light_color, lights->size(), (const GLfloat*)light_color ); } if ( flags & scene::actor::Actor::RF_USE_TINT ) { - glUniform4fv( sp->uniforms.tint_color, 1, (const GLfloat*)&mesh_actor->GetTintColor() ); + glUniform4fv( sp->uniforms.tint_color, 1, (const GLfloat*)&mesh_actor->GetTintColor().value ); } if ( flags & scene::actor::Actor::RF_USE_AREA_LIMITS ) { const auto& limits = mesh_actor->GetAreaLimits(); diff --git a/src/graphics/opengl/actor/Sprite.cpp b/src/graphics/opengl/actor/Sprite.cpp index 52e81517..fc30b4fd 100644 --- a/src/graphics/opengl/actor/Sprite.cpp +++ b/src/graphics/opengl/actor/Sprite.cpp @@ -42,8 +42,7 @@ bool Sprite::MeshReloadNeeded() { auto* actor = GetSpriteActor(); return m_last_dimensions != actor->GetDimensions() || - m_last_tex_coords != actor->GetTexCoords() - ; + m_last_tex_coords != actor->GetTexCoords(); } bool Sprite::TextureReloadNeeded() { @@ -129,14 +128,14 @@ void Sprite::Draw( shader_program::ShaderProgram* shader_program, scene::Camera* auto flags = sprite_actor->GetRenderFlags(); glUniform1ui( sp->uniforms.flags, flags ); if ( flags & scene::actor::Actor::RF_USE_2D_POSITION ) { - const types::Vec3 pos = sprite_actor->NormalizePosition(sprite_actor->GetPosition()); + const types::Vec3 pos = sprite_actor->NormalizePosition( sprite_actor->GetPosition() ); glUniform2fv( sp->uniforms.position, 1, (const GLfloat*)&pos ); } auto* lights = m_actor->GetScene()->GetLights(); if ( !lights->empty() ) { types::Vec3 light_pos[lights->size()]; - types::Color light_color[lights->size()]; + types::Color::color_t light_color[lights->size()]; size_t i = 0; for ( auto& light : *lights ) { light_pos[ i ] = light->GetPosition(); diff --git a/src/graphics/opengl/actor/Text.cpp b/src/graphics/opengl/actor/Text.cpp index 584e2c6e..75cd8b90 100644 --- a/src/graphics/opengl/actor/Text.cpp +++ b/src/graphics/opengl/actor/Text.cpp @@ -151,7 +151,7 @@ void Text::Draw( shader_program::ShaderProgram* shader_program, scene::Camera* c } glUniform1i( sp->uniforms.texture, 0 ); - glUniform4fv( sp->uniforms.color, 1, (const GLfloat*)&text_actor->GetColor() ); + glUniform4fv( sp->uniforms.color, 1, (const GLfloat*)&text_actor->GetColor().value ); auto position = m_actor->GetPosition(); glUniform1f( sp->uniforms.z_index, position.z ); diff --git a/src/gse/Exception.h b/src/gse/Exception.h index 8125fd21..fb4f8281 100644 --- a/src/gse/Exception.h +++ b/src/gse/Exception.h @@ -27,6 +27,7 @@ struct exception_ec_t { const std::string GAME_ERROR; const std::string MAP_ERROR; const std::string INVALID_EVENT; + const std::string INVALID_DEFINITION; }; extern const exception_ec_t EC; diff --git a/src/gse/Value.h b/src/gse/Value.h index 99f1859a..f4001ba6 100644 --- a/src/gse/Value.h +++ b/src/gse/Value.h @@ -20,25 +20,25 @@ namespace gse { #define WRAPDEFS_CLASS() \ static const gse::type::Object::object_class_t WRAP_CLASS; -#define WRAPDEFS_PTR( _type, ... ) \ +#define WRAPDEFS_PTR( _type ) \ WRAPDEFS_CLASS() \ - virtual const gse::Value Wrap() __VA_ARGS__; \ + virtual const gse::Value Wrap( const bool dynamic = false ) override; \ static _type* Unwrap( const gse::Value& value ); #define WRAPDEFS_DYNAMIC( _type ) \ WRAPDEFS_CLASS() \ - const gse::Value Wrap( const bool dynamic = false ); \ + virtual const gse::Value Wrap( const bool dynamic = false ) override; \ static _type* Unwrap( const gse::Value& value ); \ static void WrapSet( gse::Wrappable* wrapobj, const std::string& key, const gse::Value& value, gse::context::Context* ctx, const gse::si_t& si ); \ void OnWrapSet( const std::string& property_name ); #define WRAPDEFS_NOPTR( _type ) \ WRAPDEFS_CLASS() \ - const gse::Value Wrap(); \ + virtual const gse::Value Wrap( const bool dynamic = false ) override; \ static _type Unwrap( const gse::Value& value ); #define WRAPIMPL_CLASS( _type, _class ) \ const gse::type::Object::object_class_t _type::WRAP_CLASS = gse::type::Object::_class; #define WRAPIMPL_BEGIN( _type, _class ) \ WRAPIMPL_CLASS( _type, _class ) \ - const gse::Value _type::Wrap() { + const gse::Value _type::Wrap( const bool dynamic ) { #define WRAPIMPL_DYNAMIC_BEGIN( _type, _class ) \ WRAPIMPL_CLASS( _type, _class ) \ const gse::Value _type::Wrap( const bool dynamic ) { diff --git a/src/gse/Wrappable.h b/src/gse/Wrappable.h index 84baabb8..5f8ad482 100644 --- a/src/gse/Wrappable.h +++ b/src/gse/Wrappable.h @@ -2,6 +2,8 @@ #include +#include "gse/Value.h" + namespace gse { namespace type { @@ -11,6 +13,7 @@ class Object; class Wrappable { public: virtual ~Wrappable(); + virtual const gse::Value Wrap( const bool dynamic = false ) = 0; void Link( type::Object* wrapobj ); void Unlink( type::Object* wrapobj ); private: diff --git a/src/gse/runner/Interpreter.cpp b/src/gse/runner/Interpreter.cpp index 30b57ae4..4989e475 100644 --- a/src/gse/runner/Interpreter.cpp +++ b/src/gse/runner/Interpreter.cpp @@ -177,7 +177,7 @@ const gse::Value Interpreter::EvaluateConditional( context::Context* ctx, const } case ForCondition::FCT_IN_OF: { const auto* condition = (ForConditionInOf*)c->condition; - const auto target = EvaluateExpression( ctx, condition->expression ); + const auto target = Deref( ctx, condition->m_si, EvaluateExpression( ctx, condition->expression ) ); const auto forctx = ctx->ForkContext( ctx, condition->m_si, false ); forctx->IncRefs(); switch ( target.Get()->type ) { @@ -239,7 +239,7 @@ const gse::Value Interpreter::EvaluateConditional( context::Context* ctx, const break; } default: - THROW( "unexpected type for iteration: " + target.ToString() ); + THROW( "unexpected type for iteration (" + target.GetTypeString() + "): " + target.ToString() ); } forctx->DecRefs(); break; diff --git a/src/gse/type/Array.cpp b/src/gse/type/Array.cpp index 2ef0dcef..aefb8c63 100644 --- a/src/gse/type/Array.cpp +++ b/src/gse/type/Array.cpp @@ -4,6 +4,7 @@ #include "ArrayRef.h" #include "ArrayRangeRef.h" #include "String.h" +#include "gse/Wrappable.h" namespace gse { namespace type { @@ -65,6 +66,15 @@ const Value Array::GetRangeRef( const std::optional< size_t > from, const std::o return VALUE( ArrayRangeRef, this, from, to ); } +const Value Array::FromVector( const std::vector< Wrappable* >* data, const bool dynamic ) { + array_elements_t elements = {}; + elements.reserve( data->size() ); + for ( const auto& el : *data ) { + elements.push_back( el->Wrap( dynamic ) ); + } + return VALUE( Array, elements ); +} + void Array::ValidateFromTo( const std::optional< size_t >& from, const std::optional< size_t >& to ) const { if ( from.has_value() ) { ASSERT_NOLOG( from.value() < value.size(), "range beginning overflow ( " + std::to_string( from.value() ) + " >= " + std::to_string( value.size() ) + " )" ); diff --git a/src/gse/type/Array.h b/src/gse/type/Array.h index d9cbf9d4..b3bbb255 100644 --- a/src/gse/type/Array.h +++ b/src/gse/type/Array.h @@ -10,6 +10,9 @@ #include "gse/Value.h" namespace gse { + +class Wrappable; + namespace type { class Array : public Type { @@ -30,6 +33,8 @@ class Array : public Type { array_elements_t value = {}; + static const Value FromVector( const std::vector< Wrappable* >* data, const bool dynamic = false ); // be careful + private: void ValidateFromTo( const std::optional< size_t >& from, const std::optional< size_t >& to ) const; }; diff --git a/src/gse/type/Object.h b/src/gse/type/Object.h index ca81e658..0ed4ea80 100644 --- a/src/gse/type/Object.h +++ b/src/gse/type/Object.h @@ -34,6 +34,7 @@ class Object : public Type { CLASS_UNITDEF, CLASS_UNIT, CLASS_BASE, + CLASS_BASE_POP, }; static const std::string& GetClassString( const object_class_t object_class ); diff --git a/src/main.cpp b/src/main.cpp index 452713c2..b319ef68 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,16 +48,14 @@ #include "task/common/Common.h" -#if defined(DEBUG) - +#ifdef DEBUG #include "task/gseprompt/GSEPrompt.h" #include "task/gsetests/GSETests.h" -#include "task/game/Game.h" - #endif #include "task/intro/Intro.h" #include "task/mainmenu/MainMenu.h" +#include "task/game/Game.h" #include "game/backend/Game.h" @@ -277,15 +275,14 @@ int main( const int argc, const char* argv[] ) { &game ); -#ifdef DEBUG - if ( config.HasDebugFlag( config::Config::DF_QUICKSTART ) ) { + if ( config.HasLaunchFlag( config::Config::LF_QUICKSTART ) ) { NEWV( state, game::backend::State ); // TODO: initialize settings randomly state->m_settings.global.game_rules.Initialize(); state->InitBindings(); state->Configure(); const auto& rules = state->m_settings.global.game_rules; std::optional< game::backend::rules::Faction > faction = {}; - if ( config.HasDebugFlag( config::Config::DF_QUICKSTART_FACTION ) ) { + if ( config.HasLaunchFlag( config::Config::LF_QUICKSTART_FACTION ) ) { const auto& f = state->m_settings.global.game_rules.m_factions; const auto it = f.find( config.GetQuickstartFaction() ); if ( it == f.end() ) { @@ -314,9 +311,7 @@ int main( const int argc, const char* argv[] ) { g_engine->ShutDown(); } ); } - else -#endif - if ( config.HasLaunchFlag( config::Config::LF_SKIPINTRO ) ) { + else if ( config.HasLaunchFlag( config::Config::LF_SKIPINTRO ) ) { NEW( task, task::mainmenu::MainMenu ); } else { diff --git a/src/resource/ResourceManager.cpp b/src/resource/ResourceManager.cpp index a9479bf6..d5f2c14a 100644 --- a/src/resource/ResourceManager.cpp +++ b/src/resource/ResourceManager.cpp @@ -230,16 +230,26 @@ ResourceManager::ResourceManager() } } -void ResourceManager::Init( std::vector< std::string > possible_smac_paths, const config::smac_type_t smac_type ) { +void ResourceManager::Init( const std::vector< std::string >& possible_smac_paths, const config::smac_type_t smac_type, const std::string& data_path ) { const bool print_errors = smac_type != config::ST_AUTO; + m_data_path = util::FS::GeneratePath( + { + data_path, + "default" + } + ); for ( const auto& path : possible_smac_paths ) { - // GOG / Planetary Pack + + // GOG / Steam if ( - ( smac_type == config::ST_GOG || smac_type == config::ST_PP || smac_type == config::ST_AUTO ) && + ( smac_type == config::ST_GOG || smac_type == config::ST_STEAM || smac_type == config::ST_AUTO ) && CheckFiles( path, { "terran.exe", - "terranx.exe" + "terranx.exe", + "arialnb.ttf", + "arialnbi.ttf", + "arialni.ttf", }, print_errors ) && ResolveBuiltins( path, { @@ -247,10 +257,13 @@ void ResourceManager::Init( std::vector< std::string > possible_smac_paths, cons ".wav", "fx" }, - }, PM_NONE, print_errors + }, PM_NONE, {}, print_errors ) ) { - return; // found GOG + m_detected_smac_type = smac_type == config::ST_AUTO + ? config::ST_STEAM // for now there don't seem to be any differences between Steam and GoG so can pick whatever + : smac_type; + return; // found GOG or Steam } // Loki @@ -273,11 +286,46 @@ void ResourceManager::Init( std::vector< std::string > possible_smac_paths, cons ".ttf", "fonts" } - }, PM_SPACES_TO_UNDERSCORES, print_errors + }, PM_SPACES_TO_UNDERSCORES, {}, print_errors ) ) { - return; // found Loki + m_detected_smac_type = config::ST_LOKI; + return; } + + // Legacy + if ( + ( smac_type == config::ST_LEGACY || smac_type == config::ST_AUTO ) && + CheckFiles( + path, { + "terran.exe", + "terranx.exe", + "arialb.ttf", // legacy has different fonts + "ariali.ttf", // legacy has different fonts + "arialr.ttf", // legacy has different fonts + }, print_errors + ) && ResolveBuiltins( + path, { + { + ".wav", + "fx" + }, + }, PM_NONE, { + { + TTF_ARIALNB, + "arialn.ttf" + }, + { + PCX_LOGO, + "openinga.pcx" + }, + }, print_errors + ) + ) { + m_detected_smac_type = config::ST_LEGACY; + return; + } + if ( smac_type != config::ST_AUTO ) { break; // don't search in . if specific type was given } @@ -294,6 +342,10 @@ void ResourceManager::Init( std::vector< std::string > possible_smac_paths, cons THROW( msg ); } +const config::smac_type_t ResourceManager::GetDetectedSMACType() const { + return m_detected_smac_type; +} + const resource_t ResourceManager::GetResource( const std::string& filename ) const { const auto it = m_filenames_to_resources.find( filename ); if ( it != m_filenames_to_resources.end() ) { @@ -316,14 +368,25 @@ const std::string& ResourceManager::GetCustomPath( const std::string& path ) { std::string key = ""; key.resize( path.length() ); std::transform( path.begin(), path.end(), key.begin(), ::tolower ); - const auto& it = m_custom_resource_paths.find( key ); - if ( it != m_custom_resource_paths.end() ) { - return it->second; + + // look in datadir + auto resolved_file = util::FS::GetExistingCaseSensitivePath( m_data_path, path ); + + if ( resolved_file.empty() ) { + + // look in builtin paths + const auto& it = m_custom_resource_paths.find( key ); + if ( it != m_custom_resource_paths.end() ) { + return it->second; + } + + // look in SMAC dir + resolved_file = util::FS::GetExistingCaseSensitivePath( m_smac_path, GetFixedPath( path, m_extension_path_map, m_path_modifiers ) ); } - const auto resolved_file = util::FS::GetExistingCaseSensitivePath( m_smac_path, GetFixedPath( path, m_extension_path_map, m_path_modifiers ) ); if ( resolved_file.empty() ) { THROW( "could not resolve resource (path does not exist: " + path + ")" ); } + if ( !util::FS::IsFile( resolved_file ) ) { THROW( "could not resolve resource (path is not a file: " + path + ")" ); } @@ -369,18 +432,23 @@ const bool ResourceManager::CheckFiles( const std::string& path, const std::vect return true; } -const bool ResourceManager::ResolveBuiltins( const std::string& path, const extension_path_map_t& extension_path_map, const path_modifier_t path_modifiers, const bool print_errors ) { +const bool ResourceManager::ResolveBuiltins( const std::string& path, const extension_path_map_t& extension_path_map, const path_modifier_t path_modifiers, const resource_substitutes_t& substitutes, const bool print_errors ) { std::unordered_map< resource::resource_t, std::string > resolved_files = {}; resolved_files.reserve( m_resources_to_filenames.size() ); for ( const auto& it : m_resources_to_filenames ) { - const auto resolved_file = util::FS::GetExistingCaseSensitivePath( path, GetFixedPath( it.second, extension_path_map, path_modifiers ) ); + std::string file = it.second; + const auto& sub_it = substitutes.find( it.first ); + if ( sub_it != substitutes.end() ) { + file = sub_it->second; + } + const auto resolved_file = util::FS::GetExistingCaseSensitivePath( path, GetFixedPath( file, extension_path_map, path_modifiers ) ); if ( resolved_file.empty() || !util::FS::IsFile( resolved_file ) ) { if ( print_errors ) { Log( "Could not resolve file: " + util::FS::GeneratePath( { path, - it.second + file } ) ); diff --git a/src/resource/ResourceManager.h b/src/resource/ResourceManager.h index 5e9351ee..47384ed8 100644 --- a/src/resource/ResourceManager.h +++ b/src/resource/ResourceManager.h @@ -14,8 +14,9 @@ CLASS( ResourceManager, common::Module ) ResourceManager(); - void Init( std::vector< std::string > possible_smac_paths, const config::smac_type_t smac_type ); + void Init( const std::vector< std::string >& possible_smac_paths, const config::smac_type_t smac_type, const std::string& data_path ); + const config::smac_type_t GetDetectedSMACType() const; const resource_t GetResource( const std::string& filename ) const; const std::string& GetFilename( const resource_t res ) const; const std::string& GetPath( const resource_t res ) const; @@ -23,10 +24,14 @@ CLASS( ResourceManager, common::Module ) private: + config::smac_type_t m_detected_smac_type = config::ST_AUTO; + std::string m_data_path = ""; + const std::unordered_map< resource_t, std::string > m_resources_to_filenames = {}; std::unordered_map< std::string, resource_t > m_filenames_to_resources = {}; typedef std::unordered_map< std::string, std::string > extension_path_map_t; + typedef std::unordered_map< resource_t, std::string > resource_substitutes_t; typedef uint8_t path_modifier_t; path_modifier_t PM_NONE = 0; @@ -40,7 +45,7 @@ CLASS( ResourceManager, common::Module ) const std::string GetFixedPath( const std::string& file, const extension_path_map_t& extension_path_map, const path_modifier_t path_modifiers ); const bool CheckFiles( const std::string& path, const std::vector< std::string >& files, const bool print_errors ) const; - const bool ResolveBuiltins( const std::string& path, const extension_path_map_t& extension_path_map, const path_modifier_t path_modifiers, const bool print_errors ); + const bool ResolveBuiltins( const std::string& path, const extension_path_map_t& extension_path_map, const path_modifier_t path_modifiers, const resource_substitutes_t& substitutes, const bool print_errors ); }; diff --git a/src/task/intro/Intro.cpp b/src/task/intro/Intro.cpp index 7a53a508..5a2a787a 100644 --- a/src/task/intro/Intro.cpp +++ b/src/task/intro/Intro.cpp @@ -7,37 +7,54 @@ #include "Theme.h" #include "ui/object/Surface.h" #include "scheduler/Scheduler.h" +#include "resource/ResourceManager.h" namespace task { namespace intro { void Intro::Start() { - NEW( m_theme, Theme ); - g_engine->GetUI()->AddTheme( m_theme ); + if ( g_engine->GetResourceManager()->GetDetectedSMACType() == config::ST_LEGACY ) { + // legacy doesn't have firaxis logo + Finish(); + } + else { + NEW( m_theme, Theme ); + g_engine->GetUI()->AddTheme( m_theme ); - NEW( m_logo, ui::object::Surface, "IntroLogo" ); - g_engine->GetUI()->AddObject( m_logo ); + NEW( m_logo, ui::object::Surface, "IntroLogo" ); + g_engine->GetUI()->AddObject( m_logo ); - m_timer.SetTimeout( 1000 ); // pretend we're loading something - // TODO: do all preloading while intro is running? + m_timer.SetTimeout( 1000 ); // pretend we're loading something + // TODO: do all preloading while intro is running? + } } void Intro::Stop() { - g_engine->GetUI()->RemoveObject( m_logo ); + if ( m_logo ) { + g_engine->GetUI()->RemoveObject( m_logo ); + m_logo = nullptr; + } - g_engine->GetUI()->RemoveTheme( m_theme ); + if ( m_theme ) { + g_engine->GetUI()->RemoveTheme( m_theme ); + m_theme = nullptr; + } DELETE( m_theme ); } void Intro::Iterate() { if ( m_timer.HasTicked() ) { - // switch to main menu - g_engine->GetScheduler()->RemoveTask( this ); - NEWV( task, task::mainmenu::MainMenu ); - g_engine->GetScheduler()->AddTask( task ); + Finish(); } } +void Intro::Finish() { + // switch to main menu + g_engine->GetScheduler()->RemoveTask( this ); + NEWV( task, task::mainmenu::MainMenu ); + g_engine->GetScheduler()->AddTask( task ); +} + } } diff --git a/src/task/intro/Intro.h b/src/task/intro/Intro.h index 1da88cd0..d9f89742 100644 --- a/src/task/intro/Intro.h +++ b/src/task/intro/Intro.h @@ -18,11 +18,13 @@ CLASS( Intro, common::Task ) void Stop() override; void Iterate() override; -protected: - Theme* m_theme; +private: + Theme* m_theme = nullptr; ::ui::object::Surface* m_logo = nullptr; util::Timer m_timer; + void Finish(); + }; } diff --git a/src/types/Buffer.cpp b/src/types/Buffer.cpp index 7c17cec0..8b346ec4 100644 --- a/src/types/Buffer.cpp +++ b/src/types/Buffer.cpp @@ -224,13 +224,13 @@ const types::Vec3 Buffer::ReadVec3() { } void Buffer::WriteColor( const Color val ) { - WriteImpl( T_COLOR, (const char*)&val, sizeof( val ) ); + WriteImpl( T_COLOR, (const char*)&val.value, sizeof( val.value ) ); } const Color Buffer::ReadColor() { - Color val; + Color val = {}; uint32_t sz = 0; - ReadImpl( T_COLOR, (char*)&val, &sz, sizeof( val ) ); + ReadImpl( T_COLOR, (char*)&val.value, &sz, sizeof( val.value ) ); return val; } diff --git a/src/types/Color.cpp b/src/types/Color.cpp index db4c85ee..26360e01 100644 --- a/src/types/Color.cpp +++ b/src/types/Color.cpp @@ -4,20 +4,46 @@ namespace types { -Color::Color() { +Color::color_t::color_t( const color_t& color ) { + *this = color; +} +Color::color_t::color_t( const Color& color ) { + *this = color.value; +} +void Color::color_t::operator=( const Color& color ) { + *this = color.value; +} +Color::color_t::color_t( const channel_t red, const channel_t green, const channel_t blue, const channel_t alpha ) { + this->red = red; + this->green = green; + this->blue = blue; + this->alpha = alpha; +} + +Color::color_t::color_t( const channel_t red, const channel_t green, const channel_t blue ) { + this->red = red; + this->green = green; + this->blue = blue; +} + +Color::Color() + : gse::Wrappable() { // }; -Color::Color( const channel_t red, const channel_t green, const channel_t blue, const channel_t alpha ) { +Color::Color( const channel_t red, const channel_t green, const channel_t blue, const channel_t alpha ) + : gse::Wrappable() { Set( red, green, blue, alpha ); }; -Color::Color( const channel_t red, const channel_t green, const channel_t blue ) { +Color::Color( const channel_t red, const channel_t green, const channel_t blue ) + : gse::Wrappable() { Set( red, green, blue, 1.0 ); }; -Color::Color( const color_t& color ) { - memcpy( &value, &color, sizeof( color ) ); +Color::Color( const color_t& color ) + : gse::Wrappable() { + value = color; } void Color::Set( channel_t red, channel_t green, channel_t blue, channel_t alpha ) { @@ -28,7 +54,7 @@ void Color::Set( channel_t red, channel_t green, channel_t blue, channel_t alpha }; void Color::operator=( const color_t& color ) { - memcpy( &value, &color, sizeof( color ) ); + value = color; } bool Color::operator==( Color& other ) const { diff --git a/src/types/Color.h b/src/types/Color.h index b624a3ff..9125ceed 100644 --- a/src/types/Color.h +++ b/src/types/Color.h @@ -6,10 +6,13 @@ #include "gse/Value.h" #include "gse/type/Object.h" +#include "gse/Wrappable.h" + +// TODO: refactor namespace types { -class Color { +class Color : public gse::Wrappable { public: typedef float channel_t; @@ -20,10 +23,13 @@ class Color { channel_t green; channel_t blue; channel_t alpha; - - void operator=( const Color& color ) { - memcpy( this, &color.value, sizeof( color.value ) ); - } + color_t() = default; + ~color_t() = default; + color_t( const color_t& color ); + color_t( const Color& color ); + void operator=( const Color& color ); + color_t( const channel_t red, const channel_t green, const channel_t blue, const channel_t alpha ); + color_t( const channel_t red, const channel_t green, const channel_t blue ); }; color_t value = {}; @@ -32,6 +38,7 @@ class Color { Color( const channel_t red, const channel_t green, const channel_t blue, const channel_t alpha ); Color( const channel_t red, const channel_t green, const channel_t blue ); Color( const color_t& color ); + virtual ~Color() = default; void Set( channel_t red, channel_t green, channel_t blue, channel_t alpha ); diff --git a/src/types/mesh/Render.cpp b/src/types/mesh/Render.cpp index ea64c7a3..51ce18c2 100644 --- a/src/types/mesh/Render.cpp +++ b/src/types/mesh/Render.cpp @@ -13,7 +13,7 @@ Render::Render( const size_t vertex_count, const size_t surface_count ) } -index_t Render::AddVertex( const types::Vec3& coord, const Vec2< coord_t >& tex_coord, const Color tint, const types::Vec3& normal ) { +index_t Render::AddVertex( const types::Vec3& coord, const Vec2< coord_t >& tex_coord, const Color::color_t tint, const types::Vec3& normal ) { ASSERT( !m_is_final, "addvertex on already finalized mesh" ); ASSERT( m_vertex_i < m_vertex_count, "vertex out of bounds (" + std::to_string( m_vertex_i ) + " >= " + std::to_string( m_vertex_count ) + ")" ); size_t offset = m_vertex_i * VERTEX_SIZE * sizeof( coord_t ); @@ -29,11 +29,11 @@ index_t Render::AddVertex( const types::Vec3& coord, const Vec2< coord_t >& tex_ return ret; } -index_t Render::AddVertex( const Vec2< coord_t >& coord, const Vec2< coord_t >& tex_coord, const Color tint, const types::Vec3& normal ) { +index_t Render::AddVertex( const Vec2< coord_t >& coord, const Vec2< coord_t >& tex_coord, const Color::color_t tint, const types::Vec3& normal ) { return AddVertex( types::Vec3( coord.x, coord.y, 0.0f ), tex_coord, tint, normal ); } -void Render::SetVertex( const index_t index, const types::Vec3& coord, const Vec2< coord_t >& tex_coord, const Color tint, const types::Vec3& normal ) { +void Render::SetVertex( const index_t index, const types::Vec3& coord, const Vec2< coord_t >& tex_coord, const Color::color_t tint, const types::Vec3& normal ) { ASSERT( index < m_vertex_count, "index out of bounds" ); size_t offset = index * VERTEX_SIZE * sizeof( coord_t ); memcpy( ptr( m_vertex_data, offset, sizeof( coord ) ), &coord, sizeof( coord ) ); @@ -46,7 +46,7 @@ void Render::SetVertex( const index_t index, const types::Vec3& coord, const Vec Update(); } -void Render::SetVertex( const index_t index, const Vec2< coord_t >& coord, const Vec2< coord_t >& tex_coord, const Color tint, const types::Vec3& normal ) { +void Render::SetVertex( const index_t index, const Vec2< coord_t >& coord, const Vec2< coord_t >& tex_coord, const Color::color_t tint, const types::Vec3& normal ) { SetVertex( index, { coord.x, @@ -62,9 +62,9 @@ void Render::SetVertexTexCoord( const index_t index, const Vec2< coord_t >& tex_ Update(); } -void Render::SetVertexTint( const index_t index, const Color tint ) { +void Render::SetVertexTint( const index_t index, const Color::color_t tint ) { ASSERT( index < m_vertex_count, "index out of bounds" ); - memcpy( ptr( m_vertex_data, index * VERTEX_SIZE * sizeof( coord_t ) + ( VERTEX_COORD_SIZE + VERTEX_TEXCOORD_SIZE ) * sizeof( coord_t ), sizeof( Color ) ), &tint, sizeof( tint ) ); + memcpy( ptr( m_vertex_data, index * VERTEX_SIZE * sizeof( coord_t ) + ( VERTEX_COORD_SIZE + VERTEX_TEXCOORD_SIZE ) * sizeof( coord_t ), sizeof( tint ) ), &tint, sizeof( tint ) ); } void Render::SetVertexNormal( const index_t index, const types::Vec3& normal ) { diff --git a/src/types/mesh/Render.h b/src/types/mesh/Render.h index b2249a18..6e2c1527 100644 --- a/src/types/mesh/Render.h +++ b/src/types/mesh/Render.h @@ -20,7 +20,7 @@ CLASS( Render, Mesh ) const types::Vec3& coord, const Vec2< coord_t >& tex_coord = { 0.0f, 0.0f - }, const Color tint = { + }, const Color::color_t tint = { 1.0f, 1.0f, 1.0f, @@ -35,7 +35,7 @@ CLASS( Render, Mesh ) const Vec2< coord_t >& coord, const Vec2< coord_t >& tex_coord = { 0.0f, 0.0f - }, const Color tint = { + }, const Color::color_t tint = { 1.0f, 1.0f, 1.0f, @@ -48,7 +48,7 @@ CLASS( Render, Mesh ) ); void SetVertex( - const index_t index, const types::Vec3& coord, const Vec2< coord_t >& tex_coord, const Color tint = { + const index_t index, const types::Vec3& coord, const Vec2< coord_t >& tex_coord, const Color::color_t tint = { 1.0f, 1.0f, 1.0f, @@ -60,7 +60,7 @@ CLASS( Render, Mesh ) } ); void SetVertex( - const index_t index, const Vec2< coord_t >& coord, const Vec2< coord_t >& tex_coord, const Color tint = { + const index_t index, const Vec2< coord_t >& coord, const Vec2< coord_t >& tex_coord, const Color::color_t tint = { 1.0f, 1.0f, 1.0f, @@ -73,7 +73,7 @@ CLASS( Render, Mesh ) ); void SetVertexTexCoord( const index_t index, const Vec2< coord_t >& tex_coord ); - void SetVertexTint( const index_t index, const Color tint ); + void SetVertexTint( const index_t index, const Color::color_t tint ); void SetVertexNormal( const index_t index, const types::Vec3& normal ); void GetVertexTexCoord( const index_t index, Vec2< coord_t >* tex_coord ) const; diff --git a/src/ui/UI.cpp b/src/ui/UI.cpp index 62c57a39..b24f6878 100644 --- a/src/ui/UI.cpp +++ b/src/ui/UI.cpp @@ -507,6 +507,10 @@ bool UI::HasPopup() const { return !m_popups.empty(); } +const bool UI::HasErrorPopup() const { + return m_error && m_error->IsActive(); +} + void UI::OpenPopup( object::Popup* popup ) { m_popups.push_back( popup ); AddObject( popup ); diff --git a/src/ui/UI.h b/src/ui/UI.h index 05e6120f..4019e95e 100644 --- a/src/ui/UI.h +++ b/src/ui/UI.h @@ -114,6 +114,7 @@ CLASS( UI, common::Module ) void Redraw(); bool HasPopup() const; + const bool HasErrorPopup() const; void OpenPopup( object::Popup* popup ); void ClosePopup( object::Popup* popup, bool force = false ); void CloseLastPopup( bool force = false ); diff --git a/src/ui/module/Error.cpp b/src/ui/module/Error.cpp index 2a29a423..438057f7 100644 --- a/src/ui/module/Error.cpp +++ b/src/ui/module/Error.cpp @@ -41,6 +41,10 @@ void Error::Hide() { } } +const bool Error::IsActive() const { + return m_is_active; +} + void Error::Start() { if ( m_is_active ) { diff --git a/src/ui/module/Error.h b/src/ui/module/Error.h index 59449832..f929eb95 100644 --- a/src/ui/module/Error.h +++ b/src/ui/module/Error.h @@ -23,6 +23,7 @@ CLASS( Error, Module ) void Show( const std::string& text, const ui_handler_t on_close = UH() {} ); void Hide(); + const bool IsActive() const; void SetText( const std::string& error_text );