local BASE_HEIGHT = 80 local PAIR_FLAGS = { ["no-attack"] = "no-attack", ["no-attack-ubered"] = "no-attack-ubered", } local waitingPairBots = { Carriers = {}, Carried = {}, } local function canAttack(flag) return flag ~= PAIR_FLAGS["no-attack"] and flag ~= PAIR_FLAGS["no-attack-ubered"] end function PairBots(carrier, carried, pairFlag) local height = BASE_HEIGHT * carrier.m_flModelScale local carrierCallbacks = {} local carrierTimer local noTeleportForThisTick = 0 local lastOrigin local function teleport() if not IsValid(carrier) then -- prevents carried briefly disappearing after carrier death if not lastOrigin then return end if not IsValid(carried) or not carried:IsAlive() then return end carried:SetAbsOrigin(lastOrigin + Vector(0, 0, height)) return end local origin = carrier:GetAbsOrigin() if noTeleportForThisTick <= 0 then carried:SetAbsOrigin(origin + Vector(0, 0, height)) else noTeleportForThisTick = noTeleportForThisTick - 1 end lastOrigin = origin end local exitedSpawn = false local died = false timer.Simple(1, function() if died then return end carrierTimer = timer.Create(0, function() teleport() if pairFlag ~= PAIR_FLAGS["no-attack-ubered"] then if not carrier:InCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) then carried:RemoveCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) -- (royal): when bot exits spawn, we make the carrier bot land on the ground for 1 frame so that its TFBot thinks it's out of spawn & can now attack if not exitedSpawn then exitedSpawn = true noTeleportForThisTick = 1 carried:SetAbsOrigin(carrier:GetAbsOrigin()) end end end end, 0) end) teleport() local function endPairLogic() for _, id in pairs(carrierCallbacks) do carrier:RemoveCallback(id) end if carrierTimer then timer.Stop(carrierTimer) end died = true end carrierCallbacks.died = carrier:AddCallback(ON_DEATH, function() carried:RemoveCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) if not canAttack(pairFlag) then timer.Simple(2, function() carried:SetAttributeValue("no_attack", nil) end) end endPairLogic() if not lastOrigin then return end -- suspend in place to prevent source jank from teleporting it back to spawn local iterated = 1 for _ = 0, 1, 0.1 do timer.Simple(0.07 * iterated, function() carried:SetAbsOrigin(lastOrigin) end) iterated = iterated + 1 end end) carrierCallbacks.spawn = carrier:AddCallback(ON_SPAWN, function() endPairLogic() end) end local function waveInit(wave) waitingPairBots = { Carriers = {}, Carried = {}, } end if OnWaveInitFuncs then table.insert(OnWaveInitFuncs, waveInit) else function OnWaveInit(wave) waveInit(wave) end end local function waveSpawnBot(bot, wave, tags) for _, tag in pairs(tags) do local split = {} for k, _ in string.gmatch(tag, "([^_]+)") do table.insert(split, k) end if split[1]:lower() == "pair" then local pairName = split[2] local pairPlacement = split[3]:lower() local flag = split[4] and split[4]:lower() local waitTable local otherWaitTable if pairPlacement == "carried" then waitTable = waitingPairBots.Carried otherWaitTable = waitingPairBots.Carriers if not canAttack(flag) then timer.Simple(1, function() bot:SetAttributeValue("no_attack", 1) end) end if flag == PAIR_FLAGS["no-attack-ubered"] then bot:AddCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) end elseif pairPlacement == "carrier" then waitTable = waitingPairBots.Carriers otherWaitTable = waitingPairBots.Carried else print("invalid carrier placement tag") end if not waitTable[pairName] then waitTable[pairName] = {} end table.insert(waitTable[pairName], bot) if otherWaitTable[pairName] and otherWaitTable[pairName][1] then PairBots(waitingPairBots.Carriers[pairName][1], waitingPairBots.Carried[pairName][1], flag) waitingPairBots.Carriers[pairName] = nil waitingPairBots.Carried[pairName] = nil end end end end if OnWaveSpawnBotFuncs then table.insert(OnWaveSpawnBotFuncs, waveSpawnBot) else function OnWaveSpawnBot(wave) waveSpawnBot(wave) end end