/* * Author: Needles * https://steamcommunity.com/profiles/76561198026257137/ */ ::Debug.Print("*** PATROL"); const PATROL_PREREQUISITE_SIZE = 16384; // Maximum bounds of a tf2 map ::Patrol <- {}; ::Patrol.tagFilterMap <- {}; ::Patrol.nodeMap <- {}; // @TODO - Temporary fix until I figure out what entity should contain the data. // Actually.... having a lookup map for patrolnodes seems useful... ::Patrol.PatrolNode <- class { tag = null; nextTag = null; conclusionDelay = null; destination = null; bboxFinish = null; // We need to keep track of these for the cleanup ent_funcNavPrerequisite = null; ent_pathTrack = null; constructor(_tag, _nextTag, _conclusionDelay, _destination, _bboxFinish, _ent_funcNavPrerequisite, _ent_pathTrack) { tag = _tag; nextTag = _nextTag; conclusionDelay = _conclusionDelay; destination = _destination; bboxFinish = _bboxFinish; ent_funcNavPrerequisite = null; ent_pathTrack = null; } } // @TODO - Turn this into a handler, then add safety for handlers on players // ie - remove on death, disconnect, team change, class change, loadout change, etc... Anything that would cause a player entity to disappear ::Patrol.PatrolActor <- class { isArrived = null; arrivalTime = null; currentPatrolNode = null; player = null; constructor() { isArrived = false; arrivalTime = -1.0; currentPatrolNode = null; player = null; } function StartPatrol(patrolNode) { isArrived = false; currentPatrolNode = patrolNode; if (currentPatrolNode != null) { player.AddBotTag(currentPatrolNode.tag); } } function ArriveAtDestination() { isArrived = true; arrivalTime = Time(); } function ConcludePatrol() { player.RemoveBotTag(currentPatrolNode.tag); StartPatrol(::Patrol.GetPatrolNodeByTag(currentPatrolNode.nextTag)); } function Think() { if (currentPatrolNode == null || currentPatrolNode.nextTag == null) { return; } if (isArrived == false && ::Collision.PointInBox(player.GetOrigin(), currentPatrolNode.bboxFinish.mins, currentPatrolNode.bboxFinish.maxs)) { ArriveAtDestination(); } if (isArrived == true && Time() >= arrivalTime + currentPatrolNode.conclusionDelay) { ConcludePatrol(); } } } /* * Call this when a player spawns to initialize all of the necessary data */ function Patrol::InitPatrolActor(player) { player.ValidateScriptScope(); local playerScope = player.GetScriptScope(); playerScope.patrolActor <- ::Patrol.PatrolActor(); playerScope.patrolThink <- function() { patrolActor.Think(); } AddThinkToEnt(player, "patrolThink"); playerScope.patrolActor.arrivalTime = -1.0; playerScope.patrolActor.currentPatrolNode = null; playerScope.patrolActor.player = player; return playerScope.patrolActor; } /* * If a player has been initialized, then call this to start the patrol */ function Patrol::StartPatrol(player, tag) { player.ValidateScriptScope(); local playerScope = player.GetScriptScope(); if (!("patrolActor" in playerScope)) { return; } playerScope.patrolActor.StartPatrol(::Patrol.GetPatrolNodeByTag(tag)); } function Patrol::BuildPatrolNode(tag, nextTag, conclusionDelay, destination, bboxFinish) { if (tag in ::Patrol.nodeMap) { ::Debug.Print("*** PATROL - Failed to build patrol node. A node already exists for tag " + tag); return ::Patrol.nodeMap[tag]; } // If a filter for this node doesn't already exist, then make one. if (!(tag in ::Patrol.tagFilterMap)) { ::Patrol.tagFilterMap[tag] <- SpawnEntityFromTable("filter_tf_bot_has_tag", { targetname = tag + " " + UniqueString(), Negated = false, require_all_tags = true, tags = tag, }); ::Debug.Print("*** PATROL - Created filter for tag " + tag); } // Create the node local ent_pathTrack = SpawnEntityFromTable("path_track", { targetname = UniqueString(), origin = destination, }); local ent_funcNavPrerequisite = SpawnEntityFromTable("func_nav_prerequisite", { Entity = ent_pathTrack.GetName(), filtername = ::Patrol.GetTagFilterEntity(tag).GetName(), Task = TASK.MOVE_TO_ENTITY, Value = 0, spawnflags = 1, solid = Constants.ESolidType.SOLID_NONE, // Need this to not break touch links :z }); ent_funcNavPrerequisite.SetSize( Vector(-PATROL_PREREQUISITE_SIZE, -PATROL_PREREQUISITE_SIZE, -PATROL_PREREQUISITE_SIZE), Vector(PATROL_PREREQUISITE_SIZE, PATROL_PREREQUISITE_SIZE, PATROL_PREREQUISITE_SIZE) ); ::Patrol.nodeMap[tag] <- ::Patrol.PatrolNode(tag, nextTag, conclusionDelay, destination, bboxFinish, ent_funcNavPrerequisite, ent_pathTrack); ::Debug.Print("*** PATROL - Built patrol node for tag " + tag); return ::Patrol.nodeMap[tag]; } function Patrol::ClearPatrolNodes() { foreach (tag, patrolNode in ::Patrol.nodeMap) { // Destroy entity dependencies if (patrolNode.ent_funcNavPrerequisite != null && patrolNode.ent_funcNavPrerequisite.IsValid() == true) patrolNode.ent_funcNavPrerequisite.Destroy(); if (patrolNode.ent_pathTrack != null && patrolNode.ent_pathTrack.IsValid() == true) ent_pathTrack.ent_funcNavPrerequisite.Destroy(); // Destroy the filter if one exists if (tag in ::Patrol.tagFilterMap) { local filter = ::Patrol.tagFilterMap[tag]; if (filter != null && filter.IsValid() == true) filter.Destroy(); ::Patrol.tagFilterMap.rawdelete(tag); ::Debug.Print("*** PATROL - Removed tag filter " + tag); } ::Debug.Print("*** PATROL - Removed patrol node " + tag); } ::Patrol.nodeMap.clear(); ::Debug.Print("*** PATROL - Finished clearing patrol nodes"); } function Patrol::SpawnTagFilterEntities(tagList) { foreach(tag in tagList) { ::Patrol.tagFilterMap[tag] <- SpawnEntityFromTable("filter_tf_bot_has_tag", { targetname = "filter_tag_" + tag, Negated = false, require_all_tags = true, tags = tag, }); } } function Patrol::GetTagFilterEntity(tag) { if (tag in ::Patrol.tagFilterMap) { return ::Patrol.tagFilterMap[tag]; } return null; } function Patrol::GetPatrolNodeByTag(tag) { if (tag in ::Patrol.nodeMap) { return ::Patrol.nodeMap[tag]; } return null; } ::Events.GetGlobalEvent(EVENT.PLAYER_SPAWN_POST).AddListener( function(params) { if (::Players.IsPlayerValid(params.player) == false || ::Players.IsPlayerBot(params.player) == false) return; foreach(tag, _ in ::Patrol.nodeMap) { if (params.player.HasBotTag(tag)) { ::Patrol.InitPatrolActor(params.player); ::Patrol.StartPatrol(params.player, tag); } } } );