Merge lp:~widelands-dev/widelands/bug-1759857-territorial-lord into lp:widelands

Proposed by GunChleoc
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
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_defeated is called before points are calculated. New function to check whether a field is buildable.

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.

To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 4071. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/434910227.
Appveyor build 3867. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_bug_1759857_territorial_lord-3867.

Revision history for this message
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?

Revision history for this message
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.

review: Needs Fixing (testing)
Revision history for this message
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.

Revision history for this message
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.

review: Approve (testing)
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches

to status/vote changes: