319 lines
10 KiB
Lua
319 lines
10 KiB
Lua
GM.Name = "Feedc0de's coop Mode"
|
|
GM.Author = "0xFEEDC0DE64"
|
|
GM.Email = "daniel@brunner.ninja"
|
|
GM.Website = "brunner.ninja"
|
|
|
|
DeriveGamemode("sandbox")
|
|
|
|
include( "levels.lua" )
|
|
include( "progression_shared.lua" )
|
|
|
|
if (SERVER) then
|
|
function ChangeRandomLevel()
|
|
local randomLevel = GetRandomLevel()
|
|
if not randomLevel then
|
|
ErrorNoHalt("[custom_coop] No other installed coop map was found in levels.lua\n")
|
|
return false
|
|
end
|
|
|
|
print("[custom_coop] Changing to random level " .. randomLevel)
|
|
RunConsoleCommand("changelevel", randomLevel)
|
|
return true
|
|
end
|
|
|
|
function NextRandomLevel()
|
|
local randomLevel = GetRandomLevel()
|
|
if not randomLevel then
|
|
ErrorNoHalt("[custom_coop] Cannot set nextlevel: no other installed coop map was found\n")
|
|
return nil
|
|
end
|
|
|
|
print("[custom_coop] Setting nextlevel to " .. randomLevel)
|
|
RunConsoleCommand("nextlevel", randomLevel)
|
|
return randomLevel
|
|
end
|
|
|
|
concommand.Add("randomlevel", ChangeRandomLevel)
|
|
concommand.Add("randomnextlevel", NextRandomLevel)
|
|
|
|
hook.Add("Initialize", "SetupRandomLevels", function()
|
|
timer.Simple(0, NextRandomLevel)
|
|
end)
|
|
end
|
|
|
|
-- timer.Simple(
|
|
-- 1,
|
|
-- function()
|
|
-- function HideThings(name)
|
|
-- if (name == "CHudDamageIndicator" or name == "CHudHintDisplay") then
|
|
-- return false
|
|
-- end
|
|
-- end
|
|
-- hook.Add("HUDShouldDraw", "HideThings", HideThings)
|
|
-- end
|
|
-- )
|
|
|
|
function GM:IsSpawnpointSuitable(ply, spawnpointent, bMakeSuitable)
|
|
local Pos = spawnpointent:GetPos()
|
|
|
|
-- Note that we're searching the default hull size here for a player in the way of our spawning.
|
|
-- This seems pretty rough, seeing as our player's hull could be different.. but it should do the job
|
|
-- ( HL2DM kills everything within a 128 unit radius )
|
|
local Ents = ents.FindInBox(Pos + Vector(-16, -16, 0), Pos + Vector(16, 16, 72))
|
|
|
|
if (ply:Team() == TEAM_SPECTATOR or ply:Team() == TEAM_UNASSIGNED) then
|
|
return true
|
|
end
|
|
|
|
local Blockers = 0
|
|
|
|
for k, v in pairs(Ents) do
|
|
if (IsValid(v) and v:GetClass() == "player" and v:Alive()) then
|
|
Blockers = Blockers + 1
|
|
|
|
if (bMakeSuitable) then
|
|
v:SetVelocity(v:GetPos() + Vector(100, 100, 0))
|
|
end
|
|
end
|
|
end
|
|
|
|
if (bMakeSuitable) then
|
|
return true
|
|
end
|
|
if (Blockers > 0) then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
if SERVER then
|
|
-- Preserve the inherited selector across script auto-refreshes so the
|
|
-- emergency fallback does not build a chain of older wrappers.
|
|
local DefaultPlayerSelectSpawn = GM.CustomCoopDefaultPlayerSelectSpawn or GM.PlayerSelectSpawn
|
|
GM.CustomCoopDefaultPlayerSelectSpawn = DefaultPlayerSelectSpawn
|
|
local spawnPriorities = {
|
|
info_player_deathmatch = 1,
|
|
info_player_coop = 1,
|
|
info_coop_spawn = 1,
|
|
gmod_player_start = 1,
|
|
info_player_rebel = 2,
|
|
info_player_start = 3,
|
|
info_player_combine = 4
|
|
}
|
|
|
|
-- Include the additional spawn classes registered by Garry's Mod, while
|
|
-- retaining a preference for the spawn types used by HL2DM coop maps.
|
|
for _, className in ipairs(scripted_ents.GetMember("gmod_player_start", "SpawnPointClasses") or {}) do
|
|
spawnPriorities[className] = spawnPriorities[className] or 2
|
|
end
|
|
|
|
local playerHullMins = Vector(-16, -16, 0)
|
|
local playerHullMaxs = Vector(16, 16, 72)
|
|
|
|
local function BoxesIntersect(minsA, maxsA, minsB, maxsB)
|
|
return minsA.x <= maxsB.x and maxsA.x >= minsB.x
|
|
and minsA.y <= maxsB.y and maxsA.y >= minsB.y
|
|
and minsA.z <= maxsB.z and maxsA.z >= minsB.z
|
|
end
|
|
|
|
local function IsSpawnInsideActiveHurt(spawnpoint)
|
|
local position = spawnpoint:GetPos()
|
|
local spawnMins = position + playerHullMins
|
|
local spawnMaxs = position + playerHullMaxs
|
|
|
|
for _, hurt in ipairs(ents.FindByClass("trigger_hurt")) do
|
|
local disabled = hurt:GetInternalVariable("m_bDisabled")
|
|
local damage = tonumber(hurt:GetInternalVariable("damage") or hurt:GetKeyValues().damage) or 0
|
|
if disabled ~= true and disabled ~= 1 and damage > 0 then
|
|
local hurtMins, hurtMaxs = hurt:WorldSpaceAABB()
|
|
if BoxesIntersect(spawnMins, spawnMaxs, hurtMins, hurtMaxs) then return true end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function GM:PlayerSelectSpawn(ply, transition)
|
|
if transition then return end
|
|
|
|
local bestPriority
|
|
local candidates = {}
|
|
for _, spawnpoint in ipairs(ents.GetAll()) do
|
|
local priority = spawnPriorities[spawnpoint:GetClass()]
|
|
if priority and spawnpoint:IsInWorld() and not IsSpawnInsideActiveHurt(spawnpoint) then
|
|
if spawnpoint:HasSpawnFlags(1) then priority = 0 end
|
|
if not bestPriority or priority < bestPriority then
|
|
bestPriority = priority
|
|
candidates = { spawnpoint }
|
|
elseif priority == bestPriority then
|
|
candidates[#candidates + 1] = spawnpoint
|
|
end
|
|
end
|
|
end
|
|
|
|
if #candidates == 0 then
|
|
return DefaultPlayerSelectSpawn(self, ply, transition)
|
|
end
|
|
|
|
local lastSpawnpoint = ply:GetVar("LastSpawnpoint")
|
|
if #candidates > 1 and IsValid(lastSpawnpoint) then
|
|
table.RemoveByValue(candidates, lastSpawnpoint)
|
|
end
|
|
|
|
local chosen = candidates[math.random(#candidates)]
|
|
self.LastSpawnPoint = chosen
|
|
ply:SetVar("LastSpawnpoint", chosen)
|
|
return chosen
|
|
end
|
|
end
|
|
|
|
local hl2dmLoadout
|
|
local automaticLoadout
|
|
local BuildAutomaticLoadout
|
|
local ammoWeaponPairs = {
|
|
{ weapon = "weapon_pistol", ammo = { "item_ammo_pistol", "item_ammo_pistol_large" } },
|
|
{ weapon = "weapon_smg1", ammo = { "item_ammo_smg1", "item_ammo_smg1_large", "item_ammo_smg1_grenade" } },
|
|
{ weapon = "weapon_357", ammo = { "item_ammo_357", "item_ammo_357_large" } },
|
|
{ weapon = "weapon_ar2", ammo = { "item_ammo_ar2", "item_ammo_ar2_large", "item_ammo_ar2_altfire" } },
|
|
{ weapon = "weapon_shotgun", ammo = { "item_box_buckshot" } },
|
|
{ weapon = "weapon_crossbow", ammo = { "item_ammo_crossbow" } },
|
|
{ weapon = "weapon_rpg", ammo = { "item_rpg_round" } }
|
|
}
|
|
|
|
if SERVER then
|
|
hl2dmLoadout = CreateConVar(
|
|
"gc_hl2dmloadout",
|
|
"1",
|
|
FCVAR_ARCHIVE,
|
|
"Legacy map loadout: 0 off, 1 supply weapons missing for placed ammo, 2 full HL2DM loadout"
|
|
)
|
|
|
|
local function MapProvidesWeapon(className)
|
|
if #ents.FindByClass(className) > 0 then return true end
|
|
|
|
for _, equip in ipairs(ents.FindByClass("game_player_equip")) do
|
|
local weaponNames = equip:GetSaveTable().m_weaponNames or {}
|
|
for _, equippedClass in pairs(weaponNames) do
|
|
if string.lower(tostring(equippedClass)) == className then return true end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
BuildAutomaticLoadout = function()
|
|
automaticLoadout = {}
|
|
|
|
for _, pair in ipairs(ammoWeaponPairs) do
|
|
local hasAmmoPickup = false
|
|
for _, ammoClass in ipairs(pair.ammo) do
|
|
if #ents.FindByClass(ammoClass) > 0 then
|
|
hasAmmoPickup = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if hasAmmoPickup and not MapProvidesWeapon(pair.weapon) then
|
|
automaticLoadout[#automaticLoadout + 1] = pair.weapon
|
|
end
|
|
end
|
|
end
|
|
|
|
hook.Add("InitPostEntity", "CustomCoopDetectMissingAmmoWeapons", BuildAutomaticLoadout)
|
|
end
|
|
|
|
function GM:PlayerLoadout(ply)
|
|
if striponspawn then return end
|
|
|
|
ply:Give("weapon_fists")
|
|
local loadoutMode = hl2dmLoadout and hl2dmLoadout:GetInt() or 0
|
|
if loadoutMode <= 0 then return end
|
|
|
|
if loadoutMode == 1 then
|
|
-- InitPostEntity normally prepares this before anybody spawns. The
|
|
-- fallback also covers script auto-refresh during a running map.
|
|
if not automaticLoadout and BuildAutomaticLoadout then BuildAutomaticLoadout() end
|
|
for _, weaponClass in ipairs(automaticLoadout or {}) do
|
|
ply:Give(weaponClass)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Forced compatibility mode mirrors CHL2MP_Player::GiveDefaultItems.
|
|
ply:Give("weapon_crowbar")
|
|
ply:Give("weapon_pistol")
|
|
ply:Give("weapon_smg1")
|
|
ply:Give("weapon_frag")
|
|
ply:Give("weapon_physcannon")
|
|
|
|
ply:GiveAmmo(255, "Pistol", true)
|
|
ply:GiveAmmo(45, "SMG1", true)
|
|
ply:GiveAmmo(1, "Grenade", true)
|
|
ply:GiveAmmo(6, "Buckshot", true)
|
|
ply:GiveAmmo(6, "357", true)
|
|
end
|
|
|
|
hook.Add( "SpawnMenuOpen", "DisallowSpawnMenu", function ()
|
|
return LocalPlayer():IsAdmin()
|
|
end)
|
|
|
|
hook.Add( "ContextMenuOpen", "DisallowSpawnMenu", function ()
|
|
return LocalPlayer():IsAdmin()
|
|
end)
|
|
|
|
function GM:PlayerNoClip(ply, desiredState)
|
|
if desiredState then
|
|
return ply:IsAdmin()
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
function GM:PlayerDeathSound()
|
|
return true
|
|
end
|
|
|
|
function GM:DrawDeathNotice(x, y)
|
|
return false
|
|
end
|
|
|
|
function GM:PlayerSpawnVehicle(ply, model, name, table)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
function GM:PlayerSpawnSWEP(ply, weapon, info)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
function GM:PlayerSpawnSENT(ply, class)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
function GM:PlayerSpawnRagdoll(ply, model)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
function GM:PlayerSpawnProp(ply, model)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
function GM:PlayerSpawnObject(ply, model, skin)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
function GM:PlayerSpawnNPC(ply, npc_type, weapon)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
function GM:PlayerSpawnEffect(ply, model)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
function GM:PlayerGiveSWEP(ply, weapon, swep)
|
|
return ply:IsAdmin()
|
|
end
|
|
|
|
--function GM:HUDAmmoPickedUp( item, amount )
|
|
-- return false;
|
|
--end
|