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
_______________________________________________ Mailing list: https://launchpad.net/~widelands-dev Post to : widelands-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~widelands-dev More help : https://help.launchpad.net/ListHelp