share/hedgewars/Data/Scripts/Multiplayer/Capture_the_Flag.lua
author Wuzzy <Wuzzy2@mail.ru>
Mon, 16 Sep 2019 17:33:49 +0200
changeset 15410 8504fee3b601
parent 15252 515a4a317e52
child 15788 acf70c44065b
permissions -rw-r--r--
Racer: Fix weird water splashes after waypoint placement Does not affect official racer, as only waypoint placement is touched. The reason was that the air attack gear sometimes was not deleted fast enough so it might occassionally drop some air bombs (these are deleted now). Also, the airplane position was set to water level, which caused another water splash.

---------------------------------------
-- 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 winningClan = -1
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
------------------------

local RankTeams = function(teamList)
	local teamRank = function(a, b)
		if a.score ~= b.score then
			return a.score > b.score
		else
			return a.clan > b.clan
		end
	end
	table.sort(teamList, teamRank)
	local rank, plusRank, score, clan
	for i=1, #teamList do
		if i == 1 then
			rank = 1
			plusRank = 1
			score = teamList[i].score
			clan = teamList[i].clan
		end
		if (teamList[i].score < score) then
			rank = rank + plusRank
			plusRank = 1
		end
		if (teamList[i].score == score and teamList[i].clan ~= clan) then
			plusRank = plusRank + 1
		end
		teamList[i].rank = rank
		score = teamList[i].score
		clan = teamList[i].clan
	end

	for i=1, #teamList do
		SendStat(siPointType, "!POINTS")
		SendStat(siTeamRank, tostring(teamList[i].rank))
		SendStat(siPlayerKills, tostring(teamList[i].score), teamList[i].name)
	end
end

function CheckScore(clanID)

	if fCaptures[clanID] == captureLimit then
		gameOver = true
		winningClan = clanID
		-- 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]) ~= winningClan 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
		RankTeams(teamList)

		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, 11, 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=0, numhhs-1 do
		-- Hogs are resurrected for free, so this is pointless
		AddAmmo(hhs[h], amResurrector, 0)
		-- Remove suicidal weapons as they might wipe out the team
		AddAmmo(hhs[h], amKamikaze, 0)
		AddAmmo(hhs[h], amPiano, 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
	if gameOver and GetHogClan(gear) ~= winningClan then
		SetEffect(gear, heResurrectable, 0)
		SetHealth(gear, 0)
	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