/* * Author: Needles * https://steamcommunity.com/profiles/76561198026257137/ */ ::Debug.Print("*** HANDLER"); /* * This module is big and confusing and i hate it. * Especially the cleanup logic. It's just a mess. */ ::Handler <- {}; ::Handler.SCOPE_KEY <- UniqueString(); // Key used to access this module's data inside of an entity's scope. ::Handler.factoryMap <- {}; // Key: handler id, Value: handler factory function ::Handler.priorityList <- []; // A list of handler ids. Defines the order of execution whenever a global event is fired. ::Handler.handlerMap <- {}; // Key: handler id, Value: Ent to Handler ::Handler.entityMap <- {}; // Key: entity, Value: Id to Handler ::Handler.isHandling <- false; ::Handler.toCleanupList <- []; ::Handler.FUNC_NAME_MAP <- {}; ::Handler.FUNC_NAME_MAP[EVENT.TICK] <- "OnEvent_Tick"; ::Handler.FUNC_NAME_MAP[EVENT.PLAYER_SPAWN] <- "OnEvent_PlayerSpawn"; ::Handler.FUNC_NAME_MAP[EVENT.PLAYER_SPAWN_POST] <- "OnEvent_PlayerSpawnPost"; ::Handler.FUNC_NAME_MAP[EVENT.PLAYER_DEATH] <- "OnEvent_PlayerDeath"; ::Handler.FUNC_NAME_MAP[EVENT.PLAYER_DEATH_POST] <- "OnEvent_PlayerDeathPost"; ::Handler.FUNC_NAME_MAP[EVENT.PLAYER_RESET] <- "OnEvent_PlayerReset"; ::Handler.FUNC_NAME_MAP[EVENT.DAMAGE_PRE] <- "OnEvent_DamagePre"; ::Handler.FUNC_NAME_MAP[EVENT.DAMAGE_POST] <- "OnEvent_DamagePost"; ::Handler.FUNC_NAME_MAP[EVENT.ENT_TRACKER_SPAWN] <- "OnEvent_EntTrackerSpawn"; ::Handler.FUNC_NAME_MAP[EVENT.ENT_TRACKER_KILL] <- "OnEvent_EntTrackerKill"; ::Handler.FUNC_NAME_MAP[EVENT.WAVE_START] <- "OnEvent_WaveStart"; ::Handler.FUNC_NAME_MAP[EVENT.WAVE_END] <- "OnEvent_WaveEnd"; ::Handler.FUNC_NAME_MAP[EVENT.PLAYER_READY] <- "OnEvent_PlayerReady"; ::Handler.FUNC_NAME_MAP[EVENT.ALL_PLAYERS_READY] <- "OnEvent_AllPlayersReady"; ::Handler.FUNC_NAME_MAP[EVENT.PLAYER_CONNECT] <- "OnEvent_PlayerConnect"; ::Handler.FUNC_NAME_MAP[EVENT.PLAYER_DISCONNECT] <- "OnEvent_PlayerDisconnect"; ::Handler.BaseHandler <- class { id = null; self = null; activator = null; time = null; duration = null; dependencyList = null; constructor() { id = null; self = null; activator = null; time = null; duration = null; dependencyList = []; } function OnAdd() {} function OnRemove() {} function AddDependency(entity) { if (dependencyList.find(entity) == null) dependencyList.append(entity); } function CleanDependencies() { ::Debug.Print("*** HANDLER - " + this + " (" + id + ") is cleaning dependencies... "); foreach (dependency in dependencyList) { if (dependency == null || dependency.IsValid() == false) continue; ::Handler.MarkForCleanup(dependency); dependency.Destroy(); ::Debug.Print("*** HANDLER - " + this + " (" + id + ") destroyed dependency " + dependency); } dependencyList.clear(); } } function Handler::HandleEvent(eventId, params) { ::Handler.isHandling = true; // Create a temporary list to store the handlers. // This is so we can remove handlers from the main map without impacting the main halding loop local handlerDataList = []; foreach(handlerId in ::Handler.priorityList) { if (!(handlerId in ::Handler.handlerMap)) continue; foreach(entity, handler in ::Handler.handlerMap[handlerId]) handlerDataList.append({handlerId = handlerId, entity = entity, handler = handler}); } // Main handling loop foreach (handlerData in handlerDataList) { local handlerId = handlerData.handlerId; local entity = handlerData.entity; local handler = handlerData.handler; // Detect and clean dirty handlers if (entity == null || entity.IsValid() == false) { ::Handler.MarkForCleanup(entity); ::Handler.handlerMap[handlerId].rawdelete(entity); ::Handler.entityMap.rawdelete(entity); continue; } // Handle the event :3 local funcName = ::Handler.FUNC_NAME_MAP[eventId]; if (funcName in handler) { handler[funcName](params); } } ::Handler.isHandling = false; } function Handler::AddHandler(entity, handlerId, activator, duration, params = null) { // Handler hasn't been registered = error if (!(handlerId in ::Handler.factoryMap)) { ::Debug.Print("*** HANDLER - Failed to add handler " + handlerId + " to entity " + entity + "\n\tA handler with this id has not yet been registered!"); return null; } // Entity isn't valid = error if (::Handler.ValidateEntity(entity) == null) { ::Debug.Print("*** HANDLER - Failed to add handler " + handlerId + " to entity " + entity + "\n\tEntity scope isn't valid!"); return null; } if (!(handlerId in ::Handler.handlerMap)) ::Handler.handlerMap[handlerId] <- {}; // Handler already exists for this entity = reinitialize handler. if (entity in ::Handler.handlerMap[handlerId]) { ::Debug.Print("*** HANDLER - Handler " + handlerId + " already exists for entity " + entity + "\n\tReinitializing handler..."); local handler = ::Handler.handlerMap[handlerId][entity]; handler.activator = activator; handler.time = Time(); handler.duration = duration; return handler; } local handler = ::Handler.factoryMap[handlerId](); handler.id = handlerId; handler.self = entity; handler.activator = activator; handler.time = Time(); handler.duration = duration; if (params != null) { foreach (key, value in params) { if (key in ::Handler.BaseHandler) continue; handler[key] = value; } } ::Handler.handlerMap[handlerId][entity] <- handler; // Handler map will contain the strong reference! if (!(entity in ::Handler.entityMap)) ::Handler.entityMap[entity] <- {}; ::Handler.entityMap[entity][handlerId] <- handler; handler.OnAdd(); ::Debug.Print("*** HANDLER - Added handler " + handlerId + " to entity " + entity); return handler; } function Handler::RemoveHandler(entity, handlerId) { // Entity isn't valid = error if (::Handler.ValidateEntity(entity) == null) { ::Debug.Print("*** HANDLER - Failed to remove handler " + handlerId + " from entity " + entity + "\n\tEntity scope isn't valid!"); return; } // Handler doesn't exist on this entity = error if (!(handlerId in ::Handler.handlerMap) || !(entity in ::Handler.handlerMap[handlerId])) { ::Debug.Print("*** HANDLER - Failed to remove handler " + handlerId + " from entity " + entity + "\n\tSpecified handler doesn't exist on this entity!"); return; } local handler = ::Handler.handlerMap[handlerId][entity]; handler.OnRemove(); handler.CleanDependencies(); ::Handler.handlerMap[handlerId].rawdelete(entity); // Remove from main handler map ::Handler.entityMap[entity].rawdelete(handlerId); // Remove from entity map ::Debug.Print("*** HANDLER - Removed handler " + handlerId + " from entity " + entity); } function Handler::GetHandler(entity, handlerId) { // Entity isn't valid = error if (!(handlerId in ::Handler.handlerMap)) return null; if (!(entity in ::Handler.handlerMap[handlerId])) return null; return ::Handler.handlerMap[handlerId][entity]; } function Handler::GetAllById(handlerId) { if (!(handlerId in ::Handler.handlerMap)) return []; local handlerList = []; foreach(_, handler in ::Handler.handlerMap[handlerId]) { handlerList.append(handler); } return handlerList; } function Handler::RegisterHandler(handlerId, factory) { if (handlerId in ::Handler.factoryMap) ::Debug.Print("*** HANDLER - Warning, re-register for handler " + handlerId + " | " + factory + "\n\tThe handler factory has been reassigned!"); ::Handler.factoryMap[handlerId] <- factory; } function Handler::ValidateEntity(entity) { if (entity == null || entity.IsValid() == false || entity.ValidateScriptScope() == false) return null; local scope = entity.GetScriptScope(); if (!(::Handler.SCOPE_KEY in scope)) scope[::Handler.SCOPE_KEY] <- {}; return scope[::Handler.SCOPE_KEY]; } function Handler::SetPriority(priorityList) { ::Handler.priorityList <- priorityList; } function Handler::MarkForCleanup(entity) { // @TODO - Get rid of stupid linear search // Could use a map, then move the contents of the map into a list on post-cleanup. Then clear the map. Then check if the map is empty after the post cleanup. If it's not empty, then redo the post cleanup until it is empty. // Could store a varialbe in entity script scope. IsMarkedForCleanup or something. if (::Handler.toCleanupList.find(entity) != null) return; if (::Handler.toCleanupList.len() == 0) EntFireByHandle(::Handler.SCRIPT_ENTITY, "CallScriptFunction", ::Handler.POST_CLEANUP_FUNC_KEY, -1.0, null, null); ::Handler.toCleanupList.append(entity); ::Debug.Print("*** HANDLER - Marked " + entity + " for cleanup."); } // Initialize global handler script entity ::Handler.SCRIPT_ENTITY_TARGETNAME <- "handler_script_entity"; local scriptEntity = null; while (scriptEntity = Entities.FindByName(scriptEntity, ::Handler.SCRIPT_ENTITY_TARGETNAME)) { ::Debug.Print("*** HANDLER - Destroying existing script entity (if this was created by something else, I'm sorry!)" + scriptEntity); scriptEntity.Destroy(); } ::Handler.SCRIPT_ENTITY <- SpawnEntityFromTable("env_glow", { targetname = ::Handler.SCRIPT_ENTITY_TARGETNAME, }); ::Handler.SCRIPT_ENTITY.ValidateScriptScope(); // Post cleanup ::Handler.POST_CLEANUP_FUNC_KEY <- UniqueString(); ::Handler.SCRIPT_ENTITY.GetScriptScope()[::Handler.POST_CLEANUP_FUNC_KEY] <- function() { ::Debug.Print("*** HANDLER - Post-cleanup fired"); foreach (entity in ::Handler.toCleanupList) { if (!(entity in ::Handler.entityMap)) continue; foreach (_, handler in ::Handler.entityMap[entity]) { handler.CleanDependencies(); } } ::Handler.toCleanupList.clear(); } // Automatically remove expired handlers ::Events.GetGlobalEvent(EVENT.TICK).AddListener( function(params) { foreach(handlerId in ::Handler.priorityList) { if (!(handlerId in ::Handler.handlerMap)) continue; local toRemove = []; foreach(entity, handler in ::Handler.handlerMap[handlerId]) { if (handler == null) continue; if (handler.duration == -1.0) continue; if (Time() > handler.time + handler.duration) toRemove.append(entity); } foreach(entity in toRemove) { ::Handler.RemoveHandler(entity, handlerId); } } } );