share/hedgewars/Data/Missions/Training/Basic_Training_-_Flying_Saucer.lua
changeset 11405 307832da2756
child 12424 b9cc405541c1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/hedgewars/Data/Missions/Training/Basic_Training_-_Flying_Saucer.lua	Sat Nov 14 19:22:42 2015 +0100
@@ -0,0 +1,558 @@
+--[[
+	Flying Saucer Training
+	This is a training mission which teaches many basic (and not-so-basic) moves
+	with the flying saucer.
+
+	Lesson plan:
+	- Taking off
+	- Basic flight
+	- Landing safely
+	- Managing fuel
+	- Changing saucers in mid-flight
+	- Diving
+	- Dropping weapons from flying saucer
+	- Firing from flying saucer with [Precise] + [Attack]
+	- Aiming in flying saucer with [Precise] + [Up]/[Down]
+	- Underwater attack
+	- Free flight with inf. fuel and some weapons at end of training
+
+	FIXME:
+	- Bad respawn animation ("explosion" just happens randomly because of the way the resurrection effect works)
+	- Hide fuel if infinite (probably needs engine support)
+]]
+
+HedgewarsScriptLoad("/Scripts/Locale.lua")
+HedgewarsScriptLoad("/Scripts/Tracker.lua")
+
+local Player = nil -- Pointer to hog created in: onGameInit
+local Target = nil -- Pointer to target hog
+local Objective = false -- Get to the target
+
+local TargetNumber = 0 -- The current target number
+local GrenadeThrown = false -- Used for the Boom Target
+local BazookasLeft = 0 -- Used by the Launch Target and the Unterwater Attack Target
+
+local InfFuel = false -- If true, flying saucer has infinite fuel
+local SaucerGear = nil -- Store flying saucer gear here (if one exists)
+local TargetGears = {} -- List of remaining gears to collect or destroy in the current round
+local TargetsRemaining = 0
+local Barrels = {} -- Table contraining the explosive barrel gears
+
+local CheckTimer = 500 -- Time to wait at least before checking safe landing
+local Check = false -- The last target has recently been collected/destroyed and the CheckTimer is running
+local GrenadeTimer = 0 -- Time after a grenade has been thrown
+
+local TargetPos = {} -- Table of targets
+
+local StartPos = { X = 742, Y = 290 }
+
+--[[
+List of all targets (or "objectives"). The player has to complete them one-by-one and must always land safely afterwards.
+Some target numbers have names for easier reference.
+]]
+TargetPos[1] =  {
+	Targets = {{ X = 1027, Y = 217 }},
+	Ammo = { },
+	Message = loc("Here you will learn how to fly the flying saucer|and get so learn some cool tricks.") .. "|" ..
+	loc("Collect the first crate to begin!"),
+	MessageIcon = -amJetpack, }
+TargetPos[2] = {
+	Targets = {{ X = 1369, Y = 265 }},
+	Ammo = { [amJetpack] = 100 },
+	InfFuel = true,
+	MessageTime = 10000,
+	Message = loc("Get to the crate using your flying saucer!") .. "|" ..
+	loc("Press [Attack] (space bar by default) to start,|repeadedly tap the up, left and right movement keys to accelerate.") .. "|" ..
+	loc("Try to land softly, as you can still take fall damage!"), }
+TargetPos[3] = {
+	Targets = {{ X = 689, Y = 58 }},
+	Ammo = { [amJetpack] = 100 },
+	MessageTime = 5000,
+	Message = loc("Now collect the next crate!") .. "|" .. loc("Be careful, your fuel is limited from now on!") .."|" ..
+	loc("Tip: If you get stuck in this training, use \"Skip turn\" to restart the current objective.") }
+
+-- The Double Target
+local DoubleTarget = 4
+TargetPos[4] = {
+	Targets = { { X = 84, Y = -20 }, { X = 1980 , Y = -20 } },
+	Ammo = { [amJetpack] = 2 },
+	MessageTime = 9000,
+	Message = loc("Now collect the 2 crates to the far left and right.") .. "|" ..
+	loc("You only have 2 flying saucers this time.") .. "|" ..
+	loc("Tip: You can change your flying saucer|in mid-flight by hitting the [Attack] key twice."), }
+TargetPos[5] = {
+	Targets = {{ X = 47, Y = 804 }},
+	Ammo = { [amJetpack] = 100 },
+	MessageTime = 5000,
+	Message = loc("Time for a more interesting stunt, but first just collect the next crate!"), }
+TargetPos[6] = {
+	Targets = {{ X = 604, Y = 871}},
+	MessageTime = 15000,
+	Message = loc("You can dive with your flying saucer!") .. "|" ..
+	loc("Try it now and dive here to collect the crate on the right girder.") .. "|" ..
+	loc("You only have one flying saucer this time.") .. "|" ..
+	loc("Beware, though, you will only be able to move slowly through the water.") .. "|" ..
+	loc("Warning: Never ever leave the flying saucer while in water!"),
+	Ammo = { [amJetpack] = 1 }, }
+
+TargetPos[7] = { 
+	Targets = {{ X = 1884, Y = 704 }},
+	MessageTime = 6500,
+	Message = loc("Now dive just one more time and collect the next crate." .. "|" ..
+		loc("Tip: Don't remain for too long in the water, or you won't make it.")),
+	Ammo = { [amJetpack] = 1}, }
+
+-- The Boom Target
+local BoomTarget = 8
+TargetPos[8] = {
+	Modifier = true, Func = function()
+		Info(loc("Instructions"),
+		loc("Now let's try to drop weapons while flying!") .. "|" ..
+		loc("You have to destroy the target above by dropping a grenade on it from your flying saucer.") .. "|" ..
+		loc("It's not that easy, so listen carefully:") .. "|" ..
+		loc("Step 1: Activate your flying saucer but do NOT move yet!") .. "|" ..
+		loc("Step 2: Select your grenade.") .. "|" ..
+		loc("Step 3: Start flying and get yourself right above the target.") .. "|" ..
+		loc("Step 4: Drop your grenade by pressing the [Long jump] key.") .. "|" ..
+		loc("Step 5: Get away quickly and land safely anywhere." .. "| |" ..
+		loc("Note: We only give you grenades if you stay in your flying saucer.")), nil, 20000)
+
+		SpawnBoomTarget()
+
+		if SaucerGear ~= nil then
+			AddAmmo(Player, amGrenade, 1)
+		else
+			AddAmmo(Player, amGrenade, 0)
+		end
+		GrenadeThrown = false
+
+	end,
+	Ammo = { [amJetpack] = 100 },
+	Respawn = { X = 2000, Y = 742 }, }
+
+-- The Launch Target
+local LaunchTarget = 9
+TargetPos[9] = {
+	Targets = {{ X = 1700, Y = 640, Type = gtTarget }, { X = 1460, Y = 775, Type = gtTarget }},
+	MessageTime = 20000,
+	Message = loc("Only the best pilots can master the following stunts.") .. "|" ..
+		loc("As you've seen, the dropped grenade roughly fell into your flying direction.") .. "|" ..
+		loc("You have to destroy two targets, but the previous technique would be very difficult or dangerous to use.") .. "|" ..
+		loc("So you are able to launch projectiles into your aiming direction, always at full power.") .."|"..
+		loc("To launch a projectile in mid-flight, hold [Precise] and press [Long jump].") .. "|" ..
+		loc("You can even change your aiming direction in mid-flight if you first hold [Precice] and then press [Up] or [Down].") .. "|" ..
+		loc("Tip: Changing your aim while flying is very difficult, so adjust it before you take off."),
+	Ammo = { [amJetpack] = 1, },
+	Respawn = { X = 1764, Y = 916 },
+	ExtraFunc = function()
+		HogTurnLeft(Player, true)
+		if SaucerGear ~= nil then
+			AddAmmo(Player, amBazooka, 2)
+		else
+			AddAmmo(Player, amBazooka, 0)
+		end
+		BazookasLeft = 2
+
+	end }
+
+-- The Underwater Attack Target
+local UnderwaterAttackTarget = 10
+TargetPos[10] = {
+	MessageTime = 17000,
+	Message = loc("Now for the supreme discipline of saucer flying, the underwater attack.") .. "|" ..
+	loc("Basically this is a combination of diving and launching.") .. "|" ..
+	loc("Dropping a weapon while in water would just drown it, but launching one would work.") .."|" ..
+	loc("Based on what you've learned, destroy the target on the girder and as always, land safely!"), 
+	Targets = {{ X = 1200, Y = 930, Type = gtTarget }},
+	Ammo = { [amJetpack] = 1, },
+	Respawn = { X = 1027, Y = 217 },
+	ExtraFunc = function()
+		if SaucerGear ~= nil then
+			AddAmmo(Player, amBazooka, 1)
+		else
+			AddAmmo(Player, amBazooka, 0)
+		end
+		BazookasLeft = 1
+	end }
+TargetPos[11] = {
+	Targets = {{ X = 742, Y = 290 }},
+	MessageTime = 5000,
+	Message = loc("This almost concludes our tutorial.") .. "|" ..
+	loc("You now have infinite fuel, grenades and bazookas for fun.") .. "|" ..
+	loc("Collect or destroy the final crate to finish the training."),
+	Ammo = { [amJetpack] = 100, [amGrenade] = 100, [amBazooka] = 100 },
+	InfFuel = true, }
+TargetPos[12] = { Modifier = true, Func = function()
+	Objective = true
+	AddCaption(loc("Training complete!"), 0xFFFFFFFF, capgrpGameState)
+	Info(loc("Training complete!"), loc("Good bye!"), 4, 5000)
+
+	if SaucerGear ~= nil then
+		DeleteGear(SaucerGear)
+	end
+	SetState(Player, band(GetState(Player), bnot(gstHHDriven)))
+	SetState(Player, bor(GetState(Player), gstWinner))
+	PlaySound(sndVictory, Player)
+
+	SendStat(siGameResult, loc("You have finished the Flying Saucer Training!"))
+	SendStat(siCustomAchievement, loc("Good job!"))
+	SendStat(siPlayerKills, "0", loc("Hogonauts"))
+
+	TurnTimeLeft = 0
+	EndGame()
+end,
+}
+
+-- Just a wrapper for ShowMission
+function Info(Title, Text, Icon, Time)
+	if Time == nil then Time = 0 end
+	if Icon == nil then Icon = 2 end
+	ShowMission(loc("Flying Saucer Training"), Title, Text, Icon, Time)
+end
+
+-- Spawn all the gears for the Boom Target
+function SpawnBoomTarget()
+	if TargetsRemaining < 1 then
+		TargetGears[1] = AddGear(1602, 507, gtTarget, 0, 0, 0, 0)
+		TargetsRemaining = TargetsRemaining + 1
+	end
+
+	if Barrels[1] == nil then
+		Barrels[1] = AddGear(1563, 532, gtExplosives, 0, 0, 0, 0)
+	end
+	if Barrels[2] == nil then
+		Barrels[2] = AddGear(1648, 463, gtExplosives, 0, 0, 0, 0)
+	end
+
+	for i=1,#Barrels do
+		SetHealth(Barrels[i], 1)
+	end
+end
+
+-- Generic target spawning for the current target
+function SpawnTargets()
+	for i=1,#TargetPos[TargetNumber].Targets do
+		if TargetGears[i] == nil then
+			SpawnTarget(TargetPos[TargetNumber].Targets[i].X, TargetPos[TargetNumber].Targets[i].Y,
+				TargetPos[TargetNumber].Targets[i].Type, i)
+		end
+	end
+end
+
+function SpawnTarget( PosX, PosY, Type, ID )
+	if Type ~= nil and Type ~= gtCase then
+		if Type == gtTarget then
+			TargetGears[ID] = AddGear(PosX, PosY, gtTarget, 0, 0, 0, 0)
+		end
+	else
+		TargetGears[ID] = SpawnFakeUtilityCrate(PosX, PosY, false, false)
+	end
+	TargetsRemaining = TargetsRemaining + 1
+end
+
+function AutoSpawn() -- Auto-spawn the next target after you've obtained the current target!
+	TargetNumber = TargetNumber + 1
+	TargetsRemaining = 0
+
+	if TargetPos[TargetNumber].Ammo then
+		for ammoType, count in pairs(TargetPos[TargetNumber].Ammo) do
+			AddAmmo(Player, ammoType, count)
+		end
+		if GetCurAmmoType() ~= amJetpack then
+			SetWeapon(amJetpack)
+		end
+	end
+	if TargetPos[TargetNumber].InfFuel then
+		InfFuel = true
+	else
+		InfFuel = false
+	end
+
+	-- Func (if present) will be run instead of the ordinary spawning handling
+	if TargetPos[TargetNumber].Modifier then -- If there is a modifier, run the function
+		TargetPos[TargetNumber].Func()
+		return true
+	end
+
+	-- ExtraFunc is for additional events for a target
+	if TargetPos[TargetNumber].ExtraFunc ~= nil then
+		TargetPos[TargetNumber].ExtraFunc()
+	end
+
+	local subcap
+	if TargetNumber == 1 then
+		subcap = loc("Training")
+	else
+		subcap = loc("Instructions")
+	end
+	Info(subcap, TargetPos[TargetNumber].Message, TargetPos[TargetNumber].MessageIcon, TargetPos[TargetNumber].MessageTime)
+
+	-- Spawn targets on the next position
+	SpawnTargets()
+
+	if TargetNumber > 1 then
+		AddCaption(loc("Next target is ready!"), 0xFFFFFFFF, capgrpMessage2)
+	end
+end
+
+-- Returns true if the hedgehog has safely "landed" (alive, no flying saucer gear and not moving)
+-- This is to ensure the training only continues when the player didn't screw up and to restart the current target
+function HasHedgehogLandedYet()
+	if band(GetState(Player), gstMoving) == 0 and SaucerGear == nil and GetHealth(Player) > 0 then
+		return true
+	else
+		return false
+	end
+end
+
+-- Clean up the gear mess left behind when the player failed to get a clean state after restarting
+function CleanUpGears()
+	-- (We track flames, grenades, bazooka shells)
+	runOnGears(DeleteGear)
+end
+
+-- Completely restarts the current target/objective; the hedgehog is spawned at the last "checkpoint"
+-- Called when hedgeghog is resurrected or skips turn
+function ResetCurrentTarget()
+	GrenadeThrown = false
+	GrenadeTimer = 0
+	if TargetNumber == LaunchTarget then
+		BazookasLeft = 2
+	elseif TargetNumber == UnderwaterAttackTarget then
+		BazookasLeft = 1
+	else
+		BazookasLeft = 0
+	end
+	Check = false
+
+	CleanUpGears()
+
+	local X, Y
+	if TargetNumber == 1 then
+		X, Y = StartPos.X, StartPos.Y
+	else
+		if TargetPos[TargetNumber-1].Modifier or TargetPos[TargetNumber-1].Respawn ~= nil then
+			X, Y = TargetPos[TargetNumber-1].Respawn.X, TargetPos[TargetNumber-1].Respawn.Y
+		else
+			X, Y = TargetPos[TargetNumber-1].Targets[1].X, TargetPos[TargetNumber-1].Targets[1].Y
+		end
+	end
+	if TargetNumber == BoomTarget then
+		SpawnBoomTarget()
+	end
+	if TargetPos[TargetNumber].Modifier ~= true then
+		SpawnTargets()
+	end
+	if TargetPos[TargetNumber].Ammo then
+		for ammoType, count in pairs(TargetPos[TargetNumber].Ammo) do
+			AddAmmo(Player, ammoType, count)
+		end
+		if GetCurAmmoType() ~= amJetpack then
+			SetWeapon(amJetpack)
+		end
+	end
+	if TargetPos[TargetNumber].InfFuel then
+		InfFuel = true
+	else
+		InfFuel = false
+	end
+
+	SetGearPosition(Player, X, Y)
+end
+
+function onGameInit()
+	Seed = 1
+	GameFlags = gfInfAttack + gfOneClanMode + gfSolidLand + gfDisableWind
+	TurnTime = 2000000 --[[ This rffectively hides the turn time; a turn time above 1000s is not displayed.
+				We will also ensure this timer always stays above 999s later ]]
+	CaseFreq = 0
+	MinesNum = 0
+	Explosives = 0
+	Map = "Eyes"
+	Theme = "EarthRise"
+	SuddenDeathTurns = 50
+	WaterRise = 0
+	HealthDecrease = 0
+
+	-- Team name is a pun on “hedgehog” and “astronauts”
+	AddTeam( loc( "Hogonauts" ), 0xDDDD00, "earth", "Earth", "Default", "cm_galaxy" )
+
+	-- Hedgehog name is a pun on “Neil Armstrong”
+	Player = AddHog( loc( "Neil Hogstrong" ), 0, 1, "NoHat" )
+	SetGearPosition( Player, StartPos.X, StartPos.Y)
+	SetEffect( Player, heResurrectable, 1 )
+end
+
+function onGameStart()
+	SendHealthStatsOff()
+
+	-- Girder near first crate
+	PlaceGirder(1257, 204, 6)
+
+	-- The upper girders
+	PlaceGirder(84, 16, 0)
+	PlaceGirder(1980, 16, 0)
+
+	-- The lower girder platform at the water pit
+	PlaceGirder(509, 896, 4)
+	PlaceGirder(668, 896, 4)
+	PlaceGirder(421, 896, 2)
+	PlaceGirder(758, 896, 2)
+
+	-- Girders for the Launch Target and the Underwater Attack Target
+	PlaceGirder(1191, 960, 4)
+	PlaceGirder(1311, 960, 0)
+	PlaceGirder(1460, 827, 3)
+	PlaceGirder(1509, 763, 2)
+	PlaceGirder(1605, 672, 4)
+	PlaceGirder(1764, 672, 4)
+	PlaceGirder(1803, 577, 6)
+
+	-- Spawn our 1st target using the wrapper function
+	AutoSpawn()
+end
+
+function onAmmoStoreInit()
+	SetAmmo(amJetpack, 0, 0, 0, 0)
+	SetAmmo(amGrenade, 0, 0, 0, 0)
+	SetAmmo(amBazooka, 0, 0, 0, 0)
+
+	-- Added for resetting current target/objective when player is stuck somehow
+	SetAmmo(amSkip, 9, 0, 0, 0)
+end
+
+function onGearAdd(Gear)
+	if GetGearType(Gear) == gtJetpack then
+		SaucerGear = Gear
+		if TargetNumber == BoomTarget and GrenadeThrown == false then
+			AddAmmo(Player, amGrenade, 1)
+		end
+		if (TargetNumber == LaunchTarget or TargetNumber == UnderwaterAttackTarget) and BazookasLeft > 0 then
+			AddAmmo(Player, amBazooka, BazookasLeft)
+		end
+	end
+	if GetGearType(Gear) == gtGrenade then
+		GrenadeThrown = true
+		GrenadeTimer = 0
+	end
+	if GetGearType(Gear) == gtShell then
+		BazookasLeft = BazookasLeft - 1
+	end
+	if GetGearType(Gear) == gtFlame or GetGearType(Gear) == gtGrenade or GetGearType(Gear) == gtShell then
+		trackGear(Gear)
+	end
+end
+
+function onGearDelete(Gear)
+	if GetGearType(Player) ~= nil and (GetGearType(Gear) == gtTarget or GetGearType(Gear) == gtCase) then
+		for i=1, #TargetGears do
+			if Gear == TargetGears[i] then
+				TargetGears[i] = nil
+				TargetsRemaining = TargetsRemaining - 1
+			end
+		end
+		if TargetsRemaining <= 0 then
+			if TargetNumber == BoomTarget or not HasHedgehogLandedYet() then
+				if SaucerGear then
+					AddCaption(loc("Objective completed! Now land safely."), 0xFFFFFFFF, capgrpMessage2)
+				end
+				Check = true
+				CheckTimer = 500
+			else
+				AutoSpawn()
+			end
+		end
+	end
+	if GetGearType(Gear) == gtGrenade then
+		GrenadeTimer = 0
+		GrenadeExploded = true
+	end
+	if GetGearType(Gear) == gtJetpack then
+		SaucerGear = nil
+		if TargetNumber == BoomTarget then
+			AddAmmo(Player, amGrenade, 0)
+		end
+		if TargetNumber == LaunchTarget or TargetNumber == UnderwaterAttackTarget then
+			AddAmmo(Player, amBazooka, 0)
+		end
+	end
+	if GetGearType(Gear) == gtCase and GetGearType(Player) ~= nil then
+		PlaySound(sndShotgunReload)
+	end
+	if Gear == Barrels[1] then
+		Barrels[1] = nil
+	end
+	if Gear == Barrels[2] then
+		Barrels[2] = nil
+		AddCaption(loc("Kaboom!"), 0xFFFFFFFF, capgrpMessage)
+	end
+end
+
+
+
+function onNewTurn()
+	SetWeapon(amJetpack)
+end
+
+function onGameTick20()
+	if (TurnTimeLeft < 1500000 and not Objective) then
+		TurnTimeLeft = TurnTime
+	end
+	if Check then
+		CheckTimer = CheckTimer - 20
+		if CheckTimer <= 0 then
+			if HasHedgehogLandedYet() then
+				AutoSpawn()
+				Check = false
+				GrenadeThrown = false
+			end
+		end
+	end
+	if GrenadeExploded and TargetNumber == BoomTarget then
+		GrenadeTimer = GrenadeTimer + 20
+		if GrenadeTimer > 1500 then
+			GrenadeTimer = 0
+			GrenadeThrown = false
+			GrenadeExploded = false
+			if SaucerGear and TargetNumber == BoomTarget and TargetsRemaining > 0 then
+				PlaySound(sndShotgunReload)
+				AddCaption(loc("+1 Grenade"), 0xDDDD00FF, capgrpAmmoinfo)
+				AddAmmo(Player, amGrenade, 1)
+			end
+		end
+	end
+	ResetFuel()
+end
+
+-- Used to ensure infinite fuel
+function ResetFuel()
+	if SaucerGear and InfFuel then
+		SetHealth(SaucerGear, 2000)
+	end
+end
+
+onUp = ResetFuel
+onLeft = ResetFuel
+onRight = ResetFuel
+
+function onGearDamage(Gear)
+	if Gear == Player then
+		CleanUpGears()
+		GrenadeThrown = false
+		Check = false
+	end
+end
+
+function onGearResurrect(Gear)
+	if Gear == Player then
+		AddCaption(loc("Oh no! You have died. Try again!"), 0xFFFFFFFF, capgrpMessage2)
+		ResetCurrentTarget()
+	end
+end
+
+function onHogAttack(ammoType)
+	if ammoType == amSkip then
+		AddCaption(loc("Try again!"), 0xFFFFFFFF, capgrpMessage2)
+		ResetCurrentTarget()
+	end
+end