---------------------------------------
-- CAPTURE_THE_FLAG GAMEPLAY MODE 0.5
-- by mikade
---------------------------------------
---- Script parameter
-- With “captures=<number>” you can set your own capture limit, e.g. “captures=5” for 5 captures.
-- Version History
---------
-- 0.1
---------
-- [conversion from map-dependant CTF_Blizzard to map independant Capture the Flag]
-- added an intial starting stage where flagspawn is decided by the players (weapon set will require a jetpack unless I set)
-- changed the flag from a crate to a visual gear, and all associated methods and checks relating to flags (five hours later, lol)
-- changed starting/respawning positioning to accommodate different map sizes
-- added another circle to mark flag spawn
-- added gameFlag filter
-- changed scoring feedback
-- cleaned up some code
-- removing own flag from spawning point no longer possible
-- destroying flags no longer possible.
-- added basic glowing circle effect to spawn area
-- added expanding circle to fgear itself
-- removed teleporters
-- removed random crate drops (this should be decided by scheme)
-- removed set map criteria like minesNum, turnTime, explosives etc. except for sudden death
-- removed weapon defintions
-- removed placement and respawning methods, hopefully divideTeams will have this covered
---------
-- 0.2
---------
-- [now with user friendliness]
-- flag is now placed wherever you end up at the end of your first turn, this ensures that it is always placed by turn 3
-- removed a bunch of backup code and no-longer needed variables / methods from CTF_Blizzard days
-- removed an aura that was still mistakenly hanging about
-- added an in-game note about placements
-- added an in-game note about the rules of the game
-- added translation support and loc()'ed everything
-- changed things so the seed is no longer always the same...
-- In this version:
---------
-- 0.3
---------
-- [fufufufu kamikaze fix]
-- added nill checks to make sure the player doesn't generate errors by producing a nil value in hhs[] when he uses kamikaze
-- added a check to make sure the player doesn't kamikaze straight down and make the flag's starting point underwater
-- added a check to make sure the player drops the flag if he has it and he uses kamikaze
--------
-- 0.4
--------
-- remove user-branding and version numbers
-- removed some stuff that wasn't needed
-- fix piano strike exploit
-- changed delay to allow for better portals
-- changed starting feedback a little
-- increased the radius around the circle indicating the flag thief so that it doesn't obscure his health
--------
-- 0.5
--------
-- add support for more players
-- allow limited sudden death
-- stop TimeBox ruining my life
-- profit???
-----------------
--SCRIPT BEGINS
-----------------
-- enable awesome translaction support so we can use loc() wherever we want
HedgewarsScriptLoad("/Scripts/Locale.lua")
HedgewarsScriptLoad("/Scripts/Params.lua")
---------------------------------------------------------------
----------lots of bad variables and things
----------because someone is too lazy
----------to read about tables properly
------------------ "Oh well, they probably have the memory"
local gameStarted = false
local gameOver = false
local captureLimit = 3
--------------------------
-- hog and team tracking variales
--------------------------
local numhhs = 0 -- store number of hedgehogs
local hhs = {} -- store hedgehog gears
local teamSize = {} -- store how many hogs per team
local teamIndex = {} -- at what point in the hhs{} does each team begin
local mostCapturesHogName = nil -- name of hog who holds the record of most flags captured
local mostCapturesHogTeam = nil -- name of team who holds the record of most flags captured
local mostCaptures = 0 -- number of most per-hog captures
local capturesPerHog = {}
-------------------
-- flag variables
-------------------
local fGear = {} -- pointer to the visual gears that represent the flag
local fGearX = {}
local fGearY = {}
local fThief = {} -- pointer to the hogs who stole the flags
local fThiefFlag = {} -- contains the stolen flag type of fThief
local fIsMissing = {} -- have the flags been destroyed or captured
local fNeedsRespawn = {} -- do the flags need to be respawned
local fCaptures = {} -- the team "scores" how many captures
local fSpawnX = {} -- spawn X for flags
local fSpawnY = {} -- spawn Y for flags
local fThiefX = {}
local fThiefY = {}
local fSpawnC = {} -- spawn circle marker
local fCirc = {} -- flag/carrier marker circles
local fCol = {} -- colour of the clans
local fGearRad = 0
local fGearRadMin = 5
local fGearRadMax = 33
local fGearTimer = 0
------------------------
--flag methods
------------------------
function CheckScore(clanID)
if fCaptures[clanID] == captureLimit then
gameOver = true
-- Capture limit reached! We have a winner!
for i = 0, (numhhs-1) do
if hhs[i] ~= nil then
-- Kill all losers
if GetHogClan(hhs[i]) ~= clanID then
SetEffect(hhs[i], heResurrectable, 0)
SetHealth(hhs[i],0)
end
end
end
if CurrentHedgehog ~= nil then
AddCaption(string.format(loc("Victory for %s!"), GetHogTeamName(CurrentHedgehog)), capcolDefault, capgrpGameState)
updateScores()
end
-- Calculate team rankings
local teamList = {}
for i=0, TeamsCount-1 do
local name = GetTeamName(i)
local clan = GetTeamClan(name)
table.insert(teamList, { score = fCaptures[clan], name = name, clan = clan })
end
local teamRank = function(a, b)
return a.score > b.score
end
table.sort(teamList, teamRank)
for i=1, #teamList do
SendStat(siPointType, "!POINTS")
SendStat(siPlayerKills, tostring(teamList[i].score), teamList[i].name)
end
if mostCaptures >= 2 then
SendStat(siCustomAchievement, string.format(loc("%s (%s) has captured the flag %d times."), mostCapturesHogName, mostCapturesHogTeam, mostCaptures))
end
end
end
function DoFlagStuff(flag, flagClan)
if not CurrentHedgehog then
return
end
local wtf = flagClan
local thiefClan
for i=0, ClansCount - 1 do
if CurrentHedgehog == fThief[i] then
thiefClan = i
end
end
-- player has successfully captured the enemy flag
if (GetHogClan(CurrentHedgehog) == flagClan) and (thiefClan ~= nil) and (fIsMissing[flagClan] == false) then
fIsMissing[thiefClan] = false
fNeedsRespawn[thiefClan] = true
fCaptures[flagClan] = fCaptures[flagClan] +1
AddCaption(string.format(loc("%s has scored!"), GetHogName(CurrentHedgehog)), capcolDefault, capgrpGameState)
updateScores()
PlaySound(sndHomerun)
fThief[thiefClan] = nil -- player no longer has the enemy flag
fThiefFlag[flagClan] = nil
capturesPerHog[CurrentHedgehog] = capturesPerHog[CurrentHedgehog] + 1
if capturesPerHog[CurrentHedgehog] > mostCaptures then
mostCaptures = capturesPerHog[CurrentHedgehog]
mostCapturesHogName = GetHogName(CurrentHedgehog)
mostCapturesHogTeam = GetHogTeamName(CurrentHedgehog)
end
CheckScore(flagClan)
--if the player is returning the flag
elseif (GetHogClan(CurrentHedgehog) == flagClan) and (fIsMissing[flagClan] == true) then
DeleteVisualGear(fGear[flagClan])
fGear[flagClan] = nil -- the flag has now disappeared
fNeedsRespawn[flagClan] = true
HandleRespawns() -- this will set fIsMissing[flagClan] to false :)
AddCaption(loc("Flag returned!"), capcolDefault, capgrpMessage2)
--if the player is taking the enemy flag (not possible if already holding a flag)
elseif GetHogClan(CurrentHedgehog) ~= flagClan and (thiefClan == nil) then
DeleteVisualGear(fGear[flagClan])
fGear[flagClan] = nil -- the flag has now disappeared
fIsMissing[flagClan] = true
for i = 0,numhhs-1 do
if CurrentHedgehog ~= nil then
if CurrentHedgehog == hhs[i] then
fThief[flagClan] = hhs[i]
fThiefFlag[flagClan] = flagClan
end
end
end
AddCaption(loc("Flag captured!"), capcolDefault, capgrpMessage2)
end
end
function CheckFlagProximity()
for i = 0, ClansCount-1 do
if fGear[i] ~= nil then
local g1X = fGearX[i]
local g1Y = fGearY[i]
local g2X, g2Y = GetGearPosition(CurrentHedgehog)
local q = g1X - g2X
local w = g1Y - g2Y
local dist = (q*q) + (w*w)
if dist < 500 then
DoFlagStuff(fGear[i], i)
end
end
end
end
function HandleRespawns()
for i = 0, ClansCount-1 do
if fNeedsRespawn[i] == true then
fGear[i] = AddVisualGear(fSpawnX[i],fSpawnY[i],vgtCircle,0,true)
fGearX[i] = fSpawnX[i]
fGearY[i] = fSpawnY[i]
fNeedsRespawn[i] = false
fIsMissing[i] = false -- new, this should solve problems of a respawned flag being "returned" when a player tries to score
AddCaption(loc("Flag respawned!"), capcolDefault, capgrpMessage2)
end
end
end
-- Advance the clan score graph by one step
function DrawScores()
local clansUsed = {}
for i=0, TeamsCount-1 do
local team = GetTeamName(i)
local clan = GetTeamClan(team)
if not clansUsed[clan] then
local captures = fCaptures[clan]
SendStat(siClanHealth, captures, team)
clansUsed[clan] = true
end
end
end
function FlagThiefDead(gear)
local thiefClan
local stolenFlagClan
for i=0, ClansCount-1 do
if (gear == fThief[i]) then
thiefClan = i
stolenFlagClan = fThiefFlag[i]
break
end
end
if stolenFlagClan ~= nil then
-- falls into water
if (LAND_HEIGHT - fThiefY[thiefClan]) < 15 then
fIsMissing[stolenFlagClan] = true
fNeedsRespawn[stolenFlagClan] = true
HandleRespawns()
else --normally
fGearX[stolenFlagClan] = fThiefX[thiefClan]
fGearY[stolenFlagClan] = fThiefY[thiefClan]
fGear[stolenFlagClan] = AddVisualGear(fGearX[stolenFlagClan], fGearY[stolenFlagClan], vgtCircle, 0, true)
end
AddVisualGear(fThiefX[thiefClan], fThiefY[thiefClan], vgtBigExplosion, 0, false)
fThief[thiefClan] = nil
end
end
function HandleCircles()
fGearTimer = fGearTimer + 1
if fGearTimer == 50 then
fGearTimer = 0
fGearRad = fGearRad + 1
if fGearRad > fGearRadMax then
fGearRad = fGearRadMin
end
end
for i = 0, ClansCount-1 do
if fIsMissing[i] == false then -- draw a flag marker at the flag's spawning place
SetVisualGearValues(fCirc[i], fSpawnX[i],fSpawnY[i], 20, 20, 0, 10, 0, 33, 3, fCol[i])
if fGear[i] ~= nil then -- draw the flag gear itself
SetVisualGearValues(fGear[i], fSpawnX[i],fSpawnY[i], 20, 200, 0, 0, 100, fGearRad, 2, fCol[i])
end
elseif (fIsMissing[i] == true) and (fNeedsRespawn[i] == false) then
if fThief[i] ~= nil then -- draw circle round flag carrier -- 33
SetVisualGearValues(fCirc[i], fThiefX[i], fThiefY[i], 20, 200, 0, 0, 100, 50, 3, fCol[i])
elseif fThief[i] == nil then -- draw cirle round dropped flag
SetVisualGearValues(fCirc[i], fGearX[i], fGearY[i], 20, 200, 0, 0, 100, 33, 3, fCol[i])
if fGear[i] ~= nil then -- flag gear itself
SetVisualGearValues(fGear[i], fGearX[i], fGearY[i], 20, 200, 0, 0, 100, fGearRad, 2, fCol[i])
end
end
end
if fNeedsRespawn[i] == true then -- if the flag has been destroyed, no need for a circle
SetVisualGearValues(fCirc[i], fSpawnX[i],fSpawnY[i], 20, 200, 0, 0, 100, 0, 0, fCol[i])
end
end
end
------------------------
-- general methods
------------------------
function CheckDistance(gear1, gear2)
local g1X, g1Y = GetGearPosition(gear1)
local g2X, g2Y = GetGearPosition(gear2)
g1X = g1X - g2X
g1Y = g1Y - g2Y
local dist = (g1X*g1X) + (g1Y*g1Y)
return dist
end
function RebuildTeamInfo()
-- make a list of teams
for i = 0, (TeamsCount-1) do
teamSize[i] = 0
teamIndex[i] = 0
end
-- find out how many hogs per team, and the index of the first hog in hhs
for i = 0, (TeamsCount-1) do
for z = 0, numhhs-1 do
if GetHogTeamName(hhs[z]) == GetTeamName(i) then
if teamSize[i] == 0 then
teamIndex[i] = z -- should give starting index
end
teamSize[i] = teamSize[i] + 1
--add a pointer so this hog appears at i in hhs
end
end
end
end
function StartTheGame()
gameStarted = true
AddCaption(loc("Game Started!"), capcolDefault, capgrpGameState)
for i = 0, ClansCount-1 do
fGear[i] = AddVisualGear(fSpawnX[i],fSpawnY[i],vgtCircle,0,true)
fCirc[i] = AddVisualGear(fSpawnX[i],fSpawnY[i],vgtCircle,0,true)
fSpawnC[i] = AddVisualGear(fSpawnX[i],fSpawnY[i],vgtCircle,0,true)
fGearX[i] = fSpawnX[i]
fGearY[i] = fSpawnY[i]
fCol[i] = GetClanColor(i)
fIsMissing[i] = false
fNeedsRespawn[i] = false
fCaptures[i] = 0
SetVisualGearValues(fSpawnC[i], fSpawnX[i],fSpawnY[i], 20, 100, 0, 10, 0, 75, 5, fCol[i])
end
end
------------------------
-- game methods
------------------------
function onParameters()
parseParams()
if params["captures"] ~= nil then
local s = string.match(params["captures"], "(%d*)")
if s ~= nil then
captureLimit = math.max(1, tonumber(s))
end
end
end
function onGameInit()
DisableGameFlags(gfKing, gfAISurvival)
EnableGameFlags(gfDivideTeams)
-- Disable Sudden Death
WaterRise = 0
HealthDecrease = 0
end
function showCTFMission()
local captures
if captureLimit == 1 then
captures = string.format(loc("- First clan to capture the flag wins"), captureLimit)
else
captures = string.format(loc("- First clan to score %d captures wins"), captureLimit)
end
local rules = loc("Rules:") .. "|" ..
loc("- Place your clan flag at the end of your first turn") .. "|" ..
loc("- Return the enemy flag to your base to score") .."|"..
captures .. "|" ..
loc("- You may only score when your flag is in your base") .."|"..
loc("- Hogs will drop the flag when killed") .."|"..
loc("- Dropped flags may be returned or recaptured").."|"..
loc("- Hogs will be revived")
ShowMission(loc("Capture The Flag"), loc("A Hedgewars minigame"), rules, 0, 0)
end
function updateScores()
for i=0, TeamsCount-1 do
local team = GetTeamName(i)
local clan = GetTeamClan(team)
SetTeamLabel(team, tostring(fCaptures[clan]))
end
end
function onGameStart()
showCTFMission()
RebuildTeamInfo()
for i=0, ClansCount-1 do
fCaptures[i] = 0
end
for h=1, numhhs do
-- Hogs are resurrected for free, so this is pointless
AddAmmo(hhs[h], amResurrector, 0)
end
updateScores()
SendStat(siGraphTitle, loc("Score graph"))
SendHealthStatsOff()
SendRankingStatsOff()
end
function onNewTurn()
if gameStarted == true and not gameOver then
HandleRespawns()
end
local flagsPlaced = 0
for i=0, ClansCount-1 do
if fSpawnX[i] and fSpawnY[i] then
flagsPlaced = flagsPlaced + 1
end
end
if not gameStarted and flagsPlaced == ClansCount then
StartTheGame()
end
end
function onEndTurn()
-- if the game hasn't started yet, keep track of where we are gonna put the flags on turn end
if not gameStarted and CurrentHedgehog ~= nil then
local clan = GetHogClan(CurrentHedgehog)
if GetX(CurrentHedgehog) and not fSpawnX[clan] then
fSpawnX[clan] = GetX(CurrentHedgehog)
fSpawnY[clan] = GetY(CurrentHedgehog)
end
end
if gameStarted == true then
DrawScores()
end
end
function onGameTick()
for i = 0, ClansCount-1 do
if fThief[i] ~= nil then
fThiefX[i] = GetX(fThief[i])
fThiefY[i] = GetY(fThief[i])
end
end
if gameStarted == true and not gameOver then
HandleCircles()
if CurrentHedgehog ~= nil then
CheckFlagProximity()
end
end
end
function onGearResurrect(gear)
if GetGearType(gear) == gtHedgehog then
-- mark the flag thief as dead if he needed a respawn
for i = 0, ClansCount-1 do
if gear == fThief[i] then
FlagThiefDead(gear)
end
end
end
end
function InABetterPlaceNow(gear)
for h = 0, (numhhs-1) do
if gear == hhs[h] then
for i = 0, ClansCount-1 do
if gear == fThief[i] then
FlagThiefDead(gear)
end
end
hhs[h] = nil
end
end
end
function onHogHide(gear)
InABetterPlaceNow(gear)
end
function onHogRestore(gear)
for i = 0, (numhhs-1) do
if (hhs[i] == nil) then
hhs[i] = gear
break
end
end
end
function onHogAttack(ammoType)
if not gameStarted and ammoType == amTardis then
local i = GetHogClan(CurrentHedgehog)
fSpawnX[i] = GetX(CurrentHedgehog)
fSpawnY[i] = GetY(CurrentHedgehog)
end
end
function onGearAdd(gear)
if GetGearType(gear) == gtHedgehog then
hhs[numhhs] = gear
capturesPerHog[gear] = 0
numhhs = numhhs + 1
SetEffect(gear, heResurrectable, 1)
elseif GetGearType(gear) == gtPiano then
for i = 0, ClansCount-1 do
if CurrentHedgehog == fThief[i] then
FlagThiefDead(CurrentHedgehog)
end
end
end
end
function onGearDelete(gear)
if GetGearType(gear) == gtHedgehog then
InABetterPlaceNow(gear)
elseif GetGearType(gear) == gtKamikaze and not gameStarted then
local i = GetHogClan(CurrentHedgehog)
if i <= 1 then
fSpawnX[i] = GetX(CurrentHedgehog)
fSpawnY[i] = GetY(CurrentHedgehog)
end
end
end