share/hedgewars/Data/Scripts/Multiplayer/Construction_Mode.lua
author spudpiggy <facetakers@gmail.com>
Fri, 05 Apr 2024 13:10:55 +0100
changeset 16006 1f9f971adec4
parent 15242 0986513cd6e1
permissions -rw-r--r--
sndCover now falls back to sndWatchThis OR sndFire. sndDrat and sndBugger now fall back to each other.

--[[-----------------------------------------------------
-- CONSTRUCTION MODE --
---------------------------------------------------------
A Hedgewars gameplay mode by mikade.
Special thanks to all who helped test and offered suggestions.
Additional thanks to sheepluva/nemo for adding some extra hooks.

---------------------
-- STRUCTURES LIST --
---------------------

* Healing Station: Heals hogs to 150 health
* Teleportation Node: Allows teleporting to any other teleporter nodes
* Bio-filter: Explodes enemy hogs
* Respawner: If you have one of these, any slain hogs are resurrected here
* Generator: Generates power (used to buy stuff)
* Support Station: Allows purchasing crates
* Construction Station: Allows purchasing girders, rubber, mines, sticky mines, barrels
* Reflector Shield: Reflect projectiles
* Weapon Filter: Removes all equipement of enemy hogs passing through this area

---------------------------------------------------------
-- SCRIPT PARAMETER
---------------------------------------------------------
The script parameter can be used to configure the energy
of the game. It is a comma-seperated list of key=value pairs, where each
key is a word and each value is an integer between 0 and 4294967295.

Possible keys:
* initialenergy:  Amount of energy that each team starts with (default: 550)
                  Note: Must be smaller than or equal to maxenergy
* energyperround: Amount of energy that each team gets per round (default: 50)
* maxenergy:      Maximum amount of energy each team can hold (default: 1000)
* cratesperround: Maximum number of crates you can place per round (default: 5)

For the previous 2 keys, you can use the value “inf” for an unlimited amount.

Example: “initialenergy=750, maxenergy=2000” starts thee game with 750 energy
         and sets the maximum energy to 2000.
Example: “craterperround=inf” disables the crate placement limit.

---------------------------------------------------------
-- Ideas list --
---------------------------------------------------------

* To make the weapon filter more attractive, make it vaporize flying saucers
  and also rope, and maybe incoming gears

* Make healing thing also cure poison?
* Maybe make poison more virulent and dangerous

]]

HedgewarsScriptLoad("/Scripts/Locale.lua")
HedgewarsScriptLoad("/Scripts/Tracker.lua")
HedgewarsScriptLoad("/Scripts/Params.lua")

-- Structures stuff
local strucID = {}
local strucGear = {}
local strucClan = {}
local strucType = {}
local strucCost = {}
local strucHealth = {}

local strucCirc = {}
local strucCircCol = {}
local strucCircRadius = {}
local strucAltDisplay = {}

-- Clan stuff
local clanPower = {} -- current power for each clan. Used to build stuff
local clanPowerTag = nil -- visual gear ID of displayed clan power

local clanUsedExtraTime = {} -- has used extra time in this round?
local clanCratesSpawned = {} -- number of crates spawned in this round
local clanFirstTurn = {}

local clanBoundsSX = {}
local clanBoundsSY = {}
local clanBoundsEX = {}
local clanBoundsEY = {}

-- For tracking previous mode selection per-team
local teamLStructIndex = {}
local teamLObjectMode = {}
local teamLCrateMode = {}
local teamLMineIndex = {}
local teamLWeapIndex = {}
local teamLUtilIndex = {}

-- Wall stuff
local wallsVisible = false
local wX = {}
local wY = {}
local wWidth = {}
local wHeight = {}
local wCol = {}
local wMargin = 20
local borderEffectTimer = 0 -- timer for border clan sparkles

-- Other stuff
local placedExpense = 0 -- Cost of current selected thing
local curWep = amNothing -- current weapon, used to reduce # of calls to GetCurAmmoType()

local fortMode = false -- is using a fort map?
local tempID_CheckProximity = nil -- temporary structure variable for CheckProximity
local cGear = nil -- detects placement of girders and objects (using airattack)
local cGearPlacementDone = false
local uniqueStructureID = 0 -- Counter and ID for structures. Is incremented each time a structure spawns

-- Colors
local colorSupportStation = 0xFFFF00FF
local colorConstructionStation = 0xFFFFFFFF
local colorTeleportationNode = 0x0000FFFF
local colorHealingStation = 0xFF808040 -- Just a slight glow
local colorBioFilter = 0xFF0000FF
local colorReflectorShield = 0xFFAE00FF
local colorWeaponFilter =  0xA800FFFF

local colorHealingStationParticle = 0x00FF0080
local colorGeneratorParticle = 0xFFFF00FF

local colorMessageError = capcolDefault

-- Fake ammo types, for the overwritten weapons in Construction Mode
local amCMStructurePlacer = amAirAttack
local amCMCratePlacer = amMineStrike
local amCMObjectPlacer = amNapalm

-- Config variables (script parameter)
-- NOTE: If you change this, also change the default game scheme “Construction Mode”
local conf_initialEnergy = 550
local conf_energyPerRound = 50
local conf_maxEnergy = 1000
local conf_cratesPerRound = 5

-----------------------
-- CRATE DEFINITIONS --
-----------------------
-- format:
-- { ammoType, cost }

local costFactor = 20

-- WEAPON CRATES
-- Weapons which shouldn't be aded:
-- Air attack, napalm, drillstrike: Overwritten weapons for the Construction Mode tools
-- Mine strike: Is currently broken
-- Piano strike: Hog is resurrected by respawner. Not strictly prohibited, however.
local atkArray = {
	{amBazooka,	 2*costFactor},
	--{amBee,	 4*costFactor},
	{amMortar,	 1*costFactor},
	{amDrill,	 3*costFactor},
	{amSnowball,	 3*costFactor},
	{amKnife,	 2*costFactor},

	{amGrenade,	 2*costFactor},
	{amClusterBomb,	 3*costFactor},
	{amWatermelon,	25*costFactor},
	{amHellishBomb,	25*costFactor},
	{amMolotov,	 3*costFactor},
	{amGasBomb,	 3*costFactor},

	{amShotgun,	 2*costFactor},
	{amDEagle,	 2*costFactor},
	{amSniperRifle,	 3*costFactor},
	--{amSineGun,	 6*costFactor},
	{amIceGun,	15*costFactor},
	{amMinigun,	13*costFactor},

	{amFirePunch,	 3*costFactor},
	{amWhip,	 1*costFactor},
	{amBaseballBat,	 7*costFactor},
	--{amKamikaze,	 1*costFactor},
	{amSeduction,	 1*costFactor},
	{amHammer,	 1*costFactor},

	{amMine,	 1*costFactor},
	{amDynamite,	 9*costFactor},
	{amCake,	25*costFactor},
	{amBallgun,	40*costFactor},
	--{amRCPlane,	25*costFactor},
	{amSMine,	 5*costFactor},

	--{amPiano,	40*costFactor},

	{amPickHammer,	 2*costFactor},
	{amBlowTorch,	 4*costFactor},
	{amFlamethrower, 4*costFactor},

	{amBirdy,	 7*costFactor},
}

-- UTILITY CRATES --

-- Utilities which shouldn't be added:
-- * Teleport: We have teleportation node
-- * Switch: Infinite in default Construction Mode weapons scheme
-- * Girder, rubber: Requires construction station
-- * Resurrector: We have the resurrector structure for this

-- Utilities which might be weird for this mode:
-- * Tardis: Randomly teleports hog, maybe even into enemy clan's area
local utilArray = {
 	{amLandGun,	 5*costFactor},

	{amRope,	 7*costFactor},
	{amParachute,	 2*costFactor},
	{amJetpack,	 8*costFactor},
	{amPortalGun,	 15*costFactor},

	{amInvulnerable, 5*costFactor},
	{amLaserSight,	 2*costFactor},
	{amVampiric,	 6*costFactor},

	{amLowGravity,	 4*costFactor},
	{amExtraDamage,	 6*costFactor},
	{amExtraTime,	 8*costFactor}
}

----------------------------
-- Placement stuff
----------------------------

-- primary placement categories
local cIndex = 1 -- category index
local cat = {
	loc_noop("Girder Placement Mode"),
	loc_noop("Rubber Placement Mode"),
	loc_noop("Mine Placement Mode"),
	loc_noop("Sticky Mine Placement Mode"),
	loc_noop("Barrel Placement Mode"),
	loc_noop("Weapon Crate Placement Mode"),
	loc_noop("Utility Crate Placement Mode"),
	loc_noop("Health Crate Placement Mode"),
	loc_noop("Structure Placement Mode"),
}

-- Frames in sprTarget for the structure images
local catFrames = {
	["Respawner"] = 1,
	["Generator"] = 2,
	["Healing Station"] = 3,
	["Support Station"] = 4,
	["Weapon Filter"] = 5,
	["Teleportation Node"] = 6,
	["Bio-Filter"] = 7,
	["Construction Station"] = 8,
	["Reflector Shield"] = 9,
}

local catReverse = {}
for c=1, #cat do
	catReverse[cat[c]] = c
end

-- Track girders in proximity of CurrentHedgehog
local sProx = {
	["Girder Placement Mode"] = false,
	["Rubber Placement Mode"] = false,
	["Mine Placement Mode"] = false,
	["Sticky Mine Placement Mode"] = false,
	["Barrel Placement Mode"] = false,
	["Weapon Crate Placement Mode"] = false,
	["Utility Crate Placement Mode"] = false,
	["Health Crate Placement Mode"] = false,
	["Structure Placement Mode"] = false,
	["Teleportation Mode"] = false,
}

local pMode = {}	-- pMode contains custom subsets of the main categories
local pIndex = 1

local currentGirderRotation = 1 -- current girder rotation, we actually need this as HW remembers what rotation you last used

-- Returns true if ammoType is an ammo type with a special meaning
-- in Construction Mode.
function IsConstructionModeAmmo(ammoType)
	return ammoType == amCMStructurePlacer or
	ammoType == amCMObjectPlacer or
	ammoType == amCMCratePlacer or
	ammoType == amGirder or
	ammoType == amRubber or
	ammoType == amTeleport
end

function RenderClanPower()
	DrawClanPowerTag()
end

function UpdateTeamLabels()
	for i=0, TeamsCount-1 do
		local name = GetTeamName(i)
		SetTeamLabel(name, clanPower[GetTeamClan(name)])
	end
end

function DrawClanPowerTag()

	local zoomL = 1.1
	local xOffset, yOffset
	if INTERFACE == "touch" then
		xOffset = 126
		yOffset = ScreenHeight - 32
	else
		xOffset = 45
		yOffset = 70
	end
	local tValue = clanPower[GetHogClan(CurrentHedgehog)]
	local tCol = GetClanColor(GetHogClan(CurrentHedgehog))

	DeleteVisualGear(clanPowerTag)
	clanPowerTag = AddVisualGear(-div(ScreenWidth, 2) + xOffset, ScreenHeight - yOffset, vgtHealthTag, tValue, false)

	SetVisualGearValues(
		clanPowerTag,   -- id
		nil,            -- x offset (set above)
		nil,            -- y offset (set above)
		0,              -- dx
		0,              -- dy
		zoomL,          -- zoom
		1,              -- ~= 0 means align to screen
		nil,            -- frameticks
		nil,            -- value (set above)
		240000,         -- timer
		tCol            -- color
	)

end

function DeleteClanPowerTag()
	DeleteVisualGear(clanPowerTag)
	clanPowerTag = nil
end

function XYisInRect(px, py, psx, psy, pex, pey)

	if (px > psx) and (px < pex) and (py > psy) and (py < pey) then
		return(true)
	else
		return(false)
	end

end

function AddWall(zXMin, zYMin, zWidth, zHeight, zCol)

	table.insert(wX, zXMin)
	table.insert(wY, zYMin)
	table.insert(wWidth, zWidth)
	table.insert(wHeight, zHeight)
	table.insert(wCol, zCol)

end

function BorderSpark(zXMin,zYMin, zWidth, zHeight, bCol)

	local eX = zXMin + GetRandom(zWidth+10)
	local eY = zYMin + GetRandom(zHeight+10)
	local tempE = AddVisualGear(eX, eY, vgtDust, 0, false)
	if tempE ~= 0 then
		SetVisualGearValues(tempE, eX, eY, nil, nil, nil, nil, nil, 1, nil, bCol)
	end

end

function HandleBorderEffects()

	borderEffectTimer = borderEffectTimer + 1
	if borderEffectTimer > 15 then
		borderEffectTimer = 1
		for i = 1, #wX do
			BorderSpark(wX[i],wY[i],wWidth[i],wHeight[i], wCol[i])
		end
	end

end

----
-- old reflecting stuff from like 3 years ago lol
---

function gearCanBeDeflected(gear)

	if 	(GetGearType(gear) == gtShell) or
		(GetGearType(gear) == gtGrenade) or
		(GetGearType(gear) == gtAirBomb) or
		(GetGearType(gear) == gtClusterBomb) or
		(GetGearType(gear) == gtCluster) or
		(GetGearType(gear) == gtGasBomb) or
		(GetGearType(gear) == gtMine) or
		(GetGearType(gear) == gtMortar) or
		(GetGearType(gear) == gtHellishBomb) or
		(GetGearType(gear) == gtWatermelon) or
		(GetGearType(gear) == gtMelonPiece)	or
		(GetGearType(gear) == gtEgg) or
		(GetGearType(gear) == gtDrill) or
		(GetGearType(gear) == gtBall) or
		(GetGearType(gear) == gtExplosives) or
		(GetGearType(gear) == gtFlame) or
		(GetGearType(gear) == gtPortal) or
		(GetGearType(gear) == gtDynamite) or
		(GetGearType(gear) == gtSMine) or
		(GetGearType(gear) == gtKnife) or
		(GetGearType(gear) == gtJetpack) or
		(GetGearType(gear) == gtBirdy) or
		(GetGearType(gear) == gtSnowball) or
		(GetGearType(gear) == gtMolotov)
	then
		return(true)
	else
		return(false)
	end

end

function getThreatDamage(gear)

	local dmg
	--- damage amounts for weapons
	if 	(GetGearType(gear) == gtGrenade) or
		(GetGearType(gear) == gtClusterBomb) or
		(GetGearType(gear) == gtGasBomb) or
		(GetGearType(gear) == gtKnife) or
		(GetGearType(gear) == gtEgg) or
		(GetGearType(gear) == gtMolotov) or
		(GetGearType(gear) == gtHellishBomb) or
		(GetGearType(gear) == gtWatermelon) or
		(GetGearType(gear) == gtSMine) then
		dmg = 30

	elseif (GetGearType(gear) == gtMelonPiece) then
		dmg = 40

	elseif (GetGearType(gear) == gtAirBomb) or
			(GetGearType(gear) == gtDrill) or
			(GetGearType(gear) == gtMine) or
			(GetGearType(gear) == gtCluster) then
		dmg = 20

	elseif (GetGearType(gear) == gtFlame) or
			(GetGearType(gear) == gtPortal) or
			(GetGearType(gear) == gtDynamite) then
		dmg = 0

	elseif (GetGearType(gear) == gtBall) then
		dmg = 1

	else	-- normal shell, snowball etc
		dmg = 65
	end

	return(dmg)

end

function setGearReflectionValues(gear)

	local dmg = getThreatDamage(gear)
	setGearValue(gear,"damage",dmg)
	setGearValue(gear,"deflects",0)

	if (CurrentHedgehog ~= nil) then
		setGearValue(gear,"owner",GetHogClan(CurrentHedgehog)) -- NEW NEEDS CHANGE?
	else
		setGearValue(gear,"owner",10)
	end

end

function isATrackedGear(gear)
	if 	(GetGearType(gear) == gtHedgehog) or
		(GetGearType(gear) == gtTarget) or
		(GetGearType(gear) == gtCase)
	then
		return(true)
	else
		return(false)
	end
end

function AddStruc(pX,pY, pType, pClan)

	uniqueStructureID = uniqueStructureID + 1

	local tempG = AddGear(0, 0, gtTarget, 0, 0, 0, 0)
	SetGearPosition(tempG, pX, pY)
	setGearValue(tempG, "uniqueStructureID", uniqueStructureID)

	local tempCirc = AddVisualGear(0,0,vgtCircle,0,true)

	if pType ~= "Respawner" and pType ~= "Generator" then
		SetVisualGearValues(tempCirc, 0, 0, 100, 255, 1, 100, 0, 500, 1, 0xFFFFFF00)
		table.insert(strucCirc, tempCirc)
	else
		table.insert(strucCirc, false)
	end

	table.insert(strucID, uniqueStructureID)
	table.insert(strucType, pType)
	table.insert(strucGear,tempG)
	table.insert(strucClan,pClan)
	table.insert(strucCost,2)

	local frameID = 0
	local visualSprite = sprTarget
	local madness = AddVisualGear(GetX(tempG), GetY(tempG), vgtStraightShot, 1, true,1)

	if pType == "Reflector Shield" then
		table.insert(strucHealth,255)

	else
		table.insert(strucHealth,1)
	end

	if pType == "Bio-Filter" then
		table.insert(strucCircCol, colorBioFilter)
		table.insert(strucCircRadius,1000)
	elseif pType == "Healing Station" then
		table.insert(strucCircCol, colorHealingStation)
		table.insert(strucCircRadius,500)
	elseif pType == "Respawner" then
		table.insert(strucCircCol, 0)
		table.insert(strucCircRadius,0)
		runOnHogs(EnableHogResurrectionForThisClan)
	elseif pType == "Teleportation Node" then
		table.insert(strucCircCol, colorTeleportationNode)
		table.insert(strucCircRadius,350)
	elseif pType == "Generator" then
		table.insert(strucCircCol, 0)
		table.insert(strucCircRadius,0)
		setGearValue(tempG, "power", 0)
	elseif pType == "Support Station" then
		table.insert(strucCircCol, colorSupportStation)
		table.insert(strucCircRadius,500)
	elseif pType == "Construction Station" then
		table.insert(strucCircCol, colorConstructionStation)
		table.insert(strucCircRadius,500)
	elseif pType == "Reflector Shield" then
		table.insert(strucCircCol, colorReflectorShield)
		table.insert(strucCircRadius,750)
	elseif pType == "Weapon Filter" then
		table.insert(strucCircCol, colorWeaponFilter)
		table.insert(strucCircRadius,750)
	end
	if catFrames[pType] then
		frameID = catFrames[pType]
	end

	SetVisualGearValues(madness, nil, nil, 0, 0, nil, frameID, nil, visualSprite, nil, nil)
	SetState(tempG, bor(GetState(tempG),gstInvisible) )
	table.insert(strucAltDisplay, madness)

end

-- this is basically onStructureDelete
-- we may need to expand it for non-gear structures later
function CheckGearForStructureLink(gear)

	local respawnerDestroyed = false

	for i = 1, #strucID do
		if strucID[i] == getGearValue(gear,"uniqueStructureID") then

			if strucType[i] == "Respawner" then
				respawnerDestroyed = true
			end

			table.remove(strucID,i)
			table.remove(strucGear,i)
			table.remove(strucClan,i)
			table.remove(strucType,i)
			table.remove(strucCost,i)
			table.remove(strucHealth,i)

			DeleteVisualGear(strucCirc[i])
			table.remove(strucCirc,i)

			table.remove(strucCircCol,i)
			table.remove(strucCircRadius,i)

			if strucAltDisplay[i] ~= 1 then
				DeleteVisualGear(strucAltDisplay[i])
			end
			table.remove(strucAltDisplay,i)

		end
	end

	if respawnerDestroyed == true then
		runOnHogs(RecalibrateRespawn)
	end

end

-- called when we add a new respawner
function EnableHogResurrectionForThisClan(gear)
	if GetHogClan(gear) == GetHogClan(CurrentHedgehog) then
		SetEffect(gear, heResurrectable, 1)
	end
end

-- this is called when a respawner blows up
function RecalibrateRespawn(gear)

	local respawnerList = {}
	for i = 1, #strucID do
		if (strucType[i] == "Respawner") and (strucClan[i] == GetHogClan(gear)) then
			table.insert(respawnerList, i)
		end
	end

	if #respawnerList >= 1 then
		SetEffect(gear, heResurrectable, 1)
	else
		SetEffect(gear, heResurrectable, 0)
	end

end

--resposition dead hogs at a respawner if they own one
function FindRespawner(gear)

	local respawnerList = {}
	for i = 1, #strucID do
		if (strucType[i] == "Respawner") and (strucClan[i] == GetHogClan(gear)) then
			table.insert(respawnerList, i)
		end
	end

	if #respawnerList >= 1 then
		local i = GetRandom(#respawnerList)+1
		SetGearPosition(gear,GetX(strucGear[respawnerList[i]]),GetY(strucGear[respawnerList[i]])-25)
		AddVisualGear(GetX(gear), GetY(gear), vgtExplosion, 0, false)
		local msgs = {
			loc("The respawner respawns %s"),
			loc("%s died … and lives again!"),
			loc("%s gets an extra life"),
			loc("%s is now a zombie hedgehog"),
			loc("%s has been rescued from death"),
		}
		local r = math.random(1, #msgs)
		AddCaption(string.format(msgs[r], GetHogName(gear)))
	else	-- (this should never happen, but just in case)
		SetEffect(gear, heResurrectable, 0)
		DeleteGear(gear)
	end

end

function CheckTeleport(gear, tX, tY)

	local teleportOriginSuccessful = false
	local teleportDestinationSuccessful = false

	for i = 1, #strucID do

		if (strucType[i] == "Teleportation Node") and (strucClan[i] == GetHogClan(CurrentHedgehog)) then

			local dist = GetDistFromGearToXY(CurrentHedgehog,GetX(strucGear[i]), GetY(strucGear[i]))
			local NR
			NR = (48/100*strucCircRadius[i])/2
			if dist <= NR*NR then
				teleportOriginSuccessful = true
			end

			dist = GetDistFromXYtoXY(tX,tY,GetX(strucGear[i]), GetY(strucGear[i]))
			NR = (48/100*strucCircRadius[i])/2
			if dist <= NR*NR then
				teleportDestinationSuccessful = true
			end

		end


	end

	if ((teleportDestinationSuccessful == false) or (teleportOriginSuccessful == false)) then
		if IsHogLocal(CurrentHedgehog) then
			AddCaption(loc("Teleport unsuccessful. Please teleport within a clan teleporter's sphere of influence."), colorMessageError, capgrpMessage)
		end
		SetGearTarget(gear, GetX(CurrentHedgehog), GetY(CurrentHedgehog))
	end

end

--Check for proximity of gears to structures, and make structures behave accordingly
function CheckProximity(gear)

	local sID = tempID_CheckProximity

	local dist = GetDistFromGearToXY(gear, GetX(strucGear[sID]), GetY(strucGear[sID]))
	if not dist then
		return
	end

	-- calculate my real radius if I am an aura
	local NR
	NR = (48/100*strucCircRadius[sID])/2

	-- we're in business
	if dist <= NR*NR then

		-- heal clan hogs
		if strucType[sID] == "Healing Station" then

			if GetGearType(gear) == gtHedgehog then
				if GetHogClan(gear) == strucClan[sID] then

					local hogLife = GetHealth(gear)
					-- Heal hog by 1 HP, up to 150 HP total
					if hogLife < 150 then
						if ((hogLife + 1) % 5) == 0 then
							-- Health anim every 5 HP
							HealHog(gear, 1, false)
						else
							SetHealth(gear, hogLife+1)
						end
					end

					-- Maybe find better animation?
					local tempE = AddVisualGear(GetX(strucGear[sID]), GetY(strucGear[sID]), vgtSmoke, 0, false)
					SetVisualGearValues(tempE, nil, nil, nil, nil, nil, nil, nil, nil, nil, colorHealingStationParticle)

				end
			end

		-- explode enemy clan hogs
		elseif strucType[sID] == "Bio-Filter" then

			if GetGearType(gear) == gtHedgehog then
				if (GetHogClan(gear) ~= strucClan[sID]) and (GetHealth(gear) > 0) then
					AddGear(GetX(gear), GetY(gear), gtGrenade, 0, 0, 0, 1)
				end
			end

		-- were those weapons in your pocket, or were you just happy to see me?
		elseif strucType[sID] == "Weapon Filter" then

			if GetGearType(gear) == gtHedgehog then
				if (GetHogClan(gear) ~= strucClan[sID]) then

					-- Vaporize (almost) all of the hog's ammo

					local ammosDestroyed = 0
					for wpnIndex = 0, AmmoTypeMax do
						if (not IsConstructionModeAmmo(wpnIndex)) and wpnIndex ~= amSkip and wpnIndex ~= amNothing then
							local count = GetAmmoCount(gear, wpnIndex)
							-- Infinite ammos are spared
							if count ~= 100 then
								ammosDestroyed = ammosDestroyed + count
								AddAmmo(gear, wpnIndex, 0)
							end
						end
					end

					if ammosDestroyed > 0 then
						-- Vaporize effects
						if gear == CurrentHedgehog then
							local r = math.random(1, 2)
							if r == 1 then
								PlaySound(sndNutter, gear)
							else
								PlaySound(sndOops, gear)
							end
						end
						PlaySound(sndVaporize)
						for i=1, 5 do
							AddVisualGear(GetX(gear), GetY(gear), vgtSmoke, 0, false)
						end

						local msgs = {
							loc("%s lost all the weapons"),
							loc("The ammo of %s has been vaporized"),
							loc("%s fell victim to a weapon filter"),
							loc("%s is suddenly low on ammo"),
							loc("%s is now as poor as a church mouse"),
						}
						local r = math.random(1, #msgs)
						AddCaption(string.format(msgs[r], GetHogName(gear)), capcolDefault, capgrpAmmoinfo)
					end

				end
			end

		-- BOUNCE! POGO! POGO! POGO! POGO!
		elseif strucType[sID] == "Reflector Shield" then

			-- add check for whose projectile it is
			if gearCanBeDeflected(gear) == true then

				local gOwner = getGearValue(gear,"owner")
				local gDeflects = getGearValue(gear,"deflects")
				local gDmg = getGearValue(gear,"damage")

				if gDeflects >= 3 then
					DeleteGear(gear)
					AddVisualGear(GetX(gear), GetY(gear), vgtSmoke, 0, false)
					PlaySound(sndVaporize)
				elseif gOwner ~= strucClan[sID] then
					--whether to vaporize gears or bounce them
					if gDmg ~= 0 then
						local dx, dy = GetGearVelocity(gear)

						if (dx == 0) and (dy == 0) then
							-- static mine, explosive, etc encountered
							-- do nothing
							else

							--let's bounce something!

							dx = dx*(-1)
							dy = dy*(-1)
							SetGearVelocity(gear,dx,dy)
							setGearValue(gear,"deflects",(gDeflects+1))

							AddVisualGear(GetX(gear), GetY(gear), vgtExplosion, 0, false)
							PlaySound(sndExplosion)

							strucHealth[sID] = strucHealth[sID] - gDmg
							if strucCirc[sID] then
								strucCircCol[sID] = strucCircCol[sID] - gDmg
							end

							if strucHealth[sID] <= 0 then
								AddVisualGear(GetX(strucGear[sID]), GetY(strucGear[sID]), vgtExplosion, 0, false)
								DeleteGear(strucGear[sID])
								PlaySound(sndExplosion)
							end

						end

					else
						DeleteGear(gear)
						AddVisualGear(GetX(gear), GetY(gear), vgtSmoke, 0, false)
						PlaySound(sndVaporize)
					end
				end
			end

		--mark as within range of a teleporter node
		elseif strucType[sID] == "Teleportation Node" then

			if GetGearType(gear) == gtHedgehog then
				if GetHogClan(gear) == strucClan[sID] then

					sProx["Teleportation Mode"] = true

				end
			end

		-- mark as within range of construction station
		-- and thus allow menu access to placement modes
		-- for girders, mines, sticky mines and barrels
		elseif strucType[sID] == "Construction Station" then

			if GetGearType(gear) == gtHedgehog then
				if GetHogClan(gear) == strucClan[sID] then
					AddVisualGear(GetX(strucGear[sID]), GetY(strucGear[sID]), vgtSmoke, 0, false)

					sProx["Girder Placement Mode"] = true
					sProx["Rubber Placement Mode"] = true
					sProx["Mine Placement Mode"] = true
					sProx["Sticky Mine Placement Mode"] = true
					sProx["Barrel Placement Mode"] = true

				end
			end

		-- mark as within stupport station range
		-- and thus allow menu access to placement modes
		-- for weapon, utility, and med crates
		elseif strucType[sID] == "Support Station" then

			if GetGearType(gear) == gtHedgehog then
				if GetHogClan(gear) == strucClan[sID] then
					AddVisualGear(GetX(strucGear[sID]), GetY(strucGear[sID]), vgtSmoke, 0, false)

					sProx["Health Crate Placement Mode"] = true
					sProx["Weapon Crate Placement Mode"] = true
					sProx["Utility Crate Placement Mode"] = true

				end
			end
		end

	end

end

-- used to check if we need to run through all hogs or just currenthedgehog
function isAStructureThatAppliesToMultipleGears(pID)
	if 	strucType[pID] == "Healing Station" or
		strucType[pID] == "Reflector Shield" or
		strucType[pID] == "Weapon Filter" or
		strucType[pID] == "Bio-Filter"
	then
		return(true)
	else
		return(false)
	end
end

function HandleStructures()

	if GameTime % 100 == 0 then
		for k, _ in pairs(sProx) do
			if k ~= "Structure Placement Mode" then
				sProx[k] = false
			end
		end
	end

	for i = 1, #strucID do

		if strucCirc[i] then
			SetVisualGearValues(strucCirc[i], GetX(strucGear[i]), GetY(strucGear[i]), nil, nil, nil, nil, nil, strucCircRadius[i], nil, strucCircCol[i])
		end

		tempID_CheckProximity = i

		SetVisualGearValues(strucAltDisplay[i], GetX(strucGear[i]), GetY(strucGear[i]), 0, 0, nil, nil, 800000, sprTarget)

		if GameTime % 100 == 0 then
			-- Check For proximity of stuff to our structures
			if isAStructureThatAppliesToMultipleGears(i) then
				runOnGears(CheckProximity)
			else -- only check prox on CurrentHedgehog
				if CurrentHedgehog ~= nil then
					CheckProximity(CurrentHedgehog)
				end
			end

			if strucType[i] == "Generator" then

				for z = 0, ClansCount-1 do
					if z == strucClan[i] then
						increaseGearValue(strucGear[i],"power")
						if getGearValue(strucGear[i],"power") == 10 then
							setGearValue(strucGear[i],"power",0)

							-- Add 1 energy (if not at max. already)
							if not (conf_maxEnergy ~= "inf" and clanPower[z] + 1 > conf_maxEnergy) then
								clanPower[z] = clanPower[z] + 1

								-- Spawn one particle per energy added
								local particle = AddVisualGear(GetX(strucGear[i]), GetY(strucGear[i])-16, vgtStraightShot, sprStar, false)
								SetVisualGearValues(particle, nil, nil, math.random(-100, 100)*0.00005, 0.02, math.random(360), 0, 900, nil, 0, colorGeneratorParticle)

							else

								SetVisualGearValues(strucAltDisplay[i], GetX(strucGear[i]), GetY(strucGear[i]), 0, 0, nil, nil, 800000, sprTarget)
							end


						end

					end
				end
			end

		end

	end

	-- Add and remove ammo based on structure proximity
	if GameTime % 100 == 0 and CurrentHedgehog ~= nil then
		if sProx["Girder Placement Mode"] then
			AddAmmo(CurrentHedgehog, amGirder, 100)
		else
			AddAmmo(CurrentHedgehog, amGirder, 0)
		end
		if sProx["Rubber Placement Mode"] then
			AddAmmo(CurrentHedgehog, amRubber, 100)
		else
			AddAmmo(CurrentHedgehog, amRubber, 0)
		end
		if sProx["Mine Placement Mode"] or sProx["Sticky Mine Placement Mode"] or sProx["Barrel Placement Mode"] then
			AddAmmo(CurrentHedgehog, amCMObjectPlacer, 100)
		else
			AddAmmo(CurrentHedgehog, amCMObjectPlacer, 0)
		end
		if sProx["Teleportation Mode"] then
			AddAmmo(CurrentHedgehog, amTeleport, 100)
		else
			AddAmmo(CurrentHedgehog, amTeleport, 0)
		end
		if sProx["Weapon Crate Placement Mode"] or sProx["Utility Crate Placement Mode"] or sProx["Health Crate Placement Mode"] then
			AddAmmo(CurrentHedgehog, amCMCratePlacer, 100)
		else
			AddAmmo(CurrentHedgehog, amCMCratePlacer, 0)
		end
	end

end

------------------------
-- SOME GENERAL METHODS
------------------------

function GetDistFromGearToXY(gear, g2X, g2Y)

	local g1X, g1Y = GetGearPosition(gear)
	if not g1X then
		return nil
	end
	local q = g1X - g2X
	local w = g1Y - g2Y

	return ( (q*q) + (w*w) )

end

function GetDistFromXYtoXY(a, b, c, d)
	local q = a - c
	local w = b - d
	return ( (q*q) + (w*w) )
end

-- essentially called when user clicks the mouse
-- with girders or an airattack
function PlaceObject(x,y)

	if (clanUsedExtraTime[GetHogClan(CurrentHedgehog)] == true) and (cat[cIndex] == "Utility Crate Placement Mode") and (utilArray[pIndex][1] == amExtraTime) then
		if IsHogLocal(CurrentHedgehog) then
			AddCaption(loc("You may only place 1 Extra Time crate per turn."), colorMessageError, capgrpVolume)
		end
		PlaySound(sndDenied)
	elseif (conf_cratesPerRound ~= "inf" and clanCratesSpawned[GetHogClan(CurrentHedgehog)] >= conf_cratesPerRound) and ( (cat[cIndex] == "Health Crate Placement Mode") or (cat[cIndex] == "Utility Crate Placement Mode") or (cat[cIndex] == "Weapon Crate Placement Mode")  )  then
		if IsHogLocal(CurrentHedgehog) then
			AddCaption(string.format(loc("You may only place %d crates per round."), conf_cratesPerRound), colorMessageError, capgrpVolume)
		end
		PlaySound(sndDenied)
	elseif (XYisInRect(x,y, clanBoundsSX[GetHogClan(CurrentHedgehog)],clanBoundsSY[GetHogClan(CurrentHedgehog)],clanBoundsEX[GetHogClan(CurrentHedgehog)],clanBoundsEY[GetHogClan(CurrentHedgehog)]) == true)
	and (clanPower[GetHogClan(CurrentHedgehog)] >= placedExpense)
	then
		-- For checking if the actual placement succeeded
		local placed = false
		local gear
		if cat[cIndex] == "Girder Placement Mode" then
			placed = PlaceGirder(x, y, currentGirderRotation)
		elseif cat[cIndex] == "Rubber Placement Mode" then
			placed = PlaceRubber(x, y, currentGirderRotation)
		elseif cat[cIndex] == "Health Crate Placement Mode" then
			gear = SpawnHealthCrate(x,y)
			if gear ~= nil then
				placed = true
				SetHealth(gear, pMode[pIndex])
				clanCratesSpawned[GetHogClan(CurrentHedgehog)] = clanCratesSpawned[GetHogClan(CurrentHedgehog)] +1
			end
		elseif cat[cIndex] == "Weapon Crate Placement Mode" then
			gear = SpawnAmmoCrate(x, y, atkArray[pIndex][1])
			if gear ~= nil then
				placed = true
				clanCratesSpawned[GetHogClan(CurrentHedgehog)] = clanCratesSpawned[GetHogClan(CurrentHedgehog)] +1
			end
		elseif cat[cIndex] == "Utility Crate Placement Mode" then
			gear = SpawnUtilityCrate(x, y, utilArray[pIndex][1])
			if gear ~= nil then
				placed = true
				if utilArray[pIndex][1] == amExtraTime then
					clanUsedExtraTime[GetHogClan(CurrentHedgehog)] = true
				end
				clanCratesSpawned[GetHogClan(CurrentHedgehog)] = clanCratesSpawned[GetHogClan(CurrentHedgehog)] +1
			end
		elseif cat[cIndex] == "Barrel Placement Mode" then
			gear = AddGear(x, y, gtExplosives, 0, 0, 0, 0)
			if gear ~= nil then
				placed = true
				SetHealth(gear, pMode[pIndex])
			end
		elseif cat[cIndex] == "Mine Placement Mode" then
			gear = AddGear(x, y, gtMine, 0, 0, 0, 0)
			if gear ~= nil then
				placed = true
				SetTimer(gear, pMode[pIndex])
			end
		elseif cat[cIndex] == "Sticky Mine Placement Mode" then
			gear = AddGear(x, y, gtSMine, 0, 0, 0, 0)
			placed = gear ~= nil
		elseif cat[cIndex] == "Structure Placement Mode" then
			AddStruc(x,y, pMode[pIndex],GetHogClan(CurrentHedgehog))
			placed = true
		end

		if placed then
			-- Pay the price
			clanPower[GetHogClan(CurrentHedgehog)] = clanPower[GetHogClan(CurrentHedgehog)] - placedExpense
			RenderClanPower()
			UpdateTeamLabels()
			if cat[cIndex] == "Girder Placement Mode" or cat[cIndex] == "Rubber Placement Mode" then
				PlaySound(sndPlaced)
			end
		else
			if IsHogLocal(CurrentHedgehog) then
				AddCaption(loc("Invalid Placement"), colorMessageError, capgrpVolume)
			end
			PlaySound(sndDenied)
		end

	else
		if (clanPower[GetHogClan(CurrentHedgehog)] >= placedExpense) then
			if IsHogLocal(CurrentHedgehog) then
				AddCaption(loc("Invalid Placement"), colorMessageError, capgrpVolume)
			end
		else
			if IsHogLocal(CurrentHedgehog) then
				AddCaption(loc("Insufficient Power"), colorMessageError, capgrpVolume)
			end
		end
		PlaySound(sndDenied)
	end

end

-- called when user changes primary selection
-- either via up/down keys
-- or selecting girder/airattack
function RedefineSubset()

	pIndex = 1
	pMode = {}

	if (CurrentHedgehog == nil or band(GetState(CurrentHedgehog), gstHHDriven) == 0) then
		return false
	end

	local team = GetHogTeamName(CurrentHedgehog)

	if cat[cIndex] == "Girder Placement Mode" then
		pIndex = currentGirderRotation
		pMode = {amGirder}
	elseif cat[cIndex] == "Rubber Placement Mode" then
		pIndex = currentGirderRotation
		pMode = {amRubber}
	elseif cat[cIndex] == "Barrel Placement Mode" then
		pMode = {60}
		teamLObjectMode[team] = cat[cIndex]
	elseif cat[cIndex] == "Health Crate Placement Mode" then
		pMode = {HealthCaseAmount}
		teamLCrateMode[team] = cat[cIndex]
	elseif cat[cIndex] == "Weapon Crate Placement Mode" then
		for i = 1, #atkArray do
			pMode[i] = atkArray[i][1]
		end
		teamLCrateMode[team] = cat[cIndex]
		pIndex = teamLWeapIndex[team]
	elseif cat[cIndex] == "Utility Crate Placement Mode" then
		for i = 1, #utilArray do
			pMode[i] = utilArray[i][1]
		end
		teamLCrateMode[team] = cat[cIndex]
		pIndex = teamLUtilIndex[team]
	elseif cat[cIndex] == "Mine Placement Mode" then
		pMode = {0,1000,2000,3000,4000,5000}
		teamLObjectMode[team] = cat[cIndex]
		pIndex = teamLMineIndex[team]
	elseif cat[cIndex] == "Sticky Mine Placement Mode" then
		pMode = {amSMine}
		teamLObjectMode[team] = cat[cIndex]
	elseif cat[cIndex] == "Structure Placement Mode" then
		pMode = {
			loc_noop("Support Station"),
			loc_noop("Construction Station"),
			loc_noop("Healing Station"),
			loc_noop("Teleportation Node"),
			loc_noop("Weapon Filter"),

			loc_noop("Bio-Filter"),
			loc_noop("Reflector Shield"),
			loc_noop("Respawner"),
			loc_noop("Generator"),
		}
		pIndex = teamLStructIndex[team]
	end

	return true
end

-- Updates the handling of the main construction mode tools:
-- Structure Placer, Crate Placer, Object Placer.
-- This handles the internal category state,
-- the HUD display and the clans outline.
function HandleConstructionModeTools()
	-- Update display selection criteria
	if (CurrentHedgehog ~= nil and band(GetState(CurrentHedgehog), gstHHDriven) ~= 0) then
		curWep = GetCurAmmoType()

		local updated = false
		local team = GetHogTeamName(CurrentHedgehog)
		if (curWep == amGirder) then
			cIndex = 1
			RedefineSubset()
			updateCost()
			updated = true
		elseif (curWep == amRubber) then
			cIndex = 2
			RedefineSubset()
			updateCost()
			updated = true
		elseif (curWep == amCMStructurePlacer) then
			cIndex = 9
			RedefineSubset()
			updateCost()
			updated = true
		elseif (curWep == amCMCratePlacer) then
			cIndex = catReverse[teamLCrateMode[team]]
			RedefineSubset()
			updateCost()
			updated = true
		elseif (curWep == amCMObjectPlacer) then
			cIndex = catReverse[teamLObjectMode[team]]
			RedefineSubset()
			updateCost()
			updated = true
		end

		if curWep == amCMStructurePlacer or curWep == amCMCratePlacer or curWep == amCMObjectPlacer then
			SetSoundMask(sndIncoming, true)
		else
			SetSoundMask(sndIncoming, false)
		end
		if curWep == amGirder or curWep == amRubber then
			SetSoundMask(sndDenied, true)
		else
			SetSoundMask(sndDenied, false)
		end

		if updated then
			AddCaption(loc(cat[cIndex]), GetClanColor(GetHogClan(CurrentHedgehog)), capgrpMessage)
			showModeMessage()
			wallsVisible = true
		else
			wallsVisible = false
		end
	else
		curWep = amNothing
		wallsVisible = false
	end
end

local cursorIcon = nil
local ammoIcon = nil
local ammoIconBorder = nil

-- Handle cursor stuff. This displays a sprite under the cursor so you can see what you're going to place.
function HandleCursor()
	if curWep == amCMStructurePlacer or curWep == amCMObjectPlacer or curWep == amCMCratePlacer then
		local dFrame = 0
		local dSprite
		local yOffset = 0
		if (cat[cIndex] == "Structure Placement Mode") then
			dSprite = sprTarget
			dFrame = catFrames[pMode[pIndex]]
		elseif (cat[cIndex] == "Mine Placement Mode") then
			dSprite = sprCustom2 -- sprMineOff
		elseif (cat[cIndex] == "Sticky Mine Placement Mode") then
			dSprite = sprCustom3 -- sprSMineOff
		elseif (cat[cIndex] == "Barrel Placement Mode") then
			dSprite = sprExplosives
		elseif (cat[cIndex] == "Health Crate Placement Mode") then
			dSprite = sprFAid
		elseif (cat[cIndex] == "Weapon Crate Placement Mode") then
			dSprite = sprCase
		elseif (cat[cIndex] == "Utility Crate Placement Mode") then
			dSprite = sprUtility
		else
			dSprite = sprArrow
		end

		-- Display the gear to be spawned under the cursor
		if not cursorIcon then
			cursorIcon = AddVisualGear(CursorX, CursorY, vgtStraightShot, dSprite, true, 3)
		end
		SetVisualGearValues(cursorIcon, CursorX, CursorY, 0, 0, 0, dFrame, 1000, dSprite, 1000)

		-- Render ammo icon for weapon and utility crate.
		-- But hide this from prying eyes of your enemies online!
		if IsHogLocal(CurrentHedgehog) then
			local ammoFrame
			if (cat[cIndex] == "Weapon Crate Placement Mode") or (cat[cIndex] == "Utility Crate Placement Mode") then
				local tArr
				if (cat[cIndex] == "Weapon Crate Placement Mode") then
					tArr = atkArray
				else
					tArr = utilArray
				end

				-- Get ammo icon
				ammoFrame = tArr[pIndex][1] - 1
			end
			if ammoFrame then
				local xDisplacement = 42
				local yDisplacement = 42
				local x = CursorX + yDisplacement
				local y = CursorY + yDisplacement

				-- Border around ammo icon
				if not ammoIconBorder then
					ammoIconBorder = AddVisualGear(x, y, vgtStraightShot, sprCustom1, true, 3)
				end
				SetVisualGearValues(ammoIconBorder, x, y, 0, 0, 0, 0, 1000, nil, 1000)

				-- Ammo icon
				if not ammoIcon then
					ammoIcon = AddVisualGear(x, y, vgtStraightShot, sprAMAmmos, true, 3)
				end
				SetVisualGearValues(ammoIcon, x, y, 0, 0, 0, ammoFrame, 1000, nil, 1000)

			else
				-- Cleanup vgears if not placing ammo crates
				if ammoIcon then
					DeleteVisualGear(ammoIcon)
				end
				if ammoIconBorder then
					DeleteVisualGear(ammoIconBorder)
				end
			end
		end
	else
		-- Cleanup vgears
		if cursorIcon then
			DeleteVisualGear(cursorIcon)
		end
		if ammoIcon then
			DeleteVisualGear(ammoIcon)
		end
		if ammoIconBorder then
			DeleteVisualGear(ammoIconBorder)
		end
	end
end

function onVisualGearDelete(vg)
	if vg ~= nil then
		if vg == cursorIcon then
			cursorIcon = nil
		elseif vg == ammoIcon then
			ammoIcon = nil
		elseif vg == ammoIconBorder then
			ammoIconBorder = nil
		end
	end
end

-- called in onGameTick()
function HandleConstructionMode()

	HandleStructures()

	if GameTime % 100 == 0 then
		UpdateTeamLabels()
	end

	if CurrentHedgehog ~= nil then

		if wallsVisible == true then
			HandleBorderEffects()
		end

		HandleCursor()

		if GameTime % 100 == 0 then

			-- Force-update the construction mode tools every 100ms.
			-- This makes sure the announcer messages don't disappear
			-- while the tool is selected.
			if (band(GetState(CurrentHedgehog), gstHHDriven) ~= 0) then
				RenderClanPower()
				curWep = GetCurAmmoType()
				HandleConstructionModeTools()
			else
				DeleteClanPowerTag()
				curWep = amNothing
			end

		end

	end

	-- some kind of target detected, tell me your story
	if cGear ~= nil then

		local x,y = GetGearTarget(cGear)

		if GetGearType(cGear) == gtAirAttack then
			SetGearMessage(cGear, bor(GetGearMessage(cGear), gmDestroy))
			if not cGearPlacementDone then
				PlaceObject(x, y)
				cGearPlacementDone = true
			end
		elseif GetGearType(cGear) == gtTeleport then
			CheckTeleport(cGear, x, y)
			cGear = nil
			cGearPlacementDone = true
		elseif GetGearType(cGear) == gtGirder then
			currentGirderRotation = GetState(cGear)
			PlaceObject(x, y)
			cGearPlacementDone = true
		end

	end

end

---------------------------------------------------------------
-- Cycle through selection subsets (by changing pIndex, pMode)
-- i.e 	health of barrels, medikits,
--		timer of mines
--		contents of crates
--		gears to reposition etc.
---------------------------------------------------------------

function updateCost()

	if CurrentHedgehog == nil or band(GetState(CurrentHedgehog), gstHHDriven) == 0 then return end

	-- Fallback cost
	placedExpense = 1
	if pMode[pIndex] == "Healing Station" then
		placedExpense = 50
	elseif pMode[pIndex] == "Weapon Filter" then
		placedExpense = 50
	elseif pMode[pIndex] == "Bio-Filter" then
		placedExpense = 100
	elseif pMode[pIndex] == "Respawner" then
		placedExpense = 300
	elseif pMode[pIndex] == "Teleportation Node" then
		placedExpense = 30
	elseif pMode[pIndex] == "Support Station" then
		placedExpense = 50
	elseif pMode[pIndex] == "Construction Station" then
		placedExpense = 50
	elseif pMode[pIndex] == "Generator" then
		placedExpense = 300
	elseif pMode[pIndex] == "Reflector Shield" then
		placedExpense = 200
	elseif cat[cIndex] == "Weapon Crate Placement Mode" then
		placedExpense = atkArray[pIndex][2]
	elseif cat[cIndex] == "Utility Crate Placement Mode" then
		placedExpense = utilArray[pIndex][2]
	elseif cat[cIndex] == "Health Crate Placement Mode" then
		placedExpense = 5
	elseif cat[cIndex] == "Mine Placement Mode" then
		placedExpense = 15
	elseif cat[cIndex] == "Sticky Mine Placement Mode" then
		placedExpense = 20
	elseif cat[cIndex] == "Barrel Placement Mode" then
		placedExpense = 10
	elseif cat[cIndex] == "Girder Placement Mode" then
		placedExpense = 1
	elseif cat[cIndex] == "Rubber Placement Mode" then
		placedExpense = 3
	end

	-- Hide cost from spectators.
	-- Also, this information is hidden cuz it could be used to infer e.g. crate contents.
	if IsHogLocal(CurrentHedgehog) then
		AddCaption(string.format(loc("Cost: %d"), placedExpense), GetClanColor(GetHogClan(CurrentHedgehog)), capgrpAmmostate)
	end

end

-- Should be called when the index of the mode was changed by the player.
-- E.g. new weapon crate contents or structure type
function updateIndex()
	if (curWep == amGirder) or (curWep == amRubber) or (curWep == amCMStructurePlacer) or (curWep == amCMCratePlacer) or (curWep == amCMObjectPlacer) then
		showModeMessage()
		updateCost()
	end

	-- Update team variables so the previous state can be restored later
	if CurrentHedgehog == nil or band(GetState(CurrentHedgehog), gstHHDriven) == 0 then return end
	local val = pMode[pIndex]
	local team = GetHogTeamName(CurrentHedgehog)
	if cat[cIndex] == "Structure Placement Mode" then
		teamLStructIndex[team] = pIndex
	elseif cat[cIndex] == "Mine Placement Mode" then
		teamLMineIndex[team] = pIndex
	elseif cat[cIndex] == "Weapon Crate Placement Mode" then
		teamLWeapIndex[team] = pIndex
	elseif cat[cIndex] == "Utility Crate Placement Mode" then
		teamLUtilIndex[team] = pIndex
	end
end

function showModeMessage()
	if CurrentHedgehog == nil or band(GetState(CurrentHedgehog), gstHHDriven) == 0 then return end
	local val = pMode[pIndex]
	local str
	if cat[cIndex] == "Mine Placement Mode" then
		-- timer in seconds
		str = string.format(loc("%d sec"), div(val, 1000))
	elseif cat[cIndex] == "Structure Placement Mode" then
		str = loc(val)
	elseif cat[cIndex] == "Girder Placement Mode" then
		str = GetAmmoName(amGirder)
	elseif cat[cIndex] == "Rubber Placement Mode" then
		str = GetAmmoName(amRubber)
	elseif cat[cIndex] == "Sticky Mine Placement Mode" then
		str = GetAmmoName(amSMine)
	elseif cat[cIndex] == "Weapon Crate Placement Mode"
	or cat[cIndex] == "Utility Crate Placement Mode" then
		str = GetAmmoName(val)
	elseif cat[cIndex] == "Health Crate Placement Mode" then
		str = tostring(val)
	else
		str = tostring(val)
	end
	-- Hide the mode message from prying enemy eyes except for the structure placer.
	-- So stuff like crate contents or mine timers are secret.
	if cat[cIndex] == "Structure Placement Mode" or IsHogLocal(CurrentHedgehog) then
		AddCaption(str, GetClanColor(GetHogClan(CurrentHedgehog)), capgrpMessage2)
	end
end

function rotateMode(pDir)
	curWep = GetCurAmmoType()
	local foundMatch = false
	while(foundMatch == false) do
		cIndex = cIndex + pDir

		if (cIndex == 1) or (cIndex == 2) then -- we no longer hit girder by normal means
			cIndex = #cat
		elseif cIndex > #cat then
			cIndex = 3       -- we no longer hit girder by normal means
		end

		if (GetCurAmmoType() == amCMCratePlacer) then
			if (cat[cIndex] == "Health Crate Placement Mode") or
				(cat[cIndex] == "Weapon Crate Placement Mode") or
				(cat[cIndex] == "Utility Crate Placement Mode") then
					foundMatch = true
			end
		elseif (GetCurAmmoType() == amCMObjectPlacer) then
			if (cat[cIndex] == "Mine Placement Mode") or
				(cat[cIndex] == "Sticky Mine Placement Mode") or
				(cat[cIndex] == "Barrel Placement Mode") then
				foundMatch = true
			end
		elseif (GetCurAmmoType() == amCMStructurePlacer) then
			if cat[cIndex] == "Structure Placement Mode" then
				foundMatch = true
			end
		end
	end

	if foundMatch == true then
		RedefineSubset()
		HandleConstructionModeTools()
	end
end

---------------------
-- PLAYER CONTROLS --
---------------------

-- [Timer X]: Used as shortcut key for faster selection of stuff
function onTimer(key)
	curWep = GetCurAmmoType()

	if (curWep == amCMStructurePlacer) then
		-- Select structure directly in structure placer
		-- [Timer X] selects structures 1-5
		-- [Precise]+[Timer X] selects structures 6-10

		local structureID = key
		local precise = band(GetGearMessage(CurrentHedgehog), gmPrecise) ~= 0
		if precise then
			structureID = structureID + 5
		end
		-- Check for valid pIndex
		if structureID <= #pMode then
			pIndex = structureID
			updateIndex()
		end
	elseif (curWep == amCMObjectPlacer) then
		-- [Timer X]: Set mine time 1-5
		if cat[cIndex] == "Mine Placement Mode" then
			local index = key + 1
			if key <= #pMode then
				pIndex = index
				updateIndex()
			end
		end
	end

end

-- [Switch]: Set mine time to 0 (only in mine placement mode)
function onSwitch()
	curWep = GetCurAmmoType()
	if (curWep == amCMObjectPlacer) then
		pIndex = 1
		updateIndex()
	end
end

-- [Left]/[Right]: Change submode (e.g. structure type) of any Construction Mode tool or rotate girder/rubber
function onLeft()
	curWep = GetCurAmmoType()
	if (curWep == amGirder) or (curWep == amRubber) or (curWep == amCMStructurePlacer) or (curWep == amCMCratePlacer) or (curWep == amCMObjectPlacer) then
		pIndex = pIndex - 1
		if pIndex == 0 then
			pIndex = #pMode
		end
		updateIndex()
	end
end
function onRight()
	curWep = GetCurAmmoType()
	if (curWep == amGirder) or (curWep == amRubber) or (curWep == amCMStructurePlacer) or (curWep == amCMCratePlacer) or (curWep == amCMObjectPlacer) then
		pIndex = pIndex + 1
		if pIndex > #pMode then
			pIndex = 1
		end
		updateIndex()
	end
end

-- [Up]/[Down]
-- Cycle through the primary categories
-- (by changing cIndex) i.e. mine, sticky mine,
-- barrels, health/weapon/utility crate.
function onUp()
	curWep = GetCurAmmoType()
	if ( (curWep == amCMCratePlacer) or (curWep == amCMObjectPlacer) ) then
		if CurrentHedgehog ~= nil and band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 then
			rotateMode(-1)
		end
	end

end
function onDown()
	curWep = GetCurAmmoType()
	if ( (curWep == amCMCratePlacer) or (curWep == amCMObjectPlacer) ) then
		if CurrentHedgehog ~= nil and band(GetState(CurrentHedgehog), gstHHDriven) ~= 0 then
			rotateMode(1)
		end
	end
end

-- [Set weapon]/[Slot X]: Just update internal stuff
onSetWeapon = HandleConstructionModeTools()
onSlot = onSetWeapon

----------------------------
-- standard event handlers
----------------------------

-- Parses a positive integer
function parseInt(str, default, infinityPermitted)
	if str == "inf" and infinityPermitted then
		return "inf"
	end
	if str == nil then return default end
	local s = string.match(str, "(%d*)")
	if s ~= nil then
		return math.min(4294967295, math.max(0, tonumber(s)))
	else
		return nil
	end
end

-- Parse parameters
function onParameters()
	parseParams()
	conf_initialEnergy = parseInt(params["initialenergy"], conf_initialEnergy)
	conf_energyPerRound = parseInt(params["energyperround"], conf_energyPerRound)
	conf_maxEnergy = parseInt(params["maxenergy"], conf_maxEnergy, true)
	conf_cratesPerRound = parseInt(params["cratesperround"], conf_cratesPerRound, true)
end

function onGameInit()

	Explosives = 0
	MinesNum = 0

	EnableGameFlags(gfInfAttack)
	-- This is a hack to make sure all girder/rubber placement is handled by Construction Mode to overwrite the default behaviour
	SetMaxBuildDistance(1)

	fortMode = MapGen == mgForts

	-- if there are forts, let engine place the hogs on them
	if fortMode then
		EnableGameFlags(gfDivideTeams)
	end

	RedefineSubset()

end

function initialSetup(gear)

	-- Engine already placed hogs in fort mode
	if not fortMode then
		FindPlace(gear, false, clanBoundsSX[GetHogClan(gear)], clanBoundsEX[GetHogClan(gear)],true)
	end

	-- Add core ammo
	AddAmmo(gear, amCMStructurePlacer, 100)
	AddAmmo(gear, amSkip, 100)

	-- Remove special Construction Mode stuff.
	-- This stuff is added and removed dynamically based on
	-- proximity to structures.
	AddAmmo(gear, amCMObjectPlacer, 0)
	AddAmmo(gear, amCMCratePlacer, 0)
	AddAmmo(gear, amGirder, 0)
	AddAmmo(gear, amRubber, 0)
	AddAmmo(gear, amTeleport, 0)

	-- Drill strike is broken, so we force-remove it
	AddAmmo(gear, amDrillStrike, 0)

	-- Everything else is set by the weapon scheme.
	-- Infinite switch is recommended.
end

function onGameStart()

	trackTeams()

	ShowMission	(
				loc("CONSTRUCTION MODE"),
				loc("A Hedgewars mini-game"),
				loc("Build a fortress and destroy your enemy.") .. "|" ..
				loc("There are a variety of structures available to aid you.") .. "|" ..
				loc("Use the structure placer to place structures.")
				, -amCMStructurePlacer, 5000
				)

	SetAmmoTexts(amCMStructurePlacer, loc("Structure Placer"), loc("Construction Mode tool"), loc("Build one of multiple different structures|to aid you in victory, at the cost of energy.") .. "| |" ..
	loc("Support Station: Allows placement of crates.") .. "|"..
	loc("Construction Station: Allows placement of|    girders, rubber, mines, sticky mines|    and barrels.")  .. "|" ..
	loc("Healing Station: Heals nearby hogs.")  .. "|" ..
	loc("Teleportation Node: Allows teleportation|    between other nodes.")  .. "|" ..
	loc("Weapon Filter: Dematerializes all ammo|    carried by enemies entering it.")  .. "|" ..
	loc("Bio-Filter: Aggressively removes enemies.")  .. "|" ..
	loc("Reflector Shield: Reflects enemy projectiles.")  .. "|" ..
	loc("Respawner: Resurrects dead hogs.")  .. "|" ..
	loc("Generator: Generates energy.")  .. "|" ..
	" |" ..

	loc("Left/right: Choose structure type").."|"..
	loc("1-5, Precise + 1-4: Choose structure type").."|"..
	loc("Cursor: Build structure"))

	local txt_crateLimit = ""
	if conf_cratesPerRound ~= "inf" then
		txt_crateLimit = string.format(loc("You may only place %d crates per round."), conf_cratesPerRound) .. "|"
	end

	SetAmmoTexts(amCMCratePlacer, loc("Crate Placer"), loc("Construction Mode tool"),
		loc("This allows you to create a crate anywhere|within your clan's area of influence,|at the cost of energy.") .. "|" ..
		txt_crateLimit ..
		loc("Up/down: Choose crate type") .. "|" ..
		loc("Left/right: Choose crate contents") .. "|" ..
		loc("|Cursor: Place crate"))
	SetAmmoTexts(amCMObjectPlacer, loc("Object Placer"), loc("Construction Mode tool"),
		loc("This allows you to create and place mines,|sticky mines and barrels anywhere within your|clan's area of influence at the cost of energy.").."|"..
		loc("Up/down: Choose object type|1-5/Switch/Left/Right: Choose mine timer|Cursor: Place object")
	)

	SetAmmoDescriptionAppendix(amTeleport, loc("It only works in teleportation nodes of your own clan."))
	
	local sCirc = AddVisualGear(0,0,vgtCircle,0,true)
	SetVisualGearValues(sCirc, 0, 0, 100, 255, 1, 10, 0, 40, 3, 0x00000000)

	for i = 0, ClansCount-1 do
		clanPower[i] = math.min(conf_initialEnergy, conf_maxEnergy)

		clanUsedExtraTime[i] = false
		clanCratesSpawned[i] = 0
		clanFirstTurn[i] = true

	end
	for i = 0, TeamsCount-1 do
		local team = GetTeamName(i)
		teamLStructIndex[team] = 1
		teamLObjectMode[team] = "Mine Placement Mode"
		teamLCrateMode[team] = "Weapon Crate Placement Mode"
		teamLMineIndex[team] = 1
		teamLWeapIndex[team] = 1
		teamLUtilIndex[team] = 1
		SetTeamLabel(team, tostring(clanPower[GetTeamClan(team)]))
	end

	local tMapWidth = RightX - LeftX
	local tMapHeight = WaterLine - TopY
	local clanInterval = div(tMapWidth,ClansCount)

	-- define construction areas for each clan
	-- if there are forts-based spawn locations, adjust areas around them
	for i = 0, ClansCount-1 do
		local slot
		if fortMode then
			slot = div(GetX(getFirstHogOfClan(i))-LeftX,clanInterval)
		else
			slot = i
		end

		local color = GetClanColor(i)

		clanBoundsSX[i] = LeftX+(clanInterval*slot)+20
		clanBoundsSY[i] = TopY
		clanBoundsEX[i] = LeftX+(clanInterval*slot)+clanInterval-20
		clanBoundsEY[i] = WaterLine

		--top and bottom
		AddWall(LeftX+(clanInterval*slot),TopY,clanInterval,wMargin,color)
		AddWall(LeftX+(clanInterval*slot),WaterLine-25,clanInterval,wMargin,color)

		--add a wall to the left and right
		AddWall(LeftX+(clanInterval*slot)+20,TopY,wMargin,WaterLine,color)
		AddWall(LeftX+(clanInterval*slot)+clanInterval-20,TopY,wMargin,WaterLine,color)

	end

	runOnHogs(initialSetup)

end


function onNewTurn()

	curWep = GetCurAmmoType()

	HandleConstructionModeTools()

	local clan = GetHogClan(CurrentHedgehog)
	if clanFirstTurn[clan] then
		clanFirstTurn[clan] = false
	else
		clanPower[clan] = clanPower[clan] + conf_energyPerRound
		if conf_maxEnergy ~= "inf" and clanPower[clan] > conf_maxEnergy then
			clanPower[clan] = conf_maxEnergy
		end
	end
	clanUsedExtraTime[clan] = false
	clanCratesSpawned[clan] = 0

	RenderClanPower()
	UpdateTeamLabels()
end

function onEndTurn()
	curWep = amNothing
	HandleConstructionModeTools()
	DeleteClanPowerTag()
end

function onGameTick()
	HandleConstructionMode()
end

function onScreenResize()
	-- redraw Tags so that their screen locations are updated
	if (CurrentHedgehog ~= nil) then
		RenderClanPower()
	end
end


function onGearResurrect(gear, vGear)
	if GetGearType(gear) == gtHedgehog then
		FindRespawner(gear)
		if vGear then
			SetVisualGearValues(vGear, GetX(gear), GetY(gear))
		end
	end
end

-- track hedgehogs and placement gears
function onGearAdd(gear)

	local gt = GetGearType(gear)
	if (gt == gtAirAttack) or (gt == gtTeleport) or (gt == gtGirder) then
		cGear = gear
		cGearPlacementDone = false
	end

	if isATrackedGear(gear) then
		trackGear(gear)
	elseif gearCanBeDeflected(gear) then
		trackGear(gear)
		setGearReflectionValues(gear)
	end

end

function onGearDelete(gear)

	if GetGearType(gear) == gtTarget then
		CheckGearForStructureLink(gear)
	end

	if (GetGearType(gear) == gtAirAttack) or (GetGearType(gear) == gtTeleport) or (GetGearType(gear) == gtGirder) then
		cGear = nil
	end

	if (isATrackedGear(gear) or gearCanBeDeflected(gear)) then

		trackDeletion(gear)

	end

end