share/hedgewars/Data/Missions/Training/Basic_Training_-_Rope.lua
author Wuzzy <Wuzzy2@mail.ru>
Tue, 29 Oct 2019 22:03:08 +0100
changeset 15494 55436712b5e9
parent 15068 6f51c75994a4
permissions -rw-r--r--
Rope Training: Add flower effects when player returns all the way to the start

--[[
	Basic Rope Training
	Teaches the player how to rope! No advanced tricks, just the basics. But fun! (I hope)

	Lesson plan:
	- Select rope
	- Shoot rope, attach, detach
	- Extend, retract, swing to reach easy target
	- Multiple shots / rope re-use to go over water hazard
	- Drop grenade from rope
	- Special rules when you only got 1 rope (i.e. when the rope is officially used up)
	- Rope around obstacles
]]

HedgewarsScriptLoad("/Scripts/Locale.lua")
HedgewarsScriptLoad("/Scripts/Utils.lua")
HedgewarsScriptLoad("/Scripts/Achievements.lua")

-- Map definition automatically converted from HWMAP file by hwmap2lua.sh
local map =
{'\2\30\7\126\135\2\28\5\52\0\1\211\6\203\135\3\207\6\189\0\3\1\5\205\135\4\120\5\203\0\2\30\5\31\135\2\35\4\173\0\1\170\4\216\135\4\150\4\248\0\3\230\4\138\135\4\54\6\22\0\3\205\6\189\135\4\154\7\91\0\4\154\7\91\135\6\57\7\41\0\4\79\6\38\135\4\221\6\112\0\4\221\6\112\135\6\27\6\70\0\3\175\6\196\135\4\51\5\235\0\3\17\6\178\135\3\161\5\226\0\4\33\7\4\135\4\191\6\93\0\4\200\7\75\135\5\45\6\137\0\6\29\6\66\135\6\98\5\123\0\6\107\7\32\135\6\237\5\249\0\6\102\5\95\135\7\169\5\6\0\7\0\5\237\135\7\235\6\20\0\7\233\6\13\135\7\226\8\43\0\13\66\6\47\135\13\100\8\57\0\7\167\5\15\135\7\164\4\51\0\7\71\4\109\135\13\235\4\132\0\13\4\6\75\135\14\242\6\57\0\14\169\6\166\135\15\183\4\86\0',
'\15\208\5\13\135\15\96\2\248\0\15\206\3\104\135\13\175\2\88\0\14\171\5\17\140\14\219\4\77\138\14\166\3\136\136\13\219\3\31\136\13\184\4\228\136\13\173\3\250\0\14\2\4\19\136\7\36\3\211\0\14\52\2\85\137\7\57\2\76\0\12\196\2\245\137\11\195\3\127\137\10\174\2\223\137\9\174\3\113\137\8\160\2\211\137\8\11\3\111\137\6\249\3\218\131\6\66\3\209\0\5\88\3\202\131\4\38\3\193\0\5\33\3\207\131\5\31\5\31\0\4\235\4\228\131\6\125\4\251\0\6\98\3\214\131\6\80\5\1\0\7\41\2\104\136\3\253\2\72\0\4\31\3\207\133\1\149\3\166\0\2\243\4\15\133\1\117\2\202\0\1\149\3\60\133\1\211\1\87\0\1\138\1\138\133\3\42\0\71\0\2\131\0\96\133\4\168\0\149\0\5\116\1\124\141\7\41\0\204\141\8\252\2\42\141\10\39\0\135\141\11\40\1\124\141\10\17\1\186\141',
'\12\104\0\151\141\4\29\0\156\137\14\160\0\69\0\13\182\1\133\137\13\171\2\78\0\13\201\0\78\137\13\189\0\197\0\13\194\1\195\137\15\148\2\147\0\14\189\0\59\137\15\222\0\217\0\15\226\0\215\137\15\164\2\145\0\13\217\1\211\137\15\167\1\220\0\14\208\2\37\164\8\89\1\92\139\6\146\1\211\139\8\174\2\39\203\10\19\2\7\203\11\56\1\167\203\12\106\0\217\203\7\36\1\12\203\5\182\1\163\203\6\141\1\165\203\8\71\1\135\203\8\151\3\19\203\8\7\3\70\203\9\227\3\118\203\13\66\3\113\133\13\54\4\104\0\11\200\3\26\133\11\191\4\84\0\9\160\2\255\133\9\160\4\84\0\8\16\3\163\133\8\18\4\109\0\9\108\3\53\197\10\19\3\70\0\14\25\5\228\132\14\43\4\253\130\13\237\3\241\210\13\217\3\225\210\13\95\4\74\140\7\137\4\40\0\2\209\6\75\207\3\255\6\102\0\4\1\6\107\207',
'\4\182\6\228\0\4\182\6\226\207\6\36\6\189\0\6\36\6\201\207\6\166\5\191\0\4\138\4\100\158\1\183\4\68\0\4\145\5\8\158\4\203\5\201\0\5\141\5\198\158\5\180\5\139\0\7\116\4\230\142\5\49\5\52\0\6\185\4\47\148\6\182\4\186\0\7\64\4\88\153\4\216\4\10\143\13\255\2\42\146\15\100\2\216\0\4\1\6\20\199\2\142\6\29\0\3\255\6\22\199\5\22\6\224\0\7\231\5\198\133\7\235\6\31\0\13\70\6\68\133\13\54\5\237\0\14\36\4\253\197\4\90\2\223\133\4\239\3\97\0\2\181\2\252\133\3\95\2\156\0\2\216\1\90\133\3\113\1\218\0\4\200\1\32\133\4\122\1\167\0\7\169\1\106\133\4\10\4\106\213\1\30\4\86\0\4\58\5\54\219\1\119\5\6\139\1\119\5\157\0\1\238\5\189\139\0\85\5\164\0\0\105\6\63\139\0\119\3\182\0\0\37\3\188\134\1\231\3\195\0',
'\1\142\5\13\145\1\188\5\102\0\1\199\5\59\145\3\209\5\198\134\5\59\5\102\0\4\63\5\111\134\0\121\3\225\134\4\111\3\234\0\2\255\4\106\134\3\12\4\228\0\2\58\4\120\132\13\148\2\60\132\13\146\1\106\0'}

local function drawMap()
	for m=1, #map do
		ParseCommand("draw "..map[m])
	end
end

-- Gears
local hog
local ropeGear

-- Status vars
local ropeSelected = false	-- rope was selected the first time, used for msg
local ropeAttached = false	-- rope was attached, used for msg
local target1Reached = false	-- hog near 1st first target
local barrelsBoom = false	-- barrels exploded
local wasFirstTurn = false	-- first turn msg was displayed
local gameOver = false		-- game over (only victory possible)
local currentTarget = 0		-- current target ID. First target = 1
local flawless = true		-- flawless if no damage taken and no mistake made
local flowerPower = false	-- random flower visual gears appear all ower the place
local bonusFlowerPlaced = false -- a hidden flower sprite was placed

local cpX, cpY = 208, 1384	-- hog checkpoint, initialized with start coords

-- "Constants"
local initHogHealth = 50
local initHogHealthFinal = 1
local teamName

local girderData = {
	{2012, 1366, 6}, -- water gate
	{1156, 678, 2}, -- post-barrel gate
	{1206, 905, 2}, -- post-barrel gate
	{1064, 288, 6}, -- top level gate
	{1064, 455, 6}, -- top level gate
	{1557, 1009, 0}, -- barrel pit protection
	{1436, 1003, 4}, -- barrel pit protection
	{3607, 1307, 4}, -- post-water gate
	{3809, 1375, 0}, -- post-water gate
}

local targetData = {
	-- 1: Start
	{504, 1215},
	-- 2: Start 2
	{1082, 1348},
	-- 3: Before the water
	{1941, 1490},
	-- 4: After the water
	{3504, 1557},
	-- 5: Barrel pit
	{2060, 885},
	-- 6: Grand Finale
	{834, 381},
	-- 7: Goal
	{3802, 356},
}

function onGameInit()

	ClearGameFlags()
	EnableGameFlags(gfDisableGirders, gfDisableLandObjects, gfOneClanMode, gfInfAttack, gfSolidLand, gfDisableWind)
	Seed = "{386439b4-748a-48b1-945a-eba6a817ca83}"
	Theme = "Bamboo"
	MapGen = mgDrawn
	MapFeatureSize = 12
	TemplateFilter = 0
	TemplateNumber = 0
	TurnTime = MAX_TURN_TIME
	Explosives = 0
	MinesNum = 0
	CaseFreq = 0
	MinesTime = 0
	WaterRise = 0
	HealthDecrease = 0

	teamName = AddMissionTeam(-1)
	hog = AddMissionHog(initHogHealth)
	SetGearPosition(hog, cpX, cpY)
	SetEffect(hog, heResurrectable, 1)

	drawMap()

	SendHealthStatsOff()
	SendRankingStatsOff()

end

-- The final challenge is to rope through an obstacle course with only 1 rope.
-- If the player screws up, this functinon will restart it.
local function resetFinalChallenge(setPos)
	if setPos == nil then
		setPos = true
	end
	SetHealth(hog, initHogHealthFinal)
	AddAmmo(hog, amRope, 1)
	SetGearVelocity(hog, 0, 0)
	flowerPower = false

	if setPos then
		PlaySound(sndWarp)
		SetGearPosition(hog, cpX, cpY)
		AddVisualGear(cpX, cpY, vgtExplosion, 0, false)
		FollowGear(hog)
	end
end

-- Deletes girder with given girderData ID
local function eraseGirder(id)
	EraseSprite(girderData[id][1], girderData[id][2], sprAmGirder, girderData[id][3], false, false, false, false)
	PlaySound(sndVaporize)
	local dir = girderData[id][3]
	if dir == 4 then
		-- long horizontal
		for i=-4,4 do
			AddVisualGear(girderData[id][1] + i * 18, girderData[id][2], vgtSteam, false, 0)
		end
	elseif dir == 0 then
		-- short horizontal
		for i=-2,1 do
			AddVisualGear(10 + girderData[id][1] + i * 20, girderData[id][2], vgtSteam, false, 0)
		end
	elseif dir == 6 then
		-- long vertical
		for i=-4,4 do
			AddVisualGear(girderData[id][1], girderData[id][2] + i * 18, vgtSteam, false, 0)
		end
	elseif dir == 2 then
		-- short vertical
		for i=-2,1 do
			AddVisualGear(girderData[id][1], 10 + girderData[id][2] + i * 20, vgtSteam, false, 0)
		end
	else
		AddVisualGear(girderData[id][1], girderData[id][2], vgtSteam, false, 0)
	end

	AddCaption(loc("Barrier unlocked!"))
end

local function loadGearData()
	------ GIRDERS ------
	for g=1, #girderData do
		PlaceGirder(unpack(girderData[g]))
	end

	PlaceSprite(1678, 546, sprTargetBee, 0)

	------ BARRELS ------
	local barrels = {}
	table.insert(barrels, AddGear(1370, 1223, gtExplosives, 0, 0, 0, 0))
	table.insert(barrels, AddGear(1430, 1226, gtExplosives, 0, 0, 0, 0))
	table.insert(barrels, AddGear(1489, 1218, gtExplosives, 0, 0, 0, 0))
	table.insert(barrels, AddGear(1537, 1211, gtExplosives, 0, 0, 0, 0))
	table.insert(barrels, AddGear(1578, 1206, gtExplosives, 0, 0, 0, 0))
	for b=1, #barrels do
		SetHealth(barrels[b], 1)
	end

	------ FIRST TARGET ------
	currentTarget = 1
	AddGear(targetData[currentTarget][1], targetData[currentTarget][2], gtTarget, 0, 0, 0, 0)
end

function onGameStart()
	loadGearData()

	ShowMission(loc("Basic Rope Training"), loc("Basic Training"),
	loc("Use the rope to complete the obstacle course!"), -amRope, 0)
	FollowGear(hog)
end

function onNewTurn()
	local ctrl = ""
	if not wasFirstTurn then
		if INTERFACE == "desktop" then
			ctrl = loc("Open ammo menu: [Right click]")
		elseif INTERFACE == "touch" then
			ctrl = loc("Open ammo menu: Tap the [Suitcase]")
		end
		ShowMission(loc("Basic Rope Training"), loc("Select Rope"),
		loc("Select the rope to begin!").."|"..
		ctrl, 2, 7500)
		wasFirstTurn = true
	end
end

function onGameTick()

	if flowerPower then
		for i=1,2 do
			AddVisualGear(math.random(-1024, LAND_WIDTH+1024), math.random(TopY-1024, LAND_HEIGHT), vgtBeeTrace, 0, false)
		end
	end

	if gameOver or (not CurrentHedgehog) then
		return
	end

	-- First rope selection
	if not ropeSelected and GetCurAmmoType() == amRope then
		local ctrl = ""
		if INTERFACE == "desktop" then
			ctrl = loc("Aim: [Up]/[Down]").."|"..
			loc("Attack: [Space]")
		elseif INTERFACE == "touch" then
			ctrl = loc("Aim: [Up]/[Down]").."|"..
			loc("Attack: Tap the [Bomb]")
		end
		ShowMission(loc("Basic Rope Training"), loc("Getting Started"),
		loc("You can use the rope to reach new places.").."|"..
		loc("Aim at the ceiling and hold [Attack] pressed until the rope attaches.").."|"..
		ctrl, 2, 15000)
		ropeSelected = true
	-- Rope attach
	elseif ropeGear and band(GetState(ropeGear), gstCollision) ~= 0 then
		-- First rope attach
		if not ropeAttached and not target1Reached then
			ShowMission(loc("Basic Rope Training"), loc("How to Rope"),
			loc("Great!").."|"..
			loc("Use the rope to get to the target!").."|"..
			loc("Retract/Extend rope: [Up]/[Down]").."|"..
			loc("Swing: [Left]/[Right]").."|"..
			loc("Release rope: [Attack]"), 2, 15000)
			ropeAttached = true
		elseif currentTarget > 1 and (not (currentTarget == 6 and barrelsBoom)) then
			HideMission()
		end
	end

	-- Prevent grenade being thrown by hand (must use from rope instead)
	local allowAttack = true
	if GetCurAmmoType() == amGrenade and ropeGear == nil then
		allowAttack = false
	end
	if allowAttack then
		SetInputMask(bor(GetInputMask(), gmAttack))
	else
		SetInputMask(band(GetInputMask(), bnot(gmAttack)))
	end
	if isInFinalChallenge then
		local dX, dY = GetGearVelocity(CurrentHedgehog)
		local x, y = GetGearPosition(CurrentHedgehog)
		local driven = band(GetState(CurrentHedgehog), gstHHDriven) ~= 0
		if driven and y > 1310 and x < 338 and not flowerPower then
			-- Player reached the bonus flower. Enable Flower Power mode!
			PlaySound(sndKiss)
			flowerPower = true
		end
		if driven and GetAmmoCount(CurrentHedgehog, amRope) == 0 and
				GetFlightTime(CurrentHedgehog) == 0 and (not ropeGear) and
				math.abs(dX) < 5 and math.abs(dY) < 5 and
				(x < 3417 or y > 471) then
			flawless = false
			AddCaption(loc("Your rope is gone! Try again!"))
			resetFinalChallenge()
			PlaySound(sndWarp)
		end
	end
end

function onGameTick20()
	if flowerPower then
		if math.random(1,2) == 1 then
			local vg = AddVisualGear(GetX(CurrentHedgehog), GetY(CurrentHedgehog), vgtStraightShot, sprTargetBee, false, 1)
			SetVisualGearValues(vg, nil, nil, nil, nil, math.random(0, 360), nil, nil, nil, nil, 0xFFFFFFC0)
		end
	end
	if not gameOver and not target1Reached and CurrentHedgehog and gearIsInCircle(CurrentHedgehog, targetData[1][1], targetData[1][2], 48, false) then
		ShowMission(loc("Basic Rope Training"), loc("Target Puncher"),
		loc("Okay, now destroy the target|using the baseball bat.").."|"..
		loc("Release rope: [Attack]"), 2, 9000)
		target1Reached = true
	end
end

function onGearAdd(gear)
	if GetGearType(gear) == gtRope then
		ropeGear = gear
	elseif GetGearType(gear) == gtGrenade then
		if not ropeGear then
			DeleteGear(gear)
		end
	end
end

function onGearResurrect(gear, vGear)
	-- Teleport hog to previous checkpoint
	if gear == hog then
		flawless = false
		SetGearPosition(hog, cpX, cpY)
		if vGear then
			SetVisualGearValues(vGear, GetX(hog), GetY(hog))
		end
		FollowGear(hog)
		AddCaption(loc("Your hedgehog has been revived!"))
		if isInFinalChallenge then
			resetFinalChallenge(false)
		end
	end
end

function onGearDamage(gear)
	if gear == hog then
		flawless = false
	end
end

local function dropNadeText(time)
	ShowMission(loc("Basic Rope Training"), loc("Rope Weapons"),
	loc("Some weapons can be dropped from the rope.").."|"..
	loc("Collect the weapon crate and drop|a grenade from rope to destroy the barrels.").."|"..
	loc("Step 1: Start roping").."|"..
	loc("Step 2: Select grenade").."|"..
	loc("Step 3: Drop the grenade").."| |"..
	loc("Drop weapon (while on rope): [Long Jump]"), 2, time)
end

function onGearDelete(gear)
	if GetGearType(gear) == gtTarget then
		-- Update checkpoint
		cpX, cpY = GetGearPosition(gear)

		-- New message
		if currentTarget == 1 then
			ShowMission(loc("Basic Rope Training"), loc("Obstacle"),
			loc("Well done! Let's destroy the next target!").."|"..
			loc("The targets will guide you through the training.").."|"..
			loc("Use your rope to get to the next target, then destroy it!"), 2, 8000)
		elseif currentTarget == 2 then
			ShowMission(loc("Basic Rope Training"), loc("Speed Roping"),
			loc("Try to reach and destroy the next target quickly.").."|"..
			loc("Hint: When you shorten the rope, you move faster!|And when you lengthen it, you move slower."), 2, 15000)
		elseif currentTarget == 3 then
			ShowMission(loc("Basic Rope Training"), loc("Over the Water"),
			loc("When you're in mid-air, you can continue to aim|and fire another rope if you're not attached.").."|"..
			loc("To get over the water, you have to do multiple|rope shots and swings.").."|"..
			loc("It needs some practice, but you have infinite lives.").."|"..
			loc("Good luck!"), 2, 22500)
			eraseGirder(1)
		elseif currentTarget == 4 then
			ShowMission(loc("Basic Rope Training"), loc("Little Obstacle Course"),
			loc("Well done! The next target awaits.").."|"..
			loc("Hint: The rope only bends around objects.|When it doesn't hit anything, it's always straight."), 2, 7000)
			eraseGirder(8)
			eraseGirder(9)
		elseif currentTarget == 5 then
			dropNadeText(20000)
			AddAmmo(hog, amBaseballBat, 0)
			SpawnAmmoCrate(1849, 920, amGrenade, AMMO_INFINITE)
		elseif currentTarget == 6 then
			ShowMission(loc("Basic Rope Training"), loc("Finite Ropes"),
			loc("So far, you had infinite ropes, but in the|real world, ropes are usually limited.").."|"..
			loc("Rules:").." |"..
			loc("As long you don't touch the ground, you can|re-use the same rope as often as you like.").."|"..
			loc("If you miss a shot while trying to|re-attach, your rope is gone, too!").."| |"..
			loc("Final Challenge:").." |"..
			loc("Reach and destroy the final target to win.").."|"..
			loc("You only get 1 rope this time, don't waste it!"),
			2, 25000)
			eraseGirder(4)
			eraseGirder(5)
			-- Sneakingly place a flower sprite near spawn when player reached the last section
			-- When the player reaches it, Flower Power mode is enabled
			if not bonusFlowerPlaced then
				PlaceSprite(240, 1360, sprTargetBee, 0)
				bonusFlowerPlaced = true
			end
			AddAmmo(hog, amRope, 1)
			SetHealth(hog, initHogHealthFinal)
			isInFinalChallenge = true
		elseif currentTarget == 7 then
			SaveMissionVar("Won", "true")
			ShowMission(loc("Basic Rope Training"), loc("Training complete!"),
			loc("Congratulations!"), 4, 0)
			if flawless then
				PlaySound(sndFlawless, hog)
			else
				PlaySound(sndVictory, hog)
			end
			AddAmmo(hog, amBaseballBat, 0)
			AddAmmo(hog, amGrenade, 0)
			AddAmmo(hog, amRope, 0)
			SendStat(siCustomAchievement, loc("Oh yeah! You sure know how to rope!"))
			SendStat(siGameResult, loc("You have finished the Basic Rope Training!"))
			if flowerPower then
				awardAchievement(loc("Flower Power"))
			end
			EndGame()
			SetState(hog, gstWinner)
			gameOver = true
			SetInputMask(0)
		end
		currentTarget = currentTarget + 1

		if currentTarget <= #targetData then
			AddGear(targetData[currentTarget][1], targetData[currentTarget][2], gtTarget, 0, 0, 0, 0)
		end

	elseif GetGearType(gear) == gtExplosives then
		if not barrelsBoom then
			barrelsBoom = true
			AddAmmo(hog, amGrenade, 0)
			AddAmmo(hog, amBaseballBat, AMMO_INFINITE)
			eraseGirder(2)
			eraseGirder(3)
			ShowMission(loc("Basic Rope Training"),
				loc("Kaboom!"),
				loc("Follow the path and destroy the next target."),
				2, 5000)
		end
	elseif GetGearType(gear) == gtRope then
		ropeGear = nil
		if ropeAttached and not target1Reached then
			local ctrl = ""
			if INTERFACE == "desktop" then
				ctrl = loc("Aim: [Up]/[Down]").."|"..
				loc("Attack: [Space]")
			elseif INTERFACE == "touch" then
				ctrl = loc("Aim: [Up]/[Down]").."|"..
				loc("Attack: Tap the [Bomb]")
			end
			ShowMission(loc("Basic Rope Training"), loc("How to Rope"),
			loc("Go to the target.").."|"..
			loc("Hold [Attack] to attach the rope.").."|"..
			ctrl, 2, 13000)
			ropeAttached = false
		end
	elseif GetGearType(gear) == gtCase then
		eraseGirder(6)
		eraseGirder(7)
	end
end

function onAmmoStoreInit()
	SetAmmo(amRope, 9, 0, 0, 1)
	SetAmmo(amBaseballBat, 9, 0, 0, 1)
end

function onAttack()
	if GetCurAmmoType() == amGrenade and not ropeGear then
		AddCaption(loc("You have to drop the grenade from rope!"), 0xFF4000FF, capgrpMessage)
		dropNadeText(5000)
		PlaySound(sndDenied)
	end
end