local bridge_touchlist = {{}, {}, {}, {}}; local tcp_master = nil; precache.PrecacheSound("mega_mob_incoming.wav"); -- make function to figure out which direction (+ or -) to go shortest distance from one ang to another local player_tp_positions = { Vector(4192, 2432, 1120), Vector(4192, 2496, 1120), Vector(4192, 2560, 1120), Vector(4192, 2624, 1120), Vector(4192, 2688, 1120), Vector(4192, 2752, 1120), }; -- onwavespawnbot tag scrapbot gets a flag when they spawn -- when bot dies, flag team is changed? function PlayIntroCinematic() local pos = 1; local camera = nil; for userid, playerdata in pairs(player_list) do if (pos > 6) then pos = 1; end -- On the off chance there's more than 6 people on RED for some reason local player = ents.GetPlayerByUserId(userid); if (not IsValid(player) or player.m_iTeamNum == TEAM_SPECTATOR or not player:IsAlive()) then goto continue; end player:RunScriptCode("ScreenFade(self, 20, 20, 20, 255, 0.5, 0.5, 0.25)"); player:SetScriptOverlayMaterial(""); timer.Create(0.03, function() player:Teleport(player_tp_positions[pos], NULL_VECTOR, NULL_VECTOR); end, 1); timer.Create(2, function() if (not IsValid(camera)) then return; end player:SetScriptOverlayMaterial(""); player:ShowHudDialogue("Assembled here are the last mercenaries; beaten and weary, they expended their ammo long ago", CLR_BLUE, nil, 0.035, 0.125, 3, 0.3, 4); end, 1); timer.Create(10, function() if (not IsValid(camera)) then return; end player:ShowHudDialogue("Defending against the relentless infected hordes of hell, they have been trapped in this castle for months", CLR_BLUE, nil, 0.035, 0.125, 3.4, 0.3, 4); end, 1); timer.Create(18, function() if (not IsValid(camera)) then return; end player:ShowHudDialogue("Now they must fight once again with only limited weaponry at their disposal...", CLR_RED, nil, 0.035, 0.125, 2.6, 0.3, 4); end, 1); timer.Create(22, function() if (not IsValid(camera)) then return; end player:PlaySoundToSelf("mega_mob_incoming.wav"); timer.Create(2, function() player:SnapEyeAngles(NULL_VECTOR) end, 1); timer.Create(4, function() local annotation = ents.CreateWithKeys("training_annotation", { display_text = "Fight for your lives, the horde approaches!", lifetime = 4, }); annotation:SetAbsOrigin(Vector(6728, 2752, 808)); annotation:Show(); timer.Create(4, function() pcall(CEntity.Remove, annotation); end, 1) end, 1); end, 1); pos = pos + 1; ::continue:: end timer.Create(0.05, function() local keyframes = { { 0.0, Vector(4300, 2375, 1150), Vector(-15, 150, 0) }, { 8.0, Vector(4300, 2750, 1150), Vector(-15, -150, 0) }, { 2.0, Vector(3500, 2550, 1450), Vector(0, 0, 0), true }, { 14.0, Vector(5750, 2550, 1450), Vector(20, 0, 0) }, }; camera = AnimatedViewControlAll(keyframes, false, false, 0); end, 1); end function Clamp360Angle(ang) if (not ang or not ang.x or not ang.y or not ang.z) then return; end local t = {ang.x, ang.y, ang.z}; -- Clamp for index, angle in pairs(t) do if (angle > 360 or angle < -360) then print(angle); local x = math.abs(angle / 360); t[index] = (x - math.floor(x)) * 360; end end -- Abs for index, angle in pairs(t) do if (angle < 0) then t[index] = 360 - math.abs(angle); end end return Vector(table.unpack(t)); end -- NOTE: hide_hud will prevent taunting while under viewcontrol! (Don't ask me why the game does this...) function AnimatedViewControlAll(keyframes, player_can_move, hide_hud, hold_time) if (GLOBAL_VIEWCONTROL_ACTIVE) then return -1; end local camera; local should_enable_timer = false; function CleanupCamera() for userid, playerdata in pairs(player_list) do local player = ents.GetPlayerByUserId(userid); player._lifestate = player.m_lifeState; if (player._lifestate ~= 0) then player.m_lifeState = 0; end player._camera = nil; player:SetHUDVisibility(true); player:RunScriptCode("!self.SetForceLocalDraw(false);"); player:SetAttributeValue("ignored by bots", 0); player:PlaySoundToSelf("replay/exitperformancemode.wav"); player:RunScriptCode("ScreenFade(self, 20, 20, 20, 255, 0.5, 0.5, 1)"); player:SetAttributeValue("move speed bonus", player.__movespeed or 1); player.__movespeed = nil; player:RemoveCond(TF_COND_TAUNTING); end if (IsValid(camera)) then camera:DisableAll(); camera:Remove(); end for userid, playerdata in pairs(player_list) do local player = ents.GetPlayerByUserId(userid); player.m_lifeState = player._lifestate; player._lifestate = nil; end if (should_enable_timer) then LOSE_TIMER_ENABLED = true; end UnpauseWave(); TF_GAMERULES.m_bShowMatchSummary = false; GLOBAL_VIEWCONTROL_ACTIVE = false; end if (not keyframes or #keyframes < 1 or #keyframes[1] < 3) then return; end -- Setup varibles local start_time = CurTime(); local prev_origin = keyframes[1][2]; local prev_angles = keyframes[1][3]; -- Setup camera camera = ents.CreateWithKeys("point_viewcontrol", {["spawnflags"] = 8}); if (not IsValid(camera)) then return; end camera:SetAbsOrigin(prev_origin); camera:SetAbsAngles(prev_angles); -- We wait a short amount to allow player to acquire TF_COND_TAUNTING -- if they started taunting right as this function was called (so we can remove it) timer.Create(0.1, function() if (not IsValid(camera)) then return; end for userid, playerdata in pairs(player_list) do local player = ents.GetPlayerByUserId(userid); player._camera = camera; -- Setup visiblity if (hide_hud) then player:SetHUDVisibility(false); end player:RunScriptCode("!self.SetForceLocalDraw(true);"); player:SetAttributeValue("ignored by bots", 1); -- Hack: Our weapon isn't visible initially when we use viewcontrol, -- swapping to melee is a non destructive way to fix this (taunting and regenerating also work, but not ideal) local wep = player:GiveItem("tf_weapon_scattergun"); player:WeaponSwitchSlot(LOADOUT_POSITION_PRIMARY); timer.Create(0.1, function() if (not IsValid(player)) then return; end player:WeaponSwitchSlot(LOADOUT_POSITION_MELEE); if (IsValid(wep)) then player:RemoveItem(wep:GetItemName()); end end, 1); -- Effects player:PlaySoundToSelf("replay/enterperformancemode.wav"); player:RunScriptCode("ScreenFade(self, 20, 20, 20, 255, 0.5, 0.5, 1)"); if (not player_can_move) then player.__movespeed = player:GetAttributeValue("move speed bonus") or 1; player:SetAttributeValue("move speed bonus", 0.01); player.__position = player:GetAbsOrigin(); end end PauseWave(); -- Stop bot AI and spawning camera:EnableAll(); -- Enable camera for this player if (LOSE_TIMER_ENABLED) then LOSE_TIMER_ENABLED = false; should_enable_timer = true; end TF_GAMERULES.m_bShowMatchSummary = true; -- Hack to make it so taunting doesn't fuck up the camera GLOBAL_VIEWCONTROL_ACTIVE = true; local keyframe = 2; local think_timer = nil; local babyjail_timer = nil; hold_time = hold_time or 5; if (#keyframes == 1) then -- We've reached the end, hold if necessary and clean up timer.Create(hold_time, function() CleanupCamera(); end, 1); return; end -- Update camera origin and angles every frame linearly towards keyframe values think_timer = timer.Create(0.015, function() -- Stop thinking once we finish going through keyframes if (not IsValid(camera) or keyframe > #keyframes) then pcall(timer.Stop, think_timer); think_timer = nil; if (keyframe > #keyframes) then -- We've reached the end, hold if necessary and clean up timer.Create(hold_time, function() CleanupCamera(); end, 1); end return; end -- Ignore malformed Keyframes if (#keyframes[keyframe] < 3) then keyframe = keyframe + 1; return; end local current_time = CurTime(); local next_time, next_origin, next_angles, should_teleport = table.unpack(keyframes[keyframe]); next_angles = Clamp360Angle(next_angles); -- For some reason, this timer only executes about half (~0.57) of the calculated iterations local ticks_left = math.floor((start_time + next_time - current_time) / 0.015 * 0.57); if (should_teleport) then if (ticks_left > 0) then goto continue; else camera:SetAbsOrigin(next_origin); camera:SetAbsAngles(next_angles); end end -- We're done with this keyframe if (ticks_left <= 0) then keyframe = keyframe + 1; start_time = CurTime(); prev_origin = next_origin; prev_angles = next_angles; return; end -- Increment our position and angles based on the total time the keyframe should take local pos_vector = next_origin - camera:GetAbsOrigin(); local ang = Clamp360Angle(camera:GetAbsAngles()); local ang_vector = Vector(next_angles.x - ang.x, next_angles.y - ang.y, next_angles.z - ang.z); local incr_pos = pos_vector / ticks_left; local incr_ang = Vector(ang_vector.x / ticks_left, ang_vector.y / ticks_left, ang_vector.z / ticks_left); camera:SetAbsOrigin(camera:GetAbsOrigin() + incr_pos); camera:SetAbsAngles(camera:GetAbsAngles() + incr_ang); ::continue:: end, 0); -- This is so ghetto dude babyjail_timer = timer.Create(0.015, function() -- Stop thinking if (not IsValid(camera)) then pcall(timer.Stop, babyjail_timer); babyjail_timer = nil; return; end -- Prevent players from bypassing move speed penalty with taunts like conga if (not player_can_move) then for userid, playerdata in pairs(player_list) do local player = ents.GetPlayerByUserId(userid); -- If we're moving normally, allow movement on z axis for jumps if (player.movetype == MOVETYPE_WALK) then player:SetAbsOrigin(Vector(player.__position.x, player.__position.y, player:GetAbsOrigin().z)); -- Otherwise (e.g. noclip) fully restrict movement else player.movetype = MOVETYPE_WALK; player:SetAbsOrigin(player.__position); end end end end, 0); end, 1); return camera; end function InitGhostPirate(value, activator, caller) if (not IsValidPlayer(activator)) then return; end activator:SetDamageFilter("filter_blue"); activator.m_nRenderMode = 1; activator:AddOutput("renderamt 50"); for index, wearable in pairs(activator:GetWearables()) do wearable.m_nRenderMode = 1; wearable:AddOutput("renderamt 200"); end activator:AddCallback(ON_TOUCH, function(entity, other, hitPos, hitNormal) if (IsValidAlivePlayer(entity) and IsValidAliveRealPlayer(other)) then entity:Suicide(); timer.Create(2, function() ShowOverlayMaterialAll("vgui/ravenous/overlaytip_pickupweapons", 8); end, 1); end end); activator:AddCallback(ON_DEATH, function(entity) if (IsValidPlayer(entity)) then entity:AddOutput("renderamt 255"); end local annotation = ents.CreateWithKeys("training_annotation", { display_text = "The ghost dropped something!", lifetime = 6, }); annotation:SetAbsOrigin(entity:GetAbsOrigin() + Vector(0, 0, 64)); annotation:Show(); timer.Create(6, function() pcall(CEntity.Remove, annotation); end, 1) end); end function SpookEnemy(value, activator, caller) if (not IsValidAlivePlayer(caller)) then return; end caller:StunPlayer(2, 0.75, TF_STUNFLAGS_GHOSTSCARE, nil); end function Stun() local players = ents.GetAllPlayers(); for index, player in pairs(players) do if (IsValidAlivePlayer(player) and player:IsBot()) then if (player.tags and table.HasValue(player.tags, "bot_capbot")) then player:BotCommand("interrupt_action -pos 6380 4044 1028 -duration 10 -waituntildone"); player:StunPlayer(999, 1.0, TF_STUNFLAGS_GHOSTSCARE, nil); player:SetCollisionFilter("filter_blue"); end end end end function StartTouchBridgeTrigger(value, activator, caller) if (not IsValidAlivePlayer(activator)) then return; end value = tonumber(value); if (not table.HasValue(bridge_touchlist[value], activator)) then table.insert(bridge_touchlist[value], activator); end local already_touching = false; for i=1,#bridge_touchlist do if (i == value) then goto continue; end if (table.HasValue(bridge_touchlist[i], activator)) then already_touching = true; break; end ::continue:: end if (not already_touching) then local ent = ents.FindByName("capturearea_bridge"); if (IsValid(ent)) then ent:StartTouch(ent, activator, activator); end end end function EndTouchBridgeTrigger(value, activator, caller) value = tonumber(value); for i, player in pairs(bridge_touchlist[value]) do if (activator == player) then table.remove(bridge_touchlist[value], i); break; end end local still_touching = false; for i=1,#bridge_touchlist do if (i == value) then goto continue; end if (table.HasValue(bridge_touchlist[i], activator)) then still_touching = true; break; end ::continue:: end if (not still_touching) then local ent = ents.FindByName("capturearea_bridge"); if (IsValid(ent)) then ent:EndTouch(ent, activator, activator); end end end function OnWaveInit(wave) local entities = ents.FindInBox(Vector(4800, 3200, 900), Vector(5000, 3300, 1100)); for index, ent in pairs(entities) do if (IsValid(ent) and ent.m_iClassname == "func_respawnroomvisualizer") then ent:SetName("area02_block_left"); end end pcall(CEntity.AddOutput, ents.FindByName("wave_reset_relay"), "OnTrigger area02_block_left,Enable,,0.1,-1"); pcall(CEntity.Remove, ents.FindByName("shortcut_hint")); TF_GAMERULES:RunScriptCode("DoEntFire(`pt_area01_forcefield*`, `Disable`, ``, -1, null, null)"); TF_GAMERULES:RunScriptCode("DoEntFire(`pt_area01_forcefield*`, `AddOutput`, `solid 0`, -1, null, null)"); -- Offscreen local pos_y = "2"; if (wave == 1) then SetTotalLives(); TF_GAMERULES:RunScriptCode("DoEntFire(`pt_area01_forcefield*`, `Enable`, ``, -1, null, null)"); TF_GAMERULES:RunScriptCode("DoEntFire(`pt_area01_forcefield*`, `AddOutput`, `solid 6`, -1, null, null)"); elseif (wave == 2) then SetTotalLives(3); pcall(CEntity.Remove, ents.FindByName("controlpoint_master")); pos_y = "0.85"; elseif (wave == 3) then SetTotalLives(1); end pcall(CEntity.Remove, tcp_master); tcp_master = ents.CreateWithKeys("team_control_point_master", { -- We need to include the other cp's or the hud fucks up, so we shove them offscreen caplayout = "2,,,,,,0 1", cpm_restrict_team_cap_win = "1", custom_position_x = "0.35", custom_position_y = pos_y, partial_cap_points_rate = "0", play_all_rounds = "0", score_style = "0", StartDisabled = "0", switch_teams = "0", targetname = "controlpoint_master", team_base_icon_2 = "sprites/obj_icons/icon_base_red", team_base_icon_3 = "sprites/obj_icons/icon_base_blu", }); tcp_master:RoundSpawn(); end AddGlobalCallback("OnWaveInit", OnWaveInit);