--- /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