GunChleoc has proposed merging lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands.
Commit message: Tweak performance for saveloading and introduce an optional "init" function for winconditions with costly calculations - Affected win conditions: Artifacts, Wood Gnome, Territorial Time, Territorial Lord. - Saveload long lists of fields via C++ because Lua persistence is very expensive - Use map index for iteration when writing - Write map resources packet as string - Clean up writing CmdFlagAction - Bulk skip all commands with greater duetime when writing command queue packet Requested reviews: Widelands Developers (widelands-dev) Related bugs: Bug #1818402 in widelands: "Move territorial calculations to the init screen" https://bugs.launchpad.net/widelands/+bug/1818402 For more details, see: https://code.launchpad.net/~widelands-dev/widelands/speedup_saveloading/+merge/364205 We will need either this branch in Build 20 or a separate branch to fix Artifacts, because we broke it. I'd like this branch for its performance improvements, but it is big, so I can be convinced otherwise. Some stats for the big map "Magic Mountain" with Territorial Time: This branch: Writing Wincondition Data: 146ms Writing Scripting Data: 25ms Sum 171ms Reading Wincondition Data: 997ms Reading Scripting Data: 5ms Sum 1 002ms Trunk: Writing Scripting Data: 32 218ms Reading Scripting Data: 4 116ms Writing speed factor: ca. 188x Reading speed factor: ca. 4x -- Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands.
=== modified file 'data/scripting/win_conditions/territorial_functions.lua' --- data/scripting/win_conditions/territorial_functions.lua 2019-02-21 08:39:13 +0000 +++ data/scripting/win_conditions/territorial_functions.lua 2019-03-09 11:05:59 +0000 @@ -7,45 +7,13 @@ set_textdomain("win_conditions") include "scripting/richtext.lua" +include "scripting/win_conditions/win_condition_functions.lua" include "scripting/win_conditions/win_condition_texts.lua" local team_str = _"Team %i" local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)." local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)." --- RST --- .. function:: count_owned_fields_for_all_players(fields, players) --- --- Counts all owned fields for each player. --- --- :arg fields: Table of all buildable fields --- :arg players: Table of all players --- --- :returns: a table with ``playernumber = count_of_owned_fields`` entries --- -local function count_owned_fields_for_all_players(fields, players) - local owned_fields = {} - -- init the landsizes for each player - for idx,plr in ipairs(players) do - owned_fields[plr.number] = 0 - end - - for idx,f in ipairs(fields) do - -- check if field is owned by a player - local owner = f.owner - if owner then - local owner_number = owner.number - if owned_fields[owner_number] == nil then - -- In case player was defeated and lost all their warehouses, make sure they don't count - owned_fields[owner_number] = -1 - elseif owned_fields[owner_number] >= 0 then - owned_fields[owner_number] = owned_fields[owner_number] + 1 - end - end - end - return owned_fields -end - -- Used by calculate_territory_points keep track of when the winner changes local winning_players = {} local winning_teams = {} @@ -91,7 +59,7 @@ -- First checks if a player was defeated, then fills the ``territory_points`` table -- with current data. -- --- :arg fields: Table of all buildable fields +-- :arg fields: Number of all valuable fields -- :arg players: Table of all players -- :arg wc_descname: String with the win condition's descname -- :arg wc_version: Number with the win condition's descname @@ -100,12 +68,12 @@ local points = {} -- tracking points of teams and players without teams local territory_was_kept = false - territory_points.all_player_points = count_owned_fields_for_all_players(fields, players) + territory_points.all_player_points = count_owned_valuable_fields_for_all_players(players) local ranked_players = rank_players(territory_points.all_player_points, players) -- Check if we have a winner. The table was sorted, so we can simply grab the first entry. local winning_points = -1 - if ranked_players[1].points > ( #fields / 2 ) then + if ranked_players[1].points > ( fields / 2 ) then winning_points = ranked_players[1].points end @@ -161,7 +129,7 @@ -- Returns a string containing the current land percentages of players/teams -- for messages to the players -- --- :arg fields: Table of all buildable fields +-- :arg fields: Number of all valuable fields -- :arg has_had: Use "has" for an interim message, "had" for a game over message. -- -- :returns: a richtext-formatted string with information on current points for each player/team @@ -178,17 +146,17 @@ li( (wc_has_territory):bformat( territory_points.points[i][1], - _percent(territory_points.points[i][2], #fields), + _percent(territory_points.points[i][2], fields), territory_points.points[i][2], - #fields)) + fields)) else msg = msg .. li( (wc_had_territory):bformat( territory_points.points[i][1], - _percent(territory_points.points[i][2], #fields), + _percent(territory_points.points[i][2], fields), territory_points.points[i][2], - #fields)) + fields)) end end @@ -246,7 +214,7 @@ -- -- Updates the territory points and sends game over reports -- --- :arg fields: Table of all buildable fields +-- :arg fields: Number of all valuable fields -- :arg players: Table of all players -- function territory_game_over(fields, players, wc_descname, wc_version) === modified file 'data/scripting/win_conditions/territorial_lord.lua' --- data/scripting/win_conditions/territorial_lord.lua 2019-03-02 08:52:08 +0000 +++ data/scripting/win_conditions/territorial_lord.lua 2019-03-09 11:05:59 +0000 @@ -23,18 +23,20 @@ "that area for at least 20 minutes." ) +local fields = 0 + return { name = wc_name, description = wc_desc, + init = function() + fields = wl.Game().map:count_conquerable_fields() + end, func = function() local plrs = wl.Game().players -- set the objective with the game type for all players broadcast_objective("win_condition", wc_descname, wc_desc) - -- Table of fields that are worth conquering - local fields = wl.Game().map.conquerable_fields - -- Configure how long the winner has to hold on to the territory local time_to_keep_territory = 20 * 60 -- 20 minutes -- time in secs, if == 0 -> victory === modified file 'data/scripting/win_conditions/territorial_time.lua' --- data/scripting/win_conditions/territorial_time.lua 2019-03-02 08:52:08 +0000 +++ data/scripting/win_conditions/territorial_time.lua 2019-03-09 11:05:59 +0000 @@ -27,18 +27,20 @@ "after 4 hours, whichever comes first." ) +local fields = 0 + return { name = wc_name, description = wc_desc, + init = function() + fields = wl.Game().map:count_conquerable_fields() + end, func = function() local plrs = wl.Game().players -- set the objective with the game type for all players broadcast_objective("win_condition", wc_descname, wc_desc) - -- Table of fields that are worth conquering - local fields = wl.Game().map.conquerable_fields - -- variables to track the maximum 4 hours of gametime local remaining_max_time = 4 * 60 * 60 -- 4 hours === modified file 'data/scripting/win_conditions/win_condition_functions.lua' --- data/scripting/win_conditions/win_condition_functions.lua 2019-02-12 17:30:21 +0000 +++ data/scripting/win_conditions/win_condition_functions.lua 2019-03-09 11:05:59 +0000 @@ -147,6 +147,38 @@ -- RST +-- .. function:: count_owned_valuable_fields_for_all_players(players[, attribute]) +-- +-- Counts all owned fields for each player. +-- +-- :arg players: Table of all players +-- :arg attribute: If this is set, only count fields that have an immovable with this attribute +-- +-- :returns: a table with ``playernumber = count_of_owned_fields`` entries +-- +function count_owned_valuable_fields_for_all_players(players, attribute) + attribute = attribute or "" + + local owned_fields = {} + + -- Get number of currently owned valuable fields per player. + -- This table can contain defeated players. + local all_plrpoints = wl.Game().map:count_owned_valuable_fields(attribute) + + -- Insert points for all players who are still in the game, and 0 points for defeated players. + for idx,plr in ipairs(players) do + if (plr.defeated) then + owned_fields[plr.number] = 0 + else + owned_fields[plr.number] = all_plrpoints[plr.number] + end + end + return owned_fields +end + + + +-- RST -- .. function:: rank_players(all_player_points, plrs) -- -- Rank the players and teams according to the highest points === modified file 'data/scripting/win_conditions/wood_gnome.lua' --- data/scripting/win_conditions/wood_gnome.lua 2019-03-02 08:52:08 +0000 +++ data/scripting/win_conditions/wood_gnome.lua 2019-03-09 11:05:59 +0000 @@ -24,6 +24,10 @@ return { name = wc_name, description = wc_desc, + init = function() + -- Calculate valuable fields + wl.Game().map:count_terrestrial_fields() + end, func = function() local plrs = wl.Game().players local game = wl.Game() @@ -34,51 +38,21 @@ -- set the objective with the game type for all players broadcast_objective("win_condition", wc_descname, wc_desc) - -- Table of terrestrial fields - local fields = wl.Game().map.terrestrial_fields - -- The function to calculate the current points. local _last_time_calculated = -100000 - local _plrpoints = {} + local playerpoints = {} local function _calc_points() - local game = wl.Game() if _last_time_calculated > game.time - 5000 then - return _plrpoints - end - - -- clear out the table. We count afresh. - for k,v in pairs(_plrpoints) do - _plrpoints[k] = 0 - end - - -- Insert all players who are still in the game. - for idx,plr in ipairs(plrs) do - _plrpoints[plr.number] = 0 - end - - for idf,f in ipairs(fields) do - -- check if field is owned by a player - local owner = f.owner - if owner and not owner.defeated then - owner = owner.number - -- check if field has an immovable - local imm = f.immovable - if imm then - -- check if immovable is a tree - if imm:has_attribute("tree") then - _plrpoints[owner] = _plrpoints[owner] + 1 - end - end - end - end - + return + end + + playerpoints = count_owned_valuable_fields_for_all_players(plrs, "tree") _last_time_calculated = game.time - return _plrpoints end local function _send_state(remaining_time, plrs, show_popup) - local playerpoints = _calc_points() + _calc_points() local msg = format_remaining_time(remaining_time) .. vspace(8) .. game_status.body for idx,plr in ipairs(plrs) do @@ -92,7 +66,7 @@ end local function _game_over(plrs) - local playerpoints = _calc_points() + _calc_points() local points = {} for idx,plr in ipairs(plrs) do points[#points + 1] = { plr, playerpoints[plr.number] } @@ -131,8 +105,8 @@ name = wc_trees_owned, pic = "images/wui/stats/genstats_trees.png", calculator = function(p) - local pts = _calc_points(p) - return pts[p.number] + _calc_points(p) + return playerpoints[p.number] or 0 end, } === modified file 'src/game_io/game_cmd_queue_packet.cc' --- src/game_io/game_cmd_queue_packet.cc 2019-02-23 11:00:49 +0000 +++ src/game_io/game_cmd_queue_packet.cc 2019-03-09 11:05:59 +0000 @@ -98,6 +98,10 @@ while (!p.empty()) { const CmdQueue::CmdItem& it = p.top(); + if (it.cmd->duetime() > time) { + // Time is the primary sorting key, so we can't have any additional commands in this queue for this time + break; + } if (it.cmd->duetime() == time) { if (upcast(GameLogicCommand, cmd, it.cmd)) { // The id (aka command type) === modified file 'src/logic/game.cc' --- src/logic/game.cc 2019-03-02 10:34:39 +0000 +++ src/logic/game.cc 2019-03-09 11:05:59 +0000 @@ -314,10 +314,14 @@ // Check for win_conditions if (!settings.scenario) { + loader_ui->step(_("Initializing game…")); std::unique_ptr<LuaTable> table(lua().run_script(settings.win_condition_script)); - get_ibase()->log_message(_("Initializing game…")); table->do_not_warn_about_unaccessed_keys(); win_condition_displayname_ = table->get_string("name"); + if (table->has_key<std::string>("init")) { + std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("init"); + cr->resume(); + } std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func"); enqueue_command(new CmdLuaCoroutine(get_gametime() + 100, std::move(cr))); } else { === modified file 'src/logic/map.cc' --- src/logic/map.cc 2019-03-02 09:31:11 +0000 +++ src/logic/map.cc 2019-03-09 11:05:59 +0000 @@ -257,8 +257,11 @@ } } -std::set<FCoords> Map::calculate_all_conquerable_fields() const { - std::set<FCoords> result; +size_t Map::count_all_conquerable_fields() { + if (!valuable_fields_.empty()) { + // Already calculated + return valuable_fields_.size(); + } std::set<FCoords> coords_to_check; @@ -267,17 +270,17 @@ // If we don't have the given coordinates yet, walk the map and collect conquerable fields, // initialized with the given radius around the coordinates - const auto walk_starting_coords = [this, &result, &coords_to_check]( + const auto walk_starting_coords = [this, &coords_to_check]( const Coords& coords, int radius) { FCoords fcoords = get_fcoords(coords); // We already have these coordinates - if (result.count(fcoords) == 1) { + if (valuable_fields_.count(fcoords) == 1) { return; } // Add starting field - result.insert(fcoords); + valuable_fields_.insert(fcoords); // Add outer land coordinates around the starting field for the given radius std::unique_ptr<Widelands::HollowArea<>> hollow_area( @@ -317,8 +320,8 @@ // We do the caps check first, because the comparison is faster than the container // check - if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (result.count(fcoords) == 0)) { - result.insert(fcoords); + if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (valuable_fields_.count(fcoords) == 0)) { + valuable_fields_.insert(fcoords); coords_to_check.insert(fcoords); } } while (map_region->advance(*this)); @@ -342,23 +345,44 @@ } } - log("%" PRIuS " found ... ", result.size()); - return result; + log("%" PRIuS " found ... ", valuable_fields_.size()); + return valuable_fields_.size(); } -std::set<FCoords> Map::calculate_all_fields_excluding_caps(NodeCaps caps) const { +size_t Map::count_all_fields_excluding_caps(NodeCaps caps) { + if (!valuable_fields_.empty()) { + // Already calculated + return valuable_fields_.size(); + } + log("Collecting valuable fields ... "); ScopedTimer timer("took %ums"); - std::set<FCoords> result; for (MapIndex i = 0; i < max_index(); ++i) { Field& field = fields_[i]; if (!(field.nodecaps() & caps)) { - result.insert(get_fcoords(field)); - } - } - - log("%" PRIuS " found ... ", result.size()); + valuable_fields_.insert(get_fcoords(field)); + } + } + + log("%" PRIuS " found ... ", valuable_fields_.size()); + return valuable_fields_.size(); +} + +std::map<PlayerNumber, size_t> Map::count_owned_valuable_fields(const std::string& immovable_attribute) const { + std::map<PlayerNumber, size_t> result; + const bool use_attribute = !immovable_attribute.empty(); + const uint32_t attribute_id = use_attribute ? MapObjectDescr::get_attribute_id(immovable_attribute) : 0U; + for (const FCoords& fcoords : valuable_fields_) { + if (use_attribute) { + const BaseImmovable* imm = fcoords.field->get_immovable(); + if (imm != nullptr && imm->has_attribute(attribute_id)) { + result[fcoords.field->get_owned_by()] += 1; + } + } else { + result[fcoords.field->get_owned_by()] += 1; + } + } return result; } === modified file 'src/logic/map.h' --- src/logic/map.h 2019-02-26 05:56:01 +0000 +++ src/logic/map.h 2019-03-09 11:05:59 +0000 @@ -165,11 +165,25 @@ void recalc_whole_map(const World& world); void recalc_for_field_area(const World& world, Area<FCoords>); - /// Calculates and returns a list of the fields that could be conquered by a player throughout a - /// game. Useful for territorial win conditions. - std::set<FCoords> calculate_all_conquerable_fields() const; - /// Calculates and returns a list of the fields that do not have the given caps. - std::set<FCoords> calculate_all_fields_excluding_caps(NodeCaps caps) const; + /** + * Calculates and returns a list of the fields that could be conquered by a player throughout a game. + * Useful for territorial win conditions. + * Returns the amount of valuable fields. + */ + size_t count_all_conquerable_fields(); + + /** + * If the valuable fields are empty, calculates which fields do not have the given caps and adds the to the list of valuable fields. + * Useful for win conditions. + * Returns the amount of valuable fields. + */ + size_t count_all_fields_excluding_caps(NodeCaps caps); + + /** + * Counts the valuable fields that are owned by each player. Only players that currently own a field are added. + * Returns a map of <player number, number of owned fields>. + */ + std::map<PlayerNumber, size_t> count_owned_valuable_fields(const std::string& immovable_attribute) const; /*** * Ensures that resources match their adjacent terrains. @@ -438,6 +452,10 @@ return &objectives_; } + std::set<FCoords>* mutable_valuable_fields() { + return &valuable_fields_; + } + /// Returns the military influence on a location from an area. MilitaryInfluence calc_influence(Coords, Area<>) const; @@ -535,6 +553,9 @@ Objectives objectives_; + // Fields that are important for the player to own in a win condition + std::set<FCoords> valuable_fields_; + MapVersion map_version_; }; === modified file 'src/logic/player.cc' --- src/logic/player.cc 2019-02-23 11:00:49 +0000 +++ src/logic/player.cc 2019-03-09 11:05:59 +0000 @@ -253,6 +253,15 @@ return &other != this && (!team_number_ || team_number_ != other.team_number_); } +bool Player::is_defeated() const { + for (const auto& economy : economies()) { + if (!economy.second->warehouses().empty()) { + return false; + } + } + return true; +} + void Player::AiPersistentState::initialize() { colony_scan_area = kColonyScanStartArea; trees_around_cutters = 0; === modified file 'src/logic/player.h' --- src/logic/player.h 2019-02-23 11:00:49 +0000 +++ src/logic/player.h 2019-03-09 11:05:59 +0000 @@ -148,6 +148,11 @@ bool is_hostile(const Player&) const; + /** + * Returns whether the player lost the last warehouse. + */ + bool is_defeated() const; + // For cheating void set_see_all(bool const t) { see_all_ = t; === modified file 'src/logic/playercommand.cc' --- src/logic/playercommand.cc 2019-02-23 11:00:49 +0000 +++ src/logic/playercommand.cc 2019-03-09 11:05:59 +0000 @@ -450,14 +450,17 @@ ser.unsigned_32(serial); } -constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 1; +constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 2; void CmdFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { try { const uint16_t packet_version = fr.unsigned_16(); - if (packet_version == kCurrentPacketVersionCmdFlagAction) { + // TODO(GunChleoc): Savegame compatibility, remove after Build 21 + if (packet_version >= 1 && packet_version <= kCurrentPacketVersionCmdFlagAction) { PlayerCommand::read(fr, egbase, mol); - fr.unsigned_8(); + if (packet_version == 1) { + fr.unsigned_8(); + } serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol); } else { throw UnhandledVersionError( @@ -472,9 +475,6 @@ fw.unsigned_16(kCurrentPacketVersionCmdFlagAction); // Write base classes PlayerCommand::write(fw, egbase, mos); - // Now action - fw.unsigned_8(0); - // Now serial fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(serial))); } === modified file 'src/map_io/CMakeLists.txt' --- src/map_io/CMakeLists.txt 2017-11-20 13:50:51 +0000 +++ src/map_io/CMakeLists.txt 2019-03-09 11:05:59 +0000 @@ -87,6 +87,8 @@ map_terrain_packet.h map_version_packet.cc map_version_packet.h + map_wincondition_packet.cc + map_wincondition_packet.h DEPENDS base_exceptions base_log === modified file 'src/map_io/map_resources_packet.cc' --- src/map_io/map_resources_packet.cc 2019-02-23 11:00:49 +0000 +++ src/map_io/map_resources_packet.cc 2019-03-09 11:05:59 +0000 @@ -19,7 +19,10 @@ #include "map_io/map_resources_packet.h" +#include <boost/algorithm/string.hpp> + #include "base/log.h" +#include "base/scoped_timer.h" #include "io/fileread.h" #include "io/filewrite.h" #include "logic/editor_game_base.h" @@ -31,7 +34,7 @@ namespace Widelands { -constexpr uint16_t kCurrentPacketVersion = 1; +constexpr uint16_t kCurrentPacketVersion = 2; void MapResourcesPacket::read(FileSystem& fs, EditorGameBase& egbase, @@ -44,7 +47,7 @@ try { const uint16_t packet_version = fr.unsigned_16(); - if (packet_version == kCurrentPacketVersion) { + if (packet_version >= 1 && packet_version <= kCurrentPacketVersion) { int32_t const nr_res = fr.unsigned_16(); if (world.get_nr_resources() < nr_res) log("WARNING: Number of resources in map (%i) is bigger than in world " @@ -59,18 +62,51 @@ int32_t const res = world.get_resource(resource_name.c_str()); if (res == Widelands::INVALID_INDEX) throw GameDataError( - "resource '%s' exists in map but not in world", resource_name.c_str()); + "MapResourcesPacket: Resource '%s' exists in map but not in world", resource_name.c_str()); smap[id] = res; } - for (uint16_t y = 0; y < map->get_height(); ++y) { - for (uint16_t x = 0; x < map->get_width(); ++x) { - DescriptionIndex const id = fr.unsigned_8(); - ResourceAmount const amount = fr.unsigned_8(); - ResourceAmount const start_amount = fr.unsigned_8(); - const auto fcoords = map->get_fcoords(Coords(x, y)); - map->initialize_resources(fcoords, smap[id], start_amount); - map->set_resources(fcoords, amount); + if (packet_version == 1) { + // TODO(GunChleoc): Savegame compatibility, remove after Build21 + for (uint16_t y = 0; y < map->get_height(); ++y) { + for (uint16_t x = 0; x < map->get_width(); ++x) { + DescriptionIndex const id = fr.unsigned_8(); + ResourceAmount const amount = fr.unsigned_8(); + ResourceAmount const start_amount = fr.unsigned_8(); + const auto fcoords = map->get_fcoords(Coords(x, y)); + map->initialize_resources(fcoords, smap[id], start_amount); + map->set_resources(fcoords, amount); + } + } + } else { + // String of entries of the form id-amount-initial_amount| + std::string input = fr.c_string(); + std::vector<std::string> entries; + boost::split(entries, input, boost::is_any_of("|")); + // Check for correct size. We have a dangling |, so we expect 1 extra entry. + if (static_cast<int>((entries.size() - 1)) != (map->get_height() * map->get_width())) { + throw GameDataError( + "MapResourcesPacket: There should be map resources for %d fields, but we have %" PRIuS, + map->get_height() * map->get_width(), + entries.size() - 1); + } + auto iterator = entries.begin(); + for (uint16_t y = 0; y < map->get_height(); ++y) { + for (uint16_t x = 0; x < map->get_width(); ++x) { + std::vector<std::string> addme; + boost::split(addme, *iterator++, boost::is_any_of("-")); + if (addme.size() != 3) { + throw GameDataError( + "MapResourcesPacket: Resource info for (%d,%d) should have 3 entries (ID, amount, starting amount), but we have %" PRIuS, + x, y, addme.size()); + } + DescriptionIndex const id = static_cast<DescriptionIndex>(atoi(addme.at(0).c_str())); + ResourceAmount const amount = static_cast<ResourceAmount>(atoi(addme.at(1).c_str())); + ResourceAmount const initial_amount = static_cast<ResourceAmount>(atoi(addme.at(2).c_str())); + const auto fcoords = map->get_fcoords(Coords(x, y)); + map->initialize_resources(fcoords, smap[id], initial_amount); + map->set_resources(fcoords, amount); + } } } } else { @@ -109,20 +145,15 @@ fw.c_string(res.name().c_str()); } - // Now, all resouces as uint8_ts in order - // - resource id - // - amount - for (uint16_t y = 0; y < map.get_height(); ++y) { - for (uint16_t x = 0; x < map.get_width(); ++x) { - const Field& f = map[Coords(x, y)]; - DescriptionIndex res = f.get_resources(); - ResourceAmount const amount = f.get_resources_amount(); - ResourceAmount const start_amount = f.get_initial_res_amount(); - fw.unsigned_8(res); - fw.unsigned_8(amount); - fw.unsigned_8(start_amount); - } + // Performance: We write our resource information into a single string to reduce the number of write operations + std::ostringstream oss; + for (MapIndex i = 0; i < map.max_index(); ++i) { + Field& f = map[i]; + oss << static_cast<unsigned int>(f.get_resources()) << "-"; + oss << static_cast<unsigned int>(f.get_resources_amount()) << "-"; + oss << static_cast<unsigned int>(f.get_initial_res_amount()) << "|"; } + fw.c_string(oss.str()); fw.write(fs, "binary/resource"); } === modified file 'src/map_io/map_saver.cc' --- src/map_io/map_saver.cc 2019-02-23 11:00:49 +0000 +++ src/map_io/map_saver.cc 2019-03-09 11:05:59 +0000 @@ -58,6 +58,7 @@ #include "map_io/map_scripting_packet.h" #include "map_io/map_terrain_packet.h" #include "map_io/map_version_packet.h" +#include "map_io/map_wincondition_packet.h" namespace Widelands { @@ -220,6 +221,14 @@ log("took %ums\n ", timer.ms_since_last_query()); if (is_game) { + // Map data used by win conditions + log("Writing Wincondition Data ... "); + { + MapWinconditionPacket p; + p.write(fs_, *egbase_.mutable_map(), *mos_); + } + log("took %ums\n ", timer.ms_since_last_query()); + // DATA PACKETS if (mos_->get_nr_flags()) { log("Writing Flagdata Data ... "); === added file 'src/map_io/map_wincondition_packet.cc' --- src/map_io/map_wincondition_packet.cc 1970-01-01 00:00:00 +0000 +++ src/map_io/map_wincondition_packet.cc 2019-03-09 11:05:59 +0000 @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 by the Widelands Development Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "map_io/map_wincondition_packet.h" + +#include "io/fileread.h" +#include "io/filewrite.h" +#include "logic/game_data_error.h" + +namespace Widelands { + +constexpr uint8_t kCurrentPacketVersion = 1; + +void MapWinconditionPacket::read(FileSystem& fs, Map& map, MapObjectLoader&) { + if (!fs.file_exists("binary/wincondition")) { + // This can be empty when win conditions don't need any special map data + return; + } + + try { + FileRead fr; + fr.open(fs, "binary/wincondition"); + + const uint8_t packet_version = fr.unsigned_8(); + if (packet_version == kCurrentPacketVersion) { + const size_t no_of_fields = fr.unsigned_32(); + std::set<FCoords>* fields = map.mutable_valuable_fields(); + fields->clear(); + + for (size_t i = 0; i < no_of_fields; ++i) { + const int32_t x = fr.signed_16(); + const int32_t y = fr.signed_16(); + fields->insert(map.get_fcoords(Coords(x, y))); + } + } else { + throw UnhandledVersionError("MapWinconditionPacket", packet_version, kCurrentPacketVersion); + } + } catch (const WException& e) { + throw GameDataError("win condition data: %s", e.what()); + } +} + +void MapWinconditionPacket::write(FileSystem& fs, Map& map, MapObjectSaver&) { + // We only write this packet if we have something interesting to write to it. + std::set<FCoords>& fields = *map.mutable_valuable_fields(); + if (!fields.empty()) { + FileWrite fw; + fw.unsigned_8(kCurrentPacketVersion); + + fw.unsigned_32(fields.size()); + for (const auto& field : fields) { + fw.signed_16(field.x); + fw.signed_16(field.y); + } + + fw.write(fs, "binary/wincondition"); + } +} +} // namespace Widelands === added file 'src/map_io/map_wincondition_packet.h' --- src/map_io/map_wincondition_packet.h 1970-01-01 00:00:00 +0000 +++ src/map_io/map_wincondition_packet.h 2019-03-09 11:05:59 +0000 @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 by the Widelands Development Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef WL_MAP_IO_MAP_WINCONDITION_PACKET_H +#define WL_MAP_IO_MAP_WINCONDITION_PACKET_H + +#include "logic/map.h" + +namespace Widelands { + +/// The anything needed by win conditions +struct MapWinconditionPacket { + void read(FileSystem&, Map& map, MapObjectLoader&); + void write(FileSystem&, Map& map, MapObjectSaver&); +}; +} // namespace Widelands + +#endif // end of include guard: WL_MAP_IO_MAP_WINCONDITION_PACKET_H === modified file 'src/map_io/widelands_map_loader.cc' --- src/map_io/widelands_map_loader.cc 2019-02-23 11:00:49 +0000 +++ src/map_io/widelands_map_loader.cc 2019-03-09 11:05:59 +0000 @@ -55,6 +55,7 @@ #include "map_io/map_scripting_packet.h" #include "map_io/map_terrain_packet.h" #include "map_io/map_version_packet.h" +#include "map_io/map_wincondition_packet.h" #include "map_io/tribes_legacy_lookup_table.h" #include "map_io/world_legacy_lookup_table.h" @@ -310,6 +311,14 @@ } log("took %ums\n ", timer.ms_since_last_query()); + // Map data used by win conditions. + log("Reading Wincondition Data ... "); + { + MapWinconditionPacket p; + p.read(*fs_, *egbase.mutable_map(), *mol_); + } + log("took %ums\n ", timer.ms_since_last_query()); + // Objectives. They are not needed in the Editor, since they are fully // defined through Lua scripting. They are also not required for a game, // since they will be only be set after it has started. === modified file 'src/scripting/lua_game.cc' --- src/scripting/lua_game.cc 2019-02-23 11:00:49 +0000 +++ src/scripting/lua_game.cc 2019-03-09 11:05:59 +0000 @@ -173,17 +173,7 @@ (RO) :const:`true` if this player was defeated, :const:`false` otherwise */ int LuaPlayer::get_defeated(lua_State* L) { - Player& p = get(L, get_egbase(L)); - bool is_defeated = true; - - for (const auto& economy : p.economies()) { - if (!economy.second->warehouses().empty()) { - is_defeated = false; - break; - } - } - - lua_pushboolean(L, is_defeated); + lua_pushboolean(L, get(L, get_egbase(L)).is_defeated()); return 1; } === modified file 'src/scripting/lua_map.cc' --- src/scripting/lua_map.cc 2019-02-26 12:02:52 +0000 +++ src/scripting/lua_map.cc 2019-03-09 11:05:59 +0000 @@ -1186,9 +1186,11 @@ */ const char LuaMap::className[] = "Map"; const MethodType<LuaMap> LuaMap::Methods[] = { + METHOD(LuaMap, count_conquerable_fields), METHOD(LuaMap, count_terrestrial_fields), + METHOD(LuaMap, count_owned_valuable_fields), METHOD(LuaMap, place_immovable), METHOD(LuaMap, get_field), METHOD(LuaMap, recalculate), METHOD(LuaMap, recalculate_seafaring), - METHOD(LuaMap, set_port_space), {nullptr, nullptr}, + METHOD(LuaMap, set_port_space), {nullptr, nullptr}, }; const PropertyType<LuaMap> LuaMap::Properties[] = { PROP_RO(LuaMap, allows_seafaring), @@ -1197,8 +1199,6 @@ PROP_RO(LuaMap, width), PROP_RO(LuaMap, height), PROP_RO(LuaMap, player_slots), - PROP_RO(LuaMap, conquerable_fields), - PROP_RO(LuaMap, terrestrial_fields), {nullptr, nullptr, nullptr}, }; @@ -1280,6 +1280,7 @@ return 1; } + /* RST .. attribute:: player_slots @@ -1299,47 +1300,70 @@ return 1; } + +/* + ========================================================== + LUA METHODS + ========================================================== + */ + /* RST - .. attribute:: conquerable_fields + .. method:: count_conquerable_fields() (RO) Calculates and returns all reachable fields that a player could build on. - **Note:** This function is expensive, so call it seldom. -*/ -int LuaMap::get_conquerable_fields(lua_State* L) { - lua_newtable(L); - int counter = 0; - for (const Widelands::FCoords& fcoords : - get_egbase(L).map().calculate_all_conquerable_fields()) { - lua_pushinteger(L, ++counter); - to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords)); - lua_settable(L, -3); - } - return 1; -} - -/* RST - .. attribute:: terrestrial_fields - - (RO) Calculates and returns all fields that are not swimmable. -*/ -int LuaMap::get_terrestrial_fields(lua_State* L) { - lua_newtable(L); - int counter = 0; - for (const Widelands::FCoords& fcoords : - get_egbase(L).map().calculate_all_fields_excluding_caps(MOVECAPS_SWIM)) { - lua_pushinteger(L, ++counter); - to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords)); - lua_settable(L, -3); - } - return 1; -} - -/* - ========================================================== - LUA METHODS - ========================================================== - */ + **Note:** The fields are only calculated afresh when this is called for the first time. + + :returns: An integer with the amount of fields. +*/ +// NOCOM document +int LuaMap::count_conquerable_fields(lua_State* L) { + lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_conquerable_fields()); + return 1; +} + +/* RST + .. method:: count_terrestrial_fields() + + (RO) Counts all fields that are not swimmable. + + **Note:** The fields are only calculated afresh when this is called for the first time. + + :returns: An integer with the amount of fields. +*/ +int LuaMap::count_terrestrial_fields(lua_State* L) { + lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_fields_excluding_caps(MOVECAPS_SWIM)); + return 1; +} + + +/* RST + .. method:: count_owned_valuable_fields([immovable_attribute]) + + (RO) Counts the number of owned valuable fields for all players. + + :arg name: *Optional*. If this is set, only count fields that have an + immovable with the given atttribute. + :type name: :class:`string` + + :returns: A table mapping player numbers to their number of owned fields. +*/ +int LuaMap::count_owned_valuable_fields(lua_State* L) { + if (lua_gettop(L) > 2) { + report_error(L, "Does not take more than one argument."); + } + const std::string attribute = lua_gettop(L) == 2 ? luaL_checkstring(L, -1) : ""; + + lua_newtable(L); + for (const auto& fieldinfo : + get_egbase(L).map().count_owned_valuable_fields(attribute)) { + lua_pushinteger(L, fieldinfo.first); + lua_pushinteger(L, fieldinfo.second); + lua_settable(L, -3); + } + return 1; +} + /* RST .. method:: place_immovable(name, field, from_where) === modified file 'src/scripting/lua_map.h' --- src/scripting/lua_map.h 2019-02-24 22:50:04 +0000 +++ src/scripting/lua_map.h 2019-03-09 11:05:59 +0000 @@ -92,12 +92,14 @@ int get_width(lua_State*); int get_height(lua_State*); int get_player_slots(lua_State*); - int get_conquerable_fields(lua_State*); - int get_terrestrial_fields(lua_State*); + /* * Lua methods */ + int count_conquerable_fields(lua_State*); + int count_terrestrial_fields(lua_State*); + int count_owned_valuable_fields(lua_State*); int place_immovable(lua_State*); int get_field(lua_State*); int recalculate(lua_State*);
_______________________________________________ Mailing list: https://launchpad.net/~widelands-dev Post to : widelands-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~widelands-dev More help : https://help.launchpad.net/ListHelp