Merge lp:~widelands-dev/widelands/bug-1759857-territorial-lord into lp:widelands
- bug-1759857-territorial-lord
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 8892 | ||||
Proposed branch: | lp:~widelands-dev/widelands/bug-1759857-territorial-lord | ||||
Merge into: | lp:widelands | ||||
Diff against target: |
1026 lines (+455/-385) 8 files modified
data/scripting/win_conditions/territorial_functions.lua (+291/-0) data/scripting/win_conditions/territorial_lord.lua (+17/-157) data/scripting/win_conditions/territorial_time.lua (+23/-223) data/scripting/win_conditions/win_condition_functions.lua (+93/-0) data/tribes/scripting/help/worker_help.lua (+1/-1) src/scripting/lua_map.cc (+21/-3) src/scripting/lua_map.h (+2/-1) test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua (+7/-0) |
||||
To merge this branch: | bzr merge lp:~widelands-dev/widelands/bug-1759857-territorial-lord | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Notabilis | testing | Approve | |
Review via email: mp+355860@code.launchpad.net |
Commit message
Pulled out common code for Territorial Lord and Territorial Time. Ensure that check_player_
Description of the change
Since Territorial Lord and Territorial Time are almost the same win condition, I pulled out common code while I was touching them anyway.
Needs thorough testing to make sure that the crash is indeed gone. I left some NOCOM logging in there to help with debugging, and that needs to be removed before this branch gets merged.
bunnybot (widelandsofficial) wrote : | # |
Klaus Halfmann (klaus-halfmann) wrote : | # |
Uhhm, is there a way to debug lua inside widelands?
Checking that code just by reading is a pain.
I will play some games with territoral time lord, will this be enough?
Notabilis (notabilis27) wrote : | # |
I wasn't able to reproduce the reported bug but it didn't crashed in my tests either. However, the final lost/won message is broken, since the land ownership is reported as:
Player 1 had 179000% of the land (3580 of 2).
Unfortunately I wasn't able to spot the error. It seems as if the local variable fields changes its value but I have no idea how that should happen. Apart from that, it worked without problems and the earlier status messages were correct as well.
I also looked roughly through the code and haven't noticed any obvious mistakes, but that doesn't mean much.
GunChleoc (gunchleoc) wrote : | # |
Found it - there was an extra parameter in a function call, and Lua does not flag these things up at all.
I have also removed the debug logging output now - we don't have a Lua debugger, so console log is the best we can do, which is why I had kept it in.
Notabilis (notabilis27) wrote : | # |
Ah, that's what it was. Quite a good place to hide a bug.
Testing worked without problems now, so as far as I am concerned this can be merged.
GunChleoc (gunchleoc) wrote : | # |
Yep. I had quite a bit of fun with the refactoring in this branch, because a nonexistent variable is simply nil, which Lua will happily process... So, if you rename a variable, you better don't miss any instances of it!
Thanks for testing and the review :)
@bunnybot merge
Preview Diff
1 | === added file 'data/scripting/win_conditions/territorial_functions.lua' |
2 | --- data/scripting/win_conditions/territorial_functions.lua 1970-01-01 00:00:00 +0000 |
3 | +++ data/scripting/win_conditions/territorial_functions.lua 2018-10-16 06:54:27 +0000 |
4 | @@ -0,0 +1,291 @@ |
5 | +-- RST |
6 | +-- territorial_functions.lua |
7 | +-- --------------------------- |
8 | +-- |
9 | +-- This file contains common code for the "Territorial Lord" and "Territorial Time" win conditions. |
10 | + |
11 | +set_textdomain("win_conditions") |
12 | + |
13 | +include "scripting/richtext.lua" |
14 | +include "scripting/win_conditions/win_condition_texts.lua" |
15 | + |
16 | +local team_str = _"Team %i" |
17 | +local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)." |
18 | +local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)." |
19 | + |
20 | +-- RST |
21 | +-- .. function:: get_buildable_fields() |
22 | +-- |
23 | +-- Collects all fields that are buildable |
24 | +-- |
25 | +-- :returns: a table with the map's buildable fields |
26 | +-- |
27 | +function get_buildable_fields() |
28 | + local fields = {} |
29 | + local map = wl.Game().map |
30 | + for x=0, map.width-1 do |
31 | + for y=0, map.height-1 do |
32 | + local f = map:get_field(x,y) |
33 | + if f.buildable then |
34 | + table.insert(fields, f) |
35 | + end |
36 | + end |
37 | + end |
38 | + return fields |
39 | +end |
40 | + |
41 | +-- RST |
42 | +-- .. function:: count_owned_fields_for_all_players(fields, players) |
43 | +-- |
44 | +-- Counts all owned fields for each player. |
45 | +-- |
46 | +-- :arg fields: Table of all buildable fields |
47 | +-- :arg players: Table of all players |
48 | +-- |
49 | +-- :returns: a table with ``playernumber = count_of_owned_fields`` entries |
50 | +-- |
51 | +local function count_owned_fields_for_all_players(fields, players) |
52 | + local owned_fields = {} |
53 | + -- init the landsizes for each player |
54 | + for idx,plr in ipairs(players) do |
55 | + owned_fields[plr.number] = 0 |
56 | + end |
57 | + |
58 | + for idx,f in ipairs(fields) do |
59 | + -- check if field is owned by a player |
60 | + local owner = f.owner |
61 | + if owner then |
62 | + local owner_number = owner.number |
63 | + if owned_fields[owner_number] == nil then |
64 | + -- In case player was defeated and lost all their warehouses, make sure they don't count |
65 | + owned_fields[owner_number] = -1 |
66 | + elseif owned_fields[owner_number] >= 0 then |
67 | + owned_fields[owner_number] = owned_fields[owner_number] + 1 |
68 | + end |
69 | + end |
70 | + end |
71 | + return owned_fields |
72 | +end |
73 | + |
74 | + |
75 | +-- Used by calculate_territory_points keep track of when the winner changes |
76 | +local winning_players = {} |
77 | +local winning_teams = {} |
78 | + |
79 | + |
80 | +-- RST |
81 | +-- .. data:: territory_points |
82 | +-- |
83 | +-- This table contains information about the current points and winning status for all |
84 | +-- players and teams: |
85 | +-- |
86 | +-- .. code-block:: lua |
87 | +-- |
88 | +-- territory_points = { |
89 | +-- -- The currently winning team, if any. -1 means that no team is currently winning. |
90 | +-- last_winning_team = -1, |
91 | +-- -- The currently winning player, if any. -1 means that no player is currently winning. |
92 | +-- last_winning_player = -1, |
93 | +-- -- Remaining time in secs for victory by > 50% territory. Default value is also used to calculate whether to send a report to players. |
94 | +-- remaining_time = 10, |
95 | +-- -- Points by player |
96 | +-- all_player_points = {}, |
97 | +-- -- Points by rank, used to generate messages to the players |
98 | +-- points = {} |
99 | +-- } |
100 | +-- |
101 | +territory_points = { |
102 | + -- TODO(GunChleoc): We want to be able to list multiple winners in case of a draw. |
103 | + last_winning_team = -1, |
104 | + last_winning_player = -1, |
105 | + remaining_time = 10, |
106 | + all_player_points = {}, |
107 | + points = {} |
108 | +} |
109 | + |
110 | +-- RST |
111 | +-- .. function:: calculate_territory_points(fields, players, wc_descname, wc_version) |
112 | +-- |
113 | +-- First checks if a player was defeated, then fills the ``territory_points`` table |
114 | +-- with current data. |
115 | +-- |
116 | +-- :arg fields: Table of all buildable fields |
117 | +-- :arg players: Table of all players |
118 | +-- :arg wc_descname: String with the win condition's descname |
119 | +-- :arg wc_version: Number with the win condition's descname |
120 | +-- |
121 | +function calculate_territory_points(fields, players, wc_descname, wc_version) |
122 | + -- A player might have been defeated since the last calculation |
123 | + check_player_defeated(players, lost_game.title, lost_game.body, wc_descname, wc_version) |
124 | + |
125 | + local points = {} -- tracking points of teams and players without teams |
126 | + local territory_was_kept = false |
127 | + |
128 | + territory_points.all_player_points = count_owned_fields_for_all_players(fields, players) |
129 | + local ranked_players = rank_players(territory_points.all_player_points, players) |
130 | + |
131 | + -- Check if we have a winner. The table was sorted, so we can simply grab the first entry. |
132 | + local winning_points = -1 |
133 | + if ranked_players[1].points > ( #fields / 2 ) then |
134 | + winning_points = ranked_players[1].points |
135 | + end |
136 | + |
137 | + -- Calculate which team or player is the current winner, and whether the winner has changed |
138 | + for tidx, teaminfo in ipairs(ranked_players) do |
139 | + local is_winner = teaminfo.points == winning_points |
140 | + if teaminfo.team ~= 0 then |
141 | + points[#points + 1] = { team_str:format(teaminfo.team), teaminfo.points } |
142 | + if is_winner then |
143 | + territory_was_kept = winning_teams[teaminfo.team] ~= nil |
144 | + winning_teams[teaminfo.team] = true |
145 | + territory_points.last_winning_team = teaminfo.team |
146 | + territory_points.last_winning_player = -1 |
147 | + else |
148 | + winning_teams[teaminfo.team] = nil |
149 | + end |
150 | + end |
151 | + |
152 | + for pidx, playerinfo in ipairs(teaminfo.players) do |
153 | + if is_winner and teaminfo.team == 0 and teaminfo.points == playerinfo.points then |
154 | + territory_was_kept = winning_players[playerinfo.number] ~= nil |
155 | + winning_players[playerinfo.number] = true |
156 | + territory_points.last_winning_player = playerinfo.number |
157 | + territory_points.last_winning_team = -1 |
158 | + else |
159 | + winning_players[playerinfo.number] = nil |
160 | + end |
161 | + if teaminfo.team == 0 and players[playerinfo.number] ~= nil then |
162 | + points[#points + 1] = { players[playerinfo.number].name, playerinfo.points } |
163 | + end |
164 | + end |
165 | + end |
166 | + |
167 | + -- Set the remaining time according to whether the winner is still the same |
168 | + if territory_was_kept then |
169 | + -- Still the same winner |
170 | + territory_points.remaining_time = territory_points.remaining_time - 30 |
171 | + elseif winning_points == -1 then |
172 | + -- No winner. This value is used to calculate whether to send a report to players. |
173 | + territory_points.remaining_time = 10 |
174 | + else |
175 | + -- Winner changed |
176 | + territory_points.remaining_time = 20 * 60 -- 20 minutes |
177 | + end |
178 | + territory_points.points = points |
179 | +end |
180 | + |
181 | +-- RST |
182 | +-- .. function:: territory_status(fields, has_had) |
183 | +-- |
184 | +-- Returns a string containing the current land percentages of players/teams |
185 | +-- for messages to the players |
186 | +-- |
187 | +-- :arg fields: Table of all buildable fields |
188 | +-- :arg has_had: Use "has" for an interim message, "had" for a game over message. |
189 | +-- |
190 | +-- :returns: a richtext-formatted string with information on current points for each player/team |
191 | +-- |
192 | +function territory_status(fields, has_had) |
193 | + local function _percent(part, whole) |
194 | + return (part * 100) / whole |
195 | + end |
196 | + |
197 | + local msg = "" |
198 | + for i=1,#territory_points.points do |
199 | + if (has_had == "has") then |
200 | + msg = msg .. |
201 | + li( |
202 | + (wc_has_territory):bformat( |
203 | + territory_points.points[i][1], |
204 | + _percent(territory_points.points[i][2], #fields), |
205 | + territory_points.points[i][2], |
206 | + #fields)) |
207 | + else |
208 | + msg = msg .. |
209 | + li( |
210 | + (wc_had_territory):bformat( |
211 | + territory_points.points[i][1], |
212 | + _percent(territory_points.points[i][2], #fields), |
213 | + territory_points.points[i][2], |
214 | + #fields)) |
215 | + end |
216 | + |
217 | + end |
218 | + return p(msg) |
219 | +end |
220 | + |
221 | +-- RST |
222 | +-- .. function:: winning_status_header() |
223 | +-- |
224 | +-- Returns a string containing a status message header for a winning player |
225 | +-- |
226 | +-- :returns: a richtext-formatted string with header information for a winning player |
227 | +-- |
228 | +function winning_status_header() |
229 | + set_textdomain("win_conditions") |
230 | + local remaining_minutes = math.max(0, math.floor(territory_points.remaining_time / 60)) |
231 | + |
232 | + local message = p(_"You own more than half of the map’s area.") |
233 | + message = message .. p(ngettext("Keep it for %i more minute to win the game.", |
234 | + "Keep it for %i more minutes to win the game.", |
235 | + remaining_minutes)) |
236 | + :format(remaining_minutes) |
237 | + return message |
238 | +end |
239 | + |
240 | +-- RST |
241 | +-- .. function:: losing_status_header(players) |
242 | +-- |
243 | +-- Returns a string containing a status message header for a losing player |
244 | +-- |
245 | +-- :arg players: Table of all players |
246 | +-- |
247 | +-- :returns: a richtext-formatted string with header information for a losing player |
248 | +-- |
249 | +function losing_status_header(players) |
250 | + set_textdomain("win_conditions") |
251 | + local winner_name = "Error" |
252 | + if territory_points.last_winning_team >= 0 then |
253 | + winner_name = team_str:format(territory_points.last_winning_team) |
254 | + elseif territory_points.last_winning_player >= 0 then |
255 | + winner_name = players[territory_points.last_winning_player].name |
256 | + end |
257 | + local remaining_minutes = math.max(0, math.floor(territory_points.remaining_time / 60)) |
258 | + |
259 | + local message = p(_"%s owns more than half of the map’s area."):format(winner_name) |
260 | + message = message .. p(ngettext("You’ve still got %i minute to prevent a victory.", |
261 | + "You’ve still got %i minutes to prevent a victory.", |
262 | + remaining_minutes)) |
263 | + :format(remaining_minutes) |
264 | + return message |
265 | +end |
266 | + |
267 | +-- RST |
268 | +-- .. function:: territory_game_over(fields, players, wc_descname, wc_version) |
269 | +-- |
270 | +-- Updates the territory points and sends game over reports |
271 | +-- |
272 | +-- :arg fields: Table of all buildable fields |
273 | +-- :arg players: Table of all players |
274 | +-- |
275 | +function territory_game_over(fields, players, wc_descname, wc_version) |
276 | + calculate_territory_points(fields, players, wc_descname, wc_version) |
277 | + |
278 | + for idx, pl in ipairs(players) do |
279 | + pl.see_all = 1 |
280 | + |
281 | + local wonmsg = won_game_over.body .. game_status.body |
282 | + local lostmsg = lost_game_over.body .. game_status.body |
283 | + for i=1,#territory_points.points do |
284 | + if territory_points.points[i][1] == team_str:format(pl.team) or territory_points.points[i][1] == pl.name then |
285 | + if territory_points.points[i][2] >= territory_points.points[1][2] then |
286 | + pl:send_message(won_game_over.title, wonmsg .. territory_status(fields, "had")) |
287 | + wl.game.report_result(pl, 1, make_extra_data(pl, wc_descname, wc_version, {score=territory_points.all_player_points[pl.number]})) |
288 | + else |
289 | + pl:send_message(lost_game_over.title, lostmsg .. territory_status(fields, "had")) |
290 | + wl.game.report_result(pl, 0, make_extra_data(pl, wc_descname, wc_version, {score=territory_points.all_player_points[pl.number]})) |
291 | + end |
292 | + end |
293 | + end |
294 | + end |
295 | +end |
296 | |
297 | === modified file 'data/scripting/win_conditions/territorial_lord.lua' |
298 | --- data/scripting/win_conditions/territorial_lord.lua 2017-05-12 13:50:26 +0000 |
299 | +++ data/scripting/win_conditions/territorial_lord.lua 2018-10-16 06:54:27 +0000 |
300 | @@ -6,6 +6,7 @@ |
301 | include "scripting/messages.lua" |
302 | include "scripting/table.lua" |
303 | include "scripting/win_conditions/win_condition_functions.lua" |
304 | +include "scripting/win_conditions/territorial_functions.lua" |
305 | |
306 | set_textdomain("win_conditions") |
307 | |
308 | @@ -30,186 +31,45 @@ |
309 | -- set the objective with the game type for all players |
310 | broadcast_objective("win_condition", wc_descname, wc_desc) |
311 | |
312 | + -- Configure how long the winner has to hold on to the territory |
313 | + local time_to_keep_territory = 20 * 60 -- 20 minutes |
314 | + -- time in secs, if == 0 -> victory |
315 | + territory_points.remaining_time = time_to_keep_territory |
316 | + |
317 | -- Get all valueable fields of the map |
318 | - local fields = {} |
319 | - local map = wl.Game().map |
320 | - for x=0,map.width-1 do |
321 | - for y=0,map.height-1 do |
322 | - local f = map:get_field(x,y) |
323 | - if f then |
324 | - -- add this field to the list as long as it has not movecaps swim |
325 | - if not f:has_caps("swimmable") then |
326 | - if f:has_caps("walkable") then |
327 | - fields[#fields+1] = f |
328 | - else |
329 | - -- editor disallows placement of immovables on dead and acid fields |
330 | - if f.immovable then |
331 | - fields[#fields+1] = f |
332 | - end |
333 | - end |
334 | - end |
335 | - end |
336 | - end |
337 | - end |
338 | - |
339 | - -- these variables will be used once a player or team owns more than half |
340 | - -- of the map's area |
341 | - local currentcandidate = "" -- Name of Team or Player |
342 | - local candidateisteam = false |
343 | - local remaining_time = 10 -- (dummy) -- time in secs, if == 0 -> victory |
344 | - |
345 | - -- Find all valid teams |
346 | - local teamnumbers = {} -- array with team numbers |
347 | - for idx,p in ipairs(plrs) do |
348 | - local team = p.team |
349 | - if team > 0 then |
350 | - local found = false |
351 | - for idy,t in ipairs(teamnumbers) do |
352 | - if t == team then |
353 | - found = true |
354 | - break |
355 | - end |
356 | - end |
357 | - if not found then |
358 | - teamnumbers[#teamnumbers+1] = team |
359 | - end |
360 | - end |
361 | - end |
362 | - |
363 | - local _landsizes = {} |
364 | - local function _calc_current_landsizes() |
365 | - -- init the landsizes for each player |
366 | - for idx,plr in ipairs(plrs) do |
367 | - _landsizes[plr.number] = 0 |
368 | - end |
369 | - |
370 | - for idx,f in ipairs(fields) do |
371 | - -- check if field is owned by a player |
372 | - local o = f.owner |
373 | - if o then |
374 | - local n = o.number |
375 | - _landsizes[n] = _landsizes[n] + 1 |
376 | - end |
377 | - end |
378 | - end |
379 | - |
380 | - local function _calc_points() |
381 | - local teampoints = {} -- points of teams |
382 | - local maxplayerpoints = 0 -- the highest points of a player without team |
383 | - local maxpointsplayer = 0 -- the player |
384 | - local foundcandidate = false |
385 | - |
386 | - _calc_current_landsizes() |
387 | - |
388 | - for idx, p in ipairs(plrs) do |
389 | - local team = p.team |
390 | - if team == 0 then |
391 | - if maxplayerpoints < _landsizes[p.number] then |
392 | - maxplayerpoints = _landsizes[p.number] |
393 | - maxpointsplayer = p |
394 | - end |
395 | - else |
396 | - if not teampoints[team] then -- init the value |
397 | - teampoints[team] = 0 |
398 | - end |
399 | - teampoints[team] = teampoints[team] + _landsizes[p.number] |
400 | - end |
401 | - end |
402 | - |
403 | - if maxplayerpoints > ( #fields / 2 ) then |
404 | - -- player owns more than half of the map's area |
405 | - foundcandidate = true |
406 | - if candidateisteam == false and currentcandidate == maxpointsplayer.name then |
407 | - remaining_time = remaining_time - 30 |
408 | - else |
409 | - currentcandidate = maxpointsplayer.name |
410 | - candidateisteam = false |
411 | - remaining_time = 20 * 60 -- 20 minutes |
412 | - end |
413 | - else |
414 | - for idx, t in ipairs(teamnumbers) do |
415 | - if teampoints[t] > ( #fields / 2 ) then |
416 | - -- this team owns more than half of the map's area |
417 | - foundcandidate = true |
418 | - if candidateisteam == true and currentcandidate == t then |
419 | - remaining_time = remaining_time - 30 |
420 | - else |
421 | - currentcandidate = t |
422 | - candidateisteam = true |
423 | - remaining_time = 20 * 60 -- 20 minutes |
424 | - end |
425 | - end |
426 | - end |
427 | - end |
428 | - if not foundcandidate then |
429 | - currentcandidate = "" |
430 | - candidateisteam = false |
431 | - remaining_time = 10 |
432 | - end |
433 | - end |
434 | + local fields = get_buildable_fields() |
435 | |
436 | local function _send_state() |
437 | set_textdomain("win_conditions") |
438 | - local candidate = currentcandidate |
439 | - if candidateisteam then |
440 | - candidate = (_"Team %i"):format(currentcandidate) |
441 | - end |
442 | - local msg1 = p(_"%s owns more than half of the map’s area."):format(candidate) |
443 | - msg1 = msg1 .. p(ngettext("You’ve still got %i minute to prevent a victory.", |
444 | - "You’ve still got %i minutes to prevent a victory.", |
445 | - remaining_time / 60)) |
446 | - :format(remaining_time / 60) |
447 | - |
448 | - local msg2 = p(_"You own more than half of the map’s area.") |
449 | - msg2 = msg2 .. p(ngettext("Keep it for %i more minute to win the game.", |
450 | - "Keep it for %i more minutes to win the game.", |
451 | - remaining_time / 60)) |
452 | - :format(remaining_time / 60) |
453 | |
454 | for idx, player in ipairs(plrs) do |
455 | - if candidateisteam and currentcandidate == player.team |
456 | - or not candidateisteam and currentcandidate == player.name then |
457 | - send_message(player, game_status.title, msg2, {popup = true}) |
458 | + local msg = "" |
459 | + if territory_points.last_winning_team == player.team or territory_points.last_winning_player == player.number then |
460 | + msg = msg .. winning_status_header() .. vspace(8) |
461 | else |
462 | - send_message(player, game_status.title, msg1, {popup = true}) |
463 | + msg = msg .. losing_status_header(plrs) .. vspace(8) |
464 | end |
465 | + msg = msg .. vspace(8) .. game_status.body .. territory_status(fields, "has") |
466 | + send_message(player, game_status.title, msg, {popup = true}) |
467 | end |
468 | end |
469 | |
470 | - -- Start a new coroutine that checks for defeated players |
471 | - run(function() |
472 | - while remaining_time ~= 0 do |
473 | - sleep(5000) |
474 | - check_player_defeated(plrs, lost_game.title, lost_game.body, wc_descname, wc_version) |
475 | - end |
476 | - end) |
477 | - |
478 | -- here is the main loop!!! |
479 | while true do |
480 | -- Sleep 30 seconds == STATISTICS_SAMPLE_TIME |
481 | sleep(30000) |
482 | |
483 | -- Check if a player or team is a candidate and update variables |
484 | - _calc_points() |
485 | + calculate_territory_points(fields, plrs, wc_descname, wc_version) |
486 | |
487 | -- Do this stuff, if the game is over |
488 | - if remaining_time == 0 then |
489 | - for idx, p in ipairs(plrs) do |
490 | - p.see_all = 1 |
491 | - if candidateisteam and currentcandidate == p.team |
492 | - or not candidateisteam and currentcandidate == p.name then |
493 | - p:send_message(won_game_over.title, won_game_over.body) |
494 | - wl.game.report_result(p, 1, make_extra_data(p, wc_descname, wc_version, {score=_landsizes[p.number]})) |
495 | - else |
496 | - p:send_message(lost_game_over.title, lost_game_over.body) |
497 | - wl.game.report_result(p, 0, make_extra_data(p, wc_descname, wc_version, {score=_landsizes[p.number]})) |
498 | - end |
499 | - end |
500 | + if territory_points.remaining_time == 0 then |
501 | + territory_game_over(fields, plrs, wc_descname, wc_version) |
502 | break |
503 | end |
504 | |
505 | -- If there is a candidate, check whether we have to send an update |
506 | - if remaining_time % 300 == 0 then -- every 5 minutes (5 * 60 ) |
507 | + if (territory_points.last_winning_team >= 0 or territory_points.last_winning_player >= 0) and territory_points.remaining_time >= 0 and territory_points.remaining_time % 300 == 0 then |
508 | _send_state() |
509 | end |
510 | end |
511 | |
512 | === modified file 'data/scripting/win_conditions/territorial_time.lua' |
513 | --- data/scripting/win_conditions/territorial_time.lua 2017-05-12 13:50:26 +0000 |
514 | +++ data/scripting/win_conditions/territorial_time.lua 2018-10-16 06:54:27 +0000 |
515 | @@ -9,6 +9,7 @@ |
516 | include "scripting/messages.lua" |
517 | include "scripting/table.lua" |
518 | include "scripting/win_conditions/win_condition_functions.lua" |
519 | +include "scripting/win_conditions/territorial_functions.lua" |
520 | |
521 | set_textdomain("win_conditions") |
522 | |
523 | @@ -25,9 +26,7 @@ |
524 | "that area for at least 20 minutes, or the one with the most territory " .. |
525 | "after 4 hours, whichever comes first." |
526 | ) |
527 | -local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)." |
528 | -local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)." |
529 | -local team_str = _"Team %i" |
530 | + |
531 | |
532 | return { |
533 | name = wc_name, |
534 | @@ -39,217 +38,40 @@ |
535 | broadcast_objective("win_condition", wc_descname, wc_desc) |
536 | |
537 | -- Get all valueable fields of the map |
538 | - local fields = {} |
539 | - local map = wl.Game().map |
540 | - for x=0,map.width-1 do |
541 | - for y=0,map.height-1 do |
542 | - local f = map:get_field(x,y) |
543 | - if f then |
544 | - -- add this field to the list as long as it has not movecaps swim |
545 | - if not f:has_caps("swimmable") then |
546 | - if f:has_caps("walkable") then |
547 | - fields[#fields+1] = f |
548 | - else |
549 | - -- editor disallows placement of immovables on dead and acid fields |
550 | - if f.immovable then |
551 | - fields[#fields+1] = f |
552 | - end |
553 | - end |
554 | - end |
555 | - end |
556 | - end |
557 | - end |
558 | + local fields = get_buildable_fields() |
559 | |
560 | -- variables to track the maximum 4 hours of gametime |
561 | local remaining_max_time = 4 * 60 * 60 -- 4 hours |
562 | |
563 | - -- these variables will be used once a player or team owns more than half |
564 | - -- of the map's area |
565 | - local currentcandidate = "" -- Name of Team or Player |
566 | - local candidateisteam = false |
567 | - local remaining_time = 10 -- (dummy) -- time in secs, if == 0 -> victory |
568 | - |
569 | - -- Find all valid teams |
570 | - local teamnumbers = {} -- array with team numbers |
571 | - for idx,pl in ipairs(plrs) do |
572 | - local team = pl.team |
573 | - if team > 0 then |
574 | - local found = false |
575 | - for idy,t in ipairs(teamnumbers) do |
576 | - if t == team then |
577 | - found = true |
578 | - break |
579 | - end |
580 | - end |
581 | - if not found then |
582 | - teamnumbers[#teamnumbers+1] = team |
583 | - end |
584 | - end |
585 | - end |
586 | - |
587 | - local _landsizes = {} |
588 | - local function _calc_current_landsizes() |
589 | - -- init the landsizes for each player |
590 | - for idx,plr in ipairs(plrs) do |
591 | - _landsizes[plr.number] = 0 |
592 | - end |
593 | - |
594 | - for idx,f in ipairs(fields) do |
595 | - -- check if field is owned by a player |
596 | - local o = f.owner |
597 | - if o then |
598 | - local n = o.number |
599 | - _landsizes[n] = _landsizes[n] + 1 |
600 | - end |
601 | - end |
602 | - end |
603 | - |
604 | - local function _calc_points() |
605 | - local teampoints = {} -- points of teams |
606 | - local points = {} -- tracking points of teams and players without teams |
607 | - local maxplayerpoints = 0 -- the highest points of a player without team |
608 | - local maxpointsplayer = 0 -- the player |
609 | - local foundcandidate = false |
610 | - |
611 | - _calc_current_landsizes() |
612 | - |
613 | - for idx, pl in ipairs(plrs) do |
614 | - local team = pl.team |
615 | - if team == 0 then |
616 | - if maxplayerpoints < _landsizes[pl.number] then |
617 | - maxplayerpoints = _landsizes[pl.number] |
618 | - maxpointsplayer = pl |
619 | - end |
620 | - points[#points + 1] = { pl.name, _landsizes[pl.number] } |
621 | - else |
622 | - if not teampoints[team] then -- init the value |
623 | - teampoints[team] = 0 |
624 | - end |
625 | - teampoints[team] = teampoints[team] + _landsizes[pl.number] |
626 | - end |
627 | - end |
628 | - |
629 | - if maxplayerpoints > ( #fields / 2 ) then |
630 | - -- player owns more than half of the map's area |
631 | - foundcandidate = true |
632 | - if candidateisteam == false and currentcandidate == maxpointsplayer.name then |
633 | - remaining_time = remaining_time - 30 |
634 | - else |
635 | - currentcandidate = maxpointsplayer.name |
636 | - candidateisteam = false |
637 | - remaining_time = 20 * 60 -- 20 minutes |
638 | - end |
639 | - end |
640 | - for idx, t in ipairs(teamnumbers) do |
641 | - if teampoints[t] > ( #fields / 2 ) then |
642 | - -- this team owns more than half of the map's area |
643 | - foundcandidate = true |
644 | - if candidateisteam == true and currentcandidate == team_str:format(t) then |
645 | - remaining_time = remaining_time - 30 |
646 | - else |
647 | - currentcandidate = team_str:format(t) |
648 | - candidateisteam = true |
649 | - remaining_time = 20 * 60 -- 20 minutes |
650 | - end |
651 | - end |
652 | - points[#points + 1] = { team_str:format(t), teampoints[t] } |
653 | - end |
654 | - if not foundcandidate then |
655 | - remaining_time = 10 |
656 | - end |
657 | - return points |
658 | - end |
659 | - |
660 | - local function _percent(part, whole) |
661 | - return (part * 100) / whole |
662 | - end |
663 | - |
664 | - -- Helper function to get the points that the leader has |
665 | - local function _maxpoints(points) |
666 | - local max = 0 |
667 | - for i=1,#points do |
668 | - if points[i][2] > max then max = points[i][2] end |
669 | - end |
670 | - return max |
671 | - end |
672 | - |
673 | - -- Helper function that returns a string containing the current |
674 | - -- land percentages of players/teams. |
675 | - local function _status(points, has_had) |
676 | - local msg = "" |
677 | - for i=1,#points do |
678 | - if (has_had == "has") then |
679 | - msg = msg .. |
680 | - li( |
681 | - (wc_has_territory):bformat( |
682 | - points[i][1], |
683 | - _percent(points[i][2], #fields), |
684 | - points[i][2], |
685 | - #fields)) |
686 | - else |
687 | - msg = msg .. |
688 | - li( |
689 | - (wc_had_territory):bformat( |
690 | - points[i][1], |
691 | - _percent(points[i][2], #fields), |
692 | - points[i][2], |
693 | - #fields)) |
694 | - end |
695 | - |
696 | - end |
697 | - return p(msg) |
698 | - end |
699 | - |
700 | - local function _send_state(points) |
701 | + local function _send_state() |
702 | set_textdomain("win_conditions") |
703 | - local msg1 = p(_"%s owns more than half of the map’s area."):format(currentcandidate) |
704 | - msg1 = msg1 .. p(ngettext("You’ve still got %i minute to prevent a victory.", |
705 | - "You’ve still got %i minutes to prevent a victory.", |
706 | - remaining_time // 60)) |
707 | - :format(remaining_time // 60) |
708 | - msg1 = p(msg1) |
709 | - |
710 | - local msg2 = p(_"You own more than half of the map’s area.") |
711 | - msg2 = msg2 .. p(ngettext("Keep it for %i more minute to win the game.", |
712 | - "Keep it for %i more minutes to win the game.", |
713 | - remaining_time // 60)) |
714 | - :format(remaining_time // 60) |
715 | - msg2 = p(msg2) |
716 | - |
717 | - for idx, pl in ipairs(plrs) do |
718 | + |
719 | + local remaining_max_minutes = remaining_max_time // 60 |
720 | + for idx, player in ipairs(plrs) do |
721 | local msg = "" |
722 | - if remaining_time < remaining_max_time and _maxpoints(points) > ( #fields / 2 ) then |
723 | - if candidateisteam and currentcandidate == team_str:format(pl.team) |
724 | - or not candidateisteam and currentcandidate == pl.name then |
725 | - msg = msg .. msg2 .. vspace(8) |
726 | + if territory_points.remaining_time < remaining_max_time and |
727 | + (territory_points.last_winning_team >= 0 or territory_points.last_winning_player >= 0) then |
728 | + if territory_points.last_winning_team == player.team or territory_points.last_winning_player == player.number then |
729 | + msg = msg .. winning_status_header() .. vspace(8) |
730 | else |
731 | - msg = msg .. msg1 .. vspace(8) |
732 | + msg = msg .. losing_status_header(plrs) .. vspace(8) |
733 | end |
734 | -- TRANSLATORS: Refers to "You own more than half of the map’s area. Keep it for x more minute(s) to win the game." |
735 | msg = msg .. p((ngettext("Otherwise the game will end in %i minute.", |
736 | "Otherwise the game will end in %i minutes.", |
737 | - remaining_max_time // 60)) |
738 | - :format(remaining_max_time // 60)) |
739 | + remaining_max_minutes)) |
740 | + :format(remaining_max_minutes)) |
741 | else |
742 | msg = msg .. p((ngettext("The game will end in %i minute.", |
743 | "The game will end in %i minutes.", |
744 | - remaining_max_time // 60)) |
745 | - :format(remaining_max_time // 60)) |
746 | + remaining_max_minutes)) |
747 | + :format(remaining_max_minutes)) |
748 | end |
749 | - msg = msg .. vspace(8) .. game_status.body .. _status(points, "has") |
750 | - send_message(pl, game_status.title, msg, {popup = true}) |
751 | + msg = msg .. vspace(8) .. game_status.body .. territory_status(fields, "has") |
752 | + send_message(player, game_status.title, msg, {popup = true}) |
753 | end |
754 | end |
755 | |
756 | - -- Start a new coroutine that checks for defeated players |
757 | - run(function() |
758 | - while remaining_time ~= 0 and remaining_max_time > 0 do |
759 | - sleep(5000) |
760 | - check_player_defeated(plrs, lost_game.title, |
761 | - lost_game.body, wc_descname, wc_version) |
762 | - end |
763 | - end) |
764 | - |
765 | -- here is the main loop!!! |
766 | while true do |
767 | -- Sleep 30 seconds == STATISTICS_SAMPLE_TIME |
768 | @@ -259,44 +81,22 @@ |
769 | |
770 | -- Check if a player or team is a candidate and update variables |
771 | -- Returns the names and points for the teams and players without a team |
772 | - local points = _calc_points() |
773 | + calculate_territory_points(fields, plrs, wc_descname, wc_version) |
774 | |
775 | -- Game is over, do stuff after loop |
776 | - if remaining_time <= 0 or remaining_max_time <= 0 then break end |
777 | + if territory_points.remaining_time <= 0 or remaining_max_time <= 0 then break end |
778 | |
779 | -- at the beginning send remaining max time message only each 30 minutes |
780 | -- if only 30 minutes or less are left, send each 5 minutes |
781 | -- also check if there is a candidate and we need to send an update |
782 | if ((remaining_max_time < (30 * 60) and remaining_max_time % (5 * 60) == 0) |
783 | or remaining_max_time % (30 * 60) == 0) |
784 | - or remaining_time % 300 == 0 then |
785 | - _send_state(points) |
786 | + or territory_points.remaining_time % 300 == 0 then |
787 | + _send_state() |
788 | end |
789 | end |
790 | |
791 | - local points = _calc_points() |
792 | - table.sort(points, function(a,b) return a[2] > b[2] end) |
793 | - |
794 | -- Game has ended |
795 | - for idx, pl in ipairs(plrs) do |
796 | - pl.see_all = 1 |
797 | - |
798 | - maxpoints = points[1][2] |
799 | - local wonmsg = won_game_over.body |
800 | - wonmsg = wonmsg .. game_status.body |
801 | - local lostmsg = lost_game_over.body |
802 | - lostmsg = lostmsg .. game_status.body |
803 | - for i=1,#points do |
804 | - if points[i][1] == team_str:format(pl.team) or points[i][1] == pl.name then |
805 | - if points[i][2] >= maxpoints then |
806 | - pl:send_message(won_game_over.title, wonmsg .. _status(points, "had")) |
807 | - wl.game.report_result(pl, 1, make_extra_data(pl, wc_descname, wc_version, {score=_landsizes[pl.number]})) |
808 | - else |
809 | - pl:send_message(lost_game_over.title, lostmsg .. _status(points, "had")) |
810 | - wl.game.report_result(pl, 0, make_extra_data(pl, wc_descname, wc_version, {score=_landsizes[pl.number]})) |
811 | - end |
812 | - end |
813 | - end |
814 | - end |
815 | + territory_game_over(fields, plrs, wc_descname, wc_version) |
816 | end |
817 | } |
818 | |
819 | === modified file 'data/scripting/win_conditions/win_condition_functions.lua' |
820 | --- data/scripting/win_conditions/win_condition_functions.lua 2017-05-11 17:29:55 +0000 |
821 | +++ data/scripting/win_conditions/win_condition_functions.lua 2018-10-16 06:54:27 +0000 |
822 | @@ -144,3 +144,96 @@ |
823 | local plrs = wl.Game().players |
824 | plrs[1]:add_objective(name, title, body) |
825 | end |
826 | + |
827 | + |
828 | +-- RST |
829 | +-- .. function:: rank_players(all_player_points, plrs) |
830 | +-- |
831 | +-- Rank the players and teams according to the highest points |
832 | +-- |
833 | +-- :arg all_player_points: A table of ``playernumber = points`` entries for all players |
834 | +-- :arg plrs: A table of all Player objects |
835 | +-- |
836 | +-- :returns: A table with ranked player and team points, sorted by points descending. Example: |
837 | +-- |
838 | +-- .. code-block:: lua |
839 | +-- |
840 | +-- { |
841 | +-- -- A player without team |
842 | +-- { |
843 | +-- team = 0, |
844 | +-- points = 1000, |
845 | +-- players = { |
846 | +-- { "number" = 5, "points" = 1000 } |
847 | +-- } |
848 | +-- }, |
849 | +-- -- This team has a draw with player 5 |
850 | +-- { |
851 | +-- team = 1, |
852 | +-- points = 1000, |
853 | +-- players = { |
854 | +-- { "number" = 2, "points" = 500 } |
855 | +-- { "number" = 3, "points" = 400 } |
856 | +-- { "number" = 4, "points" = 100 } |
857 | +-- }, |
858 | +-- -- Another player without team |
859 | +-- { |
860 | +-- team = 0, |
861 | +-- points = 800, |
862 | +-- players = { |
863 | +-- { "number" = 1, "points" = 800 } |
864 | +-- } |
865 | +-- }, |
866 | +-- } |
867 | +-- |
868 | +function rank_players(all_player_points, plrs) |
869 | + local ranked_players_and_teams = {} |
870 | + local team_points = {} |
871 | + |
872 | + -- Add points for players without teams and calculate team points |
873 | + for idx, player in ipairs(plrs) do |
874 | + local player_points = all_player_points[player.number] |
875 | + local team = player.team |
876 | + if team == 0 then |
877 | + -- Player without team - add it directly |
878 | + local team_table = { |
879 | + team = 0, |
880 | + points = player_points, |
881 | + players = { |
882 | + { number = player.number, points = player_points } |
883 | + } |
884 | + } |
885 | + table.insert(ranked_players_and_teams, team_table) |
886 | + else |
887 | + -- Team player - add to team points |
888 | + if not team_points[team] then |
889 | + team_points[team] = 0 |
890 | + end |
891 | + team_points[team] = team_points[team] + player_points |
892 | + end |
893 | + end |
894 | + |
895 | + -- Add points for teams and their players |
896 | + for team, points in pairs(team_points) do |
897 | + local team_table = { |
898 | + team = team, |
899 | + points = points, |
900 | + players = {} |
901 | + } |
902 | + for idx, player in ipairs(plrs) do |
903 | + if player.team == team then |
904 | + table.insert(team_table.players, { number = player.number, points = all_player_points[player.number] }) |
905 | + end |
906 | + end |
907 | + table.insert(ranked_players_and_teams, team_table) |
908 | + end |
909 | + |
910 | + -- Sort the players by points descending |
911 | + for ids, team in pairs(ranked_players_and_teams) do |
912 | + table.sort(team.players, function(a,b) return a["points"] > b["points"] end) |
913 | + end |
914 | + |
915 | + -- Sort the teams by points descending |
916 | + table.sort(ranked_players_and_teams, function(a,b) return a["points"] > b["points"] end) |
917 | + return ranked_players_and_teams |
918 | +end |
919 | |
920 | === modified file 'data/tribes/scripting/help/worker_help.lua' |
921 | --- data/tribes/scripting/help/worker_help.lua 2018-09-25 07:20:28 +0000 |
922 | +++ data/tribes/scripting/help/worker_help.lua 2018-10-16 06:54:27 +0000 |
923 | @@ -153,7 +153,7 @@ |
924 | local result = h2(_"Purpose") .. |
925 | li_image(worker_description.icon_name, worker_helptext()) |
926 | |
927 | - if (worker_description.is_buildable) then |
928 | + if (worker_description.buildable) then |
929 | -- Get the tools for the workers. |
930 | local toolnames = {} |
931 | for j, buildcost in ipairs(worker_description.buildcost) do |
932 | |
933 | === modified file 'src/scripting/lua_map.cc' |
934 | --- src/scripting/lua_map.cc 2018-09-11 07:24:27 +0000 |
935 | +++ src/scripting/lua_map.cc 2018-10-16 06:54:27 +0000 |
936 | @@ -3003,7 +3003,7 @@ |
937 | }; |
938 | const PropertyType<LuaWorkerDescription> LuaWorkerDescription::Properties[] = { |
939 | PROP_RO(LuaWorkerDescription, becomes), PROP_RO(LuaWorkerDescription, buildcost), |
940 | - PROP_RO(LuaWorkerDescription, employers), PROP_RO(LuaWorkerDescription, is_buildable), |
941 | + PROP_RO(LuaWorkerDescription, employers), PROP_RO(LuaWorkerDescription, buildable), |
942 | PROP_RO(LuaWorkerDescription, needed_experience), {nullptr, nullptr, nullptr}, |
943 | }; |
944 | |
945 | @@ -3079,11 +3079,11 @@ |
946 | } |
947 | |
948 | /* RST |
949 | - .. attribute:: is_buildable |
950 | + .. attribute:: buildable |
951 | |
952 | (RO) returns true if this worker is buildable |
953 | */ |
954 | -int LuaWorkerDescription::get_is_buildable(lua_State* L) { |
955 | +int LuaWorkerDescription::get_buildable(lua_State* L) { |
956 | lua_pushboolean(L, get()->is_buildable()); |
957 | return 1; |
958 | } |
959 | @@ -5940,6 +5940,7 @@ |
960 | PROP_RO(LuaField, initial_resource_amount), |
961 | PROP_RO(LuaField, claimers), |
962 | PROP_RO(LuaField, owner), |
963 | + PROP_RO(LuaField, buildable), |
964 | {nullptr, nullptr, nullptr}, |
965 | }; |
966 | |
967 | @@ -6267,6 +6268,23 @@ |
968 | } |
969 | |
970 | /* RST |
971 | + .. attribute:: buildable |
972 | + |
973 | + (RO) Returns :const:`true` if a flag or building could be built on this field, |
974 | + independently of whether anybody currently owns this field. |
975 | +*/ |
976 | +int LuaField::get_buildable(lua_State* L) { |
977 | + const NodeCaps caps = fcoords(L).field->nodecaps(); |
978 | + const bool is_buildable = (caps & BUILDCAPS_FLAG) |
979 | + || (caps & BUILDCAPS_SMALL) |
980 | + || (caps & BUILDCAPS_MEDIUM) |
981 | + || (caps & BUILDCAPS_BIG) |
982 | + || (caps & BUILDCAPS_MINE); |
983 | + lua_pushboolean(L, is_buildable); |
984 | + return 1; |
985 | +} |
986 | + |
987 | +/* RST |
988 | .. attribute:: claimers |
989 | |
990 | (RO) An :class:`array` of players that have military influence over this |
991 | |
992 | === modified file 'src/scripting/lua_map.h' |
993 | --- src/scripting/lua_map.h 2018-09-02 11:44:52 +0000 |
994 | +++ src/scripting/lua_map.h 2018-10-16 06:54:27 +0000 |
995 | @@ -600,7 +600,7 @@ |
996 | int get_becomes(lua_State*); |
997 | int get_buildcost(lua_State*); |
998 | int get_employers(lua_State*); |
999 | - int get_is_buildable(lua_State*); |
1000 | + int get_buildable(lua_State*); |
1001 | int get_needed_experience(lua_State*); |
1002 | |
1003 | /* |
1004 | @@ -1413,6 +1413,7 @@ |
1005 | int get_initial_resource_amount(lua_State*); |
1006 | int get_claimers(lua_State*); |
1007 | int get_owner(lua_State*); |
1008 | + int get_buildable(lua_State*); |
1009 | |
1010 | /* |
1011 | * Lua methods |
1012 | |
1013 | === modified file 'test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua' |
1014 | --- test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua 2017-11-12 14:54:46 +0000 |
1015 | +++ test/maps/lua_testsuite.wmf/scripting/immovables_descriptions.lua 2018-10-16 06:54:27 +0000 |
1016 | @@ -551,3 +551,10 @@ |
1017 | assert_equal(19, egbase:get_worker_description("barbarians_miner").needed_experience) |
1018 | assert_equal(28, egbase:get_worker_description("barbarians_miner_chief").needed_experience) |
1019 | end |
1020 | + |
1021 | + |
1022 | +function test_descr:test_worker_buildable() |
1023 | + assert_equal(true, egbase:get_worker_description("barbarians_carrier").buildable) |
1024 | + assert_equal(true, egbase:get_worker_description("barbarians_miner").buildable) |
1025 | + assert_equal(false, egbase:get_worker_description("barbarians_miner_chief").buildable) |
1026 | +end |
Continuous integration builds have changed state:
Travis build 4071. State: passed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 434910227. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ bug_1759857_ territorial_ lord-3867.
Appveyor build 3867. State: success. Details: https:/