Map Template:Sequential Territory Control

From Fortress Forever Wiki
Jump to navigationJump to search


Foreword

This is likely a temporary script/page until an offical Lua for this gametype is released. As such, the Lua script itself likely isn't up to the same standard as the official ones. It does work as intended, though.

It is set up to behave as close to the TFC version of warpath as I could get it, with my limited mapping experience.

Last updated: 12:52, 11 January 2008 (CST)  

Introduction

In this game mode, each team must capture Command Points in order (no flag, just touch the Command Point), typically the Blue team capture 1 through 5, and the Red team 5 through 1. Upon a team capturing their final point, the round is reset.

An example of this style of map is Warpath.  

Entities

Start Gates

  • startgate_blue (func_door): Start gate for Blue team
  • startgate_red (func_door): Start gate for Red team

 

Spawns

  • bluespawn_cp1 (info_ff_teamspawn): Initial Blue team spawn
  • bluespawn_cp2 (info_ff_teamspawn): --\
  • bluespawn_cp3 (info_ff_teamspawn): -- Progressive spawns for the Blue team. "cp#" denotes Command Point control nessecary to be active(only current cp# spawns are active)
  • bluespawn_cp4 (info_ff_teamspawn): --/

 

  • redspawn_cp5 (info_ff_teamspawn): Initial Red team spawn
  • redspawn_cp4 (info_ff_teamspawn): --\
  • redspawn_cp3 (info_ff_teamspawn): -- Progressive spawns for the Red team. "cp#" denotes Command Point control nessecary to be active (only current cp# spawns are active)
  • redspawn_cp2 (info_ff_teamspawn): --/

 

Respawn Doors

  • bluerespawn_cp2_actual (func_door): --\
  • bluerespawn_cp3_actual (func_door): -- Physical door for Blue spawns. All flags should be off, opening is controlled by another entity
  • bluerespawn_cp4_actual (func_door): --/

 

  • redrespawn_cp4_actual (func_door): --\
  • redrespawn_cp3_actual (func_door): -- Physical door for Red spawns. All flags should be off, opening is controlled by another entity
  • redrespawn_cp2_actual (func_door): --/

 

  • bluerespawn_cp2 (trigger_ff_script): --\
  • bluerespawn_cp3 (trigger_ff_script): -- Trigger for the respawn doors above. Fires output "OnStartTouch:Open" to corresponding "_actual" door
  • bluerespawn_cp4 (trigger_ff_script): --/

 

  • bluerespawn_cp4 (trigger_ff_script): --\
  • bluerespawn_cp3 (trigger_ff_script): -- Trigger for the respawn doors above. Fires output "OnStartTouch:Open" to corresponding "_actual" door
  • bluerespawn_cp2 (trigger_ff_script): --/

 

Command Points

  • command_point_one (trigger_ff_script): Location for Command Point One
  • command_point_two (trigger_ff_script): ... and
  • command_point_three (trigger_ff_script): ... so
  • command_point_four (trigger_ff_script): ... on
  • command_point_five (trigger_ff_script): ... etc.

 

Flags

  • cp1_flag (prop_dynamic_override): Flag used to denote CP ownership. Cannot be picked up.
  • cp2_flag (prop_dynamic_override): ... and
  • cp3_flag (prop_dynamic_override): ... so
  • cp4_flag (prop_dynamic_override): ... on
  • cp5_flag (prop_dynamic_override): ... etc.

 

Flag Rotators

  • cp1_flag_rotator (func_rotating): Optional entity used to rotate flag on CP. Flag entity must have this entity as parent if used.
  • cp2_flag_rotator (func_rotating): ... and
  • cp3_flag_rotator (func_rotating): ... so
  • cp4_flag_rotator (func_rotating): ... on
  • cp5_flag_rotator (func_rotating): ... etc.

 

Lua Script

(NOTE: Uses a custom vgui graphic. The graphic is included in the source .vmf download linked to below the script, along with the below script)

------------------------------------------------
-- This started as cz2.lua, along with some other bits of lua from other official files, and then altered by myself
-- to accomodate warpath-style gameplay.
--
-- Pon.id
-- warpath_example.lua v1.00
------------------------------------------------


-----------------------------------------------------------------------------
-- includes
-----------------------------------------------------------------------------
IncludeScript("base_teamplay")
IncludeScript("base_location")
IncludeScript("base_respawnturret")

-----------------------------------------------------------------------------
-- globals
-----------------------------------------------------------------------------

-- Scoring globals
NUMBER_OF_COMMAND_POINTS = 5
POINTS_FOR_COMPLETE_CONTROL = 30
FORTPOINTS_FOR_COMPLETE_CONTROL = 1000
FORTPOINTS_FOR_CAP = 200


-- Other Globals (Initial round delay used only for first round, give people time to log in)

INITIAL_ROUND_DELAY = 45
NORMAL_ROUND_DELAY = 30
RECAPTURE_DELAY = 15

-- Counters for spawn validty
cp_blue = 1
cp_red = 5


-- A global table for storing some team info
team_info = {
 
	[Team.kUnassigned] = {
		team_name = "neutral",
		color_index = 1,
		skin = "0",
		flag_visibility = "TurnOff"
	},

	[Team.kBlue] = {
		team_name = "blue",
		color_index = 2,
		skin = "0",
		flag_visibility = "TurnOn"
	},

	[Team.kRed] = {
		team_name = "red",
		color_index = 0,
		skin = "1",
		flag_visibility = "TurnOn"
	}
}

-- This table is for keeping track of which team controls which CP
command_points = {
	[1] = { controlling_team = Team.kBlue, cp_number = 1, disable_capture = 0, hudstatusicon = "hud_cp_1.vtf", hudposx = -40, hudposy = 36, hudalign = 4, hudwidth = 16, hudheight = 16 },
	[2] = { controlling_team = Team.kUnassigned, cp_number = 2, disable_capture = 0, hudstatusicon = "hud_cp_2.vtf", hudposx = -20, hudposy = 36, hudalign = 4, hudwidth = 16, hudheight = 16 },
	[3] = { controlling_team = Team.kUnassigned, cp_number = 3, disable_capture = 0, hudstatusicon = "hud_cp_3.vtf", hudposx = 0, hudposy = 36, hudalign = 4, hudwidth = 16, hudheight = 16 },
	[4] = { controlling_team = Team.kUnassigned, cp_number = 4, disable_capture = 0, hudstatusicon = "hud_cp_4.vtf", hudposx = 20, hudposy = 36, hudalign = 4, hudwidth = 16, hudheight = 16 },
	[5] = { controlling_team = Team.kRed, cp_number = 5, disable_capture = 0, hudstatusicon = "hud_cp_5.vtf", hudposx = 40, hudposy = 36, hudalign = 4, hudwidth = 16, hudheight = 16 }
}

icons = {
	[Team.kBlue] = { teamicon = "hud_cp_blue.vtf", disabled = "hud_disabled.vtf" },
	[Team.kRed] = { teamicon = "hud_cp_red.vtf", disabled = "hud_disabled.vtf" },
	[Team.kUnassigned] = { teamicon = "hud_cp_neutral.vtf", disabled = "hud_disabled.vtf" }
}


function startup()

	-- set up team limits
	SetPlayerLimit( Team.kYellow, -1 )
	SetPlayerLimit( Team.kGreen, -1 )

	local team = GetTeam(Team.kBlue)
	team:SetClassLimit( Player.kScout, 0 )
	team:SetClassLimit( Player.kSniper, 0 )
	team:SetClassLimit( Player.kSoldier, 0 )
	team:SetClassLimit( Player.kDemoman, 0 )
	team:SetClassLimit( Player.kMedic, 0 )
	team:SetClassLimit( Player.kHwguy, 0 )
	team:SetClassLimit( Player.kPyro, 0 )
	team:SetClassLimit( Player.kSpy, 0 )
	team:SetClassLimit( Player.kEngineer, 0 )
	team:SetClassLimit( Player.kCivilian, -1 )


	local team = GetTeam(Team.kRed)
	team:SetClassLimit( Player.kScout, 0 )
	team:SetClassLimit( Player.kSniper, 0 )
	team:SetClassLimit( Player.kSoldier, 0 )
	team:SetClassLimit( Player.kDemoman, 0 )
	team:SetClassLimit( Player.kMedic, 0 )
	team:SetClassLimit( Player.kHwguy, 0 )
	team:SetClassLimit( Player.kPyro, 0 )
	team:SetClassLimit( Player.kSpy, 0 )
	team:SetClassLimit( Player.kEngineer, 0 )
	team:SetClassLimit( Player.kCivilian, -1 )


	-- Reset all command points
	command_point_one:ChangeControllingTeam(Team.kBlue)
	command_point_two:ChangeControllingTeam(Team.kUnassigned)
	command_point_three:ChangeControllingTeam(Team.kUnassigned)
	command_point_four:ChangeControllingTeam(Team.kUnassigned)
	command_point_five:ChangeControllingTeam(Team.kRed)

	-- Setup Doors for first round

	setup_door_timer("startgate_blue", INITIAL_ROUND_DELAY)
	setup_door_timer("startgate_red", INITIAL_ROUND_DELAY)

end

function precache()
	-- precache sounds
	PrecacheSound("yourteam.flagcap")
	PrecacheSound("otherteam.flagcap")
	PrecacheSound("otherteam.flagstolen")


	PrecacheSound("misc.bizwarn")
	PrecacheSound("misc.bloop")
	PrecacheSound("misc.buzwarn")
	PrecacheSound("misc.dadeda")
	PrecacheSound("misc.deeoo")
	PrecacheSound("misc.doop")
	PrecacheSound("misc.woop")

	

	-- Unagi Power!  Unagi!
	PrecacheSound("misc.unagi")


end

-----------------------------------------------------------------------------
-- Round Completion
-----------------------------------------------------------------------------

function complete_control_notification ( player )

	SmartSound(player, "yourteam.flagcap", "yourteam.flagcap", "otherteam.flagcap")
	SmartMessage(player, "Your team captured ALL Command Points!", "Your team captured ALL Command Points!", "The enemy team captured ALL Command Points!")


end

-----------------------------------------------------------------------------
-- Command Points
-----------------------------------------------------------------------------

base_cp_trigger = trigger_ff_script:new({
	health = 100,
	armor = 300,
	grenades = 200,
	nails = 200,
	shells = 200,
	rockets = 200,
	cells = 200,
	detpacks = 1,
	gren1 = 4,
	gren2 = 4,
	item = "",
	team = 0,
	botgoaltype = Bot.kFlagCap,
	cp_number = 0,
})

function base_cp_trigger:ontrigger( trigger_entity )
	if IsPlayer( trigger_entity ) then
		local player = CastToPlayer( trigger_entity )
		local cp = command_points[self.cp_number]


		local new_controlling_team = player:GetTeamId()
		local old_controlling_team = cp.controlling_team
		local team = GetTeam(new_controlling_team)


		-- No capping if player's team already controls this CP
		if player:GetTeamId() == cp.controlling_team then return end


		-- No Capping if team doesn't own preceeding point
		if new_controlling_team == Team.kBlue then
			local last_blue_cp = self.cp_number - 1
			if command_points[last_blue_cp].controlling_team ~= new_controlling_team then return end
		elseif new_controlling_team == Team.kRed then
			local last_red_cp = self.cp_number + 1
			if command_points[last_red_cp].controlling_team ~= new_controlling_team then return end
		end


		-- No Capping if disabled due to recent capture
		if cp.disable_capture == 1 then return end


		-- Find out if any team has complete control
		local team_with_complete_control = Team.kUnassigned
		local control_count = { [Team.kBlue] = 0, [Team.kRed] = 0 }
		control_count[new_controlling_team] = 1
		for i,v in ipairs(command_points) do
			if v.controlling_team ~= Team.kUnassigned and v.cp_number ~= self.cp_number then
				control_count[v.controlling_team] = control_count[v.controlling_team] + 1
			end
			if control_count[v.controlling_team] == NUMBER_OF_COMMAND_POINTS then
				team_with_complete_control = v.controlling_team
			end
		end

		-- Ends round if a team has all cp's, else just sorts out a normal cp capture.
		if team_with_complete_control ~= Team.kUnassigned then

			-- Team points for complete control, more FortPoints too
			team:AddScore(POINTS_FOR_COMPLETE_CONTROL)
			player:AddFortPoints(FORTPOINTS_FOR_COMPLETE_CONTROL, "Final CP Captured")

			AddSchedule("complete_control_notification", 0.1, complete_control_notification, player)
			
			-- Reset all command points
			command_point_one:ChangeControllingTeam(Team.kBlue)
			command_point_two:ChangeControllingTeam(Team.kUnassigned)
			command_point_three:ChangeControllingTeam(Team.kUnassigned)
			command_point_four:ChangeControllingTeam(Team.kUnassigned)
			command_point_five:ChangeControllingTeam(Team.kRed)

			-- Reset extended spawn areas and door
			cp_blue = 1
			cp_red = 5

			-- Reset doors and set up timers for the next round
			setup_door_timer("startgate_blue", NORMAL_ROUND_DELAY)
			setup_door_timer("startgate_red", NORMAL_ROUND_DELAY)

			-- Clean up HUD and capture disabled status for all CP's
			for i,v in ipairs(command_points) do
				RemoveHudItemFromAll( v.cp_number .. "-disabled-" .. Team.kRed )
				RemoveHudItemFromAll( v.cp_number .. "-disabled-" .. Team.kBlue )
				v.disable_capture = 0
			end

			-- Respawns everyone/clean up map. Done in schedule to give delay
			AddSchedule( "new_round_respawn", 0.1, new_round_respawn)

		else

			-- Give points to player for single cap
			player:AddFortPoints(FORTPOINTS_FOR_CAP, "Captured CP" .. self.cp_number)


			-- Set new owning team, change spawn validity, then disable capture for a bit
			self:ChangeControllingTeam(new_controlling_team, old_controlling_team)
			cp.disable_capture = 1
			AddSchedule ("cp" .. self.cp_number .. "_reenable", RECAPTURE_DELAY, cp_reenable, cp, new_controlling_team)


			-- have to do messages here since player is here
			-- could just pass player to ChangeControllingTeam, but fuck you

			SmartMessage(player, "You captured Command Point " .. self.cp_number, "Your team captured Command Point " .. self.cp_number, "The enemy team captured Command Point " .. self.cp_number)

			-- sounds will get more and more crazy
			-- NOTE: These checks are hardcoded for 5 CPs...if you have more CPs, change how these are checked

			if control_count[new_controlling_team] == 4 then
				SmartSound(player, "misc.unagi", "misc.unagi", "misc.bizwarn")
			elseif control_count[new_controlling_team] == 3 then
				SmartSound(player, "misc.woop", "misc.woop", "misc.buzwarn")
			elseif control_count[new_controlling_team] == 2 then
				SmartSound(player, "misc.doop", "misc.doop", "misc.dadeda")
			else
				SmartSound(player, "misc.bloop", "misc.bloop", "misc.deeoo")
			end


		end

		-- give player some health and armor
		if self.health ~= nil and self.health ~= 0 then player:AddHealth( self.health ) end
		if self.armor ~= nil and self.armor ~= 0 then player:AddArmor( self.armor ) end
	
		-- give player ammo
		if self.nails ~= nil and self.nails ~= 0 then player:AddAmmo(Ammo.kNails, self.nails) end
		if self.shells ~= nil and self.shells ~= 0 then player:AddAmmo(Ammo.kShells, self.shells) end
		if self.rockets ~= nil and self.rockets ~= 0 then player:AddAmmo(Ammo.kRockets, self.rockets) end
		if self.cells ~= nil and self.cells ~= 0 then player:AddAmmo(Ammo.kCells, self.cells) end
		if self.detpacks ~= nil and self.detpacks ~= 0 then player:AddAmmo(Ammo.kDetpack, self.detpacks) end
		if self.gren1 ~= nil and self.gren1 ~= 0 then player:AddAmmo(Ammo.kGren1, self.gren1) end
		if self.gren2 ~= nil and self.gren2 ~= 0 then player:AddAmmo(Ammo.kGren2, self.gren2) end

	end
end

function base_cp_trigger:ChangeControllingTeam( new_controlling_team, old_controlling_team )


		-- Change the cap flag around
		OutputEvent( "cp" .. self.cp_number .. "_flag", "Skin", team_info[new_controlling_team].skin )
		OutputEvent( "cp" .. self.cp_number .. "_flag", team_info[new_controlling_team].flag_visibility)

		local cp = command_points[self.cp_number]


		-- remove old flaginfo icons and add new ones
		RemoveHudItemFromAll( self.cp_number .. "-background-" .. cp.controlling_team )
		RemoveHudItemFromAll( self.cp_number .. "-foreground-" .. cp.controlling_team )

		AddHudIconToAll( icons[ new_controlling_team ].teamicon , self.cp_number .. "-background-" .. new_controlling_team , cp.hudposx, cp.hudposy, cp.hudwidth, cp.hudheight, cp.hudalign)

		AddHudIconToAll( cp.hudstatusicon, self.cp_number .. "-foreground-" .. new_controlling_team , cp.hudposx, cp.hudposy, cp.hudwidth, cp.hudheight, cp.hudalign)

		-- 
		if (new_controlling_team ~= Team.kUnassigned) then
			AddHudIconToAll( icons[ new_controlling_team ].disabled, self.cp_number .. "-disabled-" .. new_controlling_team , cp.hudposx, cp.hudposy, cp.hudwidth, cp.hudheight, cp.hudalign)
		end



		-- Change valid spawns
		

		if new_controlling_team == Team.kBlue then
			cp_blue = self.cp_number
			if old_controlling_team == Team.kRed then
				cp_red = self.cp_number + 1
			end
		elseif new_controlling_team == Team.kRed then
			cp_red = self.cp_number
			if old_controlling_team == Team.kBlue then
				cp_blue = self.cp_number - 1
			end
		end


		cp.controlling_team = new_controlling_team

end


command_point_one = base_cp_trigger:new({ cp_number = 1 })
command_point_two = base_cp_trigger:new({ cp_number = 2 })
command_point_three = base_cp_trigger:new({ cp_number = 3 })
command_point_four = base_cp_trigger:new({ cp_number = 4 })
command_point_five = base_cp_trigger:new({ cp_number = 5 })



-------------------------
-- Re-enable disabled CP after RECAPTURE_DELAY (default 15 seconds)
-------------------------

function cp_reenable (cp, team)
	RemoveHudItemFromAll( cp.cp_number .. "-disabled-" .. team )
	cp.disable_capture = 0
end

function new_round_respawn()
	ApplyToAll({ AT.kRemovePacks, AT.kRemoveProjectiles, AT.kRespawnPlayers, AT.kRemoveBuildables, AT.kRemoveRagdolls, kReloadClips })
end

-------------------------------------------
-- HUD information
-------------------------------------------

function flaginfo( player_entity )
	local player = CastToPlayer( player_entity )

	for i,v in ipairs(command_points) do

		AddHudIcon( player, icons[ v.controlling_team ].teamicon , v.cp_number .. "-background-" .. v.controlling_team , command_points[v.cp_number].hudposx, command_points[v.cp_number].hudposy, command_points[v.cp_number].hudwidth, command_points[v.cp_number].hudheight, command_points[v.cp_number].hudalign)
		AddHudIcon( player, command_points[v.cp_number].hudstatusicon, v.cp_number .. "-foreground-" .. v.controlling_team , command_points[v.cp_number].hudposx, command_points[v.cp_number].hudposy, command_points[v.cp_number].hudwidth, command_points[v.cp_number].hudheight, command_points[v.cp_number].hudalign)

	end
end


-------------------------------------------
-- Round start functions
-------------------------------------------

function setup_door_timer(doorname, duration)
	CloseDoor(doorname)
	AddSchedule("round_opendoor_" .. doorname, duration, round_start, doorname)
	AddSchedule("round_10secwarn", duration-10, round_10secwarn)
end

function round_start(doorname)
	BroadCastMessage("Gates are now open!")
	BroadCastSound( "otherteam.flagstolen" )
	OpenDoor(doorname)
end

function round_10secwarn() BroadCastMessage("10 Seconds until round starts") end



-------------------------------------------
-- Spawnpoints + Respawn door Stuff
-------------------------------------------

-- Spawn Points
base_blue_spawn = info_ff_teamspawn:new({ cp = 0, validspawn = function(self,player)
	return player:GetTeamId() == Team.kBlue and self.cp == cp_blue
end })
base_red_spawn = info_ff_teamspawn:new({ cp = 0, validspawn = function(self,player)
	return player:GetTeamId() == Team.kRed and self.cp == cp_red
end })
bluespawn_cp1 = base_blue_spawn:new({cp=1})
bluespawn_cp2 = base_blue_spawn:new({cp=2})
bluespawn_cp3 = base_blue_spawn:new({cp=3})
bluespawn_cp4 = base_blue_spawn:new({cp=4})
redspawn_cp5 = base_red_spawn:new({cp=5})
redspawn_cp4 = base_red_spawn:new({cp=4})
redspawn_cp3 = base_red_spawn:new({cp=3})
redspawn_cp2 = base_red_spawn:new({cp=2})


-- Spawn Doors (base entity)
blue_respawn_door = trigger_ff_script:new({cp = 0})
red_respawn_door = trigger_ff_script:new({cp = 0})

-- Spawn Doors (validity checks)
function blue_respawn_door:allowed( allowed_entity )
	if IsPlayer( allowed_entity ) then
		local player = CastToPlayer( allowed_entity )
		if player:GetTeamId() == Team.kBlue and self.cp <= cp_blue then
			return EVENT_ALLOWED
		end
	end
	return EVENT_DISALLOWED
end

function red_respawn_door:allowed( allowed_entity )
	if IsPlayer( allowed_entity ) then
		local player = CastToPlayer( allowed_entity )
		if player:GetTeamId() == Team.kRed and self.cp >= cp_red then
			return EVENT_ALLOWED
		end
	end
	return EVENT_DISALLOWED
end

--Spawn Doors (Validity checks failure)
function blue_respawn_door:onfailtouch( touch_entity )
	if IsPlayer( touch_entity ) then
		local player = CastToPlayer( touch_entity )
		if player:GetTeamId() == Team.kBlue then
			BroadCastMessageToPlayer( player, "You need to capture Command Point ".. self.cp .. " before you can use this respawn!" )
		else
			BroadCastMessageToPlayer( player, "Your team cannot use this respawn." )
		end
	end
end

function red_respawn_door:onfailtouch( touch_entity )
	if IsPlayer( touch_entity ) then
		local player = CastToPlayer( touch_entity )
		if player:GetTeamId() == Team.kRed then
			BroadCastMessageToPlayer( player, "You need to capture Command Point ".. self.cp .." before you can use this respawn!" )
		else
			BroadCastMessageToPlayer( player, "Your team cannot use this respawn." )
		end
	end
end


-- Spawn Doors (actual entities with command point condition attached)
bluerespawn_cp2 = blue_respawn_door:new({cp=2})
bluerespawn_cp3 = blue_respawn_door:new({cp=3})
bluerespawn_cp4 = blue_respawn_door:new({cp=4})
redrespawn_cp2 = red_respawn_door:new({cp=2})
redrespawn_cp3 = red_respawn_door:new({cp=3})
redrespawn_cp4 = red_respawn_door:new({cp=4})

 

Overridable Globals

(NOTE: Replace the values used in the code above with whats needed, defaults are as close to TFC Warpath as possible)

  • NUMBER_OF_COMMAND_POINTS (Default: 5): Amount of command points in play.
  • POINTS_FOR_COMPLETE_CONTROL (Default: 30): Team points scored for capturing all points.
  • FORTPOINTS_FOR_COMPLETE_CONTROL (Default: 1000): Fortress points scored for capturing final point.
  • FORTPOINTS_FOR_CAP (Default: 200): Fortress points scored for capturing a non-final point.
  • INITIAL_ROUND_DELAY (Default: 45): Delay before start of first round (to give people time to log in).
  • NORMAL_ROUND_DELAY (Default: 30): Delay before start of all other rounds.
  • RECAPTURE_DELAY (Default: 15): Time a point is inactive for after being captured.

 

Downloads

  • Source .vmf (Includes .vmf of an example map, the Lua Script, and a custom HUD graphic used):