--[[ 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: onGameInitlocal Target = nil -- Pointer to target hoglocal Objective = false -- Get to the targetlocal TargetNumber = 0 -- The current target numberlocal GrenadeThrown = false -- Used for the Boom Targetlocal BazookasLeft = 0 -- Used by the Launch Target and the Unterwater Attack Targetlocal InfFuel = false -- If true, flying saucer has infinite fuellocal SaucerGear = nil -- Store flying saucer gear here (if one exists)local TargetGears = {} -- List of remaining gears to collect or destroy in the current roundlocal TargetsRemaining = 0local Barrels = {} -- Table contraining the explosive barrel gearslocal CheckTimer = 500 -- Time to wait at least before checking safe landinglocal Check = false -- The last target has recently been collected/destroyed and the CheckTimer is runninglocal GrenadeTimer = 0 -- Time after a grenade has been thrownlocal TargetPos = {} -- Table of targetslocal 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 Targetlocal DoubleTarget = 4TargetPos[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 Targetlocal BoomTarget = 8TargetPos[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 Targetlocal LaunchTarget = 9TargetPos[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 Targetlocal UnderwaterAttackTarget = 10TargetPos[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 ShowMissionfunction 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 Targetfunction 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) endend-- Generic target spawning for the current targetfunction 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 endendfunction 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 + 1endfunction 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) endend-- 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 targetfunction HasHedgehogLandedYet() if band(GetState(Player), gstMoving) == 0 and SaucerGear == nil and GetHealth(Player) > 0 then return true else return false endend-- Clean up the gear mess left behind when the player failed to get a clean state after restartingfunction 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 turnfunction 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)endfunction 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 )endfunction 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()endfunction 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)endfunction 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) endendfunction 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) endendfunction onNewTurn() SetWeapon(amJetpack)endfunction 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 fuelfunction ResetFuel() if SaucerGear and InfFuel then SetHealth(SaucerGear, 2000) endendonUp = ResetFuelonLeft = ResetFuelonRight = ResetFuelfunction onGearDamage(Gear) if Gear == Player then CleanUpGears() GrenadeThrown = false Check = false endendfunction onGearResurrect(Gear) if Gear == Player then AddCaption(loc("Oh no! You have died. Try again!"), 0xFFFFFFFF, capgrpMessage2) ResetCurrentTarget() endendfunction onHogAttack(ammoType) if ammoType == amSkip then AddCaption(loc("Try again!"), 0xFFFFFFFF, capgrpMessage2) ResetCurrentTarget() endend