widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #14821
[Merge] lp:~widelands-dev/widelands/bug-1759857-territorial-lord into lp:widelands
GunChleoc has proposed merging lp:~widelands-dev/widelands/bug-1759857-territorial-lord into lp:widelands.
Commit message:
Pulled out common code for Territorial Lord and Territorial Time. Ensure that check_player_defeated is called before points are calculated.
Requested reviews:
Widelands Developers (widelands-dev)
Related bugs:
Bug #1759857 in widelands: ""Lua Coroutine Failed" as Inbox Message"
https://bugs.launchpad.net/widelands/+bug/1759857
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/bug-1759857-territorial-lord/+merge/355860
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.
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/bug-1759857-territorial-lord into lp:widelands.
=== added file 'data/scripting/win_conditions/territorial_functions.lua'
--- data/scripting/win_conditions/territorial_functions.lua 1970-01-01 00:00:00 +0000
+++ data/scripting/win_conditions/territorial_functions.lua 2018-09-28 18:09:51 +0000
@@ -0,0 +1,298 @@
+-- RST
+-- territorial_functions.lua
+-- ---------------------------
+--
+-- This file contains common code for the "Territorial Lord" and "Territorial Time" win conditions.
+
+set_textdomain("win_conditions")
+
+include "scripting/richtext.lua"
+include "scripting/win_conditions/win_condition_texts.lua"
+
+local team_str = _"Team %i"
+local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)."
+local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)."
+
+-- RST
+-- .. function:: get_buildable_fields()
+--
+-- Collects all fields that are buildable
+--
+-- :returns: a table with the map's buildable fields
+--
+function get_buildable_fields()
+ local fields = {}
+ local map = wl.Game().map
+ for x=0, map.width-1 do
+ for y=0, map.height-1 do
+ local f = map:get_field(x,y)
+ if f.is_buildable then
+ table.insert(fields, f)
+ end
+ end
+ end
+ print("NOCOM Found " .. #fields .. " buildable fields")
+ return fields
+end
+
+-- RST
+-- .. function:: count_owned_fields_for_all_players(fields, players)
+--
+-- Counts all owned fields for each player.
+--
+-- :arg fields: Table of all buildable fields
+-- :arg players: Table of all players
+--
+-- :returns: a table with ``playernumber = count_of_owned_fields`` entries
+--
+local function count_owned_fields_for_all_players(fields, players)
+ local owned_fields = {}
+ -- init the landsizes for each player
+ for idx,plr in ipairs(players) do
+ owned_fields[plr.number] = 0
+ end
+
+ for idx,f in ipairs(fields) do
+ -- check if field is owned by a player
+ local owner = f.owner
+ if owner then
+ local owner_number = owner.number
+ if owned_fields[owner_number] == nil then
+ -- In case player was defeated and lost all their warehouses, make sure they don't count
+ owned_fields[owner_number] = -1
+ elseif owned_fields[owner_number] >= 0 then
+ owned_fields[owner_number] = owned_fields[owner_number] + 1
+ end
+ end
+ end
+ return owned_fields
+end
+
+
+-- Used by calculate_territory_points keep track of when the winner changes
+local winning_players = {}
+local winning_teams = {}
+
+
+-- RST
+-- .. data:: territory_points
+--
+-- This table contains information about the current points and winning status for all
+-- players and teams:
+--
+-- .. code-block:: lua
+--
+-- territory_points = {
+-- -- The currently winning team, if any. -1 means that no team is currently winning.
+-- last_winning_team = -1,
+-- -- The currently winning player, if any. -1 means that no player is currently winning.
+-- last_winning_player = -1,
+-- -- Remaining time in secs for victory by > 50% territory. Default value is also used to calculate whether to send a report to players.
+-- remaining_time = 10,
+-- -- Points by player
+-- all_player_points = {},
+-- -- Points by rank, used to generate messages to the players
+-- points = {}
+-- }
+--
+territory_points = {
+ -- TODO(GunChleoc): We want to be able to list multiple winners in case of a draw.
+ last_winning_team = -1,
+ last_winning_player = -1,
+ remaining_time = 10,
+ all_player_points = {},
+ points = {}
+}
+
+-- RST
+-- .. function:: calculate_territory_points(fields, players, wc_descname, wc_version)
+--
+-- First checks if a player was defeated, then fills the ``territory_points`` table
+-- with current data.
+--
+-- :arg fields: Table of all buildable fields
+-- :arg players: Table of all players
+-- :arg wc_descname: String with the win condition's descname
+-- :arg wc_version: Number with the win condition's descname
+--
+function calculate_territory_points(fields, players, wc_descname, wc_version)
+ -- A player might have been defeated since the last calculation
+ check_player_defeated(players, lost_game.title, lost_game.body, wc_descname, wc_version)
+
+ local points = {} -- tracking points of teams and players without teams
+ local territory_was_kept = false
+
+ territory_points.all_player_points = count_owned_fields_for_all_players(fields, players)
+ local ranked_players = rank_players(territory_points.all_player_points, players)
+
+ -- Check if we have a winner. The table was sorted, so we can simply grab the first entry.
+ local winning_points = -1
+ if ranked_players[1].points > ( #fields / 2 ) then
+ winning_points = ranked_players[1].points
+ end
+
+ -- Calculate which team or player is the current winner, and whether the winner has changed
+ for tidx, teaminfo in ipairs(ranked_players) do
+ local is_winner = teaminfo.points == winning_points
+ if teaminfo.team ~= 0 then
+ points[#points + 1] = { team_str:format(teaminfo.team), teaminfo.points }
+ if is_winner then
+ print("NOCOM Winner is team " .. teaminfo.team .. " with " .. teaminfo.points .. " points")
+ territory_was_kept = winning_teams[teaminfo.team] ~= nil
+ winning_teams[teaminfo.team] = true
+ territory_points.last_winning_team = teaminfo.team
+ territory_points.last_winning_player = -1
+ else
+ winning_teams[teaminfo.team] = nil
+ end
+ end
+
+ for pidx, playerinfo in ipairs(teaminfo.players) do
+ if teaminfo.points ~= playerinfo.points then
+ winning_players[playerinfo.number] = nil
+ elseif is_winner and teaminfo.team == 0 then
+ print("NOCOM Winner is player " .. playerinfo.number .. " with " .. playerinfo.points .. " points")
+ territory_was_kept = winning_players[playerinfo.number] ~= nil
+ winning_players[playerinfo.number] = true
+ territory_points.last_winning_player = playerinfo.number
+ territory_points.last_winning_team = -1
+ else
+ winning_players[playerinfo.number] = nil
+ end
+ if teaminfo.team == 0 and players[playerinfo.number] ~= nil then
+ points[#points + 1] = { players[playerinfo.number].name, playerinfo.points }
+ end
+ end
+ end
+
+ -- Set the remaining time according to whether the winner is still the same
+ if territory_was_kept then
+ -- Still the same winner
+ territory_points.remaining_time = territory_points.remaining_time - 30
+ print("NOCOM Territory was kept by " .. territory_points.last_winning_team .. " - " .. territory_points.last_winning_player .. ". Remaining time: " .. territory_points.remaining_time)
+ elseif winning_points == -1 then
+ -- No winner. This value is used to calculate whether to send a report to players.
+ territory_points.remaining_time = 10
+ else
+ -- Winner changed
+ territory_points.remaining_time = 20 * 60 -- 20 minutes
+ print("NOCOM NEW aqcuisition by " .. territory_points.last_winning_team .. " - " .. territory_points.last_winning_player .. ". Remaining time: " .. territory_points.remaining_time)
+ end
+ territory_points.points = points
+end
+
+-- RST
+-- .. function:: territory_status(fields, has_had)
+--
+-- Returns a string containing the current land percentages of players/teams
+-- for messages to the players
+--
+-- :arg fields: Table of all buildable fields
+-- :arg has_had: Use "has" for an interim message, "had" for a game over message.
+--
+-- :returns: a richtext-formatted string with information on current points for each player/team
+--
+function territory_status(fields, has_had)
+ local function _percent(part, whole)
+ return (part * 100) / whole
+ end
+
+ local msg = ""
+ for i=1,#territory_points.points do
+ if (has_had == "has") then
+ msg = msg ..
+ li(
+ (wc_has_territory):bformat(
+ territory_points.points[i][1],
+ _percent(territory_points.points[i][2], #fields),
+ territory_points.points[i][2],
+ #fields))
+ else
+ msg = msg ..
+ li(
+ (wc_had_territory):bformat(
+ territory_points.points[i][1],
+ _percent(territory_points.points[i][2], #fields),
+ territory_points.points[i][2],
+ #fields))
+ end
+
+ end
+ return p(msg)
+end
+
+-- RST
+-- .. function:: winning_status_header()
+--
+-- Returns a string containing a status message header for a winning player
+--
+-- :returns: a richtext-formatted string with header information for a winning player
+--
+function winning_status_header()
+ set_textdomain("win_conditions")
+ local remaining_minutes = math.max(0, math.floor(territory_points.remaining_time / 60))
+
+ local message = p(_"You own more than half of the map’s area.")
+ message = message .. p(ngettext("Keep it for %i more minute to win the game.",
+ "Keep it for %i more minutes to win the game.",
+ remaining_minutes))
+ :format(remaining_minutes)
+ return message
+end
+
+-- RST
+-- .. function:: losing_status_header(players)
+--
+-- Returns a string containing a status message header for a losing player
+--
+-- :arg players: Table of all players
+--
+-- :returns: a richtext-formatted string with header information for a losing player
+--
+function losing_status_header(players)
+ set_textdomain("win_conditions")
+ local winner_name = "Error"
+ if territory_points.last_winning_team >= 0 then
+ winner_name = team_str:format(territory_points.last_winning_team)
+ elseif territory_points.last_winning_player >= 0 then
+ winner_name = players[territory_points.last_winning_player].name
+ end
+ local remaining_minutes = math.max(0, math.floor(territory_points.remaining_time / 60))
+
+ local message = p(_"%s owns more than half of the map’s area."):format(winner_name)
+ message = message .. p(ngettext("You’ve still got %i minute to prevent a victory.",
+ "You’ve still got %i minutes to prevent a victory.",
+ remaining_minutes))
+ :format(remaining_minutes)
+ return message
+end
+
+-- RST
+-- .. function:: territory_game_over(fields, players, wc_descname, wc_version)
+--
+-- Updates the territory points and sends game over reports
+--
+-- :arg fields: Table of all buildable fields
+-- :arg players: Table of all players
+--
+function territory_game_over(fields, players, wc_descname, wc_version)
+ calculate_territory_points(fields, players, wc_descname, wc_version)
+
+ for idx, pl in ipairs(players) do
+ pl.see_all = 1
+
+ local wonmsg = won_game_over.body .. game_status.body
+ local lostmsg = lost_game_over.body .. game_status.body
+ for i=1,#territory_points.points do
+ if territory_points.points[i][1] == team_str:format(pl.team) or territory_points.points[i][1] == pl.name then
+ if territory_points.points[i][2] >= territory_points.points[1][2] then
+ pl:send_message(won_game_over.title, wonmsg .. territory_status(territory_points.points, fields, "had"))
+ wl.game.report_result(pl, 1, make_extra_data(pl, wc_descname, wc_version, {score=territory_points.all_player_points[pl.number]}))
+ else
+ pl:send_message(lost_game_over.title, lostmsg .. territory_status(territory_points.points, fields, "had"))
+ wl.game.report_result(pl, 0, make_extra_data(pl, wc_descname, wc_version, {score=territory_points.all_player_points[pl.number]}))
+ end
+ end
+ end
+ end
+end
=== modified file 'data/scripting/win_conditions/territorial_lord.lua'
--- data/scripting/win_conditions/territorial_lord.lua 2017-05-12 13:50:26 +0000
+++ data/scripting/win_conditions/territorial_lord.lua 2018-09-28 18:09:51 +0000
@@ -6,6 +6,7 @@
include "scripting/messages.lua"
include "scripting/table.lua"
include "scripting/win_conditions/win_condition_functions.lua"
+include "scripting/win_conditions/territorial_functions.lua"
set_textdomain("win_conditions")
@@ -30,186 +31,45 @@
-- set the objective with the game type for all players
broadcast_objective("win_condition", wc_descname, wc_desc)
+ -- Configure how long the winner has to hold on to the territory
+ local time_to_keep_territory = 20 * 60 -- 20 minutes
+ -- time in secs, if == 0 -> victory
+ territory_points.remaining_time = time_to_keep_territory
+
-- Get all valueable fields of the map
- local fields = {}
- local map = wl.Game().map
- for x=0,map.width-1 do
- for y=0,map.height-1 do
- local f = map:get_field(x,y)
- if f then
- -- add this field to the list as long as it has not movecaps swim
- if not f:has_caps("swimmable") then
- if f:has_caps("walkable") then
- fields[#fields+1] = f
- else
- -- editor disallows placement of immovables on dead and acid fields
- if f.immovable then
- fields[#fields+1] = f
- end
- end
- end
- end
- end
- end
-
- -- these variables will be used once a player or team owns more than half
- -- of the map's area
- local currentcandidate = "" -- Name of Team or Player
- local candidateisteam = false
- local remaining_time = 10 -- (dummy) -- time in secs, if == 0 -> victory
-
- -- Find all valid teams
- local teamnumbers = {} -- array with team numbers
- for idx,p in ipairs(plrs) do
- local team = p.team
- if team > 0 then
- local found = false
- for idy,t in ipairs(teamnumbers) do
- if t == team then
- found = true
- break
- end
- end
- if not found then
- teamnumbers[#teamnumbers+1] = team
- end
- end
- end
-
- local _landsizes = {}
- local function _calc_current_landsizes()
- -- init the landsizes for each player
- for idx,plr in ipairs(plrs) do
- _landsizes[plr.number] = 0
- end
-
- for idx,f in ipairs(fields) do
- -- check if field is owned by a player
- local o = f.owner
- if o then
- local n = o.number
- _landsizes[n] = _landsizes[n] + 1
- end
- end
- end
-
- local function _calc_points()
- local teampoints = {} -- points of teams
- local maxplayerpoints = 0 -- the highest points of a player without team
- local maxpointsplayer = 0 -- the player
- local foundcandidate = false
-
- _calc_current_landsizes()
-
- for idx, p in ipairs(plrs) do
- local team = p.team
- if team == 0 then
- if maxplayerpoints < _landsizes[p.number] then
- maxplayerpoints = _landsizes[p.number]
- maxpointsplayer = p
- end
- else
- if not teampoints[team] then -- init the value
- teampoints[team] = 0
- end
- teampoints[team] = teampoints[team] + _landsizes[p.number]
- end
- end
-
- if maxplayerpoints > ( #fields / 2 ) then
- -- player owns more than half of the map's area
- foundcandidate = true
- if candidateisteam == false and currentcandidate == maxpointsplayer.name then
- remaining_time = remaining_time - 30
- else
- currentcandidate = maxpointsplayer.name
- candidateisteam = false
- remaining_time = 20 * 60 -- 20 minutes
- end
- else
- for idx, t in ipairs(teamnumbers) do
- if teampoints[t] > ( #fields / 2 ) then
- -- this team owns more than half of the map's area
- foundcandidate = true
- if candidateisteam == true and currentcandidate == t then
- remaining_time = remaining_time - 30
- else
- currentcandidate = t
- candidateisteam = true
- remaining_time = 20 * 60 -- 20 minutes
- end
- end
- end
- end
- if not foundcandidate then
- currentcandidate = ""
- candidateisteam = false
- remaining_time = 10
- end
- end
+ local fields = get_buildable_fields()
local function _send_state()
set_textdomain("win_conditions")
- local candidate = currentcandidate
- if candidateisteam then
- candidate = (_"Team %i"):format(currentcandidate)
- end
- local msg1 = p(_"%s owns more than half of the map’s area."):format(candidate)
- msg1 = msg1 .. p(ngettext("You’ve still got %i minute to prevent a victory.",
- "You’ve still got %i minutes to prevent a victory.",
- remaining_time / 60))
- :format(remaining_time / 60)
-
- local msg2 = p(_"You own more than half of the map’s area.")
- msg2 = msg2 .. p(ngettext("Keep it for %i more minute to win the game.",
- "Keep it for %i more minutes to win the game.",
- remaining_time / 60))
- :format(remaining_time / 60)
for idx, player in ipairs(plrs) do
- if candidateisteam and currentcandidate == player.team
- or not candidateisteam and currentcandidate == player.name then
- send_message(player, game_status.title, msg2, {popup = true})
+ local msg = ""
+ if territory_points.last_winning_team == player.team or territory_points.last_winning_player == player.number then
+ msg = msg .. winning_status_header() .. vspace(8)
else
- send_message(player, game_status.title, msg1, {popup = true})
+ msg = msg .. losing_status_header(plrs) .. vspace(8)
end
+ msg = msg .. vspace(8) .. game_status.body .. territory_status(fields, "has")
+ send_message(player, game_status.title, msg, {popup = true})
end
end
- -- Start a new coroutine that checks for defeated players
- run(function()
- while remaining_time ~= 0 do
- sleep(5000)
- check_player_defeated(plrs, lost_game.title, lost_game.body, wc_descname, wc_version)
- end
- end)
-
-- here is the main loop!!!
while true do
-- Sleep 30 seconds == STATISTICS_SAMPLE_TIME
sleep(30000)
-- Check if a player or team is a candidate and update variables
- _calc_points()
+ calculate_territory_points(fields, plrs, wc_descname, wc_version)
-- Do this stuff, if the game is over
- if remaining_time == 0 then
- for idx, p in ipairs(plrs) do
- p.see_all = 1
- if candidateisteam and currentcandidate == p.team
- or not candidateisteam and currentcandidate == p.name then
- p:send_message(won_game_over.title, won_game_over.body)
- wl.game.report_result(p, 1, make_extra_data(p, wc_descname, wc_version, {score=_landsizes[p.number]}))
- else
- p:send_message(lost_game_over.title, lost_game_over.body)
- wl.game.report_result(p, 0, make_extra_data(p, wc_descname, wc_version, {score=_landsizes[p.number]}))
- end
- end
+ if territory_points.remaining_time == 0 then
+ territory_game_over(fields, plrs, wc_descname, wc_version)
break
end
-- If there is a candidate, check whether we have to send an update
- if remaining_time % 300 == 0 then -- every 5 minutes (5 * 60 )
+ 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
_send_state()
end
end
=== modified file 'data/scripting/win_conditions/territorial_time.lua'
--- data/scripting/win_conditions/territorial_time.lua 2017-05-12 13:50:26 +0000
+++ data/scripting/win_conditions/territorial_time.lua 2018-09-28 18:09:51 +0000
@@ -9,6 +9,7 @@
include "scripting/messages.lua"
include "scripting/table.lua"
include "scripting/win_conditions/win_condition_functions.lua"
+include "scripting/win_conditions/territorial_functions.lua"
set_textdomain("win_conditions")
@@ -25,9 +26,7 @@
"that area for at least 20 minutes, or the one with the most territory " ..
"after 4 hours, whichever comes first."
)
-local wc_has_territory = _"%1$s has %2$3.0f%% of the land (%3$i of %4$i)."
-local wc_had_territory = _"%1$s had %2$3.0f%% of the land (%3$i of %4$i)."
-local team_str = _"Team %i"
+
return {
name = wc_name,
@@ -39,217 +38,40 @@
broadcast_objective("win_condition", wc_descname, wc_desc)
-- Get all valueable fields of the map
- local fields = {}
- local map = wl.Game().map
- for x=0,map.width-1 do
- for y=0,map.height-1 do
- local f = map:get_field(x,y)
- if f then
- -- add this field to the list as long as it has not movecaps swim
- if not f:has_caps("swimmable") then
- if f:has_caps("walkable") then
- fields[#fields+1] = f
- else
- -- editor disallows placement of immovables on dead and acid fields
- if f.immovable then
- fields[#fields+1] = f
- end
- end
- end
- end
- end
- end
+ local fields = get_buildable_fields()
-- variables to track the maximum 4 hours of gametime
local remaining_max_time = 4 * 60 * 60 -- 4 hours
- -- these variables will be used once a player or team owns more than half
- -- of the map's area
- local currentcandidate = "" -- Name of Team or Player
- local candidateisteam = false
- local remaining_time = 10 -- (dummy) -- time in secs, if == 0 -> victory
-
- -- Find all valid teams
- local teamnumbers = {} -- array with team numbers
- for idx,pl in ipairs(plrs) do
- local team = pl.team
- if team > 0 then
- local found = false
- for idy,t in ipairs(teamnumbers) do
- if t == team then
- found = true
- break
- end
- end
- if not found then
- teamnumbers[#teamnumbers+1] = team
- end
- end
- end
-
- local _landsizes = {}
- local function _calc_current_landsizes()
- -- init the landsizes for each player
- for idx,plr in ipairs(plrs) do
- _landsizes[plr.number] = 0
- end
-
- for idx,f in ipairs(fields) do
- -- check if field is owned by a player
- local o = f.owner
- if o then
- local n = o.number
- _landsizes[n] = _landsizes[n] + 1
- end
- end
- end
-
- local function _calc_points()
- local teampoints = {} -- points of teams
- local points = {} -- tracking points of teams and players without teams
- local maxplayerpoints = 0 -- the highest points of a player without team
- local maxpointsplayer = 0 -- the player
- local foundcandidate = false
-
- _calc_current_landsizes()
-
- for idx, pl in ipairs(plrs) do
- local team = pl.team
- if team == 0 then
- if maxplayerpoints < _landsizes[pl.number] then
- maxplayerpoints = _landsizes[pl.number]
- maxpointsplayer = pl
- end
- points[#points + 1] = { pl.name, _landsizes[pl.number] }
- else
- if not teampoints[team] then -- init the value
- teampoints[team] = 0
- end
- teampoints[team] = teampoints[team] + _landsizes[pl.number]
- end
- end
-
- if maxplayerpoints > ( #fields / 2 ) then
- -- player owns more than half of the map's area
- foundcandidate = true
- if candidateisteam == false and currentcandidate == maxpointsplayer.name then
- remaining_time = remaining_time - 30
- else
- currentcandidate = maxpointsplayer.name
- candidateisteam = false
- remaining_time = 20 * 60 -- 20 minutes
- end
- end
- for idx, t in ipairs(teamnumbers) do
- if teampoints[t] > ( #fields / 2 ) then
- -- this team owns more than half of the map's area
- foundcandidate = true
- if candidateisteam == true and currentcandidate == team_str:format(t) then
- remaining_time = remaining_time - 30
- else
- currentcandidate = team_str:format(t)
- candidateisteam = true
- remaining_time = 20 * 60 -- 20 minutes
- end
- end
- points[#points + 1] = { team_str:format(t), teampoints[t] }
- end
- if not foundcandidate then
- remaining_time = 10
- end
- return points
- end
-
- local function _percent(part, whole)
- return (part * 100) / whole
- end
-
- -- Helper function to get the points that the leader has
- local function _maxpoints(points)
- local max = 0
- for i=1,#points do
- if points[i][2] > max then max = points[i][2] end
- end
- return max
- end
-
- -- Helper function that returns a string containing the current
- -- land percentages of players/teams.
- local function _status(points, has_had)
- local msg = ""
- for i=1,#points do
- if (has_had == "has") then
- msg = msg ..
- li(
- (wc_has_territory):bformat(
- points[i][1],
- _percent(points[i][2], #fields),
- points[i][2],
- #fields))
- else
- msg = msg ..
- li(
- (wc_had_territory):bformat(
- points[i][1],
- _percent(points[i][2], #fields),
- points[i][2],
- #fields))
- end
-
- end
- return p(msg)
- end
-
- local function _send_state(points)
+ local function _send_state()
set_textdomain("win_conditions")
- local msg1 = p(_"%s owns more than half of the map’s area."):format(currentcandidate)
- msg1 = msg1 .. p(ngettext("You’ve still got %i minute to prevent a victory.",
- "You’ve still got %i minutes to prevent a victory.",
- remaining_time // 60))
- :format(remaining_time // 60)
- msg1 = p(msg1)
-
- local msg2 = p(_"You own more than half of the map’s area.")
- msg2 = msg2 .. p(ngettext("Keep it for %i more minute to win the game.",
- "Keep it for %i more minutes to win the game.",
- remaining_time // 60))
- :format(remaining_time // 60)
- msg2 = p(msg2)
-
- for idx, pl in ipairs(plrs) do
+
+ local remaining_max_minutes = remaining_max_time // 60
+ for idx, player in ipairs(plrs) do
local msg = ""
- if remaining_time < remaining_max_time and _maxpoints(points) > ( #fields / 2 ) then
- if candidateisteam and currentcandidate == team_str:format(pl.team)
- or not candidateisteam and currentcandidate == pl.name then
- msg = msg .. msg2 .. vspace(8)
+ if territory_points.remaining_time < remaining_max_time and
+ (territory_points.last_winning_team >= 0 or territory_points.last_winning_player >= 0) then
+ if territory_points.last_winning_team == player.team or territory_points.last_winning_player == player.number then
+ msg = msg .. winning_status_header() .. vspace(8)
else
- msg = msg .. msg1 .. vspace(8)
+ msg = msg .. losing_status_header(plrs) .. vspace(8)
end
-- TRANSLATORS: Refers to "You own more than half of the map’s area. Keep it for x more minute(s) to win the game."
msg = msg .. p((ngettext("Otherwise the game will end in %i minute.",
"Otherwise the game will end in %i minutes.",
- remaining_max_time // 60))
- :format(remaining_max_time // 60))
+ remaining_max_minutes))
+ :format(remaining_max_minutes))
else
msg = msg .. p((ngettext("The game will end in %i minute.",
"The game will end in %i minutes.",
- remaining_max_time // 60))
- :format(remaining_max_time // 60))
+ remaining_max_minutes))
+ :format(remaining_max_minutes))
end
- msg = msg .. vspace(8) .. game_status.body .. _status(points, "has")
- send_message(pl, game_status.title, msg, {popup = true})
+ msg = msg .. vspace(8) .. game_status.body .. territory_status(fields, "has")
+ send_message(player, game_status.title, msg, {popup = true})
end
end
- -- Start a new coroutine that checks for defeated players
- run(function()
- while remaining_time ~= 0 and remaining_max_time > 0 do
- sleep(5000)
- check_player_defeated(plrs, lost_game.title,
- lost_game.body, wc_descname, wc_version)
- end
- end)
-
-- here is the main loop!!!
while true do
-- Sleep 30 seconds == STATISTICS_SAMPLE_TIME
@@ -259,44 +81,22 @@
-- Check if a player or team is a candidate and update variables
-- Returns the names and points for the teams and players without a team
- local points = _calc_points()
+ calculate_territory_points(fields, plrs, wc_descname, wc_version)
-- Game is over, do stuff after loop
- if remaining_time <= 0 or remaining_max_time <= 0 then break end
+ if territory_points.remaining_time <= 0 or remaining_max_time <= 0 then break end
-- at the beginning send remaining max time message only each 30 minutes
-- if only 30 minutes or less are left, send each 5 minutes
-- also check if there is a candidate and we need to send an update
if ((remaining_max_time < (30 * 60) and remaining_max_time % (5 * 60) == 0)
or remaining_max_time % (30 * 60) == 0)
- or remaining_time % 300 == 0 then
- _send_state(points)
+ or territory_points.remaining_time % 300 == 0 then
+ _send_state()
end
end
- local points = _calc_points()
- table.sort(points, function(a,b) return a[2] > b[2] end)
-
-- Game has ended
- for idx, pl in ipairs(plrs) do
- pl.see_all = 1
-
- maxpoints = points[1][2]
- local wonmsg = won_game_over.body
- wonmsg = wonmsg .. game_status.body
- local lostmsg = lost_game_over.body
- lostmsg = lostmsg .. game_status.body
- for i=1,#points do
- if points[i][1] == team_str:format(pl.team) or points[i][1] == pl.name then
- if points[i][2] >= maxpoints then
- pl:send_message(won_game_over.title, wonmsg .. _status(points, "had"))
- wl.game.report_result(pl, 1, make_extra_data(pl, wc_descname, wc_version, {score=_landsizes[pl.number]}))
- else
- pl:send_message(lost_game_over.title, lostmsg .. _status(points, "had"))
- wl.game.report_result(pl, 0, make_extra_data(pl, wc_descname, wc_version, {score=_landsizes[pl.number]}))
- end
- end
- end
- end
+ territory_game_over(fields, plrs, wc_descname, wc_version)
end
}
=== modified file 'data/scripting/win_conditions/win_condition_functions.lua'
--- data/scripting/win_conditions/win_condition_functions.lua 2017-05-11 17:29:55 +0000
+++ data/scripting/win_conditions/win_condition_functions.lua 2018-09-28 18:09:51 +0000
@@ -144,3 +144,96 @@
local plrs = wl.Game().players
plrs[1]:add_objective(name, title, body)
end
+
+
+-- RST
+-- .. function:: rank_players(all_player_points, plrs)
+--
+-- Rank the players and teams according to the highest points
+--
+-- :arg all_player_points: A table of ``playernumber = points`` entries for all players
+-- :arg plrs: A table of all Player objects
+--
+-- :returns: A table with ranked player and team points, sorted by points descending. Example:
+--
+-- .. code-block:: lua
+--
+-- {
+-- -- A player without team
+-- {
+-- team = 0,
+-- points = 1000,
+-- players = {
+-- { "number" = 5, "points" = 1000 }
+-- }
+-- },
+-- -- This team has a draw with player 5
+-- {
+-- team = 1,
+-- points = 1000,
+-- players = {
+-- { "number" = 2, "points" = 500 }
+-- { "number" = 3, "points" = 400 }
+-- { "number" = 4, "points" = 100 }
+-- },
+-- -- Another player without team
+-- {
+-- team = 0,
+-- points = 800,
+-- players = {
+-- { "number" = 1, "points" = 800 }
+-- }
+-- },
+-- }
+--
+function rank_players(all_player_points, plrs)
+ local ranked_players_and_teams = {}
+ local team_points = {}
+
+ -- Add points for players without teams and calculate team points
+ for idx, player in ipairs(plrs) do
+ local player_points = all_player_points[player.number]
+ local team = player.team
+ if team == 0 then
+ -- Player without team - add it directly
+ local team_table = {
+ team = 0,
+ points = player_points,
+ players = {
+ { number = player.number, points = player_points }
+ }
+ }
+ table.insert(ranked_players_and_teams, team_table)
+ else
+ -- Team player - add to team points
+ if not team_points[team] then
+ team_points[team] = 0
+ end
+ team_points[team] = team_points[team] + player_points
+ end
+ end
+
+ -- Add points for teams and their players
+ for team, points in pairs(team_points) do
+ local team_table = {
+ team = team,
+ points = points,
+ players = {}
+ }
+ for idx, player in ipairs(plrs) do
+ if player.team == team then
+ table.insert(team_table.players, { number = player.number, points = all_player_points[player.number] })
+ end
+ end
+ table.insert(ranked_players_and_teams, team_table)
+ end
+
+ -- Sort the players by points descending
+ for ids, team in pairs(ranked_players_and_teams) do
+ table.sort(team.players, function(a,b) return a["points"] > b["points"] end)
+ end
+
+ -- Sort the teams by points descending
+ table.sort(ranked_players_and_teams, function(a,b) return a["points"] > b["points"] end)
+ return ranked_players_and_teams
+end
=== modified file 'src/scripting/lua_map.cc'
--- src/scripting/lua_map.cc 2018-09-11 07:24:27 +0000
+++ src/scripting/lua_map.cc 2018-09-28 18:09:51 +0000
@@ -5940,6 +5940,7 @@
PROP_RO(LuaField, initial_resource_amount),
PROP_RO(LuaField, claimers),
PROP_RO(LuaField, owner),
+ PROP_RO(LuaField, is_buildable),
{nullptr, nullptr, nullptr},
};
@@ -6267,6 +6268,23 @@
}
/* RST
+ .. attribute:: is_buildable
+
+ Returns :const:`true` if a flag or building could be built on this field,
+ independently of whether anybody currently owns this field.
+*/
+int LuaField::get_is_buildable(lua_State* L) {
+ const NodeCaps caps = fcoords(L).field->nodecaps();
+ const bool is_buildable = (caps & BUILDCAPS_FLAG)
+ || (caps & BUILDCAPS_SMALL)
+ || (caps & BUILDCAPS_MEDIUM)
+ || (caps & BUILDCAPS_BIG)
+ || (caps & BUILDCAPS_MINE);
+ lua_pushboolean(L, is_buildable);
+ return 1;
+}
+
+/* RST
.. attribute:: claimers
(RO) An :class:`array` of players that have military influence over this
=== modified file 'src/scripting/lua_map.h'
--- src/scripting/lua_map.h 2018-09-02 11:44:52 +0000
+++ src/scripting/lua_map.h 2018-09-28 18:09:51 +0000
@@ -1413,6 +1413,7 @@
int get_initial_resource_amount(lua_State*);
int get_claimers(lua_State*);
int get_owner(lua_State*);
+ int get_is_buildable(lua_State*);
/*
* Lua methods
Follow ups