Merge lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands

Proposed by GunChleoc
Status: Merged
Merged at revision: 9014
Proposed branch: lp:~widelands-dev/widelands/speedup_saveloading
Merge into: lp:widelands
Diff against target: 958 lines (+361/-170)
21 files modified
data/scripting/win_conditions/territorial_functions.lua (+10/-42)
data/scripting/win_conditions/territorial_lord.lua (+5/-3)
data/scripting/win_conditions/territorial_time.lua (+5/-3)
data/scripting/win_conditions/win_condition_functions.lua (+32/-0)
data/scripting/win_conditions/wood_gnome.lua (+13/-39)
src/game_io/game_cmd_queue_packet.cc (+4/-0)
src/logic/game.cc (+5/-1)
src/logic/map.cc (+40/-16)
src/logic/map.h (+26/-5)
src/logic/player.cc (+9/-0)
src/logic/player.h (+5/-0)
src/logic/playercommand.cc (+6/-6)
src/map_io/CMakeLists.txt (+2/-0)
src/map_io/map_saver.cc (+9/-0)
src/map_io/map_wincondition_packet.cc (+75/-0)
src/map_io/map_wincondition_packet.h (+34/-0)
src/map_io/widelands_map_loader.cc (+9/-0)
src/scripting/lua_game.cc (+1/-11)
src/scripting/lua_map.cc (+60/-38)
src/scripting/lua_map.h (+3/-2)
test/maps/lua_testsuite.wmf/scripting/cfield.lua (+8/-4)
To merge this branch: bzr merge lp:~widelands-dev/widelands/speedup_saveloading
Reviewer Review Type Date Requested Status
Toni Förster Approve
Review via email: mp+364205@code.launchpad.net

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
- Clean up writing CmdFlagAction
- Bulk skip all commands with greater duetime when writing command queue packet

Description of the change

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

To post a comment you must log in.
Revision history for this message
Toni Förster (stonerl) wrote :

Tested this, also with old savegames. Works like a charm. Tremendously speeds up the saving and loading (territorial & woodgnome).

review: Approve (tested)
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4572. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/503990492.
Appveyor build 4359. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_speedup_saveloading-4359.

Revision history for this message
Toni Förster (stonerl) wrote :

Regressiontestsfail, though

review: Needs Fixing (travis)
Revision history for this message
GunChleoc (gunchleoc) wrote :

I forgot to adapt the tests to the new data types. Should be working now.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4579. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/504410525.
Appveyor build 4366. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_speedup_saveloading-4366.

Revision history for this message
Toni Förster (stonerl) :
review: Approve
Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the review!

@bunnybot merge

Revision history for this message
kaputtnik (franku) wrote :

Here are my values from my old laptop. Seem this branch makes loading slower?

Version bzr9012[speedup_saveloading] (Release)
WidelandsMapLoader::load_map_complete() for 'Magic Mountain' took 5136ms
GameLoader::load() took 11895ms
MapSaver::save() for 'Magic Mountain' took 12376ms
MapSaver::save() for 'Magic Mountain' took 12096ms
GameSaver::save() took 13103ms
WidelandsMapLoader::load_map_complete() for 'Magic Mountain' took 7798ms
GameLoader::load() took 7921ms

Version bzr9008[trunk] (Release)
WidelandsMapLoader::load_map_complete() for 'Magic Mountain' took 5042ms
GameLoader::load() took 12061ms
MapSaver::save() for 'Magic Mountain' took 11165ms
MapSaver::save() for 'Magic Mountain' took 11309ms
GameSaver::save() took 12220ms
WidelandsMapLoader::load_map_complete() for 'Magic Mountain' took 7495ms
GameLoader::load() took 7617ms

Revision history for this message
GunChleoc (gunchleoc) wrote :

The only change that could possibly make it slower are the changes to the map resources packet, so I have reverted that. The important speed up is for the win conditions. All the other changes are omitting data, and the extra boolean comparisons should be a lot faster than writing to / reading from disk.

Have you made more than one test run? The numbers are not that radically different.

Revision history for this message
Toni Förster (stonerl) wrote :

Well I'd say these numbers are within the margin of error. If you compare saving and loading times, you can see the difference this branch makes, though.

@GunChleoc not sure if r9015 was necessary. I don't really see a difference.

Revision history for this message
GunChleoc (gunchleoc) wrote :

It didn't gain us that much anyway, so better safe than sorry this close to the release. The big think here was removing the field lists from Lua ;)

Revision history for this message
Toni Förster (stonerl) wrote :

Sound sane :-) are you planning to add them for b21?

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4580. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/504553847.
Appveyor build 4367. State: failed. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_speedup_saveloading-4367.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Refusing to merge, since Travis is not green. Use @bunnybot merge force for merging anyways.

Travis build 4580. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/504553847.

Revision history for this message
Toni Förster (stonerl) wrote :

Travis hiccup while downloading brew bottles.

@bunnybot merge force

Revision history for this message
GunChleoc (gunchleoc) wrote :

I guess we could.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I did some more measurements on my slow machine, and the change in the resources packet didn't matter there. Let's keep it as it is, because the code is more readable.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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-11 07:15:58 +0000
@@ -7,45 +7,13 @@
7set_textdomain("win_conditions")7set_textdomain("win_conditions")
88
9include "scripting/richtext.lua"9include "scripting/richtext.lua"
10include "scripting/win_conditions/win_condition_functions.lua"
10include "scripting/win_conditions/win_condition_texts.lua"11include "scripting/win_conditions/win_condition_texts.lua"
1112
12local team_str = _"Team %i"13local team_str = _"Team %i"
13local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)."14local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)."
14local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)."15local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)."
1516
16-- RST
17-- .. function:: count_owned_fields_for_all_players(fields, players)
18--
19-- Counts all owned fields for each player.
20--
21-- :arg fields: Table of all buildable fields
22-- :arg players: Table of all players
23--
24-- :returns: a table with ``playernumber = count_of_owned_fields`` entries
25--
26local function count_owned_fields_for_all_players(fields, players)
27 local owned_fields = {}
28 -- init the landsizes for each player
29 for idx,plr in ipairs(players) do
30 owned_fields[plr.number] = 0
31 end
32
33 for idx,f in ipairs(fields) do
34 -- check if field is owned by a player
35 local owner = f.owner
36 if owner then
37 local owner_number = owner.number
38 if owned_fields[owner_number] == nil then
39 -- In case player was defeated and lost all their warehouses, make sure they don't count
40 owned_fields[owner_number] = -1
41 elseif owned_fields[owner_number] >= 0 then
42 owned_fields[owner_number] = owned_fields[owner_number] + 1
43 end
44 end
45 end
46 return owned_fields
47end
48
49-- Used by calculate_territory_points keep track of when the winner changes17-- Used by calculate_territory_points keep track of when the winner changes
50local winning_players = {}18local winning_players = {}
51local winning_teams = {}19local winning_teams = {}
@@ -91,7 +59,7 @@
91-- First checks if a player was defeated, then fills the ``territory_points`` table59-- First checks if a player was defeated, then fills the ``territory_points`` table
92-- with current data.60-- with current data.
93--61--
94-- :arg fields: Table of all buildable fields62-- :arg fields: Number of all valuable fields
95-- :arg players: Table of all players63-- :arg players: Table of all players
96-- :arg wc_descname: String with the win condition's descname64-- :arg wc_descname: String with the win condition's descname
97-- :arg wc_version: Number with the win condition's descname65-- :arg wc_version: Number with the win condition's descname
@@ -100,12 +68,12 @@
100 local points = {} -- tracking points of teams and players without teams68 local points = {} -- tracking points of teams and players without teams
101 local territory_was_kept = false69 local territory_was_kept = false
10270
103 territory_points.all_player_points = count_owned_fields_for_all_players(fields, players)71 territory_points.all_player_points = count_owned_valuable_fields_for_all_players(players)
104 local ranked_players = rank_players(territory_points.all_player_points, players)72 local ranked_players = rank_players(territory_points.all_player_points, players)
10573
106 -- Check if we have a winner. The table was sorted, so we can simply grab the first entry.74 -- Check if we have a winner. The table was sorted, so we can simply grab the first entry.
107 local winning_points = -175 local winning_points = -1
108 if ranked_players[1].points > ( #fields / 2 ) then76 if ranked_players[1].points > ( fields / 2 ) then
109 winning_points = ranked_players[1].points77 winning_points = ranked_players[1].points
110 end78 end
11179
@@ -161,7 +129,7 @@
161-- Returns a string containing the current land percentages of players/teams129-- Returns a string containing the current land percentages of players/teams
162-- for messages to the players130-- for messages to the players
163--131--
164-- :arg fields: Table of all buildable fields132-- :arg fields: Number of all valuable fields
165-- :arg has_had: Use "has" for an interim message, "had" for a game over message.133-- :arg has_had: Use "has" for an interim message, "had" for a game over message.
166--134--
167-- :returns: a richtext-formatted string with information on current points for each player/team135-- :returns: a richtext-formatted string with information on current points for each player/team
@@ -178,17 +146,17 @@
178 li(146 li(
179 (wc_has_territory):bformat(147 (wc_has_territory):bformat(
180 territory_points.points[i][1],148 territory_points.points[i][1],
181 _percent(territory_points.points[i][2], #fields),149 _percent(territory_points.points[i][2], fields),
182 territory_points.points[i][2],150 territory_points.points[i][2],
183 #fields))151 fields))
184 else152 else
185 msg = msg ..153 msg = msg ..
186 li(154 li(
187 (wc_had_territory):bformat(155 (wc_had_territory):bformat(
188 territory_points.points[i][1],156 territory_points.points[i][1],
189 _percent(territory_points.points[i][2], #fields),157 _percent(territory_points.points[i][2], fields),
190 territory_points.points[i][2],158 territory_points.points[i][2],
191 #fields))159 fields))
192 end160 end
193161
194 end162 end
@@ -246,7 +214,7 @@
246--214--
247-- Updates the territory points and sends game over reports215-- Updates the territory points and sends game over reports
248--216--
249-- :arg fields: Table of all buildable fields217-- :arg fields: Number of all valuable fields
250-- :arg players: Table of all players218-- :arg players: Table of all players
251--219--
252function territory_game_over(fields, players, wc_descname, wc_version)220function territory_game_over(fields, players, wc_descname, wc_version)
253221
=== 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-11 07:15:58 +0000
@@ -23,18 +23,20 @@
23 "that area for at least 20 minutes."23 "that area for at least 20 minutes."
24)24)
2525
26local fields = 0
27
26return {28return {
27 name = wc_name,29 name = wc_name,
28 description = wc_desc,30 description = wc_desc,
31 init = function()
32 fields = wl.Game().map:count_conquerable_fields()
33 end,
29 func = function()34 func = function()
30 local plrs = wl.Game().players35 local plrs = wl.Game().players
3136
32 -- set the objective with the game type for all players37 -- set the objective with the game type for all players
33 broadcast_objective("win_condition", wc_descname, wc_desc)38 broadcast_objective("win_condition", wc_descname, wc_desc)
3439
35 -- Table of fields that are worth conquering
36 local fields = wl.Game().map.conquerable_fields
37
38 -- Configure how long the winner has to hold on to the territory40 -- Configure how long the winner has to hold on to the territory
39 local time_to_keep_territory = 20 * 60 -- 20 minutes41 local time_to_keep_territory = 20 * 60 -- 20 minutes
40 -- time in secs, if == 0 -> victory42 -- time in secs, if == 0 -> victory
4143
=== 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-11 07:15:58 +0000
@@ -27,18 +27,20 @@
27 "after 4 hours, whichever comes first."27 "after 4 hours, whichever comes first."
28)28)
2929
30local fields = 0
31
30return {32return {
31 name = wc_name,33 name = wc_name,
32 description = wc_desc,34 description = wc_desc,
35 init = function()
36 fields = wl.Game().map:count_conquerable_fields()
37 end,
33 func = function()38 func = function()
34 local plrs = wl.Game().players39 local plrs = wl.Game().players
3540
36 -- set the objective with the game type for all players41 -- set the objective with the game type for all players
37 broadcast_objective("win_condition", wc_descname, wc_desc)42 broadcast_objective("win_condition", wc_descname, wc_desc)
3843
39 -- Table of fields that are worth conquering
40 local fields = wl.Game().map.conquerable_fields
41
42 -- variables to track the maximum 4 hours of gametime44 -- variables to track the maximum 4 hours of gametime
43 local remaining_max_time = 4 * 60 * 60 -- 4 hours45 local remaining_max_time = 4 * 60 * 60 -- 4 hours
4446
4547
=== 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-11 07:15:58 +0000
@@ -147,6 +147,38 @@
147147
148148
149-- RST149-- RST
150-- .. function:: count_owned_valuable_fields_for_all_players(players[, attribute])
151--
152-- Counts all owned fields for each player.
153--
154-- :arg players: Table of all players
155-- :arg attribute: If this is set, only count fields that have an immovable with this attribute
156--
157-- :returns: a table with ``playernumber = count_of_owned_fields`` entries
158--
159function count_owned_valuable_fields_for_all_players(players, attribute)
160 attribute = attribute or ""
161
162 local owned_fields = {}
163
164 -- Get number of currently owned valuable fields per player.
165 -- This table can contain defeated players.
166 local all_plrpoints = wl.Game().map:count_owned_valuable_fields(attribute)
167
168 -- Insert points for all players who are still in the game, and 0 points for defeated players.
169 for idx,plr in ipairs(players) do
170 if (plr.defeated) then
171 owned_fields[plr.number] = 0
172 else
173 owned_fields[plr.number] = all_plrpoints[plr.number]
174 end
175 end
176 return owned_fields
177end
178
179
180
181-- RST
150-- .. function:: rank_players(all_player_points, plrs)182-- .. function:: rank_players(all_player_points, plrs)
151--183--
152-- Rank the players and teams according to the highest points184-- Rank the players and teams according to the highest points
153185
=== 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-11 07:15:58 +0000
@@ -24,6 +24,10 @@
24return {24return {
25 name = wc_name,25 name = wc_name,
26 description = wc_desc,26 description = wc_desc,
27 init = function()
28 -- Calculate valuable fields
29 wl.Game().map:count_terrestrial_fields()
30 end,
27 func = function()31 func = function()
28 local plrs = wl.Game().players32 local plrs = wl.Game().players
29 local game = wl.Game()33 local game = wl.Game()
@@ -34,51 +38,21 @@
34 -- set the objective with the game type for all players38 -- set the objective with the game type for all players
35 broadcast_objective("win_condition", wc_descname, wc_desc)39 broadcast_objective("win_condition", wc_descname, wc_desc)
3640
37 -- Table of terrestrial fields
38 local fields = wl.Game().map.terrestrial_fields
39
40 -- The function to calculate the current points.41 -- The function to calculate the current points.
41 local _last_time_calculated = -10000042 local _last_time_calculated = -100000
42 local _plrpoints = {}43 local playerpoints = {}
43 local function _calc_points()44 local function _calc_points()
44 local game = wl.Game()
4545
46 if _last_time_calculated > game.time - 5000 then46 if _last_time_calculated > game.time - 5000 then
47 return _plrpoints47 return
48 end48 end
4949
50 -- clear out the table. We count afresh.50 playerpoints = count_owned_valuable_fields_for_all_players(plrs, "tree")
51 for k,v in pairs(_plrpoints) do
52 _plrpoints[k] = 0
53 end
54
55 -- Insert all players who are still in the game.
56 for idx,plr in ipairs(plrs) do
57 _plrpoints[plr.number] = 0
58 end
59
60 for idf,f in ipairs(fields) do
61 -- check if field is owned by a player
62 local owner = f.owner
63 if owner and not owner.defeated then
64 owner = owner.number
65 -- check if field has an immovable
66 local imm = f.immovable
67 if imm then
68 -- check if immovable is a tree
69 if imm:has_attribute("tree") then
70 _plrpoints[owner] = _plrpoints[owner] + 1
71 end
72 end
73 end
74 end
75
76 _last_time_calculated = game.time51 _last_time_calculated = game.time
77 return _plrpoints
78 end52 end
7953
80 local function _send_state(remaining_time, plrs, show_popup)54 local function _send_state(remaining_time, plrs, show_popup)
81 local playerpoints = _calc_points()55 _calc_points()
82 local msg = format_remaining_time(remaining_time) .. vspace(8) .. game_status.body56 local msg = format_remaining_time(remaining_time) .. vspace(8) .. game_status.body
8357
84 for idx,plr in ipairs(plrs) do58 for idx,plr in ipairs(plrs) do
@@ -92,7 +66,7 @@
92 end66 end
9367
94 local function _game_over(plrs)68 local function _game_over(plrs)
95 local playerpoints = _calc_points()69 _calc_points()
96 local points = {}70 local points = {}
97 for idx,plr in ipairs(plrs) do71 for idx,plr in ipairs(plrs) do
98 points[#points + 1] = { plr, playerpoints[plr.number] }72 points[#points + 1] = { plr, playerpoints[plr.number] }
@@ -131,8 +105,8 @@
131 name = wc_trees_owned,105 name = wc_trees_owned,
132 pic = "images/wui/stats/genstats_trees.png",106 pic = "images/wui/stats/genstats_trees.png",
133 calculator = function(p)107 calculator = function(p)
134 local pts = _calc_points(p)108 _calc_points(p)
135 return pts[p.number]109 return playerpoints[p.number] or 0
136 end,110 end,
137 }111 }
138112
139113
=== 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-11 07:15:58 +0000
@@ -98,6 +98,10 @@
9898
99 while (!p.empty()) {99 while (!p.empty()) {
100 const CmdQueue::CmdItem& it = p.top();100 const CmdQueue::CmdItem& it = p.top();
101 if (it.cmd->duetime() > time) {
102 // Time is the primary sorting key, so we can't have any additional commands in this queue for this time
103 break;
104 }
101 if (it.cmd->duetime() == time) {105 if (it.cmd->duetime() == time) {
102 if (upcast(GameLogicCommand, cmd, it.cmd)) {106 if (upcast(GameLogicCommand, cmd, it.cmd)) {
103 // The id (aka command type)107 // The id (aka command type)
104108
=== modified file 'src/logic/game.cc'
--- src/logic/game.cc 2019-03-02 10:34:39 +0000
+++ src/logic/game.cc 2019-03-11 07:15:58 +0000
@@ -314,10 +314,14 @@
314314
315 // Check for win_conditions315 // Check for win_conditions
316 if (!settings.scenario) {316 if (!settings.scenario) {
317 loader_ui->step(_("Initializing game…"));
317 std::unique_ptr<LuaTable> table(lua().run_script(settings.win_condition_script));318 std::unique_ptr<LuaTable> table(lua().run_script(settings.win_condition_script));
318 get_ibase()->log_message(_("Initializing game…"));
319 table->do_not_warn_about_unaccessed_keys();319 table->do_not_warn_about_unaccessed_keys();
320 win_condition_displayname_ = table->get_string("name");320 win_condition_displayname_ = table->get_string("name");
321 if (table->has_key<std::string>("init")) {
322 std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("init");
323 cr->resume();
324 }
321 std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func");325 std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func");
322 enqueue_command(new CmdLuaCoroutine(get_gametime() + 100, std::move(cr)));326 enqueue_command(new CmdLuaCoroutine(get_gametime() + 100, std::move(cr)));
323 } else {327 } else {
324328
=== modified file 'src/logic/map.cc'
--- src/logic/map.cc 2019-03-02 09:31:11 +0000
+++ src/logic/map.cc 2019-03-11 07:15:58 +0000
@@ -257,8 +257,11 @@
257 }257 }
258}258}
259259
260std::set<FCoords> Map::calculate_all_conquerable_fields() const {260size_t Map::count_all_conquerable_fields() {
261 std::set<FCoords> result;261 if (!valuable_fields_.empty()) {
262 // Already calculated
263 return valuable_fields_.size();
264 }
262265
263 std::set<FCoords> coords_to_check;266 std::set<FCoords> coords_to_check;
264267
@@ -267,17 +270,17 @@
267270
268 // If we don't have the given coordinates yet, walk the map and collect conquerable fields,271 // If we don't have the given coordinates yet, walk the map and collect conquerable fields,
269 // initialized with the given radius around the coordinates272 // initialized with the given radius around the coordinates
270 const auto walk_starting_coords = [this, &result, &coords_to_check](273 const auto walk_starting_coords = [this, &coords_to_check](
271 const Coords& coords, int radius) {274 const Coords& coords, int radius) {
272 FCoords fcoords = get_fcoords(coords);275 FCoords fcoords = get_fcoords(coords);
273276
274 // We already have these coordinates277 // We already have these coordinates
275 if (result.count(fcoords) == 1) {278 if (valuable_fields_.count(fcoords) == 1) {
276 return;279 return;
277 }280 }
278281
279 // Add starting field282 // Add starting field
280 result.insert(fcoords);283 valuable_fields_.insert(fcoords);
281284
282 // Add outer land coordinates around the starting field for the given radius285 // Add outer land coordinates around the starting field for the given radius
283 std::unique_ptr<Widelands::HollowArea<>> hollow_area(286 std::unique_ptr<Widelands::HollowArea<>> hollow_area(
@@ -317,8 +320,8 @@
317320
318 // We do the caps check first, because the comparison is faster than the container321 // We do the caps check first, because the comparison is faster than the container
319 // check322 // check
320 if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (result.count(fcoords) == 0)) {323 if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (valuable_fields_.count(fcoords) == 0)) {
321 result.insert(fcoords);324 valuable_fields_.insert(fcoords);
322 coords_to_check.insert(fcoords);325 coords_to_check.insert(fcoords);
323 }326 }
324 } while (map_region->advance(*this));327 } while (map_region->advance(*this));
@@ -342,23 +345,44 @@
342 }345 }
343 }346 }
344347
345 log("%" PRIuS " found ... ", result.size());348 log("%" PRIuS " found ... ", valuable_fields_.size());
346 return result;349 return valuable_fields_.size();
347}350}
348351
349std::set<FCoords> Map::calculate_all_fields_excluding_caps(NodeCaps caps) const {352size_t Map::count_all_fields_excluding_caps(NodeCaps caps) {
353 if (!valuable_fields_.empty()) {
354 // Already calculated
355 return valuable_fields_.size();
356 }
357
350 log("Collecting valuable fields ... ");358 log("Collecting valuable fields ... ");
351 ScopedTimer timer("took %ums");359 ScopedTimer timer("took %ums");
352360
353 std::set<FCoords> result;
354 for (MapIndex i = 0; i < max_index(); ++i) {361 for (MapIndex i = 0; i < max_index(); ++i) {
355 Field& field = fields_[i];362 Field& field = fields_[i];
356 if (!(field.nodecaps() & caps)) {363 if (!(field.nodecaps() & caps)) {
357 result.insert(get_fcoords(field));364 valuable_fields_.insert(get_fcoords(field));
358 }365 }
359 }366 }
360367
361 log("%" PRIuS " found ... ", result.size());368 log("%" PRIuS " found ... ", valuable_fields_.size());
369 return valuable_fields_.size();
370}
371
372std::map<PlayerNumber, size_t> Map::count_owned_valuable_fields(const std::string& immovable_attribute) const {
373 std::map<PlayerNumber, size_t> result;
374 const bool use_attribute = !immovable_attribute.empty();
375 const uint32_t attribute_id = use_attribute ? MapObjectDescr::get_attribute_id(immovable_attribute) : 0U;
376 for (const FCoords& fcoords : valuable_fields_) {
377 if (use_attribute) {
378 const BaseImmovable* imm = fcoords.field->get_immovable();
379 if (imm != nullptr && imm->has_attribute(attribute_id)) {
380 result[fcoords.field->get_owned_by()] += 1;
381 }
382 } else {
383 result[fcoords.field->get_owned_by()] += 1;
384 }
385 }
362 return result;386 return result;
363}387}
364388
365389
=== modified file 'src/logic/map.h'
--- src/logic/map.h 2019-02-26 05:56:01 +0000
+++ src/logic/map.h 2019-03-11 07:15:58 +0000
@@ -165,11 +165,25 @@
165 void recalc_whole_map(const World& world);165 void recalc_whole_map(const World& world);
166 void recalc_for_field_area(const World& world, Area<FCoords>);166 void recalc_for_field_area(const World& world, Area<FCoords>);
167167
168 /// Calculates and returns a list of the fields that could be conquered by a player throughout a168 /**
169 /// game. Useful for territorial win conditions.169 * If the valuable fields are empty, calculates all fields that could be conquered by a player throughout a game.
170 std::set<FCoords> calculate_all_conquerable_fields() const;170 * Useful for territorial win conditions.
171 /// Calculates and returns a list of the fields that do not have the given caps.171 * Returns the amount of valuable fields.
172 std::set<FCoords> calculate_all_fields_excluding_caps(NodeCaps caps) const;172 */
173 size_t count_all_conquerable_fields();
174
175 /**
176 * If the valuable fields are empty, calculates which fields do not have the given caps and adds the to the list of valuable fields.
177 * Useful for win conditions.
178 * Returns the amount of valuable fields.
179 */
180 size_t count_all_fields_excluding_caps(NodeCaps caps);
181
182 /**
183 * Counts the valuable fields that are owned by each player. Only players that currently own a field are added.
184 * Returns a map of <player number, number of owned fields>.
185 */
186 std::map<PlayerNumber, size_t> count_owned_valuable_fields(const std::string& immovable_attribute) const;
173187
174 /***188 /***
175 * Ensures that resources match their adjacent terrains.189 * Ensures that resources match their adjacent terrains.
@@ -438,6 +452,10 @@
438 return &objectives_;452 return &objectives_;
439 }453 }
440454
455 std::set<FCoords>* mutable_valuable_fields() {
456 return &valuable_fields_;
457 }
458
441 /// Returns the military influence on a location from an area.459 /// Returns the military influence on a location from an area.
442 MilitaryInfluence calc_influence(Coords, Area<>) const;460 MilitaryInfluence calc_influence(Coords, Area<>) const;
443461
@@ -535,6 +553,9 @@
535553
536 Objectives objectives_;554 Objectives objectives_;
537555
556 // Fields that are important for the player to own in a win condition
557 std::set<FCoords> valuable_fields_;
558
538 MapVersion map_version_;559 MapVersion map_version_;
539};560};
540561
541562
=== modified file 'src/logic/player.cc'
--- src/logic/player.cc 2019-02-23 11:00:49 +0000
+++ src/logic/player.cc 2019-03-11 07:15:58 +0000
@@ -253,6 +253,15 @@
253 return &other != this && (!team_number_ || team_number_ != other.team_number_);253 return &other != this && (!team_number_ || team_number_ != other.team_number_);
254}254}
255255
256bool Player::is_defeated() const {
257 for (const auto& economy : economies()) {
258 if (!economy.second->warehouses().empty()) {
259 return false;
260 }
261 }
262 return true;
263}
264
256void Player::AiPersistentState::initialize() {265void Player::AiPersistentState::initialize() {
257 colony_scan_area = kColonyScanStartArea;266 colony_scan_area = kColonyScanStartArea;
258 trees_around_cutters = 0;267 trees_around_cutters = 0;
259268
=== modified file 'src/logic/player.h'
--- src/logic/player.h 2019-02-23 11:00:49 +0000
+++ src/logic/player.h 2019-03-11 07:15:58 +0000
@@ -148,6 +148,11 @@
148148
149 bool is_hostile(const Player&) const;149 bool is_hostile(const Player&) const;
150150
151 /**
152 * Returns whether the player lost the last warehouse.
153 */
154 bool is_defeated() const;
155
151 // For cheating156 // For cheating
152 void set_see_all(bool const t) {157 void set_see_all(bool const t) {
153 see_all_ = t;158 see_all_ = t;
154159
=== modified file 'src/logic/playercommand.cc'
--- src/logic/playercommand.cc 2019-02-23 11:00:49 +0000
+++ src/logic/playercommand.cc 2019-03-11 07:15:58 +0000
@@ -450,14 +450,17 @@
450 ser.unsigned_32(serial);450 ser.unsigned_32(serial);
451}451}
452452
453constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 1;453constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 2;
454454
455void CmdFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) {455void CmdFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) {
456 try {456 try {
457 const uint16_t packet_version = fr.unsigned_16();457 const uint16_t packet_version = fr.unsigned_16();
458 if (packet_version == kCurrentPacketVersionCmdFlagAction) {458 // TODO(GunChleoc): Savegame compatibility, remove after Build 21
459 if (packet_version >= 1 && packet_version <= kCurrentPacketVersionCmdFlagAction) {
459 PlayerCommand::read(fr, egbase, mol);460 PlayerCommand::read(fr, egbase, mol);
460 fr.unsigned_8();461 if (packet_version == 1) {
462 fr.unsigned_8();
463 }
461 serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol);464 serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol);
462 } else {465 } else {
463 throw UnhandledVersionError(466 throw UnhandledVersionError(
@@ -472,9 +475,6 @@
472 fw.unsigned_16(kCurrentPacketVersionCmdFlagAction);475 fw.unsigned_16(kCurrentPacketVersionCmdFlagAction);
473 // Write base classes476 // Write base classes
474 PlayerCommand::write(fw, egbase, mos);477 PlayerCommand::write(fw, egbase, mos);
475 // Now action
476 fw.unsigned_8(0);
477
478 // Now serial478 // Now serial
479 fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(serial)));479 fw.unsigned_32(mos.get_object_file_index_or_zero(egbase.objects().get_object(serial)));
480}480}
481481
=== 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-11 07:15:58 +0000
@@ -87,6 +87,8 @@
87 map_terrain_packet.h87 map_terrain_packet.h
88 map_version_packet.cc88 map_version_packet.cc
89 map_version_packet.h89 map_version_packet.h
90 map_wincondition_packet.cc
91 map_wincondition_packet.h
90 DEPENDS92 DEPENDS
91 base_exceptions93 base_exceptions
92 base_log94 base_log
9395
=== 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-11 07:15:58 +0000
@@ -58,6 +58,7 @@
58#include "map_io/map_scripting_packet.h"58#include "map_io/map_scripting_packet.h"
59#include "map_io/map_terrain_packet.h"59#include "map_io/map_terrain_packet.h"
60#include "map_io/map_version_packet.h"60#include "map_io/map_version_packet.h"
61#include "map_io/map_wincondition_packet.h"
6162
62namespace Widelands {63namespace Widelands {
6364
@@ -220,6 +221,14 @@
220 log("took %ums\n ", timer.ms_since_last_query());221 log("took %ums\n ", timer.ms_since_last_query());
221222
222 if (is_game) {223 if (is_game) {
224 // Map data used by win conditions
225 log("Writing Wincondition Data ... ");
226 {
227 MapWinconditionPacket p;
228 p.write(fs_, *egbase_.mutable_map(), *mos_);
229 }
230 log("took %ums\n ", timer.ms_since_last_query());
231
223 // DATA PACKETS232 // DATA PACKETS
224 if (mos_->get_nr_flags()) {233 if (mos_->get_nr_flags()) {
225 log("Writing Flagdata Data ... ");234 log("Writing Flagdata Data ... ");
226235
=== 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-11 07:15:58 +0000
@@ -0,0 +1,75 @@
1/*
2 * Copyright (C) 2019 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#include "map_io/map_wincondition_packet.h"
21
22#include "io/fileread.h"
23#include "io/filewrite.h"
24#include "logic/game_data_error.h"
25
26namespace Widelands {
27
28constexpr uint8_t kCurrentPacketVersion = 1;
29
30void MapWinconditionPacket::read(FileSystem& fs, Map& map, MapObjectLoader&) {
31 if (!fs.file_exists("binary/wincondition")) {
32 // This can be empty when win conditions don't need any special map data
33 return;
34 }
35
36 try {
37 FileRead fr;
38 fr.open(fs, "binary/wincondition");
39
40 const uint8_t packet_version = fr.unsigned_8();
41 if (packet_version == kCurrentPacketVersion) {
42 const size_t no_of_fields = fr.unsigned_32();
43 std::set<FCoords>* fields = map.mutable_valuable_fields();
44 fields->clear();
45
46 for (size_t i = 0; i < no_of_fields; ++i) {
47 const int32_t x = fr.signed_16();
48 const int32_t y = fr.signed_16();
49 fields->insert(map.get_fcoords(Coords(x, y)));
50 }
51 } else {
52 throw UnhandledVersionError("MapWinconditionPacket", packet_version, kCurrentPacketVersion);
53 }
54 } catch (const WException& e) {
55 throw GameDataError("win condition data: %s", e.what());
56 }
57}
58
59void MapWinconditionPacket::write(FileSystem& fs, Map& map, MapObjectSaver&) {
60 // We only write this packet if we have something interesting to write to it.
61 std::set<FCoords>& fields = *map.mutable_valuable_fields();
62 if (!fields.empty()) {
63 FileWrite fw;
64 fw.unsigned_8(kCurrentPacketVersion);
65
66 fw.unsigned_32(fields.size());
67 for (const auto& field : fields) {
68 fw.signed_16(field.x);
69 fw.signed_16(field.y);
70 }
71
72 fw.write(fs, "binary/wincondition");
73 }
74}
75} // namespace Widelands
076
=== 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-11 07:15:58 +0000
@@ -0,0 +1,34 @@
1/*
2 * Copyright (C) 2019 by the Widelands Development Team
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#ifndef WL_MAP_IO_MAP_WINCONDITION_PACKET_H
21#define WL_MAP_IO_MAP_WINCONDITION_PACKET_H
22
23#include "logic/map.h"
24
25namespace Widelands {
26
27/// The anything needed by win conditions
28struct MapWinconditionPacket {
29 void read(FileSystem&, Map& map, MapObjectLoader&);
30 void write(FileSystem&, Map& map, MapObjectSaver&);
31};
32} // namespace Widelands
33
34#endif // end of include guard: WL_MAP_IO_MAP_WINCONDITION_PACKET_H
035
=== 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-11 07:15:58 +0000
@@ -55,6 +55,7 @@
55#include "map_io/map_scripting_packet.h"55#include "map_io/map_scripting_packet.h"
56#include "map_io/map_terrain_packet.h"56#include "map_io/map_terrain_packet.h"
57#include "map_io/map_version_packet.h"57#include "map_io/map_version_packet.h"
58#include "map_io/map_wincondition_packet.h"
58#include "map_io/tribes_legacy_lookup_table.h"59#include "map_io/tribes_legacy_lookup_table.h"
59#include "map_io/world_legacy_lookup_table.h"60#include "map_io/world_legacy_lookup_table.h"
6061
@@ -310,6 +311,14 @@
310 }311 }
311 log("took %ums\n ", timer.ms_since_last_query());312 log("took %ums\n ", timer.ms_since_last_query());
312313
314 // Map data used by win conditions.
315 log("Reading Wincondition Data ... ");
316 {
317 MapWinconditionPacket p;
318 p.read(*fs_, *egbase.mutable_map(), *mol_);
319 }
320 log("took %ums\n ", timer.ms_since_last_query());
321
313 // Objectives. They are not needed in the Editor, since they are fully322 // Objectives. They are not needed in the Editor, since they are fully
314 // defined through Lua scripting. They are also not required for a game,323 // defined through Lua scripting. They are also not required for a game,
315 // since they will be only be set after it has started.324 // since they will be only be set after it has started.
316325
=== 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-11 07:15:58 +0000
@@ -173,17 +173,7 @@
173 (RO) :const:`true` if this player was defeated, :const:`false` otherwise173 (RO) :const:`true` if this player was defeated, :const:`false` otherwise
174*/174*/
175int LuaPlayer::get_defeated(lua_State* L) {175int LuaPlayer::get_defeated(lua_State* L) {
176 Player& p = get(L, get_egbase(L));176 lua_pushboolean(L, get(L, get_egbase(L)).is_defeated());
177 bool is_defeated = true;
178
179 for (const auto& economy : p.economies()) {
180 if (!economy.second->warehouses().empty()) {
181 is_defeated = false;
182 break;
183 }
184 }
185
186 lua_pushboolean(L, is_defeated);
187 return 1;177 return 1;
188}178}
189179
190180
=== 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-11 07:15:58 +0000
@@ -1186,9 +1186,11 @@
1186*/1186*/
1187const char LuaMap::className[] = "Map";1187const char LuaMap::className[] = "Map";
1188const MethodType<LuaMap> LuaMap::Methods[] = {1188const MethodType<LuaMap> LuaMap::Methods[] = {
1189 METHOD(LuaMap, count_conquerable_fields), METHOD(LuaMap, count_terrestrial_fields),
1190 METHOD(LuaMap, count_owned_valuable_fields),
1189 METHOD(LuaMap, place_immovable), METHOD(LuaMap, get_field),1191 METHOD(LuaMap, place_immovable), METHOD(LuaMap, get_field),
1190 METHOD(LuaMap, recalculate), METHOD(LuaMap, recalculate_seafaring),1192 METHOD(LuaMap, recalculate), METHOD(LuaMap, recalculate_seafaring),
1191 METHOD(LuaMap, set_port_space), {nullptr, nullptr},1193 METHOD(LuaMap, set_port_space), {nullptr, nullptr},
1192};1194};
1193const PropertyType<LuaMap> LuaMap::Properties[] = {1195const PropertyType<LuaMap> LuaMap::Properties[] = {
1194 PROP_RO(LuaMap, allows_seafaring),1196 PROP_RO(LuaMap, allows_seafaring),
@@ -1197,8 +1199,6 @@
1197 PROP_RO(LuaMap, width),1199 PROP_RO(LuaMap, width),
1198 PROP_RO(LuaMap, height),1200 PROP_RO(LuaMap, height),
1199 PROP_RO(LuaMap, player_slots),1201 PROP_RO(LuaMap, player_slots),
1200 PROP_RO(LuaMap, conquerable_fields),
1201 PROP_RO(LuaMap, terrestrial_fields),
1202 {nullptr, nullptr, nullptr},1202 {nullptr, nullptr, nullptr},
1203};1203};
12041204
@@ -1299,47 +1299,69 @@
1299 return 1;1299 return 1;
1300}1300}
13011301
1302/* RST
1303 .. attribute:: conquerable_fields
1304
1305 (RO) Calculates and returns all reachable fields that a player could build on.
1306
1307 **Note:** This function is expensive, so call it seldom.
1308*/
1309int LuaMap::get_conquerable_fields(lua_State* L) {
1310 lua_newtable(L);
1311 int counter = 0;
1312 for (const Widelands::FCoords& fcoords :
1313 get_egbase(L).map().calculate_all_conquerable_fields()) {
1314 lua_pushinteger(L, ++counter);
1315 to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords));
1316 lua_settable(L, -3);
1317 }
1318 return 1;
1319}
1320
1321/* RST
1322 .. attribute:: terrestrial_fields
1323
1324 (RO) Calculates and returns all fields that are not swimmable.
1325*/
1326int LuaMap::get_terrestrial_fields(lua_State* L) {
1327 lua_newtable(L);
1328 int counter = 0;
1329 for (const Widelands::FCoords& fcoords :
1330 get_egbase(L).map().calculate_all_fields_excluding_caps(MOVECAPS_SWIM)) {
1331 lua_pushinteger(L, ++counter);
1332 to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords));
1333 lua_settable(L, -3);
1334 }
1335 return 1;
1336}
13371302
1338/*1303/*
1339 ==========================================================1304 ==========================================================
1340 LUA METHODS1305 LUA METHODS
1341 ==========================================================1306 ==========================================================
1342 */1307 */
1308
1309/* RST
1310 .. method:: count_conquerable_fields()
1311
1312 (RO) Counts all reachable fields that a player could build on.
1313
1314 **Note:** The fields are only calculated afresh when this is called for the first time.
1315
1316 :returns: An integer with the amount of fields.
1317*/
1318int LuaMap::count_conquerable_fields(lua_State* L) {
1319 lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_conquerable_fields());
1320 return 1;
1321}
1322
1323/* RST
1324 .. method:: count_terrestrial_fields()
1325
1326 (RO) Counts all fields that are not swimmable.
1327
1328 **Note:** The fields are only calculated afresh when this is called for the first time.
1329
1330 :returns: An integer with the amount of fields.
1331*/
1332int LuaMap::count_terrestrial_fields(lua_State* L) {
1333 lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_fields_excluding_caps(MOVECAPS_SWIM));
1334 return 1;
1335}
1336
1337
1338/* RST
1339 .. method:: count_owned_valuable_fields([immovable_attribute])
1340
1341 (RO) Counts the number of owned valuable fields for all players.
1342
1343 :arg name: *Optional*. If this is set, only count fields that have an
1344 immovable with the given atttribute.
1345 :type name: :class:`string`
1346
1347 :returns: A table mapping player numbers to their number of owned fields.
1348*/
1349int LuaMap::count_owned_valuable_fields(lua_State* L) {
1350 if (lua_gettop(L) > 2) {
1351 report_error(L, "Does not take more than one argument.");
1352 }
1353 const std::string attribute = lua_gettop(L) == 2 ? luaL_checkstring(L, -1) : "";
1354
1355 lua_newtable(L);
1356 for (const auto& fieldinfo :
1357 get_egbase(L).map().count_owned_valuable_fields(attribute)) {
1358 lua_pushinteger(L, fieldinfo.first);
1359 lua_pushinteger(L, fieldinfo.second);
1360 lua_settable(L, -3);
1361 }
1362 return 1;
1363}
1364
1343/* RST1365/* RST
1344 .. method:: place_immovable(name, field, from_where)1366 .. method:: place_immovable(name, field, from_where)
13451367
13461368
=== 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-11 07:15:58 +0000
@@ -92,12 +92,13 @@
92 int get_width(lua_State*);92 int get_width(lua_State*);
93 int get_height(lua_State*);93 int get_height(lua_State*);
94 int get_player_slots(lua_State*);94 int get_player_slots(lua_State*);
95 int get_conquerable_fields(lua_State*);
96 int get_terrestrial_fields(lua_State*);
9795
98 /*96 /*
99 * Lua methods97 * Lua methods
100 */98 */
99 int count_conquerable_fields(lua_State*);
100 int count_terrestrial_fields(lua_State*);
101 int count_owned_valuable_fields(lua_State*);
101 int place_immovable(lua_State*);102 int place_immovable(lua_State*);
102 int get_field(lua_State*);103 int get_field(lua_State*);
103 int recalculate(lua_State*);104 int recalculate(lua_State*);
104105
=== modified file 'test/maps/lua_testsuite.wmf/scripting/cfield.lua'
--- test/maps/lua_testsuite.wmf/scripting/cfield.lua 2019-02-23 09:58:39 +0000
+++ test/maps/lua_testsuite.wmf/scripting/cfield.lua 2019-03-11 07:15:58 +0000
@@ -330,13 +330,17 @@
330end330end
331331
332function field_caps_tests:test_conquerable_fields_does_not_crash()332function field_caps_tests:test_conquerable_fields_does_not_crash()
333 local fields = map.conquerable_fields333 assert_equal(5028, map:count_conquerable_fields())
334 assert_equal(false, fields[1] == nil)334
335 local owned_fields = map:count_owned_valuable_fields()
336 assert_equal(404, owned_fields[1])
335end337end
336338
337function field_caps_tests:test_terrestrial_fields_does_not_crash()339function field_caps_tests:test_terrestrial_fields_does_not_crash()
338 local fields = map.terrestrial_fields340 assert_equal(5028, map:count_terrestrial_fields())
339 assert_equal(false, fields[1] == nil)341
342 local owned_fields = map:count_owned_valuable_fields("tree")
343 assert_equal(nil, owned_fields[1])
340end344end
341345
342-- ===============346-- ===============

Subscribers

People subscribed via source and target branches

to status/vote changes: