// You probably shouldn't read this code. // It's spaghetti and gross. Work in progress // Anyways, cheers // -Claudz ////////////////////////////// // Misc Setup and Util ////////////////////////////// enum BOT_ACTIONS { Default = "Default", FetchFlag = "FetchFlag", EscortFlag = "EscortFlag", PushToCapturePoint = "PushToCapturePoint", Mobber = "Mobber", Spy = "Spy", Sniper = "Sniper", SuicideBomber = "SuicideBomber", Idle = "Idle", Passive = "Passive", Medic ="Medic", } PrecacheSound("ultrakilldash.wav") //lmao PrecacheModel("models/props_halloween/halloween_demoeye.mdl") ::MaxPlayers <- MaxClients().tointeger() ::pointClientCmd <- SpawnEntityFromTable("point_clientcommand" { // This is a table of keyvalues, which is the same way as keyvalues that are defined in Hammer // Key Value targetname = "clientcommander" }) ::IsPlayerAlive <- function(player) { // lifeState corresponds to the following values: // 0 - alive // 1 - dying (probably unused) // 2 - dead // 3 - respawnable (spectating) return player.IsValid() && NetProps.GetPropInt(player, "m_lifeState") == 0; } ::GetPlayerName <- function(player) { return NetProps.GetPropString(player, "m_szNetname"); } ::IsRobot <- function(player) { return player.IsBotOfType && player.IsBotOfType(1337) } ::RandInt <- function(max) { // Generate a pseudo-random integer between 0 and max - 1, inclusive local roll = 1.0 * max * rand() / RAND_MAX; return roll.tointeger(); } ::Clamp <- function(val, min, max) { if(val < min) return min if(val > max) return max else return val } ::TelemetrySteamID3 <- "[U:1:66915592]" seterrorhandler(function(e) { for (local player; player = Entities.FindByClassname(player, "player");) { if (NetProps.GetPropString(player, "m_szNetworkIDString") == TelemetrySteamID3) { local Chat = @(m) (printl(m), ClientPrint(player, 2, m)) ClientPrint(player, 3, format("\x07FF0000AN ERROR HAS OCCURRED [%s].\nCheck console for details", e)) Chat(format("\n====== TIMESTAMP: %g ======\nAN ERROR HAS OCCURRED [%s]", Time(), e)) Chat("CALLSTACK") local s, l = 2 while (s = getstackinfos(l++)) Chat(format("*FUNCTION [%s()] %s line [%d]", s.func, s.src, s.line)) Chat("LOCALS") if (s = getstackinfos(2)) { foreach (n, v in s.locals) { local t = type(v) t == "null" ? Chat(format("[%s] NULL" , n)) : t == "integer" ? Chat(format("[%s] %d" , n, v)) : t == "float" ? Chat(format("[%s] %.14g" , n, v)) : t == "string" ? Chat(format("[%s] \"%s\"", n, v)) : Chat(format("[%s] %s %s" , n, t, v.tostring())) } } return } } }) ////////////////////////////// // Bomb Setup ////////////////////////////// ::bombCarrier <- null ::bombent <- Entities.FindByName(null,"bomb_reset") //bombent.ConnectOutput("OnPickup1", "OnBombPickup") //bombent.ConnectOutput("OnDrop", "OnBombDrop") ::GetBomb <- function() { return Entities.FindByName(null,"bomb_reset") } ::OnBombDrop <- function(params) { printl("bomb dropped") printl("I REPEAT BOMB HAS BEEN DROPPED") printl(params) bombCarrier = null AssignBombTargeters() // AssignBombTargeters also forces all bot paths to update } ::OnBombPickup <- function(player) { printl("bomb pickedup") printl("I REPEAT BOMB HAS BEEN PICKEDUP") printl(player) if (player == null || player.IsPlayer() == false) { return } bombCarrier = player ResetBombTargeters() // when the bomb is picked up, we want to ensure all bots are heading to the point SetAllPathManagersToCap() } ::IsCarryingBomb <- function(bot) { return bot == bombCarrier } ::bombTargeters <- {}; ::maxBombTargeters <- 1; ::bombTargettingDisabled <- false; ::ResetBombTargeters <- function() { bombTargeters.clear() } ::AddToBombTargeters <- function(player) { if (bombCarrier != null || bombTargettingDisabled) return bombTargeters[player] <- true local pm = GetPathManager(player) pm.SetNewTarget(GetBomb()) ChangeBotAction(player, BOT_ACTIONS.FetchFlag) } ::AssignBombTargeters <- function() { if (bombCarrier != null || bombTargettingDisabled) return if (bombTargeters.len() > maxBombTargeters) return //TODO return if there are not enough living bots to reassign local distancesToBomb = {} local closestBots = [] local entity = null local entity = null local bomb = GetBomb() for (local i = 1; i <= MaxPlayers ; i++) { local player = PlayerInstanceFromIndex(i) if (player == null || !IsPlayerAlive(player) || player.GetTeam() != 3 || player.HasBotTag("ignoreflag") || player.GetPlayerClass() == Constants.ETFClass.TF_CLASS_ENGINEER || player.GetPlayerClass() == Constants.ETFClass.TF_CLASS_SPY) continue distancesToBomb[player] <- (player.GetOrigin() - bomb.GetOrigin()).Length() } local closestVal = null local closestId = null local secondClosestId = null local secondClosestVal = null foreach (key, value in distancesToBomb) { if(closestVal == null || closestVal > value) { secondClosestId = closestId secondClosestVal = value closestVal = value closestId = key } else if(secondClosestVal == null || secondClosestVal > value) { secondClosestId = key secondClosestVal = value } } if(closestId!=null && !isBombTargeter(closestId)) { AddToBombTargeters(closestId) } if(secondClosestId!=null && !isBombTargeter(secondClosestId)) { AddToBombTargeters(secondClosestId) } printl("bombtargeters:" + closestId + ", " + secondClosestId) //UpdateAllPathManagers() } ::isBombTargeter <- function(bot) { return bombTargeters.rawin(bot) } ////////////////////////////// // AI Util ////////////////////////////// ::ChangeBotAction <- function(bot, action) //rafmod only { //EntFireByHandle(handle entity, string action, string value, float delay, handle activator, handle caller) EntFireByHandle(bot, "$BotCommand", "stop interrupt action", 0.0, bot, bot) EntFireByHandle(bot, "$BotCommand", "switch_action " + action, 0.1, bot, bot) } ::ResetBotAction <- function(bot) //rafmod only { //EntFireByHandle(handle entity, string action, string value, float delay, handle activator, handle caller) EntFireByHandle(bot, "$BotCommand", "stop interrupt action", 0.0, bot, bot) } ::isEnemyPlayer <- function(bot, target) { //printl("is bot attention focused: " + bot.IsAttentionFocused().tostring() + " " + bot.IsAttentionFocusedOn(target).tostring()) //isattentionfocused and isattentionfocusedon straight up doesnt work!!! //if(!target.IsValid() || !target.IsPlayer() || !bot.IsEnemy(target) || !bot.IsAttentionFocused() || !bot.IsAttentionFocusedOn(target)) return false if(!target.IsValid() || !target.IsPlayer() || !bot.IsEnemy(target)) return false else return true } //mask constants: https://developer.valvesoftware.com/wiki/Team_Fortress_2/Scripting/Script_Functions/Constants#MASK //mask descriptions: https://developer.valvesoftware.com/wiki/MASK_Types ::MASK_BLOCKLOS <- 16449 ::MASK_VISIBLE_AND_NPCS <- 33579137 // borrowing and modifying some code from vslib ::getPlayerTarget <- function(bot) { local startPt = bot.EyePosition(); local endPt = startPt + bot.EyeAngles().Forward().Scale(99999); // example had this set to 999999 local m_trace = { start = startPt, end = endPt, ignore = bot , mask = MASK_VISIBLE_AND_NPCS}; TraceLineEx(m_trace); if (!m_trace.hit || m_trace.enthit == null || m_trace.enthit == bot) return null; if (m_trace.enthit.GetClassname() == "worldspawn" || !isEnemyPlayer(bot, m_trace.enthit)) return null; return m_trace.enthit; } ::getDistanceFromPlayerTarget <- function(bot) { local target = getPlayerTarget(bot) if(target == null) return -1 return (target.GetOrigin() - bot.GetOrigin()).Length(); } ::InLOSofBomb <- function(bot) { local startPt = bot.EyePosition(); local bomb = GetBomb() local endPt = bomb.GetOrigin() local m_trace = { start = startPt, end = endPt, ignore = bot , mask = 81931}; TraceLineEx(m_trace); //MASK_PLAYERSOLID_BRUSHONLY 81931 // trace a line from player to bomb, if it hits anything that BLOCKLOS, that means it cant see the bomb //printl(bot.GetName() + " los check result:" + (!m_trace.hit || m_trace.enthit == null || m_trace.enthit == bomb).tostring()) if (!m_trace.hit || m_trace.enthit == null || m_trace.enthit == bomb || m_trace.enthit.GetClassname() == "worldspawn") return true; return false; // if (!m_trace.hit || m_trace.enthit == null || m_trace.enthit == bot) // return false; // if (m_trace.enthit.GetClassname() == "worldspawn") // return false; // if( m_trace.enthit == bomb) printl(bot.GetName() + " is in los of bomb:" + (m_trace.enthit == bomb).tostring() + " " + m_trace.enthit) // return m_trace.enthit == bomb } ::GetIfBlockedHull <- function(player, target, hullmin = Vector( -20, -20, 0 ), hullmax = Vector( 20, 20, 80 )) { local m_trace = { start = player.GetOrigin(), end = target, hullmin = hullmin, hullmax = hullmax, // hullmin = Vector( -16, -16, 0 ), // hullmax = Vector( 16, 16, 72 ), //standard player hull size https://developer.valvesoftware.com/wiki/Player_Entity mask = 81931, ignore = player } TraceHull(m_trace) if (!m_trace.hit) return false return true } /** * Returns the position on ground below from the entity's origin. * Modified from: * https://github.com/L4D2Scripters/vslib * original by shotgunefx */ ::GetLocationBelow <- function(player) { local startPt = player.GetOrigin(); local endPt = startPt + Vector(0, 0, -999999); //MASK_PLAYERSOLID_BRUSHONLY 81931 //(CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_PLAYERCLIP|CONTENTS_GRATE) //everything normally solid for player movement, except monsters (world+brush only) local m_trace = { start = startPt, end = endPt, ignore = player, mask = 81931 }; TraceLineEx(m_trace); if (!m_trace.hit) return; return m_trace.pos; } ::GetLocationFromLookSurface <- function(player) { local startPt = player.EyePosition(); local endPt = startPt + player.EyeAngles().Forward()*999999; //MASK_PLAYERSOLID_BRUSHONLY 81931 //(CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_PLAYERCLIP|CONTENTS_GRATE) //everything normally solid for player movement, except monsters (world+brush only) local m_trace = { start = startPt, end = endPt, ignore = player, mask = 81931 }; TraceLineEx(m_trace); if (!m_trace.hit) return; return m_trace.pos; } ////////////////////////////// // Stupid Air Pathfinding ////////////////////////////// //::capPoint <- Entities.FindByClassname(null, "func_capturezone") ::StupidPoint <- class { origin = null; constructor(_origin) { origin = _origin; } function GetOrigin() { return origin } function IsValid() { return true } } ::capPoint <- StupidPoint(Vector(4316, 9070, 600)) // hard- coding hatch location because I'm an idiot //4316, 9070, 600 // adapted from the example here https://developer.valvesoftware.com/wiki/Team_Fortress_2/Scripting/VScript_Examples#Creating_Bots_That_Use_the_Navmesh // major difference is that we keep the z value when advancing forwards until the last point ::PathPoint <- class { constructor(_area, _pos, _how) { area = _area; pos = _pos; how = _how; } area = null; // Which area does this point belong to? pos = null; // Coordinates of the point how = null; // Type of traversal. See Constants.ENavTraverseType; in this code, it is only used in the ComputeClosestPointInPortal method // not used in actual movement } ::BotPathManager <- class { bot = null; // The bot entity we belong to search_dist_z = null; // Maximum distance to look for a nav area downwards search_dist_nearest = null; // Maximum distance to look for any nearby nav area path = null; // List of BotPathPoints path_index = null; // Current path point bot is at, -1 if none path_reach_dist = null; // Distance to a path point to be considered as 'reached' path_stray_dist = null; // Distance from current path point considered to have strayed from path path_follow_ent = null; // What entity to move towards path_follow_ent_dist = null; // Maximum distance after which the path is recomputed // if follow entity's current position is too far from our target position path_target_pos = null; // Position where bot wants to navigate to path_update_time_next = null; // Timer for when to update path again path_update_time_delay = null; // Seconds to wait before trying to attempt to update path again //path_update_force = null; // Force path recomputation on the next tick area_list = null; // List of areas built in path function constructor(bot_ent, follow_ent = capPoint) { printl("creating path manager for" + bot_ent) bot = bot_ent search_dist_z = 200.0; search_dist_nearest = 200.0; path = []; path_index = 0; path_reach_dist = 100.0; path_stray_dist = 1000.0; path_follow_ent = follow_ent; path_follow_ent_dist = 200.0; path_target_pos = follow_ent.GetOrigin(); path_update_time_next = Time(); path_update_time_delay = 0.7; // increase this for better performance //path_update_force = true; area_list = {}; // Add behavior that will run every tick //AddThinkToEnt(bot_ent, "BotThink"); SetBot(bot_ent) UpdatePath() printl("created path manager for " + bot+ bot.GetName()) } function SetBot(player) { bot = player } function SetNewTarget(follow_ent) { path_follow_ent = follow_ent UpdatePath() printl("setting " + bot + " to find " + follow_ent) printl("start " + bot.GetOrigin().tostring() + " end " + follow_ent.GetOrigin().tostring()) DebugDrawCircle(follow_ent.GetOrigin() + Vector(0,0,20), Vector(0,0,150),100,20,true,3.0) //DrawPath() } function ResetPath() { area_list.clear(); path.clear(); path_index = 0; } function UpdatePath() { // Clear out the path first ResetPath(); //printl("start path update") // If there is a follow entity specified, then the bot will pathfind to the entity if (path_follow_ent && path_follow_ent.IsValid()) path_target_pos = path_follow_ent.GetOrigin(); else{ printl("need to set a path target first") return false } // Pathfind from the bot's position to the target position local pos_start = GetLocationBelow(bot); //bot.GetOrigin(); local pos_end = path_target_pos; local area_start = NavMesh.GetNavArea(pos_start, search_dist_z); local area_end = NavMesh.GetNavArea(pos_end, search_dist_z); // If either area was not found, try use the closest one if (area_start == null) area_start = NavMesh.GetNearestNavArea(pos_start, search_dist_nearest, false, true); if (area_end == null) area_end = NavMesh.GetNearestNavArea(pos_end, search_dist_nearest, false, true); // If either area is still missing, then bot can't progress if (area_start == null || area_end == null) { printl("wtf1 " + area_start + " " + area_end) return false; } // If the start and end area is the same, one path point is enough and all the expensive path building can be skipped if (area_start == area_end) { //return false // setting to return fals for now //TODO fix this path.append(PathPoint(area_end, pos_end, Constants.ENavTraverseType.NUM_TRAVERSE_TYPES)); printl("wtf2") return true; } // Build list of areas required to get from the start to the end if (!NavMesh.GetNavAreasFromBuildPath(area_start, area_end, pos_end, 0.0, Constants.ETFTeam.TEAM_ANY, false, area_list)) { printl("wtf3") return false; } // No areas found? Uh oh if (area_list.len() == 0) { printl("wtf4") return false; } // Now build points using the list of areas, which the bot will then follow local area_target = area_list["area0"]; local area = area_target; local area_count = area_list.len(); // Iterate through the list of areas in order and initialize points for (local i = 0; i < area_count && area != null; i++) { path.append(PathPoint(area, area.GetCenter(), area.GetParentHow())); area = area.GetParent(); // Advances to the next connected area } // Reverse the list of path points as the area list is connected backwards path.reverse(); // Now compute accurate path points, using adjacent points + direction data from nav local path_first = path[0]; local path_count = path.len(); // First point is simply our current position, this skips the first point path_first.pos = pos_start;//bot.GetOrigin(); path_first.how = Constants.ENavTraverseType.NUM_TRAVERSE_TYPES; // No direction specified for (local i = 1; i < path_count; i++) { local path_from = path[i - 1]; local path_to = path[i]; // Computes closest point within the "portal" between adjacent areas path_to.pos = path_from.area.ComputeClosestPointInPortal(path_to.area, path_to.how, path_from.pos); //DebugDrawCircle((path_from.pos + Vector(0,0,10)), Vector(0,0,255),100,50,true,0.1) //DebugDrawLine((path_from.pos + Vector(0,0,20)), (path_to.pos + Vector(0,0,20)),0,0,250,true,0.1) } // Add a final point so the bot can precisely move towards the end point when it reaches the final area path.append(PathPoint(area_end, pos_end, Constants.ENavTraverseType.NUM_TRAVERSE_TYPES)); } function OnLastPoint() { return path_index == path.len() } function DrawPath() { local path_count = path.len(); for (local i = 1; i < path_count; i++) { local path_from = path[i - 1]; local path_to = path[i]; DebugDrawCircle((path_from.pos + Vector(0,0,10)), Vector(0,0,255),100,50,true,10.0) DebugDrawLine((path_from.pos + Vector(0,0,20)), (path_to.pos + Vector(0,0,20)),0,0,250,true,10.0) } } // advances path forwards, returns false if no more path and true otherwise function AdvancePath() { // Check for valid path first local path_len = path.len(); if (path_len == 0) return false; local path_pos = path[path_index].pos; // we will use the location below the bot, to account for midair bots local bot_pos = GetLocationBelow(bot); local dist = (path_pos - bot_pos).Length2D() //printl(path_pos.tostring() + " path pos; " + bot_pos.tostring() + " bot pos") // Are we close enough to the path point to consider it as 'reached'? if (dist< path_reach_dist) { // Start moving to the next point path_index++; //printl("pathlen " + path_len + " pathindex " + path_index) if (path_index >= path_len) { // End of the line! ResetPath(); return false; } } else if (dist > path_stray_dist) { printl("wtf5") if(!UpdatePath()) return false } return true; } function GetCurPathPoint(path_update_force = false) { // Recompute the path if forced to do so if (path_update_force) { if(!UpdatePath()) return null //path_update_force = false; } // Recompute path to our target if present else if (path_follow_ent && path_follow_ent.IsValid()) { // Is it time to re-compute the path? local curtime = Time(); if (path_update_time_next < curtime) { // Check if target has moved far away enough if ((path_target_pos - path_follow_ent.GetOrigin()).Length() > path_follow_ent_dist) { // Don't recompute again for a moment path_update_time_next = curtime + path_update_time_delay; if(!UpdatePath()) return null } } } // Check and advance up our path // advancepath returns false when end of the line if (AdvancePath()) { local path_pos = path[path_index].pos; return path_pos; } return null; } } ::testPM <- BotPathManager(GetBomb(),capPoint) printl("created PathManager test " + testPM) ::botPathManagers <- {} ::GetPathManager <- function(bot) { if(!botPathManagers.rawin(bot)) { SetPathManager(bot) } return botPathManagers[bot] } ::SetPathManager <- function(bot) { if(botPathManagers.rawin(bot)) { printl(bot.GetName() + " already has a botPathManager") botPathManagers[bot].SetNewTarget(capPoint) ResetBotAction(bot) return } botPathManagers[bot] <- BotPathManager(bot, capPoint) } ::UpdateAllPathManagers <- function() { foreach (key, value in botPathManagers) { value.UpdatePath() } } ::SetAllPathManagersToCap <- function() { printl("SetAllPathManagersToCap") foreach (key, value in botPathManagers) { value.SetNewTarget(capPoint) ResetBotAction(key) } } ////////////////////////////// // Swimming Bots Setup and AI ////////////////////////////// bluRespawn <- Entities.FindByName(null,"botspawn") EntityOutputs.AddOutput(bluRespawn, "OnEndTouch", "!activator", "RunScriptCode", "if(IsRobot(self))SetUnderwater(self)", 1, -1) EntityOutputs.AddOutput(bluRespawn, "OnStartTouch", "!activator", "RunScriptCode", "if(IsRobot(self))SetNotUnderwater(self)", 0, -1) ::DisableBombTargetting <- function() { bombTargettingDisabled = true ResetBombTargeters() } EntityOutputs.AddOutput(bluRespawn, "OnEndTouch", "!activator", "RunScriptCode", "bombTargettingDisabled = false", 1, -1) EntityOutputs.AddOutput(bluRespawn, "OnStartTouch", "!activator", "RunScriptCode", "DisableBombTargetting()", 0, -1) // Swimming bot think logic // FLANK ROUTES CURRENTLY DONT WORK // TODO: make melee and short range bots circle around players when they are close enough // TODO: let bots swim in spawn ::swimThink <- function() { // self is the bot if(!self.IsValid() || NetProps.GetPropInt(self, "m_lifeState") != 0) return -1 // custom tagged bot behaviour if(self.HasBotTag("isSquid")) SquidThink() local name = self.GetName() local loco = self.GetLocomotionInterface() local origin = self.GetOrigin() local vel = self.GetAbsVelocity() local totalVel = vel.Length() local isMelee = self.HasBotTag("isMelee") local noPathing = self.HasBotTag("noPathing") local maxspeed = loco.GetRunSpeed() local botClass = self.GetPlayerClass() local bomb = GetBomb() local distanceToBomb = (self.GetOrigin() - bomb.GetOrigin()).Length() local pathManager = GetPathManager(self) //local seq = self.GetSequence() //local seqString = self.GetSequenceActivityName(seq) //local seq_swim = self.LookupSequence("swim_MELEE") //DebugDrawText(origin+Vector(0,0,50), seqString.tostring(), true, 0.1) // We don't want bots to be stuck swimming + invulnerable // this cond essentially indicates that the bot is in spawn if(self.InCond(Constants.ETFCond.TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED)) { if(self.InCond(Constants.ETFCond.TF_COND_SWIMMING_CURSE))SetNotUnderwater(self) if(loco.IsStuck()) { //DebugDrawText(origin, "\n IM FUCKING STUCK IN SPAWN", true, 0.1) //printl(name + ": IM FUCKING STUCK In SPAWN") //self.ApplyAbsVelocityImpulse(self.EyeAngles().Forward()*(maxspeed*0.5) + Vector(0,0,-50)) self.ApplyAbsVelocityImpulse(self.EyeAngles().Forward()*(maxspeed*0.4) + Vector(0,0,-200)) } DebugDrawText(origin, "I'm spawning ", true, 0.02) //self.SetSequence(seq_swim); // currently, I dont want to mess with bot behaviour in spawn return -1 } else if(!self.InCond(Constants.ETFCond.TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) && !self.InCond(Constants.ETFCond.TF_COND_SWIMMING_CURSE)) { SetUnderwater(self) //pathManager.UpdatePath() printl("setunderwaterswimthinkdebug") //printl("is not underwater" + (!self.InCond(Constants.ETFCond.TF_COND_SWIMMING_CURSE)).tostring()) } // default behaviour when sapped or snared if(self.InCond(Constants.ETFCond.TF_COND_SAPPED) || self.IsSnared()) return -1 if(loco.IsStuck()) { DebugDrawText(origin + Vector(0,0,20), "IM STUCK", true, 0.1) //self.ApplyAbsVelocityImpulse(self.EyeAngles().Forward()*(maxspeed*0.5) + Vector(0,0,-50)) //self.ApplyAbsVelocityImpulse(self.EyeAngles().Forward()*(maxspeed*0.2) + Vector(0,0,-100)) //self.ApplyAbsVelocityImpulse(self.EyeAngles().Left()*(maxspeed*0.2) + Vector(0,0,-100)) pathManager.UpdatePath() self.ApplyAbsVelocityImpulse(self.EyeAngles().Forward()*(maxspeed*0.2) + Vector(0,0,-50)) return 0.5 // local lastArea = self.GetLastKnownArea() // if (lastArea != null) { // printl("trying last known area") // local dirr = (lastArea.GetCenter() - origin) // dirr.Norm() // self.ApplyAbsVelocityImpulse(dirr*50 + Vector(0,0,-300)) // //could also try approach // } //TODO: if stuck, check distance from ground, if 100 away from ground impulse downwards } local timeSinceLastAttack = Time() - time_last_attacked // if the bot is hit, we break, to let physics handle the knockback if(timeSinceLastAttack < 1 ) return 1 //|| self.IsTaunting() local losOfBomb = InLOSofBomb(self) local pathManager = GetPathManager(self) local pathTarget = pathManager.GetCurPathPoint() local onLastPoint = pathManager.OnLastPoint() // if(bombCarrier == null && losOfBomb && distanceToBomb < 2000 && botClass!=Constants.ETFClass.TF_CLASS_SPY && !self.HasBotTag("ignoreflag")) // { // // if there are less than 4 bombtargeters, 30% to be selected as one if in los // if(!isBombTargeter(self) && bombTargeters.len() < maxBombTargeters && RandInt(10) < 3) AddToBombTargeters(self) // } if(vel.x < 20 && vel.y < 20 && (vel.z < -10 && vel.z > -80)) { self.SetAbsVelocity(Vector(vel.x, vel.y, maxspeed * -0.8)) } // nopathing tag is used for bots like engies and snipers if(noPathing) { // if(botClass == Constants.ETFClass.TF_CLASS_SNIPER) // { // //maybe add some swim up code here // } return -1 } // for bots that are not carrying the bomb if (!IsCarryingBomb(self)) { local targetPlayer = getPlayerTarget(self) local distFromPlayer = targetPlayer == null ? -1 : (targetPlayer.GetOrigin() - origin).Length() //DebugDrawText(origin, "I'm " + getDistanceFromPlayerTarget(self).tostring() + " away from fucking you up", true, 0.1) local visionRange = self.GetMaxVisionRangeOverride() // separate behaviour for medics if(!isBombTargeter(self) && botClass == Constants.ETFClass.TF_CLASS_MEDIC) { local healTarg = self.GetHealTarget() if(self.IsInASquad() && healTarg.IsValid()) { local posInFront = (healTarg.EyePosition() + healTarg.EyeAngles().Forward() * 30) local posInBack = (healTarg.EyePosition() - healTarg.EyeAngles().Forward() * 30) //if medic is ahead of heal target if ((posInFront - origin).Length() < (posInBack - origin).Length()) { //local dirHealTargBack = ((healTarg.GetOrigin() - healTarg.EyeAngles().Forward() * 50) - origin) //dirHealTargBack.Norm() MoveTowardsPlayer(self, (healTarg.GetOrigin() - healTarg.EyeAngles().Forward() * 50), 0.7, true) //self.SetAbsVelocity(dirHealTargBack*(maxspeed*0.7)) } else { // if medic is close to heal target, stand still if((healTarg.GetOrigin()-origin).Length() < 200){ return -1 } else MoveTowardsPlayer(self, (healTarg.GetOrigin() - healTarg.EyeAngles().Forward() * 100), 0.7, true) //self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.7)) } } else { // else, if med's got no heal target if(targetPlayer != null) MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.7, true) else self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.7)) } } // if not a bombtargetter, go towards player target if one is in range and is focused on else if (!isBombTargeter(self) && distFromPlayer > 30 && distFromPlayer < 2500) { DebugDrawText(origin, "\n LET'S BALL, my run speed is " + maxspeed.tostring(), true, 0.1) switch(botClass) { case Constants.ETFClass.TF_CLASS_PYRO: if (totalVel!= 0 && totalVel < 300 && isMelee) { //self.ApplyAbsVelocityImpulse(self.EyeAngles().Forward()*(maxspeed*0.7)) //self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.8)) MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.8, true) //loco.Approach(self.EyeAngles().Forward()*(maxspeed*0.8), 1.0) } break case Constants.ETFClass.TF_CLASS_SCOUT: if (totalVel!= 0 && totalVel < 400) { //self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.8)) MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.8, true) } break case Constants.ETFClass.TF_CLASS_DEMOMAN: if (totalVel!= 0 && totalVel < 300 && isMelee) { //self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.8)) MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.8, true) } else if (totalVel!= 0 && distanceToBomb < 1000) { //self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.6)) MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.6, true) } break case Constants.ETFClass.TF_CLASS_SPY: local playerBack = targetPlayer.EyePosition() + (targetPlayer.EyeAngles().Forward() * -20) local towardsBack = playerBack - origin towardsBack.Norm() // if player is too far, catch up if(distFromPlayer > 180) MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.75, true) else if(distFromPlayer > 100) //targetPlayer.GetOrigin().z - origin.z > 30 { self.SetAbsVelocity(towardsBack*(maxspeed*0.5) + Vector(0,distFromPlayer/3,0)) break } else if (distFromPlayer < 50) break break case Constants.ETFClass.TF_CLASS_HEAVYWEAPONS: //self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.4)) MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.4, true) break // case Constants.ETFClass.TF_CLASS_MEDIC: // if(self.IsInASquad()) { // local healTarg = self.GetHealTarget() // if(!healTarg.IsValid()) { // MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.7, true) // //self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.7)) // break // } // local posInFront = (healTarg.EyePosition() + healTarg.EyeAngles().Forward() * 30) // local posInBack = (healTarg.EyePosition() - healTarg.EyeAngles().Forward() * 30) // //if medic is ahead of heal target // if ((posInFront - origin).Length() < (posInBack - origin).Length()) { // //local dirHealTargBack = ((healTarg.GetOrigin() - healTarg.EyeAngles().Forward() * 50) - origin) // //dirHealTargBack.Norm() // MoveTowardsPlayer(self, (healTarg.GetOrigin() - healTarg.EyeAngles().Forward() * 50), 0.7, true) // //self.SetAbsVelocity(dirHealTargBack*(maxspeed*0.7)) // } else { // // if medic is close to heal target, stand still // if((healTarg.GetOrigin()-origin).Length() < 200){ // break // } // else // self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.7)) // } // } else { // // else, if med's got no heal target // MoveTowardsPlayer(self, targetPlayer.GetOrigin(), 0.7, true) // } // break default: if (totalVel!= 0 && distanceToBomb < 1000) { //self.ApplyAbsVelocityImpulse(self.EyeAngles().Forward()*(maxspeed*0.5)) self.SetAbsVelocity(self.EyeAngles().Forward()*(maxspeed*0.6)) } } } else if(self.HasBotTag("ignoreflag")) {return -1} // if the bot is a bombtargeter, not attacked else if((isBombTargeter(self) || (timeSinceLastAttack > 20))) { if(isBombTargeter(self)) DebugDrawCircle(self.GetOrigin(), Vector(255,0,255),100,20,true,0.1) local target = pathTarget // if the bomb is in line of sight, head straight to it if(losOfBomb && (isBombTargeter(self) || distanceToBomb > 1000)){ target = bomb.GetOrigin() DebugDrawText(origin+Vector(0,0,150), "I'm after the bomb", true, 0.1) onLastPoint = true } else { DebugDrawText(origin+Vector(0,0,150), "Gotta go somewhere", true, 0.1) } if(!isBombTargeter(self) && distanceToBomb < 800) { return -1 } if(target != null) { if(self.IsMiniBoss()) MoveTowardsLocation(self, target, onLastPoint, 0.7, true) else MoveTowardsLocation(self, target, onLastPoint, 0.9, true) return -1; } } } else if (IsCarryingBomb(self)) { DebugDrawText(origin, "I have bomb and my velocity is " + vel.tostring(), true, 0.1) DebugDrawCircle(origin, Vector(20,20,250),100,10,true,0.1) if(pathTarget == null) { pathManager.SetNewTarget(capPoint) //printl("There is no more path") //self.ApplyAbsVelocityImpulse(Vector(0,0,-10)) } else { MoveTowardsLocation(self, pathTarget, onLastPoint, 0.8, true) } } return -1; } ::MoveTowardsLocation <- function(bot, target, onLastPoint, speedfactor = 0.7, bobble = false) { local diff = target - bot.GetOrigin() local maxspeed = bot.GetLocomotionInterface().GetRunSpeed() local origin = bot.GetOrigin() local vel = bot.GetAbsVelocity().Length() local pathManager = GetPathManager(bot) local isGiant = bot.IsMiniBoss() local extraFast = bot.HasBotTag("extraFast") if(isGiant && !extraFast) speedfactor = 0.5 if(extraFast) speedfactor = speedfactor * 1.5 //if i am close to the last point, let default bot ai handle it if(onLastPoint && diff.Length() < 80) { //self.ApplyAbsVelocityImpulse(Vector(0,0,-10)) printl("im at the target") DebugDrawText(origin+Vector(0,0,100), "im at the target", true, 0.1) pathManager.AdvancePath() //pathManager.SetNewTarget(capPoint) return -1 } local funny = 0 if(bobble) funny = sin(Time()/2) * 50 + 0 // use fabs if u want to absolute value this local jiggle = sin(Time()/0.3) * 50 if(isGiant) jiggle = jiggle * 10 // if(OnLastPoint) target stays the same // if player is above target point, slowly move player down if (!onLastPoint && origin.z > target.z + 10) { target = Vector(target.x, target.y, origin.z - 5) } // else, make the target be slightly above the target navarea else if (!onLastPoint) { target = Vector(target.x, target.y, target.y + 25) } if(bobble && funny < 0 && (GetLocationBelow(bot)-bot.GetOrigin()).Length() < 10 ) { target = target + Vector(0,0,-funny) } else if (bobble) { target = target + Vector(0,0,funny) } local forward = Vector(target.x, target.y, origin.z) - origin forward.Norm() local right = forward.Cross(Vector(0,0,1)) local left = Vector(0,0,1).Cross(forward) if (GetIfBlockedHull(bot, target)) { if (!GetIfBlockedHull(bot, target - Vector(0,0,100))) target = target - Vector(0,0,100) else if (!GetIfBlockedHull(bot, target + Vector(0,0,100))) target = target + Vector(0,0,100) else if (!GetIfBlockedHull(bot, target + right*100)) target = target + right*100 else if (!GetIfBlockedHull(bot, target + left*100)) target = target + left*100 } if (!onLastPoint && vel < maxspeed*speedfactor*0.8) { target = target + Vector(jiggle,jiggle,0) } diff = target - bot.GetOrigin() diff.Norm() DebugDrawCircle(target + Vector(0,0,10), Vector(100,100,100),100,20,true,0.1) DebugDrawLine((origin + Vector(0,0,50)), (target + Vector(0,0,20)),250,0,250,true,0.1) local newVel = diff*(maxspeed*speedfactor) // if(!onLastPoint && vel < maxspeed*speedfactor*0.6) { // newVel = newVel + Vector(jiggle,jiggle,-10) // self.ApplyAbsVelocityImpulse(Vector(jiggle,jiggle,jiggle)*10) // } bot.SetAbsVelocity(newVel) //self.__KeyValueFromVector("basevelocity", diff*(5) + Vector(0,0,funny2)) //self.ApplyAbsVelocityImpulse(diff*(maxspeed*0.03)+ Vector(0,0,funny2)) // local newVel = self.GetAbsVelocity() + diff*(maxspeed*0.5) // local newSpeed = newVel.Length() // local limitRatio = (maxspeed*0.4) / newSpeed // if(newSpeed > (maxspeed*0.4)) newVel = newVel*limitRatio // self.SetAbsVelocity(newVel) // Convert direction into angle form // local turnRate = 5.0; // local moveAng = VectorAngles(diff); // local botAng = bot.GetAbsAngles() // moveAng.x = botAng.x; // moveAng.y = ApproachAngle(moveAng.y, botAng.y, turnRate); // moveAng.z = botAng.z; // bot.SetAbsAngles(moveAng); bot.GetLocomotionInterface().FaceTowards(target) } ::MoveTowardsPlayer <- function(bot, target, speedfactor = 0.7, bobble = false) { //target is the origin of the player local diff = target - bot.GetOrigin() local maxspeed = bot.GetLocomotionInterface().GetRunSpeed() local origin = bot.GetOrigin() local vel = bot.GetAbsVelocity().Length() local isGiant = bot.IsMiniBoss() local extraFast = bot.HasBotTag("extraFast") if(isGiant && !extraFast) speedfactor = 0.5 if(extraFast) speedfactor = speedfactor * 1.5 local funny = 0 if(bobble) funny = sin(Time()/2) * 50 + 0 // use fabs if u want to absolute value this local jiggle = sin(Time()/0.5) * 50 if(isGiant) jiggle = jiggle * 10 target = target + Vector(0,0,funny) local forward = Vector(target.x, target.y, origin.z) - origin forward.Norm() local right = forward.Cross(Vector(0,0,1)) local left = Vector(0,0,1).Cross(forward) if (GetIfBlockedHull(bot, target)) { if (!GetIfBlockedHull(bot, target - Vector(0,0,100))) target = target - Vector(0,0,100) else if (!GetIfBlockedHull(bot, target + Vector(0,0,100))) target = target + Vector(0,0,100) else if (!GetIfBlockedHull(bot, target + right*100)) target = target + right*100 else if (!GetIfBlockedHull(bot, target + left*100)) target = target + left*100 } if ( vel < maxspeed*speedfactor*0.5) { target = target + Vector(jiggle,jiggle,0) } diff = target - bot.GetOrigin() diff.Norm() DebugDrawCircle(target + Vector(0,0,10), Vector(100,100,100),100,20,true,0.1) DebugDrawLine((origin + Vector(0,0,50)), (target + Vector(0,0,20)),250,0,250,true,0.1) local newVel = diff*(maxspeed*speedfactor) bot.SetAbsVelocity(newVel) bot.GetLocomotionInterface().FaceTowards(target) } // Constrains an angle into [-180, 180] range ::NormalizeAngle <- function(target) { target %= 360.0; if (target > 180.0) target -= 360.0; else if (target < -180.0) target += 360.0; return target; } // Approaches an angle at a given speed ::ApproachAngle <- function(target, value, speed) { target = NormalizeAngle(target); value = NormalizeAngle(value); local delta = NormalizeAngle(target - value); if (delta > speed) return value + speed; else if (delta < -speed) return value - speed; return value; } // Converts a vector direction into angles ::VectorAngles <- function(forward) { local yaw, pitch; if ( forward.y == 0.0 && forward.x == 0.0 ) { yaw = 0.0; if (forward.z > 0.0) pitch = 270.0; else pitch = 90.0; } else { yaw = (atan2(forward.y, forward.x) * 180.0 / Constants.Math.Pi); if (yaw < 0.0) yaw += 360.0; pitch = (atan2(-forward.z, forward.Length2D()) * 180.0 / Constants.Math.Pi); if (pitch < 0.0) pitch += 360.0; } return QAngle(pitch, yaw, 0.0); } // Setting up bots tagged "swim" AddRobotTag("swim"{ // Called when the robot is spawned OnSpawn = function(bot, tag) { //bot.ValidateScriptScope() bot.GetScriptScope().time_last_attacked <- Time() SetPathManager(bot) //bot.SetGravity(20) //bot.SetMaxVisionRangeOverride(1000.0) AddThinkToEnt(bot, "swimThink") if(bombCarrier == null) AssignBombTargeters() // //custom weapon logic // if(bot.GetPlayerClass() == Constants.ETFClass.TF_CLASS_PYRO){ // for (local i = 0; i < 7; i++) // { // local weapon = NetProps.GetPropEntityArray(bot, "m_hMyWeapons", i); // // AN ERROR HAS OCCURRED [the index 'GetName' does not exist] // // CALLSTACK // // *FUNCTION [unknown()] sea_of_europa_logic.nut line [1018] // // *FUNCTION [PopulatorThink()] popextensions_hooks.nut line [179] // printl(weapon) // if (!weapon == null) printl(weapon.GetPrintName()) // if (!weapon == null) printl("subtype" + weapon.GetSubType()) // if (!weapon == null) printl("name" + weapon.GetName()) // if (!weapon == null) printl("target" + CBaseEntity.GetName.call(weapon)) // //Jellyfish Launcher // if (weapon == null || weapon.IsMeleeWeapon() || !weapon.GetName() == "The Jellyfish") // // continue; // // sadly it is not as simple as setting these props // // weps like the flamethrower check if the owner is underwater (getwaterlevel) // // in their PrimaryAttack() method // // NetProps.SetPropBool(weapon, "m_bFiresUnderwater", true); // // NetProps.SetPropBool(weapon, "m_bAltFiresUnderwater", true); // weapon.ValidateScriptScope(); // weapon.GetScriptScope().last_fire_time <- -1.0; // weapon.GetScriptScope().fire_delay <- 1.0; // AddThinkToEnt(weapon, "CheckWeaponFire"); // } // } //set custom tagged bot hooks here if(bot.HasBotTag("isSquid")) { local squidName = "squidscout" + bot.GetBotId().tostring() printl("SQUID SPAWN") bot.ValidateScriptScope() bot.GetScriptScope().time_last_ink <- Time() NetProps.SetPropString(bot, "m_iName", squidName) printl("Squid's name is" + bot.GetName()) local inkTrigger = SpawnEntityFromTable("trigger_multiple" { //targetname = "inkTrigger" origin = bot.GetOrigin() + Vector(0,0,40), parentname = squidName, filtername = "filter_redteam", StartDisabled = 0, spawnflags = 1, }) bot.GetScriptScope().inkTrigger <- inkTrigger bot.GetScriptScope().inkDelay <- 8 // secs between inking bot.GetScriptScope().lastInkTime <- Time() EntFireByHandle(inkTrigger, "SetParent", squidName, -1.0, null, null) inkTrigger.KeyValueFromInt("solid", 2) inkTrigger.KeyValueFromString("mins", "-500 -500 -500") inkTrigger.KeyValueFromString("maxs", "500 500 500") EntityOutputs.AddOutput(inkTrigger, "OnStartTouch", "!activator", "RunScriptCode", "if(!IsRobot(self))ApplyInkOverlay(self)", -1.0, -1) EntityOutputs.AddOutput(inkTrigger, "OnEndTouch", "!activator", "RunScriptCode", "printl(`out trigger`)", -1.0, -1) EntFireByHandle(inkTrigger, "Disable", null, -1.0, null, null) } } OnDeath = function(bot, params) { if(bombCarrier == null && isBombTargeter(bot)) { GetPathManager(bot).SetNewTarget(capPoint) ResetBotAction(bot) delete bombTargeters[bot] AssignBombTargeters() } NetProps.SetPropString(bot, "m_iName", "") if(bot.HasBotTag("isSquid")) { EntFireByHandle(bot.GetScriptScope().inkTrigger, "Kill", null, -1.0, null, null) } } OnTakeDamagePost = function(bot, params) { printl(" attacker: " + params.attacker + " bot: " + bot + " was attacked. last attack: " + bot.GetScriptScope().time_last_attacked + " time new attack: " + Time() ) PrintTable(params) bot.GetScriptScope().time_last_attacked <- Time() } }) ////////////////////////////// // Underwater/Swimming cond Setup and Util ////////////////////////////// //r_screenoverlay effects/jarate_overlay enum liquidOverlay { PISS = "effects/jarate_overlay", WATER = "effects/clearwater_warp" , //"effects/water_warp" NOTHING = "" }; enum liquidOverlayId { PISS = 0, WATER = 1, NOTHING = 2 } ::notInPiss <- {} // 0 or null - piss, 1 - water, 2 - nothing ::SetPissStatus <- function(player, overlayId) { local overlay = null switch (overlayId) { case 0: overlay = liquidOverlay.PISS break case 1: overlay = liquidOverlay.WATER break case 2: overlay = liquidOverlay.NOTHING break default: } notInPiss[player] <- overlay SetPissEffect(player) } ::SetPissEffect <- function(player) { if(player == null || !player.IsValid() || IsRobot(player) || pointClientCmd == null) return //SetPissStatus(player, 0) local overlay = notInPiss.rawin(player) ? notInPiss[player] : liquidOverlay.WATER //printl(pointClientCmd + " " + overlay + " " + player) //EntFireByHandle(pointClientCmd, "Command", "r_screenoverlay " + overlay, -1, player, player) player.SetScriptOverlayMaterial(overlay) EntFireByHandle(pointClientCmd, "Command", "r_screenoverlay clear", -1, player, player) EntFireByHandle(pointClientCmd, "Command", "firstperson", -1, player, player) EntFireByHandle(pointClientCmd, "Command", "firstperson", 1, player, player) EntFireByHandle(pointClientCmd, "Command", "firstperson", 5, player, player) //EntFireByHandle(pointClientCmd, "Command", "firstperson", 10, player, player) } // swim overlay uses "effects/jarate_overlay" by default // "effects/water_warp" is an alternative // How to make players swim except for engineer ::SetUnderwater <- function(player) { // engineers can build but can't pick up buildings underwater // we give them low grav instead if (player.GetPlayerClass() == Constants.ETFClass.TF_CLASS_ENGINEER ) { printl("setunderwater") player.SetGravity(0.8) //player.AddCond(Constants.ETFCond.TF_COND_SWIMMING_CURSE) } else if (IsRobot(player) && (player.GetName() == "Sentry Buster" || !player.HasBotTag("swim"))) return else { if(!player.InCond(Constants.ETFCond.TF_COND_SWIMMING_CURSE)) player.AddCond(Constants.ETFCond.TF_COND_SWIMMING_CURSE) } if(IsRobot(player)){ GetPathManager(player).UpdatePath() } else { SetPissEffect(player) } } ::SetNotUnderwater <- function(player) { // if(player == GetBomb) // { // this currently does not work // printl("BOMB RESET BOMB RESET BOMB RESET") // } player.RemoveCond(Constants.ETFCond.TF_COND_SWIMMING_CURSE) SetPissEffect(player) printl("setnotunderwater") } ::SetupPlayerPostSpawn <- function(player) { SetUnderwater(player) SetPissEffect(player) player.ValidateScriptScope(); player.GetScriptScope().buttons_last <- 0; player.GetScriptScope().water_down_accel <- 100.0; //arbitrary, I have no idea //for scout dash player.GetScriptScope().next_dash_time <- Time() player.GetScriptScope().dash_cooldown <- 0.5; player.GetScriptScope().jetpack_cooldown <- 0.9; player.GetScriptScope().clear_custom_overlay_time <- -1 NetProps.SetPropString(player, "m_iName", GetPlayerName(player)) player.GetScriptScope().breathTime <- Time() AddThinkToEnt(player, "PlayerThink"); } ::SetupLivingPlayers <- function() { // Set existing players to swimming for (local i = 1; i <= MaxPlayers ; i++) { local player = PlayerInstanceFromIndex(i) if (player == null || IsRobot(player)) continue //player.ForceRespawn() SetupPlayerPostSpawn(player) } } SetupLivingPlayers() ::ClearPlayerThinks <- function() { for (local i = 1; i <= MaxPlayers ; i++) { local player = PlayerInstanceFromIndex(i) if (player == null) continue NetProps.SetPropString(player, "m_iszScriptThinkFunction", ""); player.SetScriptOverlayMaterial("") } } ::PostPlayerSpawnSetPissEffect <- function() { // if it's a new player then init piss status if(!self.InCond(Constants.ETFCond.TF_COND_SWIMMING_CURSE)) SetUnderwater(self) printl("PostPlayerSpawnSetPissEffect") if(!notInPiss.rawin(self)) SetPissStatus(self, liquidOverlayId.WATER) //set default water colour here SetPissEffect(self) //self.AddCustomAttribute("no jump", 1, -1); } ::SwimUp <- function() { local vel = self.GetAbsVelocity() local maxSpeed = NetProps.GetPropFloat(self, "m_flMaxspeed") local buttons = NetProps.GetPropInt(self, "m_nButtons"); if (buttons & Constants.FButtons.IN_DUCK) return if(vel.z < maxSpeed) { self.ApplyAbsVelocityImpulse(Vector(0,0,20)) } } ::SwimDown <- function() { local vel = self.GetAbsVelocity() local maxSpeed = NetProps.GetPropFloat(self, "m_flMaxspeed") //if(self.IsSnared()) maxSpeed = maxSpeed* 0.4 // TODO: check if maxspeed property already accounts for snare and stun //adjust z speed ratio such that overal vel does not exceed max speed //yes this is probably not how acceleration works in the base game //and yes this negates air strafe, but I dunno how that works //if(vel.z > 100) return local buttons = NetProps.GetPropInt(self, "m_nButtons"); // if jump button is being pressed, do nothing if (buttons & Constants.FButtons.IN_JUMP) return // if player is on the ground, do nothing //printl(GetLocationBelow(self)) //if(abs(GetLocationBelow(self).z - self.GetOrigin().z) < 70) return if(GetIfBlockedHull(self, self.GetOrigin() - Vector(0,0,50))) return // local speedRatio = 0.8 // if(self.GetPlayerClass() == Constants.ETFClass.TF_CLASS_HEAVYWEAPONS) // //add downward accesl to z velocity until maxspeed is reached // if(vel.z > 0) vel.z = -10 //use this line with basevelocity // printl("maxspeed " + maxSpeed.tostring()) // vel.z = Clamp(vel.z - water_down_accel, -(maxSpeed*0.8), maxSpeed) // printl("clamped velz " + vel.z.tostring()) // local newSpeed = vel.Length() // local limitRatio = maxSpeed / newSpeed // if(newSpeed > maxSpeed) vel.z = vel.z*limitRatio //option 1 //vel.z = vel.z * (limitRatio) //self.SetAbsVelocity(vel) //option 2 // self.SetAbsVelocity(vel.Scale(limitRatio)) // printl("base vel " + self.GetBaseVelocity().tostring()) //seems to be reset to 0 each tick // self.__KeyValueFromVector("basevelocity",Vector(0,0,vel.z*0.15)) //option 4 dont use this one, u can get stuck in fence if u lower onto one //SetAbsOrigin //vel.Norm() //self.SetAbsOrigin(self.GetOrigin() + (vel*maxSpeed*FrameTime())) //smooth in firstperson but slow //looks weird in thirdperson, unknown how it looks to others if(vel.z > -100) { vel.z = -100 self.ApplyAbsVelocityImpulse(Vector(0,0,-5)) } vel.z = Clamp(vel.z - water_down_accel, -(maxSpeed*0.8), maxSpeed) self.SetAbsOrigin(self.GetOrigin() + (Vector(0,0,vel.z)*FrameTime())) } ::ParticleKillDelay <- function(delay) { EntFireByHandle( self, "Kill", "", 5, self, self ) } /** * @param {string} particle - A string param. * @param {string} origin - An optional param (Google Closure syntax) * @param {string} [angles] - Another optional param (JSDoc syntax). * @param {string} [parent] - An optional param with a default value * @param {float} [lifespan=-1] */ ::EmitParticle <- function(particle, origin, angles=Vector(0,0,0), parent=null, lifespan=-1) { //info particle system local particleSys = SpawnEntityFromTable("info_particle_system", { // This is a table of keyvalues, which is the same way as keyvalues that are defined in Hammer // Key Value effect_name = particle, origin = origin, start_active = true, angles = angles, //parentname = parent.GetName() }) if(parent != null)EntFireByHandle(particleSys, "SetParent", self.GetName(), -1, null, null) if(lifespan != -1) EntFireByHandle( particleSys, "Kill", "", lifespan, self, self ) //SetEntityColor(rocket, 250,250,250,0.6) //rocket.__KeyValueFromString("rendercolor", "0 0 255") } ::ManagePlayerStatus <- function() { local curTime = Time() if (clear_custom_overlay_time > 0 && curTime > clear_custom_overlay_time){ //self.SetScriptOverlayMaterial("") clear_custom_overlay_time = -1 // EntFire("inkOverlay", "StopOverlays", null,0,null); // EntFire("inkOverlay", "Kill",null,0.5,null); EntFireByHandle(pointClientCmd, "Command", "r_screenoverlay " + "clear", -1, self, self) } } // scout doublejump is essential for changing directions abruptly to dodge // this is not available underwater. Instead lets give scout a forward dash // activated on jump ::DashForward <- function() { local curTime = Time() if (curTime > next_dash_time){ local buttons = NetProps.GetPropInt(self, "m_nButtons") local maxSpeed = NetProps.GetPropFloat(self, "m_flMaxspeed") local dir = Vector(0,0,0) if (buttons & Constants.FButtons.IN_MOVELEFT) { dir = self.EyeAngles().Left() * -1 } else if (buttons & Constants.FButtons.IN_MOVERIGHT){ dir = self.EyeAngles().Left() } if (buttons & Constants.FButtons.IN_FORWARD) { dir = dir + self.EyeAngles().Forward() dir.Norm() } else if (buttons & Constants.FButtons.IN_BACK) { dir = dir + self.EyeAngles().Forward()*-1 dir.Norm() } self.ApplyAbsVelocityImpulse(dir*maxSpeed*2.5) //local vel = self.GetAbsVelocity() //vel.Norm() //self.ApplyAbsVelocityImpulse(vel*maxSpeed*2) //TODO: account for left and right keyboard input as well //self.ApplyAbsVelocityImpulse(self.GetAbsVelocity()*5) next_dash_time = curTime + dash_cooldown PlayClientOnlySound(self, "ultrakillDash.wav") //the following code is stupid and I don't know what im doing //DispatchParticleEffect("bday_1balloon", self.GetOrigin(), dir*-1) // only works origin is within visible range EmitParticle("lowV_water_bubbles", self.GetOrigin(), dir*-1, null,0.5) EmitParticle("unusual_bubbles", self.GetOrigin(), dir*-1, self,0.5) EmitParticle("unusual_bubbles", self.GetOrigin() + Vector(10,10,10), dir*-1, null,1.0) EmitParticle("unusual_bubbles", self.GetOrigin() + Vector(-10,-10,15), dir*-1, null,0.5) EmitParticle("unusual_bubbles", self.GetOrigin() + dir*-1*10, dir*-1, null,1.0) EmitParticle("unusual_bubbles", self.GetOrigin() + dir*-1*15, dir*-1, null,0.5) EmitParticle("unusual_bubbles", self.GetOrigin() + dir*-1*3, dir*-1, null,0.3) EmitParticle("unusual_bubbles", self.GetOrigin() + dir*-1*5, dir*-1, null,0.5) EmitParticle("unusual_bubbles", self.GetOrigin() + dir*20, dir*-1, null,1.0) //unusual_bubbles flamethrower_underwater } printl("dash") } ::PlayClientOnlySound <- function(player, soundname, vol = 0.5) { local soundtable = { sound_name = soundname, channel = 4, //CHAN_BODY 4 Impacts, ragdolls and footsteps volume = vol, entity = player, origin = player.GetOrigin(), filter_type = Constants.EScriptRecipientFilter.RECIPIENT_FILTER_SINGLE_PLAYER } EmitSoundEx(soundtable) } ::PlayerThink <- function() { if(Time() - self.GetScriptScope().breathTime > 3){ DispatchParticleEffect("duck_pickup_bubbles", self.EyePosition() + Vector(0,0,-2), self.EyeAngles().Forward()) //+ self.EyeAngles().Forward()*(-5) //DispatchParticleEffect("duck_pickup_bubbles", self.EyePosition() + self.EyeAngles().Forward()*(10) + Vector(0,0,-2), self.EyeAngles().Forward()) self.GetScriptScope().breathTime = Time() } local buttons = NetProps.GetPropInt(self, "m_nButtons"); local buttons_changed = buttons_last ^ buttons; local buttons_pressed = buttons_changed & buttons; local buttons_released = buttons_changed & (~buttons); if (buttons & Constants.FButtons.IN_DUCK) { if(self.GetPlayerClass() != Constants.ETFClass.TF_CLASS_ENGINEER) { SwimDown() } printl("duck pressed") } // I wanted heavy to be able to swim up when revved but it seems the jump button // is straight up disabled when revved // if(self.GetPlayerClass() == Constants.ETFClass.TF_CLASS_HEAVYWEAPONS) // { // if (buttons & Constants.FButtons.IN_JUMP) // { // printl("jump pressed") // SwimUp() // } // } if(self.GetPlayerClass() == Constants.ETFClass.TF_CLASS_SCOUT) { if (buttons_pressed & Constants.FButtons.IN_JUMP) { DashForward() printl("dash key pressed") } } if(self.GetPlayerClass() == Constants.ETFClass.TF_CLASS_PYRO) { local wep = self.GetActiveWeapon() local curTime = Time() local time_since_last = curTime - NetProps.GetPropFloat(wep, "m_flLastFireTime"); local dir = self.EyeAngles().Forward() // printl("printname" + wep.GetPrintName()) // if (wep != null) printl(NetProps.GetPropString(wep, "m_AttributeManager.m_Item.m_VarCharCustomName")) //m_VarCharCustomName ,m_szCustomName // if (wep != null) printl(NetProps.GetPropString(wep, "m_szCustomName")) // if (wep != null) printl(NetProps.GetPropString(wep, "m_iszName")) // if (wep != null) printl(NetProps.GetPropString(wep, "m_iName")) // if (wep != null) printl(NetProps.GetPropString(wep, "m_ModelName")) // if (wep != null) printl(NetProps.GetPropInt(wep, "m_AttributeManager.m_Item.m_iItemDefinitionIndex")) // if (wep != null) printl(NetProps.GetPropType(wep, "m_Attributes")) // if (wep != null) printl("subtype" + wep.GetSubType()) // if (wep != null) printl("name" + wep.GetName()) //tf_weapon_base local wepID = NetProps.GetPropInt(wep, "m_AttributeManager.m_Item.m_iItemDefinitionIndex") if(buttons & Constants.FButtons.IN_ATTACK) { local isShotgun = wep.GetName() == "tf_weapon_shotgun_pyro" if (isShotgun && wepID != 12 && wepID != 1153 && wepID != 415 && time_since_last < 0.05) //tf_weapon_shotgun_pyro && wep.Clip1()>0 { //printl(self.LookupBone("bip_hand_R").tostring()) //printl(wep.GetModelName()) local dir = self.EyeAngles().Forward() //EmitParticle("unusual_bubbles", self.GetOrigin() + vector(0,0,20) + dir*5, self.EyeAngles().Forward(), self, 0.1) EmitParticle("pyrovision_scorchshot_trail_red", self.EyePosition() + dir*40 + Vector(0,0,-5) , dir, self, 0.05) } } if(buttons_pressed & Constants.FButtons.IN_ATTACK){ if(wepID == 1179 && (curTime > next_dash_time))//&& time_since_last < 2.0 { printl("pootis") self.ApplyAbsVelocityImpulse(self.EyeAngles().Forward()*1000) next_dash_time = curTime + jetpack_cooldown } } } if(buttons & Constants.FButtons.IN_ATTACK2) { // local wep = self.GetActiveWeapon() // local dir = self.EyeAngles().Forward() // printl("printname" + wep.GetPrintName()) // if (wep != null) printl(NetProps.GetPropString(wep, "m_AttributeManager.m_Item.m_VarCharCustomName")) //m_VarCharCustomName ,m_szCustomName // if (wep != null) printl(NetProps.GetPropString(wep, "m_szCustomName")) // //__DumpScope(1, testTable); // //DumpObject(wep) // if (wep != null) printl("subtype" + wep.GetSubType()) // if (wep != null) printl("name" + wep.GetName()) // //m_iItemIDHigh // if (NetProps.GetPropInt(wep, "m_AttributeManager.m_Item.m_iItemDefinitionIndex") == 415) //tf_weapon_shotgun_pyro // { // //Clip1() // printl(self.LookupBone("bip_hand_R").tostring()) // printl(wep.GetModelName()) // local dir = self.EyeAngles().Forward() // //EmitParticle("unusual_bubbles", self.GetOrigin() + vector(0,0,20) + dir*5, self.EyeAngles().Forward(), self, 0.1) // EmitParticle("pyrovision_scorchshot_trail_red", self.EyePosition() + dir*40 + Vector(0,0,0), dir, self, 0.1) // } // // NetProps.SetPropBool(wep, "m_bFiresUnderwater", true); // // NetProps.SetPropBool(wep, "m_bAltFiresUnderwater", true); // // NetProps.SetPropBool(self, "m_bLagCompensation", false); // // //wep.PrimaryAttack(); // // NetProps.SetPropInt(self, "m_nWaterLevel", 0); // // NetProps.SetPropInt(wep, "m_nWaterLevel", 0); // // self.SetWaterLevel(0) // // //wep.SecondaryAttack(); // // wep.PrimaryAttack(); // // //NetProps.SetPropInt(self, "m_nWaterLevel", 3); // // // local owner = self // // // // preserve old charge meter and ammo count // // // local charge = NetProps.GetPropFloat(owner, "m_Shared.m_flItemChargeMeter"); // // // local ammo = NetProps.GetPropIntArray(owner, "m_iAmmo", 1); // // // // set up stuff needed to ensure the weapon always fires // // // NetProps.SetPropIntArray(owner, "m_iAmmo", 99, 1); // // // NetProps.SetPropFloat(owner, "m_Shared.m_flItemChargeMeter", 100.0); // // // NetProps.SetPropBool(owner, "m_bLagCompensation", false); // // // NetProps.SetPropInt(owner, "m_nWaterLevel", 0); // // // NetProps.SetPropFloat(FireballMaker, "m_flNextPrimaryAttack", 0); // // // NetProps.SetPropFloat(FireballMaker, "m_flNextSecondaryAttack", 0); // // // NetProps.SetPropBool(FireballMaker, "m_bFiresUnderwater", true); // // // NetProps.SetPropBool(FireballMaker, "m_bAltFiresUnderwater", true); // // // NetProps.SetPropEntity(FireballMaker, "m_hOwner", owner); // // // FireballMaker.PrimaryAttack(); // // // //FireballMaker.SecondaryAttack(); // // // //FireballMaker.FireAirBlast(1); // // // // revert changes // // // NetProps.SetPropBool(owner, "m_bLagCompensation", true); // // // NetProps.SetPropIntArray(owner, "m_iAmmo", ammo, 1); // // // NetProps.SetPropFloat(owner, "m_Shared.m_flItemChargeMeter", charge); // // // NetProps.SetPropInt(owner, "m_nWaterLevel", 3); } buttons_last = buttons; ManagePlayerStatus() return -1; } ///////////////////////////// // Custom Squid Logic ///////////////////////////// ::SquidThink <- function (){ local curTime = Time() if(curTime - lastInkTime > inkDelay) { printl("inktime") // local inkParticles = SpawnEntityFromTable("info_particle_system" // { // //targetname = "inkParticles" // origin = self.GetOrigin() + Vector(0,0,40), // parentname = self.GetName(), // effect_name = "mvm_hatch_destroy_smoke", //envelope_smoke_puff // start_active = true // }) // EntFireByHandle(inkParticles, "SetParent", self.GetName(), 0.0, null, null) // EntFireByHandle(inkParticles, "Kill", null, 2.0, null, null) EmitParticle("mvm_hatch_destroy_smoke", self.GetOrigin(), Vector(0,0,0), self, 6) EmitParticle("unusual_buble_smokebuble", self.GetOrigin(), Vector(0,0,0), self, 6) //mvm_tank_destroy_smokefront EmitParticle("mvm_tank_destroy_smoke", self.GetOrigin(), Vector(0,0,0), self, 6) EntFireByHandle(inkTrigger, "Enable", null, -1.0, null, null) EntFireByHandle(inkTrigger, "Disable", null, 5.5, null, null) lastInkTime = curTime } } ::ApplyInkOverlay <- function(player) { printl("inking " + player.tostring()+ GetPlayerName(player)) local curTime = Time() player.GetScriptScope().clear_custom_overlay_time <- curTime + 6 EntFireByHandle(pointClientCmd, "Command", "r_screenoverlay " + "effects/inksplat_overlay4", -1, player, player) } ////////////////////////////// // Custom Weapon Logic ////////////////////////////// //test function ::TakeDamage <- function(player) { player.SetHealth(1) } ::ShrinkMe <- function() { local baseScale = self.GetModelScale() if(baseScale > 0.1){ self.SetModelScale(0.1,0.0) } } ::CheckWeaponFire <- function() { // self is weapon //local fire_time = NetProps.GetPropFloat(self, "m_flLastFireTime"); //printl(fire_time.tostring() + " " + last_fire_time.tostring()) //self.AddAttribute("override projectile type", 2,-1) local curTime = Time() if (curTime - last_fire_time > fire_delay) { //printf("%f %s : Fired\n", Time(), self.GetClassname()); local owner = self.GetOwner(); local pos = owner.Weapon_ShootPosition() //tf_flame //tf_projectile_rocket //tf_projectile_energy_ball local rocket = SpawnEntityFromTable("tf_projectile_energy_ball", { // This is a table of keyvalues, which is the same way as keyvalues that are defined in Hammer // Key Value basevelocity = owner.EyeAngles().Forward()*100, teamnum = owner.GetTeam(), origin = pos + owner.EyeAngles().Forward()*100, angles = owner.EyeAngles() }) // replace self with desired weapon here: NetProps.SetPropEntity(rocket,"m_hOriginalLauncher",self) NetProps.SetPropEntity(rocket,"m_hLauncher",self) NetProps.SetPropEntity(rocket,"m_hOwnerEntity",self) //rocket.SetOwner(owner) // make it not collide with owner and give proper kill credits local baseScale = rocket.GetModelScale() //NetProps.SetPropVector(rocket,"m_vInitialVelocity",owner.EyeAngles().Forward()*1000) //rocket.SetAbsVelocity(owner.EyeAngles().Forward()*10) //printl(baseScale) //rocket.SetModelScale(0.1, 0.0) //AddThinkToEnt(rocket, "ShrinkMe") //EntityOutputs.AddOutput(rocket, "OnStartTouch", "!activator", "RunScriptCode", "TakeDamage(self)", 0, -1) //EntityOutputs.AddOutput(rocket, "OnUser1", "!activator", "RunScriptCode", "TakeDamage(self)", 0, -1) //EntityOutputs.AddOutput(rocket, "OnHit", "!activator", "RunScriptCode", "TakeDamage(self)", 0, -1) //rocket.SetModelScale(baseScale, 10000.0) // owner.SetWaterLevel(0) // self.SetWaterLevel(0) // self.PrimaryAttack() // owner.SetWaterLevel(3) // // preserve old charge meter and ammo count // local charge = NetProps.GetPropFloat(owner, "m_Shared.m_flItemChargeMeter"); // local ammo = NetProps.GetPropIntArray(owner, "m_iAmmo", 1); // // set up stuff needed to ensure the weapon always fires // NetProps.SetPropIntArray(owner, "m_iAmmo", 99, 1); // NetProps.SetPropFloat(owner, "m_Shared.m_flItemChargeMeter", 100.0); // NetProps.SetPropBool(owner, "m_bLagCompensation", false); // NetProps.SetPropFloat(FireballMaker, "m_flNextPrimaryAttack", 0); // NetProps.SetPropEntity(FireballMaker, "m_hOwner", owner); // FireballMaker.PrimaryAttack(); // // revert changes // NetProps.SetPropBool(owner, "m_bLagCompensation", true); // NetProps.SetPropIntArray(owner, "m_iAmmo", ammo, 1); // NetProps.SetPropFloat(owner, "m_Shared.m_flItemChargeMeter", charge); // if (owner) // { // owner.SetAbsVelocity(owner.GetAbsVelocity() - owner.EyeAngles().Forward() * 800.0); // } last_fire_time = Time(); } return -1; } ////////////////////////////// // Leviathan ////////////////////////////// ::LeviathanBotThink <- function(){ self.SetAbsOrigin(leviathan.GetOrigin()) } ::filter_red <- SpawnEntityFromTable("filter_activator_tfteam", { targetname = "filter_red", TeamNum = 2 }) ::Marker <- class { mrkLocation = null; mrkAngles = null; function constructor(loc, ang) { mrkLocation = Vector(loc.x, loc.y, loc.z); mrkAngles = QAngle(ang.x, ang.y, ang.z); } function getMrkAngles() { return QAngle(mrkAngles.x, mrkAngles.y, mrkAngles.z) } function getMrkLocation() { return mrkLocation } function printMarker(){ return "loc: " + getMrkLocation().tostring() + "; ang: " + getMrkAngles().tostring() } } enum LEV_ACTIONS { IDLE, CHASING, } ::LeviathanBrain <- class { leftHalf = null; rightHalf = null; core = null; prevSegment = null; brainName = null; target = null; prevOrigin = null; speed = null; isHead = null; maxHealth = null; botDmgTracker = null; timeToStart = null; curAction = null; lifetime = null; // squirrel integers are 32 bits long and signed // max int value is 2,147,483,647. 66 ticks per second at the most for TF2. 32537631 secs. 542293 mins. // plenty of room to store the lifetime in num of ticks markerList = null; locationsList = null; anglesList = null; isDead = null; function constructor(corebase, prev, brainNamep, botp, timeToStartp) { core = corebase; prevSegment = prev; brainName = brainNamep; target = null; prevOrigin = corebase.GetOrigin(); speed = 350; isHead = prev == null; maxHealth = core.GetHealth(); botDmgTracker = botp; timeToStart = timeToStartp; curAction = LEV_ACTIONS.IDLE; isDead = false; lifetime = 0; // squirrel integers are 32 bits long and signed // max int value is 2,147,483,647. 66 ticks per second at the most for TF2. 32537631 secs. 542293 mins. // plenty of room to store the lifetime in num of ticks markerList = {}; locationsList = {}; anglesList = {} updateMarkerList() // printl("params: " + core.tostring() + prev + brainName + botDmgTracker.tostring() + timeToStart.tostring()) // printl("prevseg " + prevSegment) // if(prevSegment != null) {printl("prevseg has brain" + prevSegment.GetScriptScope().brain.printName())} if (isHead) { //AddThinkToEnt(core, "LeviathanThink"); getNewTarget(); } else { // if body //AddThinkToEnt(core, "LeviathanWaitToLive"); } // The left and right 'halves' are currently just decorative. // TODO: add weapon mimics? leftHalf = CreateLeviathanChunk(brainName, "prop_dynamic","left", 0) leftHalf.SetAbsAngles(QAngle(0,90,0)) EntFireByHandle(leftHalf, "SetParent", brainName + "core", -1.0, null, null) EntFireByHandle(leftHalf, "DisableCollision", null, -1.0, null, null) AddThinkToEnt(leftHalf, "LeviathanSideThink"); rightHalf = CreateLeviathanChunk(brainName, "prop_dynamic","right", 0) rightHalf.SetAbsAngles(QAngle(0,270,0)) EntFireByHandle(rightHalf, "SetParent", brainName + "core", -1.0, null, null) EntFireByHandle(rightHalf, "DisableCollision", null, -1.0, null, null) AddThinkToEnt(rightHalf, "LeviathanSideThink"); local touchDamage = SpawnEntityFromTable("trigger_hurt", { targetname = "goodbye", filtername = "filter_red" //filter_redteam ? damage = 300, damagemodel = 1, //Doubling w/forgiveness nodmgforce = 0, damagetype = 256, origin = core.GetOrigin() - Vector(0,0,50), spawnflags = 4097, //Clients 1 + Disallow Bots 4096 startdisabled = 0, solid = 2 }) touchDamage.KeyValueFromInt("solid", 2) touchDamage.KeyValueFromString("mins", "-120 -120 -120") touchDamage.KeyValueFromString("maxs", "120 120 120") //DebugDrawLine(touchDamage.GetOrigin() + Vector(-100,-100,-100), touchDamage.GetOrigin() + Vector(100,100,100), 255, 255, 0, false, 10) EntFireByHandle(touchDamage, "SetParent", brainName + "core", -1.0, null, null) EntFireByHandle(touchDamage, "Enable", null, -1.0, null, null) //EntityOutputs.AddOutput(touchDamage, "OnStartTouch", "!activator", "RunScriptCode", "self.TakeDamageCustom(caller, caller, null, Vector(), Vector(), 100, 0, Constants.ETFDmgCustom.TF_DMG_CUSTOM_EYEBALL_ROCKET)",-1,1) printl("created brainname" + brainName) //printl("created brain" + tostring()) } function updateMarkerList(){ locationsList[lifetime] <- core.GetOrigin() anglesList[lifetime] <- core.GetAbsAngles() lifetime += 1 // local ang = core.GetAbsAngles() // local orig = core.GetOrigin() // printl("core for marker" + core) // //printl(" angles is null? " + (ang==null).tostring()) // printl(" angles" + ang) // printl(" angles x" + ang.x) // printl(" angles y" + ang.y) // printl(" angles z" + ang.z) // printl(" angles str" + QAngle(ang.x, ang.y, ang.z).tostring()) // printl(" core origin" +core.GetOrigin().tostring() ) // local newMarker = Marker(core.GetOrigin(), core.GetAbsAngles()) // printl("new marker " + newMarker.printMarker()) // markerList[lifetime] <- newMarker } function addToMarkerList(_loc, _ang){ // printl("adding marker" + _mark.tostring()) // markerList[lifetime] <- _mark locationsList[lifetime] <- _loc anglesList[lifetime] <- _ang lifetime += 1 } function tostring() { return "[brain entity: " + printName() + "]" } function printName() { return brainName; } // should only be used by head function getNewTarget(){ Entities.FindByClassnameNearest("player", core.GetOrigin(), 2000) local closest = null local closestDist = 999999; for (local i = 1; i <= MaxPlayers ; i++) { local player = PlayerInstanceFromIndex(i) if (player == null || IsRobot(player) || !IsPlayerAlive(player) ||player.GetPlayerClass() == Constants.ETFClass.TF_CLASS_SPY) continue //TODO: include visible spies local d = (player.GetOrigin() - core.GetOrigin()).Length() if( d < closestDist) { closest = player closestDist = d } } target = closest if (closest != null) { curAction = LEV_ACTIONS.CHASING } } function changeColorsByHealth(percentage){ local r = 0 local g = 114 * percentage local b = 255 * percentage EntFireByHandle(leftHalf, "Color", r.tostring() + " " + g.tostring() + " " + b.tostring(), -1.0, null, null) EntFireByHandle(rightHalf, "Color", r.tostring() + " " + g.tostring() + " " + b.tostring(), -1.0, null, null) EntFireByHandle(core, "Color", r.tostring() + " " + g.tostring() + " " + b.tostring(), -1.0, null, null) } function onTakeDamage(damageAmount, nDamageType, attacker){ printl("isdead: " + isDead.tostring()) if(isDead){ changeColorsByHealth(10/maxHealth) return } printl("make " + botDmgTracker.GetName() + " take damage " + damageAmount.tostring()) botDmgTracker.TakeDamage(damageAmount, nDamageType, attacker) botDmgTracker.TakeDamageCustom(core, core, null, Vector(), Vector(), damageAmount, 0, Constants.ETFDmgCustom.TF_DMG_CUSTOM_EYEBALL_ROCKET) changeColorsByHealth(core.GetHealth()/maxHealth) } function SetAsDead(){ isDead = true EntFireByHandle(core, "Alpha", "100", -1.0, null, null) EntFireByHandle(leftHalf, "Alpha", "10", -1.0, null, null) EntFireByHandle(rightHalf, "Alpha", "10", -1.0, null, null) EntFireByHandle(leftHalf, "Color", "0,0,0", -1.0, null, null) EntFireByHandle(rightHalf, "Color", "0,0,0", -1.0, null, null) EntFireByHandle(leftHalf, "TurnOff", null, -1.0, null, null) EntFireByHandle(rightHalf, "TurnOff", null, -1.0, null, null) NetProps.SetPropString(leftHalf, "m_iszScriptThinkFunction", ""); NetProps.SetPropString(rightHalf, "m_iszScriptThinkFunction", ""); } function onKilled(){ NetProps.SetPropString(core, "m_iszScriptThinkFunction", ""); NetProps.SetPropString(leftHalf, "m_iszScriptThinkFunction", ""); NetProps.SetPropString(rightHalf, "m_iszScriptThinkFunction", ""); EntFireByHandle(core, "Kill", null, -1.0, null, null) } //basically think function update(){ if (isHead) { moveAsHead() } else { moveAsBody() } lifetime += 1 } function moveAsBody(){ if(!prevSegment.GetScriptScope().brain.locationsList.rawin(lifetime) || !prevSegment.GetScriptScope().brain.anglesList.rawin(lifetime)) { printl("SOMETHING HAS GONE HORRIBLY WRONG 2 ELECTRIC BOOGALOO") return } // local prevmarker = prevSegment.GetScriptScope().brain.markerList[lifetime]; // core.SetAbsAngles(prevmarker.mangles); // core.SetAbsOrigin(prevmarker.mlocation); local prevLoc = prevSegment.GetScriptScope().brain.locationsList[lifetime]; local prevAng = prevSegment.GetScriptScope().brain.anglesList[lifetime]; if(prevLoc == null ||prevAng == null) {printl("SOMETHING HAS GONE HORRIBLY WRONG")} core.SetAbsAngles(prevAng); core.SetAbsOrigin(prevLoc); delete prevSegment.GetScriptScope().brain.locationsList[lifetime]; delete prevSegment.GetScriptScope().brain.anglesList[lifetime]; addToMarkerList(prevLoc, prevAng) //delete prevSegment.GetScriptScope().brain.markerList[lifetime] } function moveAsHead(){ local newOrigin = prevOrigin; switch (curAction) { case LEV_ACTIONS.CHASING: if(!IsPlayerAlive(target)) break //TODO local funny = sin(Time()) * 200 local funny2 = sin(Time()) * 360 local dir = (target.GetOrigin() + Vector(0,0, funny)) - prevOrigin dir.Norm() newOrigin = prevOrigin + dir*speed*FrameTime() + Vector(0,0, funny)*FrameTime() + Vector(funny,0, 0)*FrameTime() // local pitch = (asin(-dir.y) * 180) / PI; // local yaw = (atan2(dir.x, dir.z)* 180) / PI // //core.GetLocomotionInterface().FaceTowards(target.GetOrigin()) // //core.GetLocomotionInterface().FaceTowards(target.GetOrigin()) // printl("pitch: " + pitch.tostring() + " yaw: " + yaw.tostring()) // core.SetAbsAngles(QAngle(pitch, yaw, 0)) core.SetAbsAngles(VectorAngles(dir) ) //+ QAngle(0,0,funny2) //printl("angles" + (VectorAngles(dir)).tostring()) //printl("playereyeangles" + (target.EyeAngles()*-1).tostring()) //core.SetAbsAngles(Quaternion(dir.x, dir.y, dir.z, 1).ToQAngle()) //core.SetAbsAngles(target.EyeAngles()*-1) core.__KeyValueFromVector("basevelocity", Vector(0,0,0)) // DebugDrawCircle(self.GetOrigin(), Vector(0,0,150),50,20,true,3.0) // DebugDrawLine(self.GetOrigin(), self.GetOrigin() + self.GetAbsAngles().Forward()*100, 255, 255, 255, false, 1) break default: break } core.SetAbsOrigin(newOrigin) prevOrigin = newOrigin core.SetAbsVelocity(Vector(0,0,0)) //save marker to markerlist, index is lifetime updateMarkerList() } } ::VectorAngles <- function(forward) { local yaw, pitch; if ( forward.y == 0.0 && forward.x == 0.0 ) { yaw = 0.0; if (forward.z > 0.0) pitch = 270.0; else pitch = 90.0; } else { yaw = (atan2(forward.y, forward.x) * 180.0 / Constants.Math.Pi); if (yaw < 0.0) yaw += 360.0; pitch = (atan2(-forward.z, forward.Length2D()) * 180.0 / Constants.Math.Pi); if (pitch < 0.0) pitch += 360.0; } return QAngle(pitch, yaw, 0.0); } ::CreateLeviathanChunk <- function (name, entType, halfType, health){ local component = SpawnEntityFromTable(entType, { targetname = name + halfType model = "models/props_halloween/halloween_demoeye.mdl", skin = halfType == "core" ? 1 : 3, //3 solid = 0, sequence = "general_noise", DefaultAnim = "general_noise", health = health, origin = Vector(5043, 5236, 1488), angles = Vector(0,95,0), renderamt = 180, rendermode = 5, rendercolor = "0 114 255", damagefilter = "filter_red", StartDisabled = 0, modelScale = halfType == "core" ? 2 : 1.5, playbackrate = 1.0, gravity = 0.0, movetype = 4, speed = 0.0, effects = 4, TeamNum = 3 }) component.ValidateScriptScope() component.GetScriptScope().halfType <- halfType component.GetScriptScope().prevOrigin <- Vector(5043, 5236, 1488) return component } ::LeviathanSideThink <- function(){ local core = self.GetMoveParent() if(core == null || !core.GetAbsAngles) return -1 if(halfType == "left"){ local dir = core.GetAbsAngles() + QAngle(0,270,0) self.SetAbsAngles(dir) self.SetAbsOrigin(core.GetOrigin() + dir.Forward()*80) } else if(halfType == "right"){ local dir = core.GetAbsAngles() + QAngle(0,90,0) self.SetAbsAngles(dir) self.SetAbsOrigin(core.GetOrigin()+ dir.Forward()*80) } return FrameTime() } ::LeviathanWaitToLive <- function(){ if (Time() < brain.timeToStart) { brain.core.SetAbsOrigin(brain.prevOrigin) return -1 } else { NetProps.SetPropString(self, "m_iszScriptThinkFunction", ""); AddThinkToEnt(self, "LeviathanThink") return -1 } } ::LeviathanThink <- function(){ self.GetScriptScope().brain.update() return -1 } ::LeviathanBodyThink <- function(){ local newOrigin = prevOrigin + Vector(0,2,0) self.SetAbsOrigin(newOrigin) prevOrigin = newOrigin self.SetAbsVelocity(Vector(0,0,0)) self.__KeyValueFromVector("basevelocity", Vector(0,0,0)) DebugDrawCircle(self.GetOrigin(), Vector(0,0,150),50,20,true,3.0) DebugDrawLine(self.GetOrigin(), self.GetOrigin() + self.GetAbsAngles().Forward()*100, 255, 255, 255, false, 1) return -1 } // { // crit_type = 0 // damage_type = 538968064 // early_out = null // damage_bonus = 0 // damage_force = (vector : (-10739.796875, -21124.769531, -3725.761719) // damage_position = (vector : (4698.630371, 10492.385742, 575.253845) // damage = 60 // const_entity = ([618] base_boss: lev3core) // inflictor = ([1] player: Claudz) // force_friendly_fire = false // ammo_type = -1 // player_penetration_count = 0 // reported_position = (vector : (0.000000, 0.000000, 0.000000) // damaged_other_players = 0 // damage_bonus_provider = null // attacker = ([1] player: Claudz) // damage_custom = 0 // const_base_damage = 60 // damage_stats = 0 // max_damage = 6 // damage_for_force_calc = 0 // weapon = ([590] tf_weapon_soda_popper) // } //this is before damage amount has been finalized, and thus can be changed function OnScriptHook_OnTakeDamage(params) { local ent = params.const_entity; local inf = params.inflictor; // printl("OnScriptHook_OnTakeDamage:") // PrintTable(params) if (ent.GetClassname() == "base_boss" ) { printl("hit") ent.GetScriptScope().attackedBy <- params.inflictor } } // { // damageamount = 100 // health = 187 // weaponid = 76 // attacker_player = 48 // crit = 0 // entindex = 618 // } //this is after damage amount has been calculated but not yet applied function OnGameEvent_npc_hurt(params) { local ent = EntIndexToHScript(params.entindex); // printl(params.damageamount.tostring() + " health: " + ent.GetHealth().tostring()) // printl("OnGameEvent_npc_hurt:") // PrintTable(params) // local ent = EntIndexToHScript(params.entindex); // if (HasBotScript(ent)) // { // // Check if a bot is about to die printl("hurt npc: " + ent.GetName()) if ((ent.GetHealth() - params.damageamount) <= 0) { ent.GetScriptScope().brain.SetAsDead() // Run the bot's OnKilled function ent.SetHealth(9999) //EntFireByHandle(ent, "Alpha", "100", -1.0, null, null) //ent.GetScriptScope().brain.changeColorsByHealth(0.1) //EntFireByHandle(ent, "Color", "255 255 255", -1.0, null, null) } ent.GetScriptScope().brain.onTakeDamage(params.damageamount, 0, ent.GetScriptScope().attackedBy) // } } ::listOfLeviathanChunks <- {} ::ResetLeviathan <- function() { foreach (k,v in listOfLeviathanChunks) { v.GetScriptScope().brain.onKilled() } listOfLeviathanChunks.clear() } // ::NUM_OF_SEGMENTS <- 10 // ::HEALTH_PER_SEGMENT <- 2000 // ::HEALTH_OF_HEAD <- 10000 // total health is currently 30k const NUM_OF_SEGMENTS = 30 //15 const HEALTH_PER_SEGMENT = 20 const HEALTH_OF_HEAD = 300 // use for testing. total is 600 // TODO: split leviathan in half when health falls below 50%... const SEGMENT_DELAY = 0.3 AddRobotTag("leviathan", { // Called when the robot is spawned OnSpawn = function(bot, tag) { local botName = "leviathan_core" NetProps.SetPropString(bot, "m_iName", botName) //AddThinkToEnt(bot, "LeviathanSideThink"); bot.ValidateScriptScope() printl("leviathan bot spawned") // Create all the segments of the leviathan local prev = null; local healthToSet = 0; local timeToStart = 0; for (local index = 0 ; index < NUM_OF_SEGMENTS ; index = index + 1) { printl("spawning segment") if (index == 0) { prev = null; healthToSet = HEALTH_OF_HEAD; } else { prev = listOfLeviathanChunks[index-1] healthToSet = HEALTH_PER_SEGMENT; timeToStart = Time() + index*SEGMENT_DELAY; } local corename = "lev" + bot.GetBotId().tostring() + "_" + index.tostring() local core = CreateLeviathanChunk(corename, "base_boss", "core", healthToSet) core.ValidateScriptScope() local newBrain = LeviathanBrain(core, prev, corename, bot, timeToStart) // listOfLeviathanBrains[corename + "core"] <- newBrain core.GetScriptScope().brain <- LeviathanBrain(core, prev, corename, bot, timeToStart); printl("made brain for " + corename + " " + core.GetScriptScope().brain.tostring() + "; index:" + index.tostring()) listOfLeviathanChunks[index] <- core; if(prev != null){ printl("fucking brains man " + listOfLeviathanChunks[index-1].GetScriptScope().brain.tostring())} if (index == 0) { AddThinkToEnt(core, "LeviathanThink"); } else { // if body AddThinkToEnt(core, "LeviathanWaitToLive"); } } bot.GetScriptScope().leviathan <- listOfLeviathanChunks[0] }, // Called when the robot is killed // Params as in https://wiki.alliedmods.net/Team_Fortress_2_Events#mvm_tank_destroyed_by_players:~:text=they%20changed%20to-,player_death,-Note%3A%20When // Params may be null if the bot was forcefully changed to spectator team OnDeath = function(bot, params) { NetProps.SetPropString(bot, "m_iName", "") ResetLeviathan() //EntFireByHandle(leviathanHead, "Kill", null, -1, null, null) }, // Called when the robot takes damage // Non const prefixed params when altered will affect the damage taken // Params as in https://developer.valvesoftware.com/wiki/List_of_TF2_Script_Functions#:~:text=Description-,const_entity,-handle OnTakeDamage = function(bot, params) { printl(bot.GetName() + " has taken damage") }, // Called when the robot takes damage, after the damage is dealt // Params as in https://wiki.alliedmods.net/Team_Fortress_2_Events#player_hurt:~:text=type-,player_hurt,-Note%3A%20When OnTakeDamagePost = function(bot, params) { } }) ////////////////////////////// // Misc Hooks ////////////////////////////// ::setupHooks <- function() { // Set up point_clientcommand to send console commands to client pointClientCmd <- SpawnEntityFromTable("point_clientcommand", { name = "clientcommander" }) infoMsg <- "Welcome to Mann Co's off-world undersea base" infoMsg = infoMsg + "\n - Items that don't work underwater are blacklisted" infoMsg = infoMsg + "\n (food, liquids, throwables, fire, etc.)" infoMsg = infoMsg + "\n - Engi can't swim. Scout can jet boost" infoMsg = infoMsg + "\n - Soldier and Demo get explosive propulsion." infoMsg2 <-" - Crouch to swim down quickly" infoMsg2 = infoMsg2 + "\n - dsp_water 0 in console to disable muffled sound if you'd like" infoMsg2 = infoMsg2 + "\n dsp_water 14 to restore" //4724 9944 739 //-4 -90 0 ::infoSign <- SpawnEntityFromTable("point_worldtext", { targetname = "water_info_sign", //origin = Vector(4724, 9960, 739), origin = Vector(4870, 9960, 770), angles = Vector(0, 270, 0), message = infoMsg, font = 1, orientation = 0, textspacingy = 5, textsize = 8 }) ::infoSign2 <- SpawnEntityFromTable("point_worldtext", { targetname = "water_info_sign2", //origin = Vector(4724, 9960, 739), origin = Vector(4870, 9960, 725), angles = Vector(0, 270, 0), message = infoMsg2, font = 1, orientation = 0, textspacingy = 5, textsize = 8 }) function OnGameEvent_player_spawn(params) { local player = GetPlayerFromUserID(params.userid); if (player != null) { printl("SPAWNING " + GetPlayerName(player) + " isrobot " + IsRobot(player)) } else return // Give human players swimming on spawn if (!IsRobot(player)) { SetupPlayerPostSpawn(player) EntFireByHandle(player, "CallScriptFunction", "PostPlayerSpawnSetPissEffect", -1, null, null); EntFireByHandle(player, "CallScriptFunction", "PostPlayerSpawnSetPissEffect", 1, null, null); EntFireByHandle(player, "CallScriptFunction", "PostPlayerSpawnSetPissEffect", 2, null, null); EntFireByHandle(player, "CallScriptFunction", "SetPissEffect(self)", 2, null, null); // params.team equals 0 on player's first spawn apparently // if(params.team == 0){ //} } else { player.ValidateScriptScope() player.GetScriptScope().time_last_attacked <- Time() } } // Keep underwater effect when players die function OnGameEvent_player_death(params) { printl("player died") local player = GetPlayerFromUserID(params.userid); if (player != null && !IsRobot(player)) { SetPissEffect(player) } } function OnGameEvent_teamplay_flag_event(params) { if (params.eventtype == 1) { OnBombPickup(PlayerInstanceFromIndex(params.player)) } else if (params.eventtype == 4) { OnBombDrop(params) } } function OnGameEvent_rocket_jump(params){ printl("rocket jump") local player = GetPlayerFromUserID(params.userid); if (player != null && !IsRobot(player) && IsPlayerAlive(player)) { local dist = (player.GetOrigin() - GetLocationFromLookSurface(player)).Length() printl(dist) local scaleFactor = 600 - dist*2 local heightFactor = 250 - dist //printl(player.GetAbsAngles()) //printl(player.EyeAngles()) player.ApplyAbsVelocityImpulse(player.EyeAngles().Forward()*-scaleFactor + Vector(0,0,heightFactor)) //(-45,10,0) } } function OnGameEvent_sticky_jump(params){ printl("sticky_jump") } function OnGameEvent_player_hurt(params){ //__DumpScope(1, params); if(params.userid == params.attacker){ local player = GetPlayerFromUserID(params.userid); // give demo explosions a little more umph for demo if (player != null && !IsRobot(player) && IsPlayerAlive(player) && player.GetPlayerClass() == Constants.ETFClass.TF_CLASS_DEMOMAN) { local vel =player.GetAbsVelocity().Length() local scaleFactor = (sqrt(vel) -5)/10 printl(player.GetAbsVelocity().Length()) player.ApplyAbsVelocityImpulse(player.GetAbsVelocity()*scaleFactor) } } } // If we don't set sentries to be underwater, they cant target the bots function OnGameEvent_sentry_on_go_active(params) { printl("SENTRY ACTIVE") for (local sentry; sentry = Entities.FindByClassname(sentry, "obj_sentrygun");) { sentry.SetWaterLevel(3) } } function OnGameEvent_player_builtobject(params) { //__DumpScope(1, params); if(params.object == 1) { local player = GetPlayerFromUserID(params.userid); printl("teleporter built") for (local tele; tele = Entities.FindByClassname(tele, "obj_teleporter"); ) { // 1 entrance; 2 exit local teleName = GetPlayerName(player)+"tele" + NetProps.GetPropInt(tele,"m_iTeleportType").tostring() NetProps.SetPropString(tele, "m_iName", teleName) local triggerName = "teleTrigger_" + teleName local teleTrigger = Entities.FindByName(null, triggerName) if(teleTrigger == null){ printl("create new tele trigger") teleTrigger = SpawnEntityFromTable("trigger_multiple" { // This is a table of keyvalues, which is the same way as keyvalues that are defined in Hammer // Key Value targetname = triggerName, wait = 50.0, origin = tele.GetOrigin() + Vector(0,0,20), parentname = teleName, filtername = "filter_redteam", StartDisabled = 0, spawnflags = 1 }) EntFireByHandle(teleTrigger, "SetParent", teleName, 0.0, null, null) teleTrigger.KeyValueFromInt("solid", 2) teleTrigger.KeyValueFromString("mins", "-24 -24 -24") teleTrigger.KeyValueFromString("maxs", "24 24 24") teleTrigger.ValidateScriptScope() teleTrigger.GetScriptScope().teleporter <- tele EntityOutputs.AddOutput(teleTrigger, "OnStartTouch", "!activator", "RunScriptCode", "SetWaterIfTeleReady(self, false)", -1.0, -1) EntityOutputs.AddOutput(teleTrigger, "OnEndTouch", "!activator", "RunScriptCode", "SetWaterIfTeleReady(self, true)", -1.0, -1) EntityOutputs.AddOutput(tele, "OnDestroyed", "!activator", "RunScriptCode", "EntFire("+ triggerName + ", `Kill`)", -1.0, -1) } } } } ::SetWaterIfTeleReady <- function(player, setUnderwater = true) { // printl("caller:" + caller) // printl("activator:" + activator) // printl("caller tele:" + caller.GetScriptScope().teleporter) local tele = caller.GetScriptScope().teleporter // printl("state:" + NetProps.GetPropInt(tele, "m_iState").tostring()) if(setUnderwater) { SetUnderwater(player) return } // only set not underwater when the tele is ready // TELEPORTER_STATE_READY 2 if (NetProps.GetPropInt(tele, "m_iState") == 2) { if(!setUnderwater) SetNotUnderwater(player) } } function OnGameEvent_player_carryobject(params) { // __DumpScope(1, params); if(params.object == 1) { local player = GetPlayerFromUserID(params.userid); printl("teleporter carried") for (local tele; tele = Entities.FindByClassname(tele, "obj_teleporter"); ) { local teleName = GetPlayerName(player)+"tele" + NetProps.GetPropInt(tele,"m_iTeleportType").tostring() local triggerName = "teleTrigger_" + teleName local teleTrigger = Entities.FindByName(null, triggerName) if(!teleTrigger == null){ EntFire( triggerName, "Disable") } } } } function OnGameEvent_player_dropobject(params) { // __DumpScope(1, params); if(params.object == 1) { local player = GetPlayerFromUserID(params.userid); printl("teleporter dropped") for (local tele; tele = Entities.FindByClassname(tele, "obj_teleporter"); ) { local teleName = GetPlayerName(player)+"tele" + NetProps.GetPropInt(tele,"m_iTeleportType").tostring() local triggerName = "teleTrigger_" + teleName local teleTrigger = Entities.FindByName(null, triggerName) if(!teleTrigger == null){ EntFire( triggerName, "Enable") } } } } // local objRes = Entities.FindByClassname(null, "tf_objective_resource") // if (objRes) // { // NetProps.SetPropString(objRes, "m_iszMvMPopfileName", "ADV Sea of Europa") // } function OnGameEvent_mvm_wave_failed(params){ //SetupLivingPlayers() printl("WAVE FAILED EVENT") //ClearGameEventCallbacks(); //ClearPlayerThinks(); //printl("root table at wave fail:") //PrintRootTable() ClearGameEventCallbacks(); ClearPlayerThinks(); ResetLeviathan(); this = {}; EntFireByHandle(infoSign, "Kill", null, -1.0, null, null) EntFireByHandle(infoSign2, "Kill", null, -1.0, null, null) } function OnGameEvent_mvm_wave_complete(params){ //SetupLivingPlayers() printl("WAVE COMPLETE EVENT") // ClearGameEventCallbacks(); // ClearPlayerThinks(); // this = {}; } function OnGameEvent_mvm_begin_wave(params){ printl("WAVE BEGIN EVENT") } function OnGameEvent_mvm_mission_complete(params){ printl("MVM MISSION COMPLETE EVENT") ClearGameEventCallbacks(); ClearPlayerThinks(); ResetLeviathan(); this = {}; EntFireByHandle(infoSign, "Kill", null, -1.0, null, null) EntFireByHandle(infoSign2, "Kill", null, -1.0, null, null) } function OnGameEvent_mvm_mission_update(params){ printl("MISSION UPDATE EVENT") } function OnGameEvent_mvm_reset_stats(params){ printl("MVM RESET STATS EVENT") // ClearGameEventCallbacks(); // ClearPlayerThinks(); // this = {}; } function OnGameEvent_teamplay_round_selected(params){ printl("ROUND SELECTED EVENT") } function OnGameEvent_teamplay_restart_round(params){ printl("ROUND RESTART EVENT") } function OnGameEvent_teamplay_round_start(params){ SetSkyboxTexture("marssky") printl("ROUND START EVENT") ClearGameEventCallbacks(); ClearPlayerThinks(); ResetLeviathan(); this = {}; EntFireByHandle(infoSign, "Kill", null, -1.0, null, null) EntFireByHandle(infoSign2, "Kill", null, -1.0, null, null) } __CollectGameEventCallbacks(this) } setupHooks() __CollectGameEventCallbacks(this)