Merge lp:~widelands-dev/widelands/speedup_saveloading into lp:widelands
- speedup_saveloading
- Merge into trunk
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 | ||||
Related bugs: |
|
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
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 4572. State: errored. Details: https:/
Appveyor build 4359. State: failed. Details: https:/
Toni Förster (stonerl) wrote : | # |
Regressiontests
GunChleoc (gunchleoc) wrote : | # |
I forgot to adapt the tests to the new data types. Should be working now.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 4579. State: passed. Details: https:/
Appveyor build 4366. State: failed. Details: https:/
Toni Förster (stonerl) : | # |
GunChleoc (gunchleoc) wrote : | # |
Thanks for the review!
@bunnybot merge
kaputtnik (franku) wrote : | # |
Here are my values from my old laptop. Seem this branch makes loading slower?
Version bzr9012[
WidelandsMapLoa
GameLoader::load() took 11895ms
MapSaver::save() for 'Magic Mountain' took 12376ms
MapSaver::save() for 'Magic Mountain' took 12096ms
GameSaver::save() took 13103ms
WidelandsMapLoa
GameLoader::load() took 7921ms
Version bzr9008[trunk] (Release)
WidelandsMapLoa
GameLoader::load() took 12061ms
MapSaver::save() for 'Magic Mountain' took 11165ms
MapSaver::save() for 'Magic Mountain' took 11309ms
GameSaver::save() took 12220ms
WidelandsMapLoa
GameLoader::load() took 7617ms
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.
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.
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 ;)
Toni Förster (stonerl) wrote : | # |
Sound sane :-) are you planning to add them for b21?
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 4580. State: failed. Details: https:/
Appveyor build 4367. State: failed. Details: https:/
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:/
Toni Förster (stonerl) wrote : | # |
Travis hiccup while downloading brew bottles.
@bunnybot merge force
GunChleoc (gunchleoc) wrote : | # |
I guess we could.
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
1 | === modified file 'data/scripting/win_conditions/territorial_functions.lua' | |||
2 | --- data/scripting/win_conditions/territorial_functions.lua 2019-02-21 08:39:13 +0000 | |||
3 | +++ data/scripting/win_conditions/territorial_functions.lua 2019-03-11 07:15:58 +0000 | |||
4 | @@ -7,45 +7,13 @@ | |||
5 | 7 | set_textdomain("win_conditions") | 7 | set_textdomain("win_conditions") |
6 | 8 | 8 | ||
7 | 9 | include "scripting/richtext.lua" | 9 | include "scripting/richtext.lua" |
8 | 10 | include "scripting/win_conditions/win_condition_functions.lua" | ||
9 | 10 | include "scripting/win_conditions/win_condition_texts.lua" | 11 | include "scripting/win_conditions/win_condition_texts.lua" |
10 | 11 | 12 | ||
11 | 12 | local team_str = _"Team %i" | 13 | local team_str = _"Team %i" |
12 | 13 | local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)." | 14 | local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)." |
13 | 14 | local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)." | 15 | local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)." |
14 | 15 | 16 | ||
15 | 16 | -- RST | ||
16 | 17 | -- .. function:: count_owned_fields_for_all_players(fields, players) | ||
17 | 18 | -- | ||
18 | 19 | -- Counts all owned fields for each player. | ||
19 | 20 | -- | ||
20 | 21 | -- :arg fields: Table of all buildable fields | ||
21 | 22 | -- :arg players: Table of all players | ||
22 | 23 | -- | ||
23 | 24 | -- :returns: a table with ``playernumber = count_of_owned_fields`` entries | ||
24 | 25 | -- | ||
25 | 26 | local function count_owned_fields_for_all_players(fields, players) | ||
26 | 27 | local owned_fields = {} | ||
27 | 28 | -- init the landsizes for each player | ||
28 | 29 | for idx,plr in ipairs(players) do | ||
29 | 30 | owned_fields[plr.number] = 0 | ||
30 | 31 | end | ||
31 | 32 | |||
32 | 33 | for idx,f in ipairs(fields) do | ||
33 | 34 | -- check if field is owned by a player | ||
34 | 35 | local owner = f.owner | ||
35 | 36 | if owner then | ||
36 | 37 | local owner_number = owner.number | ||
37 | 38 | if owned_fields[owner_number] == nil then | ||
38 | 39 | -- In case player was defeated and lost all their warehouses, make sure they don't count | ||
39 | 40 | owned_fields[owner_number] = -1 | ||
40 | 41 | elseif owned_fields[owner_number] >= 0 then | ||
41 | 42 | owned_fields[owner_number] = owned_fields[owner_number] + 1 | ||
42 | 43 | end | ||
43 | 44 | end | ||
44 | 45 | end | ||
45 | 46 | return owned_fields | ||
46 | 47 | end | ||
47 | 48 | |||
48 | 49 | -- Used by calculate_territory_points keep track of when the winner changes | 17 | -- Used by calculate_territory_points keep track of when the winner changes |
49 | 50 | local winning_players = {} | 18 | local winning_players = {} |
50 | 51 | local winning_teams = {} | 19 | local winning_teams = {} |
51 | @@ -91,7 +59,7 @@ | |||
52 | 91 | -- First checks if a player was defeated, then fills the ``territory_points`` table | 59 | -- First checks if a player was defeated, then fills the ``territory_points`` table |
53 | 92 | -- with current data. | 60 | -- with current data. |
54 | 93 | -- | 61 | -- |
56 | 94 | -- :arg fields: Table of all buildable fields | 62 | -- :arg fields: Number of all valuable fields |
57 | 95 | -- :arg players: Table of all players | 63 | -- :arg players: Table of all players |
58 | 96 | -- :arg wc_descname: String with the win condition's descname | 64 | -- :arg wc_descname: String with the win condition's descname |
59 | 97 | -- :arg wc_version: Number with the win condition's descname | 65 | -- :arg wc_version: Number with the win condition's descname |
60 | @@ -100,12 +68,12 @@ | |||
61 | 100 | local points = {} -- tracking points of teams and players without teams | 68 | local points = {} -- tracking points of teams and players without teams |
62 | 101 | local territory_was_kept = false | 69 | local territory_was_kept = false |
63 | 102 | 70 | ||
65 | 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) |
66 | 104 | local ranked_players = rank_players(territory_points.all_player_points, players) | 72 | local ranked_players = rank_players(territory_points.all_player_points, players) |
67 | 105 | 73 | ||
68 | 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. |
69 | 107 | local winning_points = -1 | 75 | local winning_points = -1 |
71 | 108 | if ranked_players[1].points > ( #fields / 2 ) then | 76 | if ranked_players[1].points > ( fields / 2 ) then |
72 | 109 | winning_points = ranked_players[1].points | 77 | winning_points = ranked_players[1].points |
73 | 110 | end | 78 | end |
74 | 111 | 79 | ||
75 | @@ -161,7 +129,7 @@ | |||
76 | 161 | -- Returns a string containing the current land percentages of players/teams | 129 | -- Returns a string containing the current land percentages of players/teams |
77 | 162 | -- for messages to the players | 130 | -- for messages to the players |
78 | 163 | -- | 131 | -- |
80 | 164 | -- :arg fields: Table of all buildable fields | 132 | -- :arg fields: Number of all valuable fields |
81 | 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. |
82 | 166 | -- | 134 | -- |
83 | 167 | -- :returns: a richtext-formatted string with information on current points for each player/team | 135 | -- :returns: a richtext-formatted string with information on current points for each player/team |
84 | @@ -178,17 +146,17 @@ | |||
85 | 178 | li( | 146 | li( |
86 | 179 | (wc_has_territory):bformat( | 147 | (wc_has_territory):bformat( |
87 | 180 | territory_points.points[i][1], | 148 | territory_points.points[i][1], |
89 | 181 | _percent(territory_points.points[i][2], #fields), | 149 | _percent(territory_points.points[i][2], fields), |
90 | 182 | territory_points.points[i][2], | 150 | territory_points.points[i][2], |
92 | 183 | #fields)) | 151 | fields)) |
93 | 184 | else | 152 | else |
94 | 185 | msg = msg .. | 153 | msg = msg .. |
95 | 186 | li( | 154 | li( |
96 | 187 | (wc_had_territory):bformat( | 155 | (wc_had_territory):bformat( |
97 | 188 | territory_points.points[i][1], | 156 | territory_points.points[i][1], |
99 | 189 | _percent(territory_points.points[i][2], #fields), | 157 | _percent(territory_points.points[i][2], fields), |
100 | 190 | territory_points.points[i][2], | 158 | territory_points.points[i][2], |
102 | 191 | #fields)) | 159 | fields)) |
103 | 192 | end | 160 | end |
104 | 193 | 161 | ||
105 | 194 | end | 162 | end |
106 | @@ -246,7 +214,7 @@ | |||
107 | 246 | -- | 214 | -- |
108 | 247 | -- Updates the territory points and sends game over reports | 215 | -- Updates the territory points and sends game over reports |
109 | 248 | -- | 216 | -- |
111 | 249 | -- :arg fields: Table of all buildable fields | 217 | -- :arg fields: Number of all valuable fields |
112 | 250 | -- :arg players: Table of all players | 218 | -- :arg players: Table of all players |
113 | 251 | -- | 219 | -- |
114 | 252 | function territory_game_over(fields, players, wc_descname, wc_version) | 220 | function territory_game_over(fields, players, wc_descname, wc_version) |
115 | 253 | 221 | ||
116 | === modified file 'data/scripting/win_conditions/territorial_lord.lua' | |||
117 | --- data/scripting/win_conditions/territorial_lord.lua 2019-03-02 08:52:08 +0000 | |||
118 | +++ data/scripting/win_conditions/territorial_lord.lua 2019-03-11 07:15:58 +0000 | |||
119 | @@ -23,18 +23,20 @@ | |||
120 | 23 | "that area for at least 20 minutes." | 23 | "that area for at least 20 minutes." |
121 | 24 | ) | 24 | ) |
122 | 25 | 25 | ||
123 | 26 | local fields = 0 | ||
124 | 27 | |||
125 | 26 | return { | 28 | return { |
126 | 27 | name = wc_name, | 29 | name = wc_name, |
127 | 28 | description = wc_desc, | 30 | description = wc_desc, |
128 | 31 | init = function() | ||
129 | 32 | fields = wl.Game().map:count_conquerable_fields() | ||
130 | 33 | end, | ||
131 | 29 | func = function() | 34 | func = function() |
132 | 30 | local plrs = wl.Game().players | 35 | local plrs = wl.Game().players |
133 | 31 | 36 | ||
134 | 32 | -- set the objective with the game type for all players | 37 | -- set the objective with the game type for all players |
135 | 33 | broadcast_objective("win_condition", wc_descname, wc_desc) | 38 | broadcast_objective("win_condition", wc_descname, wc_desc) |
136 | 34 | 39 | ||
137 | 35 | -- Table of fields that are worth conquering | ||
138 | 36 | local fields = wl.Game().map.conquerable_fields | ||
139 | 37 | |||
140 | 38 | -- Configure how long the winner has to hold on to the territory | 40 | -- Configure how long the winner has to hold on to the territory |
141 | 39 | local time_to_keep_territory = 20 * 60 -- 20 minutes | 41 | local time_to_keep_territory = 20 * 60 -- 20 minutes |
142 | 40 | -- time in secs, if == 0 -> victory | 42 | -- time in secs, if == 0 -> victory |
143 | 41 | 43 | ||
144 | === modified file 'data/scripting/win_conditions/territorial_time.lua' | |||
145 | --- data/scripting/win_conditions/territorial_time.lua 2019-03-02 08:52:08 +0000 | |||
146 | +++ data/scripting/win_conditions/territorial_time.lua 2019-03-11 07:15:58 +0000 | |||
147 | @@ -27,18 +27,20 @@ | |||
148 | 27 | "after 4 hours, whichever comes first." | 27 | "after 4 hours, whichever comes first." |
149 | 28 | ) | 28 | ) |
150 | 29 | 29 | ||
151 | 30 | local fields = 0 | ||
152 | 31 | |||
153 | 30 | return { | 32 | return { |
154 | 31 | name = wc_name, | 33 | name = wc_name, |
155 | 32 | description = wc_desc, | 34 | description = wc_desc, |
156 | 35 | init = function() | ||
157 | 36 | fields = wl.Game().map:count_conquerable_fields() | ||
158 | 37 | end, | ||
159 | 33 | func = function() | 38 | func = function() |
160 | 34 | local plrs = wl.Game().players | 39 | local plrs = wl.Game().players |
161 | 35 | 40 | ||
162 | 36 | -- set the objective with the game type for all players | 41 | -- set the objective with the game type for all players |
163 | 37 | broadcast_objective("win_condition", wc_descname, wc_desc) | 42 | broadcast_objective("win_condition", wc_descname, wc_desc) |
164 | 38 | 43 | ||
165 | 39 | -- Table of fields that are worth conquering | ||
166 | 40 | local fields = wl.Game().map.conquerable_fields | ||
167 | 41 | |||
168 | 42 | -- variables to track the maximum 4 hours of gametime | 44 | -- variables to track the maximum 4 hours of gametime |
169 | 43 | local remaining_max_time = 4 * 60 * 60 -- 4 hours | 45 | local remaining_max_time = 4 * 60 * 60 -- 4 hours |
170 | 44 | 46 | ||
171 | 45 | 47 | ||
172 | === modified file 'data/scripting/win_conditions/win_condition_functions.lua' | |||
173 | --- data/scripting/win_conditions/win_condition_functions.lua 2019-02-12 17:30:21 +0000 | |||
174 | +++ data/scripting/win_conditions/win_condition_functions.lua 2019-03-11 07:15:58 +0000 | |||
175 | @@ -147,6 +147,38 @@ | |||
176 | 147 | 147 | ||
177 | 148 | 148 | ||
178 | 149 | -- RST | 149 | -- RST |
179 | 150 | -- .. function:: count_owned_valuable_fields_for_all_players(players[, attribute]) | ||
180 | 151 | -- | ||
181 | 152 | -- Counts all owned fields for each player. | ||
182 | 153 | -- | ||
183 | 154 | -- :arg players: Table of all players | ||
184 | 155 | -- :arg attribute: If this is set, only count fields that have an immovable with this attribute | ||
185 | 156 | -- | ||
186 | 157 | -- :returns: a table with ``playernumber = count_of_owned_fields`` entries | ||
187 | 158 | -- | ||
188 | 159 | function count_owned_valuable_fields_for_all_players(players, attribute) | ||
189 | 160 | attribute = attribute or "" | ||
190 | 161 | |||
191 | 162 | local owned_fields = {} | ||
192 | 163 | |||
193 | 164 | -- Get number of currently owned valuable fields per player. | ||
194 | 165 | -- This table can contain defeated players. | ||
195 | 166 | local all_plrpoints = wl.Game().map:count_owned_valuable_fields(attribute) | ||
196 | 167 | |||
197 | 168 | -- Insert points for all players who are still in the game, and 0 points for defeated players. | ||
198 | 169 | for idx,plr in ipairs(players) do | ||
199 | 170 | if (plr.defeated) then | ||
200 | 171 | owned_fields[plr.number] = 0 | ||
201 | 172 | else | ||
202 | 173 | owned_fields[plr.number] = all_plrpoints[plr.number] | ||
203 | 174 | end | ||
204 | 175 | end | ||
205 | 176 | return owned_fields | ||
206 | 177 | end | ||
207 | 178 | |||
208 | 179 | |||
209 | 180 | |||
210 | 181 | -- RST | ||
211 | 150 | -- .. function:: rank_players(all_player_points, plrs) | 182 | -- .. function:: rank_players(all_player_points, plrs) |
212 | 151 | -- | 183 | -- |
213 | 152 | -- Rank the players and teams according to the highest points | 184 | -- Rank the players and teams according to the highest points |
214 | 153 | 185 | ||
215 | === modified file 'data/scripting/win_conditions/wood_gnome.lua' | |||
216 | --- data/scripting/win_conditions/wood_gnome.lua 2019-03-02 08:52:08 +0000 | |||
217 | +++ data/scripting/win_conditions/wood_gnome.lua 2019-03-11 07:15:58 +0000 | |||
218 | @@ -24,6 +24,10 @@ | |||
219 | 24 | return { | 24 | return { |
220 | 25 | name = wc_name, | 25 | name = wc_name, |
221 | 26 | description = wc_desc, | 26 | description = wc_desc, |
222 | 27 | init = function() | ||
223 | 28 | -- Calculate valuable fields | ||
224 | 29 | wl.Game().map:count_terrestrial_fields() | ||
225 | 30 | end, | ||
226 | 27 | func = function() | 31 | func = function() |
227 | 28 | local plrs = wl.Game().players | 32 | local plrs = wl.Game().players |
228 | 29 | local game = wl.Game() | 33 | local game = wl.Game() |
229 | @@ -34,51 +38,21 @@ | |||
230 | 34 | -- set the objective with the game type for all players | 38 | -- set the objective with the game type for all players |
231 | 35 | broadcast_objective("win_condition", wc_descname, wc_desc) | 39 | broadcast_objective("win_condition", wc_descname, wc_desc) |
232 | 36 | 40 | ||
233 | 37 | -- Table of terrestrial fields | ||
234 | 38 | local fields = wl.Game().map.terrestrial_fields | ||
235 | 39 | |||
236 | 40 | -- The function to calculate the current points. | 41 | -- The function to calculate the current points. |
237 | 41 | local _last_time_calculated = -100000 | 42 | local _last_time_calculated = -100000 |
239 | 42 | local _plrpoints = {} | 43 | local playerpoints = {} |
240 | 43 | local function _calc_points() | 44 | local function _calc_points() |
241 | 44 | local game = wl.Game() | ||
242 | 45 | 45 | ||
243 | 46 | if _last_time_calculated > game.time - 5000 then | 46 | if _last_time_calculated > game.time - 5000 then |
273 | 47 | return _plrpoints | 47 | return |
274 | 48 | end | 48 | end |
275 | 49 | 49 | ||
276 | 50 | -- clear out the table. We count afresh. | 50 | playerpoints = count_owned_valuable_fields_for_all_players(plrs, "tree") |
248 | 51 | for k,v in pairs(_plrpoints) do | ||
249 | 52 | _plrpoints[k] = 0 | ||
250 | 53 | end | ||
251 | 54 | |||
252 | 55 | -- Insert all players who are still in the game. | ||
253 | 56 | for idx,plr in ipairs(plrs) do | ||
254 | 57 | _plrpoints[plr.number] = 0 | ||
255 | 58 | end | ||
256 | 59 | |||
257 | 60 | for idf,f in ipairs(fields) do | ||
258 | 61 | -- check if field is owned by a player | ||
259 | 62 | local owner = f.owner | ||
260 | 63 | if owner and not owner.defeated then | ||
261 | 64 | owner = owner.number | ||
262 | 65 | -- check if field has an immovable | ||
263 | 66 | local imm = f.immovable | ||
264 | 67 | if imm then | ||
265 | 68 | -- check if immovable is a tree | ||
266 | 69 | if imm:has_attribute("tree") then | ||
267 | 70 | _plrpoints[owner] = _plrpoints[owner] + 1 | ||
268 | 71 | end | ||
269 | 72 | end | ||
270 | 73 | end | ||
271 | 74 | end | ||
272 | 75 | |||
277 | 76 | _last_time_calculated = game.time | 51 | _last_time_calculated = game.time |
278 | 77 | return _plrpoints | ||
279 | 78 | end | 52 | end |
280 | 79 | 53 | ||
281 | 80 | local function _send_state(remaining_time, plrs, show_popup) | 54 | local function _send_state(remaining_time, plrs, show_popup) |
283 | 81 | local playerpoints = _calc_points() | 55 | _calc_points() |
284 | 82 | local msg = format_remaining_time(remaining_time) .. vspace(8) .. game_status.body | 56 | local msg = format_remaining_time(remaining_time) .. vspace(8) .. game_status.body |
285 | 83 | 57 | ||
286 | 84 | for idx,plr in ipairs(plrs) do | 58 | for idx,plr in ipairs(plrs) do |
287 | @@ -92,7 +66,7 @@ | |||
288 | 92 | end | 66 | end |
289 | 93 | 67 | ||
290 | 94 | local function _game_over(plrs) | 68 | local function _game_over(plrs) |
292 | 95 | local playerpoints = _calc_points() | 69 | _calc_points() |
293 | 96 | local points = {} | 70 | local points = {} |
294 | 97 | for idx,plr in ipairs(plrs) do | 71 | for idx,plr in ipairs(plrs) do |
295 | 98 | points[#points + 1] = { plr, playerpoints[plr.number] } | 72 | points[#points + 1] = { plr, playerpoints[plr.number] } |
296 | @@ -131,8 +105,8 @@ | |||
297 | 131 | name = wc_trees_owned, | 105 | name = wc_trees_owned, |
298 | 132 | pic = "images/wui/stats/genstats_trees.png", | 106 | pic = "images/wui/stats/genstats_trees.png", |
299 | 133 | calculator = function(p) | 107 | calculator = function(p) |
302 | 134 | local pts = _calc_points(p) | 108 | _calc_points(p) |
303 | 135 | return pts[p.number] | 109 | return playerpoints[p.number] or 0 |
304 | 136 | end, | 110 | end, |
305 | 137 | } | 111 | } |
306 | 138 | 112 | ||
307 | 139 | 113 | ||
308 | === modified file 'src/game_io/game_cmd_queue_packet.cc' | |||
309 | --- src/game_io/game_cmd_queue_packet.cc 2019-02-23 11:00:49 +0000 | |||
310 | +++ src/game_io/game_cmd_queue_packet.cc 2019-03-11 07:15:58 +0000 | |||
311 | @@ -98,6 +98,10 @@ | |||
312 | 98 | 98 | ||
313 | 99 | while (!p.empty()) { | 99 | while (!p.empty()) { |
314 | 100 | const CmdQueue::CmdItem& it = p.top(); | 100 | const CmdQueue::CmdItem& it = p.top(); |
315 | 101 | if (it.cmd->duetime() > time) { | ||
316 | 102 | // Time is the primary sorting key, so we can't have any additional commands in this queue for this time | ||
317 | 103 | break; | ||
318 | 104 | } | ||
319 | 101 | if (it.cmd->duetime() == time) { | 105 | if (it.cmd->duetime() == time) { |
320 | 102 | if (upcast(GameLogicCommand, cmd, it.cmd)) { | 106 | if (upcast(GameLogicCommand, cmd, it.cmd)) { |
321 | 103 | // The id (aka command type) | 107 | // The id (aka command type) |
322 | 104 | 108 | ||
323 | === modified file 'src/logic/game.cc' | |||
324 | --- src/logic/game.cc 2019-03-02 10:34:39 +0000 | |||
325 | +++ src/logic/game.cc 2019-03-11 07:15:58 +0000 | |||
326 | @@ -314,10 +314,14 @@ | |||
327 | 314 | 314 | ||
328 | 315 | // Check for win_conditions | 315 | // Check for win_conditions |
329 | 316 | if (!settings.scenario) { | 316 | if (!settings.scenario) { |
330 | 317 | loader_ui->step(_("Initializing game…")); | ||
331 | 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)); |
332 | 318 | get_ibase()->log_message(_("Initializing game…")); | ||
333 | 319 | table->do_not_warn_about_unaccessed_keys(); | 319 | table->do_not_warn_about_unaccessed_keys(); |
334 | 320 | win_condition_displayname_ = table->get_string("name"); | 320 | win_condition_displayname_ = table->get_string("name"); |
335 | 321 | if (table->has_key<std::string>("init")) { | ||
336 | 322 | std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("init"); | ||
337 | 323 | cr->resume(); | ||
338 | 324 | } | ||
339 | 321 | std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func"); | 325 | std::unique_ptr<LuaCoroutine> cr = table->get_coroutine("func"); |
340 | 322 | enqueue_command(new CmdLuaCoroutine(get_gametime() + 100, std::move(cr))); | 326 | enqueue_command(new CmdLuaCoroutine(get_gametime() + 100, std::move(cr))); |
341 | 323 | } else { | 327 | } else { |
342 | 324 | 328 | ||
343 | === modified file 'src/logic/map.cc' | |||
344 | --- src/logic/map.cc 2019-03-02 09:31:11 +0000 | |||
345 | +++ src/logic/map.cc 2019-03-11 07:15:58 +0000 | |||
346 | @@ -257,8 +257,11 @@ | |||
347 | 257 | } | 257 | } |
348 | 258 | } | 258 | } |
349 | 259 | 259 | ||
352 | 260 | std::set<FCoords> Map::calculate_all_conquerable_fields() const { | 260 | size_t Map::count_all_conquerable_fields() { |
353 | 261 | std::set<FCoords> result; | 261 | if (!valuable_fields_.empty()) { |
354 | 262 | // Already calculated | ||
355 | 263 | return valuable_fields_.size(); | ||
356 | 264 | } | ||
357 | 262 | 265 | ||
358 | 263 | std::set<FCoords> coords_to_check; | 266 | std::set<FCoords> coords_to_check; |
359 | 264 | 267 | ||
360 | @@ -267,17 +270,17 @@ | |||
361 | 267 | 270 | ||
362 | 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, |
363 | 269 | // initialized with the given radius around the coordinates | 272 | // initialized with the given radius around the coordinates |
365 | 270 | const auto walk_starting_coords = [this, &result, &coords_to_check]( | 273 | const auto walk_starting_coords = [this, &coords_to_check]( |
366 | 271 | const Coords& coords, int radius) { | 274 | const Coords& coords, int radius) { |
367 | 272 | FCoords fcoords = get_fcoords(coords); | 275 | FCoords fcoords = get_fcoords(coords); |
368 | 273 | 276 | ||
369 | 274 | // We already have these coordinates | 277 | // We already have these coordinates |
371 | 275 | if (result.count(fcoords) == 1) { | 278 | if (valuable_fields_.count(fcoords) == 1) { |
372 | 276 | return; | 279 | return; |
373 | 277 | } | 280 | } |
374 | 278 | 281 | ||
375 | 279 | // Add starting field | 282 | // Add starting field |
377 | 280 | result.insert(fcoords); | 283 | valuable_fields_.insert(fcoords); |
378 | 281 | 284 | ||
379 | 282 | // Add outer land coordinates around the starting field for the given radius | 285 | // Add outer land coordinates around the starting field for the given radius |
380 | 283 | std::unique_ptr<Widelands::HollowArea<>> hollow_area( | 286 | std::unique_ptr<Widelands::HollowArea<>> hollow_area( |
381 | @@ -317,8 +320,8 @@ | |||
382 | 317 | 320 | ||
383 | 318 | // We do the caps check first, because the comparison is faster than the container | 321 | // We do the caps check first, because the comparison is faster than the container |
384 | 319 | // check | 322 | // check |
387 | 320 | if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (result.count(fcoords) == 0)) { | 323 | if ((fcoords.field->maxcaps() & MOVECAPS_WALK) && (valuable_fields_.count(fcoords) == 0)) { |
388 | 321 | result.insert(fcoords); | 324 | valuable_fields_.insert(fcoords); |
389 | 322 | coords_to_check.insert(fcoords); | 325 | coords_to_check.insert(fcoords); |
390 | 323 | } | 326 | } |
391 | 324 | } while (map_region->advance(*this)); | 327 | } while (map_region->advance(*this)); |
392 | @@ -342,23 +345,44 @@ | |||
393 | 342 | } | 345 | } |
394 | 343 | } | 346 | } |
395 | 344 | 347 | ||
398 | 345 | log("%" PRIuS " found ... ", result.size()); | 348 | log("%" PRIuS " found ... ", valuable_fields_.size()); |
399 | 346 | return result; | 349 | return valuable_fields_.size(); |
400 | 347 | } | 350 | } |
401 | 348 | 351 | ||
403 | 349 | std::set<FCoords> Map::calculate_all_fields_excluding_caps(NodeCaps caps) const { | 352 | size_t Map::count_all_fields_excluding_caps(NodeCaps caps) { |
404 | 353 | if (!valuable_fields_.empty()) { | ||
405 | 354 | // Already calculated | ||
406 | 355 | return valuable_fields_.size(); | ||
407 | 356 | } | ||
408 | 357 | |||
409 | 350 | log("Collecting valuable fields ... "); | 358 | log("Collecting valuable fields ... "); |
410 | 351 | ScopedTimer timer("took %ums"); | 359 | ScopedTimer timer("took %ums"); |
411 | 352 | 360 | ||
412 | 353 | std::set<FCoords> result; | ||
413 | 354 | for (MapIndex i = 0; i < max_index(); ++i) { | 361 | for (MapIndex i = 0; i < max_index(); ++i) { |
414 | 355 | Field& field = fields_[i]; | 362 | Field& field = fields_[i]; |
415 | 356 | if (!(field.nodecaps() & caps)) { | 363 | if (!(field.nodecaps() & caps)) { |
421 | 357 | result.insert(get_fcoords(field)); | 364 | valuable_fields_.insert(get_fcoords(field)); |
422 | 358 | } | 365 | } |
423 | 359 | } | 366 | } |
424 | 360 | 367 | ||
425 | 361 | log("%" PRIuS " found ... ", result.size()); | 368 | log("%" PRIuS " found ... ", valuable_fields_.size()); |
426 | 369 | return valuable_fields_.size(); | ||
427 | 370 | } | ||
428 | 371 | |||
429 | 372 | std::map<PlayerNumber, size_t> Map::count_owned_valuable_fields(const std::string& immovable_attribute) const { | ||
430 | 373 | std::map<PlayerNumber, size_t> result; | ||
431 | 374 | const bool use_attribute = !immovable_attribute.empty(); | ||
432 | 375 | const uint32_t attribute_id = use_attribute ? MapObjectDescr::get_attribute_id(immovable_attribute) : 0U; | ||
433 | 376 | for (const FCoords& fcoords : valuable_fields_) { | ||
434 | 377 | if (use_attribute) { | ||
435 | 378 | const BaseImmovable* imm = fcoords.field->get_immovable(); | ||
436 | 379 | if (imm != nullptr && imm->has_attribute(attribute_id)) { | ||
437 | 380 | result[fcoords.field->get_owned_by()] += 1; | ||
438 | 381 | } | ||
439 | 382 | } else { | ||
440 | 383 | result[fcoords.field->get_owned_by()] += 1; | ||
441 | 384 | } | ||
442 | 385 | } | ||
443 | 362 | return result; | 386 | return result; |
444 | 363 | } | 387 | } |
445 | 364 | 388 | ||
446 | 365 | 389 | ||
447 | === modified file 'src/logic/map.h' | |||
448 | --- src/logic/map.h 2019-02-26 05:56:01 +0000 | |||
449 | +++ src/logic/map.h 2019-03-11 07:15:58 +0000 | |||
450 | @@ -165,11 +165,25 @@ | |||
451 | 165 | void recalc_whole_map(const World& world); | 165 | void recalc_whole_map(const World& world); |
452 | 166 | void recalc_for_field_area(const World& world, Area<FCoords>); | 166 | void recalc_for_field_area(const World& world, Area<FCoords>); |
453 | 167 | 167 | ||
459 | 168 | /// Calculates and returns a list of the fields that could be conquered by a player throughout a | 168 | /** |
460 | 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. |
461 | 170 | std::set<FCoords> calculate_all_conquerable_fields() const; | 170 | * Useful for territorial win conditions. |
462 | 171 | /// Calculates and returns a list of the fields that do not have the given caps. | 171 | * Returns the amount of valuable fields. |
463 | 172 | std::set<FCoords> calculate_all_fields_excluding_caps(NodeCaps caps) const; | 172 | */ |
464 | 173 | size_t count_all_conquerable_fields(); | ||
465 | 174 | |||
466 | 175 | /** | ||
467 | 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. | ||
468 | 177 | * Useful for win conditions. | ||
469 | 178 | * Returns the amount of valuable fields. | ||
470 | 179 | */ | ||
471 | 180 | size_t count_all_fields_excluding_caps(NodeCaps caps); | ||
472 | 181 | |||
473 | 182 | /** | ||
474 | 183 | * Counts the valuable fields that are owned by each player. Only players that currently own a field are added. | ||
475 | 184 | * Returns a map of <player number, number of owned fields>. | ||
476 | 185 | */ | ||
477 | 186 | std::map<PlayerNumber, size_t> count_owned_valuable_fields(const std::string& immovable_attribute) const; | ||
478 | 173 | 187 | ||
479 | 174 | /*** | 188 | /*** |
480 | 175 | * Ensures that resources match their adjacent terrains. | 189 | * Ensures that resources match their adjacent terrains. |
481 | @@ -438,6 +452,10 @@ | |||
482 | 438 | return &objectives_; | 452 | return &objectives_; |
483 | 439 | } | 453 | } |
484 | 440 | 454 | ||
485 | 455 | std::set<FCoords>* mutable_valuable_fields() { | ||
486 | 456 | return &valuable_fields_; | ||
487 | 457 | } | ||
488 | 458 | |||
489 | 441 | /// Returns the military influence on a location from an area. | 459 | /// Returns the military influence on a location from an area. |
490 | 442 | MilitaryInfluence calc_influence(Coords, Area<>) const; | 460 | MilitaryInfluence calc_influence(Coords, Area<>) const; |
491 | 443 | 461 | ||
492 | @@ -535,6 +553,9 @@ | |||
493 | 535 | 553 | ||
494 | 536 | Objectives objectives_; | 554 | Objectives objectives_; |
495 | 537 | 555 | ||
496 | 556 | // Fields that are important for the player to own in a win condition | ||
497 | 557 | std::set<FCoords> valuable_fields_; | ||
498 | 558 | |||
499 | 538 | MapVersion map_version_; | 559 | MapVersion map_version_; |
500 | 539 | }; | 560 | }; |
501 | 540 | 561 | ||
502 | 541 | 562 | ||
503 | === modified file 'src/logic/player.cc' | |||
504 | --- src/logic/player.cc 2019-02-23 11:00:49 +0000 | |||
505 | +++ src/logic/player.cc 2019-03-11 07:15:58 +0000 | |||
506 | @@ -253,6 +253,15 @@ | |||
507 | 253 | return &other != this && (!team_number_ || team_number_ != other.team_number_); | 253 | return &other != this && (!team_number_ || team_number_ != other.team_number_); |
508 | 254 | } | 254 | } |
509 | 255 | 255 | ||
510 | 256 | bool Player::is_defeated() const { | ||
511 | 257 | for (const auto& economy : economies()) { | ||
512 | 258 | if (!economy.second->warehouses().empty()) { | ||
513 | 259 | return false; | ||
514 | 260 | } | ||
515 | 261 | } | ||
516 | 262 | return true; | ||
517 | 263 | } | ||
518 | 264 | |||
519 | 256 | void Player::AiPersistentState::initialize() { | 265 | void Player::AiPersistentState::initialize() { |
520 | 257 | colony_scan_area = kColonyScanStartArea; | 266 | colony_scan_area = kColonyScanStartArea; |
521 | 258 | trees_around_cutters = 0; | 267 | trees_around_cutters = 0; |
522 | 259 | 268 | ||
523 | === modified file 'src/logic/player.h' | |||
524 | --- src/logic/player.h 2019-02-23 11:00:49 +0000 | |||
525 | +++ src/logic/player.h 2019-03-11 07:15:58 +0000 | |||
526 | @@ -148,6 +148,11 @@ | |||
527 | 148 | 148 | ||
528 | 149 | bool is_hostile(const Player&) const; | 149 | bool is_hostile(const Player&) const; |
529 | 150 | 150 | ||
530 | 151 | /** | ||
531 | 152 | * Returns whether the player lost the last warehouse. | ||
532 | 153 | */ | ||
533 | 154 | bool is_defeated() const; | ||
534 | 155 | |||
535 | 151 | // For cheating | 156 | // For cheating |
536 | 152 | void set_see_all(bool const t) { | 157 | void set_see_all(bool const t) { |
537 | 153 | see_all_ = t; | 158 | see_all_ = t; |
538 | 154 | 159 | ||
539 | === modified file 'src/logic/playercommand.cc' | |||
540 | --- src/logic/playercommand.cc 2019-02-23 11:00:49 +0000 | |||
541 | +++ src/logic/playercommand.cc 2019-03-11 07:15:58 +0000 | |||
542 | @@ -450,14 +450,17 @@ | |||
543 | 450 | ser.unsigned_32(serial); | 450 | ser.unsigned_32(serial); |
544 | 451 | } | 451 | } |
545 | 452 | 452 | ||
547 | 453 | constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 1; | 453 | constexpr uint16_t kCurrentPacketVersionCmdFlagAction = 2; |
548 | 454 | 454 | ||
549 | 455 | void CmdFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { | 455 | void CmdFlagAction::read(FileRead& fr, EditorGameBase& egbase, MapObjectLoader& mol) { |
550 | 456 | try { | 456 | try { |
551 | 457 | const uint16_t packet_version = fr.unsigned_16(); | 457 | const uint16_t packet_version = fr.unsigned_16(); |
553 | 458 | if (packet_version == kCurrentPacketVersionCmdFlagAction) { | 458 | // TODO(GunChleoc): Savegame compatibility, remove after Build 21 |
554 | 459 | if (packet_version >= 1 && packet_version <= kCurrentPacketVersionCmdFlagAction) { | ||
555 | 459 | PlayerCommand::read(fr, egbase, mol); | 460 | PlayerCommand::read(fr, egbase, mol); |
557 | 460 | fr.unsigned_8(); | 461 | if (packet_version == 1) { |
558 | 462 | fr.unsigned_8(); | ||
559 | 463 | } | ||
560 | 461 | serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol); | 464 | serial = get_object_serial_or_zero<Flag>(fr.unsigned_32(), mol); |
561 | 462 | } else { | 465 | } else { |
562 | 463 | throw UnhandledVersionError( | 466 | throw UnhandledVersionError( |
563 | @@ -472,9 +475,6 @@ | |||
564 | 472 | fw.unsigned_16(kCurrentPacketVersionCmdFlagAction); | 475 | fw.unsigned_16(kCurrentPacketVersionCmdFlagAction); |
565 | 473 | // Write base classes | 476 | // Write base classes |
566 | 474 | PlayerCommand::write(fw, egbase, mos); | 477 | PlayerCommand::write(fw, egbase, mos); |
567 | 475 | // Now action | ||
568 | 476 | fw.unsigned_8(0); | ||
569 | 477 | |||
570 | 478 | // Now serial | 478 | // Now serial |
571 | 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))); |
572 | 480 | } | 480 | } |
573 | 481 | 481 | ||
574 | === modified file 'src/map_io/CMakeLists.txt' | |||
575 | --- src/map_io/CMakeLists.txt 2017-11-20 13:50:51 +0000 | |||
576 | +++ src/map_io/CMakeLists.txt 2019-03-11 07:15:58 +0000 | |||
577 | @@ -87,6 +87,8 @@ | |||
578 | 87 | map_terrain_packet.h | 87 | map_terrain_packet.h |
579 | 88 | map_version_packet.cc | 88 | map_version_packet.cc |
580 | 89 | map_version_packet.h | 89 | map_version_packet.h |
581 | 90 | map_wincondition_packet.cc | ||
582 | 91 | map_wincondition_packet.h | ||
583 | 90 | DEPENDS | 92 | DEPENDS |
584 | 91 | base_exceptions | 93 | base_exceptions |
585 | 92 | base_log | 94 | base_log |
586 | 93 | 95 | ||
587 | === modified file 'src/map_io/map_saver.cc' | |||
588 | --- src/map_io/map_saver.cc 2019-02-23 11:00:49 +0000 | |||
589 | +++ src/map_io/map_saver.cc 2019-03-11 07:15:58 +0000 | |||
590 | @@ -58,6 +58,7 @@ | |||
591 | 58 | #include "map_io/map_scripting_packet.h" | 58 | #include "map_io/map_scripting_packet.h" |
592 | 59 | #include "map_io/map_terrain_packet.h" | 59 | #include "map_io/map_terrain_packet.h" |
593 | 60 | #include "map_io/map_version_packet.h" | 60 | #include "map_io/map_version_packet.h" |
594 | 61 | #include "map_io/map_wincondition_packet.h" | ||
595 | 61 | 62 | ||
596 | 62 | namespace Widelands { | 63 | namespace Widelands { |
597 | 63 | 64 | ||
598 | @@ -220,6 +221,14 @@ | |||
599 | 220 | log("took %ums\n ", timer.ms_since_last_query()); | 221 | log("took %ums\n ", timer.ms_since_last_query()); |
600 | 221 | 222 | ||
601 | 222 | if (is_game) { | 223 | if (is_game) { |
602 | 224 | // Map data used by win conditions | ||
603 | 225 | log("Writing Wincondition Data ... "); | ||
604 | 226 | { | ||
605 | 227 | MapWinconditionPacket p; | ||
606 | 228 | p.write(fs_, *egbase_.mutable_map(), *mos_); | ||
607 | 229 | } | ||
608 | 230 | log("took %ums\n ", timer.ms_since_last_query()); | ||
609 | 231 | |||
610 | 223 | // DATA PACKETS | 232 | // DATA PACKETS |
611 | 224 | if (mos_->get_nr_flags()) { | 233 | if (mos_->get_nr_flags()) { |
612 | 225 | log("Writing Flagdata Data ... "); | 234 | log("Writing Flagdata Data ... "); |
613 | 226 | 235 | ||
614 | === added file 'src/map_io/map_wincondition_packet.cc' | |||
615 | --- src/map_io/map_wincondition_packet.cc 1970-01-01 00:00:00 +0000 | |||
616 | +++ src/map_io/map_wincondition_packet.cc 2019-03-11 07:15:58 +0000 | |||
617 | @@ -0,0 +1,75 @@ | |||
618 | 1 | /* | ||
619 | 2 | * Copyright (C) 2019 by the Widelands Development Team | ||
620 | 3 | * | ||
621 | 4 | * This program is free software; you can redistribute it and/or | ||
622 | 5 | * modify it under the terms of the GNU General Public License | ||
623 | 6 | * as published by the Free Software Foundation; either version 2 | ||
624 | 7 | * of the License, or (at your option) any later version. | ||
625 | 8 | * | ||
626 | 9 | * This program is distributed in the hope that it will be useful, | ||
627 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
628 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
629 | 12 | * GNU General Public License for more details. | ||
630 | 13 | * | ||
631 | 14 | * You should have received a copy of the GNU General Public License | ||
632 | 15 | * along with this program; if not, write to the Free Software | ||
633 | 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
634 | 17 | * | ||
635 | 18 | */ | ||
636 | 19 | |||
637 | 20 | #include "map_io/map_wincondition_packet.h" | ||
638 | 21 | |||
639 | 22 | #include "io/fileread.h" | ||
640 | 23 | #include "io/filewrite.h" | ||
641 | 24 | #include "logic/game_data_error.h" | ||
642 | 25 | |||
643 | 26 | namespace Widelands { | ||
644 | 27 | |||
645 | 28 | constexpr uint8_t kCurrentPacketVersion = 1; | ||
646 | 29 | |||
647 | 30 | void MapWinconditionPacket::read(FileSystem& fs, Map& map, MapObjectLoader&) { | ||
648 | 31 | if (!fs.file_exists("binary/wincondition")) { | ||
649 | 32 | // This can be empty when win conditions don't need any special map data | ||
650 | 33 | return; | ||
651 | 34 | } | ||
652 | 35 | |||
653 | 36 | try { | ||
654 | 37 | FileRead fr; | ||
655 | 38 | fr.open(fs, "binary/wincondition"); | ||
656 | 39 | |||
657 | 40 | const uint8_t packet_version = fr.unsigned_8(); | ||
658 | 41 | if (packet_version == kCurrentPacketVersion) { | ||
659 | 42 | const size_t no_of_fields = fr.unsigned_32(); | ||
660 | 43 | std::set<FCoords>* fields = map.mutable_valuable_fields(); | ||
661 | 44 | fields->clear(); | ||
662 | 45 | |||
663 | 46 | for (size_t i = 0; i < no_of_fields; ++i) { | ||
664 | 47 | const int32_t x = fr.signed_16(); | ||
665 | 48 | const int32_t y = fr.signed_16(); | ||
666 | 49 | fields->insert(map.get_fcoords(Coords(x, y))); | ||
667 | 50 | } | ||
668 | 51 | } else { | ||
669 | 52 | throw UnhandledVersionError("MapWinconditionPacket", packet_version, kCurrentPacketVersion); | ||
670 | 53 | } | ||
671 | 54 | } catch (const WException& e) { | ||
672 | 55 | throw GameDataError("win condition data: %s", e.what()); | ||
673 | 56 | } | ||
674 | 57 | } | ||
675 | 58 | |||
676 | 59 | void MapWinconditionPacket::write(FileSystem& fs, Map& map, MapObjectSaver&) { | ||
677 | 60 | // We only write this packet if we have something interesting to write to it. | ||
678 | 61 | std::set<FCoords>& fields = *map.mutable_valuable_fields(); | ||
679 | 62 | if (!fields.empty()) { | ||
680 | 63 | FileWrite fw; | ||
681 | 64 | fw.unsigned_8(kCurrentPacketVersion); | ||
682 | 65 | |||
683 | 66 | fw.unsigned_32(fields.size()); | ||
684 | 67 | for (const auto& field : fields) { | ||
685 | 68 | fw.signed_16(field.x); | ||
686 | 69 | fw.signed_16(field.y); | ||
687 | 70 | } | ||
688 | 71 | |||
689 | 72 | fw.write(fs, "binary/wincondition"); | ||
690 | 73 | } | ||
691 | 74 | } | ||
692 | 75 | } // namespace Widelands | ||
693 | 0 | 76 | ||
694 | === added file 'src/map_io/map_wincondition_packet.h' | |||
695 | --- src/map_io/map_wincondition_packet.h 1970-01-01 00:00:00 +0000 | |||
696 | +++ src/map_io/map_wincondition_packet.h 2019-03-11 07:15:58 +0000 | |||
697 | @@ -0,0 +1,34 @@ | |||
698 | 1 | /* | ||
699 | 2 | * Copyright (C) 2019 by the Widelands Development Team | ||
700 | 3 | * | ||
701 | 4 | * This program is free software; you can redistribute it and/or | ||
702 | 5 | * modify it under the terms of the GNU General Public License | ||
703 | 6 | * as published by the Free Software Foundation; either version 2 | ||
704 | 7 | * of the License, or (at your option) any later version. | ||
705 | 8 | * | ||
706 | 9 | * This program is distributed in the hope that it will be useful, | ||
707 | 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
708 | 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
709 | 12 | * GNU General Public License for more details. | ||
710 | 13 | * | ||
711 | 14 | * You should have received a copy of the GNU General Public License | ||
712 | 15 | * along with this program; if not, write to the Free Software | ||
713 | 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
714 | 17 | * | ||
715 | 18 | */ | ||
716 | 19 | |||
717 | 20 | #ifndef WL_MAP_IO_MAP_WINCONDITION_PACKET_H | ||
718 | 21 | #define WL_MAP_IO_MAP_WINCONDITION_PACKET_H | ||
719 | 22 | |||
720 | 23 | #include "logic/map.h" | ||
721 | 24 | |||
722 | 25 | namespace Widelands { | ||
723 | 26 | |||
724 | 27 | /// The anything needed by win conditions | ||
725 | 28 | struct MapWinconditionPacket { | ||
726 | 29 | void read(FileSystem&, Map& map, MapObjectLoader&); | ||
727 | 30 | void write(FileSystem&, Map& map, MapObjectSaver&); | ||
728 | 31 | }; | ||
729 | 32 | } // namespace Widelands | ||
730 | 33 | |||
731 | 34 | #endif // end of include guard: WL_MAP_IO_MAP_WINCONDITION_PACKET_H | ||
732 | 0 | 35 | ||
733 | === modified file 'src/map_io/widelands_map_loader.cc' | |||
734 | --- src/map_io/widelands_map_loader.cc 2019-02-23 11:00:49 +0000 | |||
735 | +++ src/map_io/widelands_map_loader.cc 2019-03-11 07:15:58 +0000 | |||
736 | @@ -55,6 +55,7 @@ | |||
737 | 55 | #include "map_io/map_scripting_packet.h" | 55 | #include "map_io/map_scripting_packet.h" |
738 | 56 | #include "map_io/map_terrain_packet.h" | 56 | #include "map_io/map_terrain_packet.h" |
739 | 57 | #include "map_io/map_version_packet.h" | 57 | #include "map_io/map_version_packet.h" |
740 | 58 | #include "map_io/map_wincondition_packet.h" | ||
741 | 58 | #include "map_io/tribes_legacy_lookup_table.h" | 59 | #include "map_io/tribes_legacy_lookup_table.h" |
742 | 59 | #include "map_io/world_legacy_lookup_table.h" | 60 | #include "map_io/world_legacy_lookup_table.h" |
743 | 60 | 61 | ||
744 | @@ -310,6 +311,14 @@ | |||
745 | 310 | } | 311 | } |
746 | 311 | log("took %ums\n ", timer.ms_since_last_query()); | 312 | log("took %ums\n ", timer.ms_since_last_query()); |
747 | 312 | 313 | ||
748 | 314 | // Map data used by win conditions. | ||
749 | 315 | log("Reading Wincondition Data ... "); | ||
750 | 316 | { | ||
751 | 317 | MapWinconditionPacket p; | ||
752 | 318 | p.read(*fs_, *egbase.mutable_map(), *mol_); | ||
753 | 319 | } | ||
754 | 320 | log("took %ums\n ", timer.ms_since_last_query()); | ||
755 | 321 | |||
756 | 313 | // Objectives. They are not needed in the Editor, since they are fully | 322 | // Objectives. They are not needed in the Editor, since they are fully |
757 | 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, |
758 | 315 | // since they will be only be set after it has started. | 324 | // since they will be only be set after it has started. |
759 | 316 | 325 | ||
760 | === modified file 'src/scripting/lua_game.cc' | |||
761 | --- src/scripting/lua_game.cc 2019-02-23 11:00:49 +0000 | |||
762 | +++ src/scripting/lua_game.cc 2019-03-11 07:15:58 +0000 | |||
763 | @@ -173,17 +173,7 @@ | |||
764 | 173 | (RO) :const:`true` if this player was defeated, :const:`false` otherwise | 173 | (RO) :const:`true` if this player was defeated, :const:`false` otherwise |
765 | 174 | */ | 174 | */ |
766 | 175 | int LuaPlayer::get_defeated(lua_State* L) { | 175 | int LuaPlayer::get_defeated(lua_State* L) { |
778 | 176 | Player& p = get(L, get_egbase(L)); | 176 | lua_pushboolean(L, get(L, get_egbase(L)).is_defeated()); |
768 | 177 | bool is_defeated = true; | ||
769 | 178 | |||
770 | 179 | for (const auto& economy : p.economies()) { | ||
771 | 180 | if (!economy.second->warehouses().empty()) { | ||
772 | 181 | is_defeated = false; | ||
773 | 182 | break; | ||
774 | 183 | } | ||
775 | 184 | } | ||
776 | 185 | |||
777 | 186 | lua_pushboolean(L, is_defeated); | ||
779 | 187 | return 1; | 177 | return 1; |
780 | 188 | } | 178 | } |
781 | 189 | 179 | ||
782 | 190 | 180 | ||
783 | === modified file 'src/scripting/lua_map.cc' | |||
784 | --- src/scripting/lua_map.cc 2019-02-26 12:02:52 +0000 | |||
785 | +++ src/scripting/lua_map.cc 2019-03-11 07:15:58 +0000 | |||
786 | @@ -1186,9 +1186,11 @@ | |||
787 | 1186 | */ | 1186 | */ |
788 | 1187 | const char LuaMap::className[] = "Map"; | 1187 | const char LuaMap::className[] = "Map"; |
789 | 1188 | const MethodType<LuaMap> LuaMap::Methods[] = { | 1188 | const MethodType<LuaMap> LuaMap::Methods[] = { |
790 | 1189 | METHOD(LuaMap, count_conquerable_fields), METHOD(LuaMap, count_terrestrial_fields), | ||
791 | 1190 | METHOD(LuaMap, count_owned_valuable_fields), | ||
792 | 1189 | METHOD(LuaMap, place_immovable), METHOD(LuaMap, get_field), | 1191 | METHOD(LuaMap, place_immovable), METHOD(LuaMap, get_field), |
793 | 1190 | METHOD(LuaMap, recalculate), METHOD(LuaMap, recalculate_seafaring), | 1192 | METHOD(LuaMap, recalculate), METHOD(LuaMap, recalculate_seafaring), |
795 | 1191 | METHOD(LuaMap, set_port_space), {nullptr, nullptr}, | 1193 | METHOD(LuaMap, set_port_space), {nullptr, nullptr}, |
796 | 1192 | }; | 1194 | }; |
797 | 1193 | const PropertyType<LuaMap> LuaMap::Properties[] = { | 1195 | const PropertyType<LuaMap> LuaMap::Properties[] = { |
798 | 1194 | PROP_RO(LuaMap, allows_seafaring), | 1196 | PROP_RO(LuaMap, allows_seafaring), |
799 | @@ -1197,8 +1199,6 @@ | |||
800 | 1197 | PROP_RO(LuaMap, width), | 1199 | PROP_RO(LuaMap, width), |
801 | 1198 | PROP_RO(LuaMap, height), | 1200 | PROP_RO(LuaMap, height), |
802 | 1199 | PROP_RO(LuaMap, player_slots), | 1201 | PROP_RO(LuaMap, player_slots), |
803 | 1200 | PROP_RO(LuaMap, conquerable_fields), | ||
804 | 1201 | PROP_RO(LuaMap, terrestrial_fields), | ||
805 | 1202 | {nullptr, nullptr, nullptr}, | 1202 | {nullptr, nullptr, nullptr}, |
806 | 1203 | }; | 1203 | }; |
807 | 1204 | 1204 | ||
808 | @@ -1299,47 +1299,69 @@ | |||
809 | 1299 | return 1; | 1299 | return 1; |
810 | 1300 | } | 1300 | } |
811 | 1301 | 1301 | ||
812 | 1302 | /* RST | ||
813 | 1303 | .. attribute:: conquerable_fields | ||
814 | 1304 | |||
815 | 1305 | (RO) Calculates and returns all reachable fields that a player could build on. | ||
816 | 1306 | |||
817 | 1307 | **Note:** This function is expensive, so call it seldom. | ||
818 | 1308 | */ | ||
819 | 1309 | int LuaMap::get_conquerable_fields(lua_State* L) { | ||
820 | 1310 | lua_newtable(L); | ||
821 | 1311 | int counter = 0; | ||
822 | 1312 | for (const Widelands::FCoords& fcoords : | ||
823 | 1313 | get_egbase(L).map().calculate_all_conquerable_fields()) { | ||
824 | 1314 | lua_pushinteger(L, ++counter); | ||
825 | 1315 | to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords)); | ||
826 | 1316 | lua_settable(L, -3); | ||
827 | 1317 | } | ||
828 | 1318 | return 1; | ||
829 | 1319 | } | ||
830 | 1320 | |||
831 | 1321 | /* RST | ||
832 | 1322 | .. attribute:: terrestrial_fields | ||
833 | 1323 | |||
834 | 1324 | (RO) Calculates and returns all fields that are not swimmable. | ||
835 | 1325 | */ | ||
836 | 1326 | int LuaMap::get_terrestrial_fields(lua_State* L) { | ||
837 | 1327 | lua_newtable(L); | ||
838 | 1328 | int counter = 0; | ||
839 | 1329 | for (const Widelands::FCoords& fcoords : | ||
840 | 1330 | get_egbase(L).map().calculate_all_fields_excluding_caps(MOVECAPS_SWIM)) { | ||
841 | 1331 | lua_pushinteger(L, ++counter); | ||
842 | 1332 | to_lua<LuaMaps::LuaField>(L, new LuaMaps::LuaField(fcoords)); | ||
843 | 1333 | lua_settable(L, -3); | ||
844 | 1334 | } | ||
845 | 1335 | return 1; | ||
846 | 1336 | } | ||
847 | 1337 | 1302 | ||
848 | 1338 | /* | 1303 | /* |
849 | 1339 | ========================================================== | 1304 | ========================================================== |
850 | 1340 | LUA METHODS | 1305 | LUA METHODS |
851 | 1341 | ========================================================== | 1306 | ========================================================== |
852 | 1342 | */ | 1307 | */ |
853 | 1308 | |||
854 | 1309 | /* RST | ||
855 | 1310 | .. method:: count_conquerable_fields() | ||
856 | 1311 | |||
857 | 1312 | (RO) Counts all reachable fields that a player could build on. | ||
858 | 1313 | |||
859 | 1314 | **Note:** The fields are only calculated afresh when this is called for the first time. | ||
860 | 1315 | |||
861 | 1316 | :returns: An integer with the amount of fields. | ||
862 | 1317 | */ | ||
863 | 1318 | int LuaMap::count_conquerable_fields(lua_State* L) { | ||
864 | 1319 | lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_conquerable_fields()); | ||
865 | 1320 | return 1; | ||
866 | 1321 | } | ||
867 | 1322 | |||
868 | 1323 | /* RST | ||
869 | 1324 | .. method:: count_terrestrial_fields() | ||
870 | 1325 | |||
871 | 1326 | (RO) Counts all fields that are not swimmable. | ||
872 | 1327 | |||
873 | 1328 | **Note:** The fields are only calculated afresh when this is called for the first time. | ||
874 | 1329 | |||
875 | 1330 | :returns: An integer with the amount of fields. | ||
876 | 1331 | */ | ||
877 | 1332 | int LuaMap::count_terrestrial_fields(lua_State* L) { | ||
878 | 1333 | lua_pushinteger(L, get_egbase(L).mutable_map()->count_all_fields_excluding_caps(MOVECAPS_SWIM)); | ||
879 | 1334 | return 1; | ||
880 | 1335 | } | ||
881 | 1336 | |||
882 | 1337 | |||
883 | 1338 | /* RST | ||
884 | 1339 | .. method:: count_owned_valuable_fields([immovable_attribute]) | ||
885 | 1340 | |||
886 | 1341 | (RO) Counts the number of owned valuable fields for all players. | ||
887 | 1342 | |||
888 | 1343 | :arg name: *Optional*. If this is set, only count fields that have an | ||
889 | 1344 | immovable with the given atttribute. | ||
890 | 1345 | :type name: :class:`string` | ||
891 | 1346 | |||
892 | 1347 | :returns: A table mapping player numbers to their number of owned fields. | ||
893 | 1348 | */ | ||
894 | 1349 | int LuaMap::count_owned_valuable_fields(lua_State* L) { | ||
895 | 1350 | if (lua_gettop(L) > 2) { | ||
896 | 1351 | report_error(L, "Does not take more than one argument."); | ||
897 | 1352 | } | ||
898 | 1353 | const std::string attribute = lua_gettop(L) == 2 ? luaL_checkstring(L, -1) : ""; | ||
899 | 1354 | |||
900 | 1355 | lua_newtable(L); | ||
901 | 1356 | for (const auto& fieldinfo : | ||
902 | 1357 | get_egbase(L).map().count_owned_valuable_fields(attribute)) { | ||
903 | 1358 | lua_pushinteger(L, fieldinfo.first); | ||
904 | 1359 | lua_pushinteger(L, fieldinfo.second); | ||
905 | 1360 | lua_settable(L, -3); | ||
906 | 1361 | } | ||
907 | 1362 | return 1; | ||
908 | 1363 | } | ||
909 | 1364 | |||
910 | 1343 | /* RST | 1365 | /* RST |
911 | 1344 | .. method:: place_immovable(name, field, from_where) | 1366 | .. method:: place_immovable(name, field, from_where) |
912 | 1345 | 1367 | ||
913 | 1346 | 1368 | ||
914 | === modified file 'src/scripting/lua_map.h' | |||
915 | --- src/scripting/lua_map.h 2019-02-24 22:50:04 +0000 | |||
916 | +++ src/scripting/lua_map.h 2019-03-11 07:15:58 +0000 | |||
917 | @@ -92,12 +92,13 @@ | |||
918 | 92 | int get_width(lua_State*); | 92 | int get_width(lua_State*); |
919 | 93 | int get_height(lua_State*); | 93 | int get_height(lua_State*); |
920 | 94 | int get_player_slots(lua_State*); | 94 | int get_player_slots(lua_State*); |
921 | 95 | int get_conquerable_fields(lua_State*); | ||
922 | 96 | int get_terrestrial_fields(lua_State*); | ||
923 | 97 | 95 | ||
924 | 98 | /* | 96 | /* |
925 | 99 | * Lua methods | 97 | * Lua methods |
926 | 100 | */ | 98 | */ |
927 | 99 | int count_conquerable_fields(lua_State*); | ||
928 | 100 | int count_terrestrial_fields(lua_State*); | ||
929 | 101 | int count_owned_valuable_fields(lua_State*); | ||
930 | 101 | int place_immovable(lua_State*); | 102 | int place_immovable(lua_State*); |
931 | 102 | int get_field(lua_State*); | 103 | int get_field(lua_State*); |
932 | 103 | int recalculate(lua_State*); | 104 | int recalculate(lua_State*); |
933 | 104 | 105 | ||
934 | === modified file 'test/maps/lua_testsuite.wmf/scripting/cfield.lua' | |||
935 | --- test/maps/lua_testsuite.wmf/scripting/cfield.lua 2019-02-23 09:58:39 +0000 | |||
936 | +++ test/maps/lua_testsuite.wmf/scripting/cfield.lua 2019-03-11 07:15:58 +0000 | |||
937 | @@ -330,13 +330,17 @@ | |||
938 | 330 | end | 330 | end |
939 | 331 | 331 | ||
940 | 332 | function field_caps_tests:test_conquerable_fields_does_not_crash() | 332 | function field_caps_tests:test_conquerable_fields_does_not_crash() |
943 | 333 | local fields = map.conquerable_fields | 333 | assert_equal(5028, map:count_conquerable_fields()) |
944 | 334 | assert_equal(false, fields[1] == nil) | 334 | |
945 | 335 | local owned_fields = map:count_owned_valuable_fields() | ||
946 | 336 | assert_equal(404, owned_fields[1]) | ||
947 | 335 | end | 337 | end |
948 | 336 | 338 | ||
949 | 337 | function field_caps_tests:test_terrestrial_fields_does_not_crash() | 339 | function field_caps_tests:test_terrestrial_fields_does_not_crash() |
952 | 338 | local fields = map.terrestrial_fields | 340 | assert_equal(5028, map:count_terrestrial_fields()) |
953 | 339 | assert_equal(false, fields[1] == nil) | 341 | |
954 | 342 | local owned_fields = map:count_owned_valuable_fields("tree") | ||
955 | 343 | assert_equal(nil, owned_fields[1]) | ||
956 | 340 | end | 344 | end |
957 | 341 | 345 | ||
958 | 342 | -- =============== | 346 | -- =============== |
Tested this, also with old savegames. Works like a charm. Tremendously speeds up the saving and loading (territorial & woodgnome).