share/hedgewars/Data/Scripts/RopeKnocking.lua
changeset 15490 0039baa9895e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Scripts/RopeKnocking.lua	Mon Oct 28 20:40:07 2019 +0100
@@ -0,0 +1,441 @@
+HedgewarsScriptLoad("/Scripts/Locale.lua")
+HedgewarsScriptLoad("/Scripts/Utils.lua")
+
+local hhs = {}
+local deadHogs = {}
+local missionWon = nil
+local endTimer = 1000
+local hogsKilled = 0
+local totalEnemies = 0
+local finishTime
+local playerFail = false
+local ropeGear = nil
+local endGameCalled = false
+local missionEndHandled = false
+local valkyriesTimer = -1
+
+local valkyriesDuration = 20000
+local timeBonus = 6000
+local killBonus = 6000
+local playValkyries = false
+
+local extraTime
+
+local playerTeamName
+local missionName = loc("Rope-knocking Challenge")
+-- Mission type:
+-- 0 = none (no special handling)
+-- 1 = challenge (saves mission vars)
+local missionType = 1
+
+local function getKillScore()
+	return div(hogsKilled * killBonus, totalEnemies)
+end
+
+local function protectEnemies()
+	-- Protect enemy hogs
+	for i=1, totalEnemies do
+		if hhs[i] and GetHealth(hhs[i]) then
+			SetEffect(hhs[i], heInvulnerable, 1)
+			SetEffect(hhs[i], heResurrectable, 1)
+		end
+	end
+end
+
+local function killStr(killed, total, score)
+	if total == 16 then
+		return string.format(loc("You have killed %d of 16 hedgehogs (+%d points)."), killed, score)
+	else
+		return string.format(loc("You have killed %d of %d hedgehogs (+%d points)."), killed, total, score)
+	end
+end
+
+local function gameOver()
+	StopMusicSound(sndRideOfTheValkyries)
+	valkyriesTimer = -1
+	missionWon = false
+	SendStat(siGameResult, loc("Challenge over!"))
+	local score = getKillScore()
+	SendStat(siCustomAchievement, killStr(hogsKilled, totalEnemies, score))
+	SendStat(siPointType, "!POINTS")
+	SendStat(siPlayerKills, tostring(score), playerTeamName)
+	protectEnemies()
+	if not endGameCalled then
+		EndGame()
+		endGameCalled = true
+	end
+	if missionType == 1 then
+		-- Update highscore
+		updateChallengeRecord("Highscore", score)
+	end
+end
+
+local function victory(onVictory)
+	missionWon = true
+	local e = 0
+	if extraTime then
+		e = extraTime
+	end
+	local totalTime = TurnTime + e * totalEnemies
+	local completeTime = (totalTime - finishTime) / 1000
+	ShowMission(missionName, loc("Challenge completed!"), loc("Congratulations!") .. "|" .. string.format(loc("Completion time: %.2fs"), completeTime), 0, 0)
+	PlaySound(sndHomerun)
+	-- Protect player hog
+	if hhs[0] and GetHealth(hhs[0]) then
+		SetEffect(hhs[0], heInvulnerable, 1)
+		SetEffect(hhs[0], heResurrectable, 1)
+	end
+	SendStat(siGameResult, loc("Challenge completed!"))
+	local hogScore = getKillScore()
+	local timeScore = div(finishTime * timeBonus, totalTime)
+	local score = hogScore + timeScore
+	SendStat(siCustomAchievement, killStr(hogsKilled, totalEnemies, hogScore))
+	SendStat(siCustomAchievement, string.format(loc("You have completed this challenge in %.2f s (+%d points)."), completeTime, timeScore))
+	SendStat(siPointType, "!POINTS")
+	SendStat(siPlayerKills, tostring(score), playerTeamName)
+	SetTeamLabel(playerTeamName, tostring(score))
+	SetTurnTimeLeft(MAX_TURN_TIME)
+
+	if missionType == 1 then
+		-- Update highscore
+		updateChallengeRecord("Highscore", score)
+	end
+	if onVictory then
+		onVictory()
+	end
+end
+
+local function knockTaunt()
+	local r = math.random(0,23)
+	local taunt
+	if r == 0 then taunt =		loc("%s has been knocked out.")
+	elseif r == 1 then taunt =	loc("%s hit the ground.")
+	elseif r == 2 then taunt =	loc("%s splatted.")
+	elseif r == 3 then taunt =	loc("%s was smashed.")
+	elseif r == 4 then taunt =	loc("%s felt unstable.")
+	elseif r == 5 then taunt =	loc("%s exploded.")
+	elseif r == 6 then taunt =	loc("%s fell from a high cliff.")
+	elseif r == 7 then taunt =	loc("%s goes the way of the lemming.")
+	elseif r == 8 then taunt =	loc("%s was knocked away.")
+	elseif r == 9 then taunt =	loc("%s was really unlucky.")
+	elseif r == 10 then taunt =	loc("%s felt victim to rope-knocking.")
+	elseif r == 11 then taunt =	loc("%s had no chance.")
+	elseif r == 12 then taunt =	loc("%s was a good target.")
+	elseif r == 13 then taunt =	loc("%s spawned at a really bad position.")
+	elseif r == 14 then taunt =	loc("%s was doomed from the beginning.")
+	elseif r == 15 then taunt =	loc("%s has fallen victim to gravity.")
+	elseif r == 16 then taunt =	loc("%s hates Newton.")		-- Isaac Newton
+	elseif r == 17 then taunt =	loc("%s had it coming.")
+	elseif r == 18 then taunt =	loc("%s is eliminated!")
+	elseif r == 19 then taunt =	loc("%s fell too fast.")
+	elseif r == 20 then taunt =	loc("%s flew like a rock.")
+	elseif r == 21 then taunt =	loc("%s stumbled.")
+	elseif r == 22 then taunt =	loc("%s was shoved away.")
+	elseif r == 23 then taunt =	loc("%s didn't expect that.")
+	end
+	return taunt
+end
+
+local function declareEnemyKilled(gear, onVictory)
+	if deadHogs[gear] or playerFail then
+		return
+	end
+	deadHogs[gear] = true
+	hogsKilled = hogsKilled + 1
+
+	-- Award extra time, if available
+	if extraTime and extraTime ~= 0 then
+		SetTurnTimeLeft(TurnTimeLeft + extraTime)
+		AddCaption(string.format(loc("+%d seconds!"), div(extraTime, 1000)), 0xFFFFFFFF, capgrpMessage2)
+	end
+
+	SetTeamLabel(playerTeamName, tostring(getKillScore()))
+
+	if hogsKilled == totalEnemies - 1 then
+		if playValkyries then
+			PlayMusicSound(sndRideOfTheValkyries)
+			valkyriesTimer = valkyriesDuration
+		end
+	elseif hogsKilled == totalEnemies then
+		finishTime = TurnTimeLeft
+		victory(onVictory)
+	end
+end
+
+--[[
+RopeKnocking function!
+
+This creates a rope-knocking challenge.
+The player spawns with one hog and a rope and must kill all other hogs
+by rope-knocking before the time runs out.
+The player wins points for each kill and gets a time bonus for killing
+all enemies.
+
+params is a table with all the required parameters.
+Fields of the params table:
+
+	MANDATORY:
+	- map: Map name
+	- theme: Theme name
+	- turnTime: Turn time
+	- playerTeam: Player team info:
+		{
+			x, y: Start position
+			faceLeft: If true, hog faces left
+		}
+	- enemyTeams: Table of enemy team tables. each enemy team table has this format:
+		{
+			name: Team name
+			flag: Flag
+			hogs: Hogs table:
+			{
+				x, y: Position
+				faceLeft: If true, hog faces left
+				hat: Hat name
+				name: Hog name
+			}
+		}
+
+	OPTIONAL:
+	- missionName: Mission name
+	- missionType:
+		0: None/other: No special handling
+		1: Challenge: Will save mission variables at end (default)
+	- killBonus: Score for killing all hogs (one hog scores ca. (killBonus/<number of enemies)) (default: 6000)
+	- timeBonus: Maximum theoretically possible time bonus (default: 6000)
+	- gameFlags: List of game flags, if you want to set your own
+	- extraTime: Extra time awarded for each kill, in milliseconds (default: 0)
+	- valkyries: If true, play "Ride of the Valkyries" at final enemy (default: false)
+	- onGameInit: Custom onGameInit callback
+	- onGameStart: Custom onGameStart callback
+	- onVictory: Function that is called when the mission is won.
+
+	Hint: Use onGameInit and onGameStart to place custom gears and girders
+	Hint: Use onVictory to save campaign variables if using this in a campaign
+
+]]
+function RopeKnocking(params)
+	if params.missionName then
+		missionName = params.missionName
+	end
+	if params.extraTime then
+		extraTime = params.extraTime
+	end
+	if params.valkyries then
+		playValkyries = params.valkyries
+	end
+	if params.missionType then
+		missionType = params.missionType
+	end
+	if params.killBonus then
+		killBonus = params.killBonus
+	end
+	if params.timeBonus then
+		timeBonus = params.timeBonus
+	end
+
+	_G.onGameInit = function()
+
+		if params.gameFlags then
+			for g=1, #params.gameFlags do
+				EnableGameFlags(params.gameFlags[g])
+			end
+		end
+
+		EnableGameFlags(gfBorder, gfSolidLand)
+
+		TurnTime = params.turnTime
+		Delay = 500
+		Map = params.map
+		Theme = params.theme
+
+		-- Disable Sudden Death
+		WaterRise = 0
+		HealthDecrease = 0
+
+		CaseFreq = 0
+		MinesNum = 0
+		Explosives = 0
+
+		-- Player team
+		playerTeamName = AddMissionTeam(-1)
+		hhs[0] = AddMissionHog(1)
+		SetGearPosition(hhs[0], params.playerTeam.x, params.playerTeam.y)
+		if params.playerTeam.faceLeft == true then
+			HogTurnLeft(hhs[0], true)
+		end
+
+		-- Enemy teams
+		for t=1, #params.enemyTeams do
+			local team = params.enemyTeams[t]
+			params.enemyTeams[t].name = AddTeam(team.name, -2, "Simple", "Tank", "Default_qau", team.flag)
+			for h=1, #team.hogs do
+				local hogData = team.hogs[h]
+				local name = hogData.name
+				local hat = hogData.hat
+				if not hat then
+					hat = "NoHat"
+				end
+				local hog = AddHog(name, 0, 1, hat)
+				SetGearPosition(hog, hogData.x, hogData.y)
+				if hogData.faceLeft == true then
+					HogTurnLeft(hog, true)
+				end
+				table.insert(hhs, hog)
+
+				totalEnemies = totalEnemies + 1
+			end
+		end
+
+		if params.onGameInit then
+			params.onGameInit()
+		end
+	end
+
+	_G.onGameStart = function()
+		SendHealthStatsOff()
+
+		local timeTxt = ""
+		local displayTime = 4000
+		if extraTime and extraTime ~= 0 then
+			timeTxt = string.format(loc("For each kill you win %d seconds."), div(extraTime, 1000))
+			displayTime = 5000
+		end
+		local recordInfo = getReadableChallengeRecord("Highscore")
+		if recordInfo == nil then
+			recordInfo = ""
+		else
+			recordInfo = "|" .. recordInfo
+		end
+		ShowMission(
+			missionName,
+			loc("Challenge"),
+			loc("Use the rope to knock your enemies to their doom.") .. "|" ..
+			loc("Finish this challenge as fast as possible to earn bonus points.") .. "|" ..
+			timeTxt .. recordInfo, -amRope, displayTime)
+
+		SetTeamLabel(playerTeamName, "0")
+
+		if params.onGameStart then
+			params.onGameStart()
+		end
+	end
+
+	_G.onGameTick = function()
+
+		if (TurnTimeLeft == 1) and (missionWon == nil) then
+			PlaySound(sndBoring, CurrentHedgehog)
+			gameOver()
+		end
+
+		if missionWon ~= nil then
+
+			endTimer = endTimer - 1
+			if endTimer == 1 then
+				if not endGameCalled then
+					EndGame()
+					endGameCalled = true
+				end
+			end
+
+			if not missionEndHandled then
+				if missionWon == true then
+					AddCaption(loc("Victory!"), 0xFFFFFFFF, capgrpGameState)
+					if missionType == 1 then
+						SaveMissionVar("Won", "true")
+					end
+				else
+					AddCaption(loc("Challenge over!"), 0xFFFFFFFF, capgrpGameState)
+				end
+				missionEndHandled = true
+			end
+
+		end
+
+	end
+
+	_G.onGameTick20 = function()
+		if (valkyriesTimer > 0) then
+			valkyriesTimer = valkyriesTimer - 20
+			if valkyriesTimer <= 0 then
+				StopMusicSound(sndRideOfTheValkyries)
+			end
+		end
+		local drown = (hhs[0]) and (band(GetState(hhs[0]), gstDrowning) ~= 0)
+		if drown and missionWon == nil then
+			-- Player hog drowns
+			playerFail = true
+			return
+		end
+		for i=1, totalEnemies do
+			local hog = hhs[i]
+			drown = (hog) and (not deadHogs[hog]) and (band(GetState(hhs[i]), gstDrowning) ~= 0)
+			if drown then
+				declareEnemyKilled(hog, params.onVictory)
+			end
+		end
+
+		if ropeGear and not missionWon and band(GetState(ropeGear), gstCollision) ~= 0 then
+			-- Hide mission on first rope attach
+			HideMission()
+		end
+	end
+
+	_G.onGearDamage = function(gear, damage)
+
+		if gear == hhs[0] then
+			-- Player hog hurts itself
+			playerFail = true
+			StopMusicSound(sndRideOfTheValkyries)
+			valkyriesTimer = -1
+			protectEnemies()
+		end
+
+		if gear ~= hhs[0] and GetGearType(gear) == gtHedgehog and not deadHogs[gear] and missionWon == nil and playerFail == false then
+			-- Enemy hog took damage
+			AddVisualGear(GetX(gear), GetY(gear), vgtBigExplosion, 0, false)
+			DeleteGear(gear)
+			PlaySound(sndExplosion)
+			AddCaption(string.format(knockTaunt(), GetHogName(gear)), 0xFFFFFFFF, capgrpMessage)
+
+			declareEnemyKilled(gear, params.onVictory)
+		end
+
+	end
+
+	_G.onGearAdd = function(gear)
+		if GetGearType(gear) == gtRope then
+			ropeGear = gear
+		end
+	end
+
+	_G.onGearDelete = function(gear)
+
+		if (gear == hhs[0]) and (missionWon == nil) then
+			playerFail = true
+			gameOver()
+		end
+
+		if GetGearType(gear) == gtHedgehog and gear ~= hhs[0] and not deadHogs[gear] then
+			declareEnemyKilled(gear, params.onVictory)
+		end
+
+		if GetGearType(gear) == gtRope then
+			ropeGear = nil
+		end
+
+	end
+
+	if params.onAmmoStoreInit then
+		_G.onAmmoStoreInit = params.onAmmoStoreInit
+	else
+		_G.onAmmoStoreInit = function()
+			SetAmmo(amRope, 9, 0, 0, 0)
+		end
+
+		_G.onNewTurn = function()
+			SetWeapon(amRope)
+		end
+	end
+
+end