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.
8699. By GunChleoc

Documentation tweak.

8700. By GunChleoc

Improved conditional statement

8701. By GunChleoc

Renamed is_buildable to buildable for consistency

8702. By GunChleoc

Same for workers

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)
8703. By GunChleoc

Merged trunk.

8704. By GunChleoc

Fix points in status message when game is over

8705. By GunChleoc

Remove debug log output

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: