mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
610 lines
16 KiB
Lua
610 lines
16 KiB
Lua
--[[
|
|
| This file was obtained through the combined efforts
|
|
| of Madbluntz & Plymouth Antiquarian Society.
|
|
|
|
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
|
| Maloy, DrPepper10 @ RIP, Atle!
|
|
|
|
|
| Visit for more: https://plymouth.thetwilightzone.ru/
|
|
--]]
|
|
|
|
---- Replace old and boring ents with new and shiny SENTs
|
|
|
|
ents.TTT = {}
|
|
|
|
local table = table
|
|
local math = math
|
|
local pairs = pairs
|
|
|
|
local function ReplaceSingle(ent, newname)
|
|
|
|
-- Ammo that has been mapper-placed will not have a pos yet at this point for
|
|
-- reasons that have to do with being really annoying. So don't touch those
|
|
-- so we can replace them later. Grumble grumble.
|
|
if ent:GetPos() == vector_origin then
|
|
return
|
|
end
|
|
|
|
ent:SetSolid(SOLID_NONE)
|
|
|
|
local rent = ents.Create(newname)
|
|
rent:SetPos(ent:GetPos())
|
|
rent:SetAngles(ent:GetAngles())
|
|
rent:Spawn()
|
|
|
|
rent:Activate()
|
|
rent:PhysWake()
|
|
|
|
ent:Remove()
|
|
end
|
|
|
|
local hl2_ammo_replace = {
|
|
["item_ammo_pistol"] = "item_ammo_pistol_ttt",
|
|
["item_box_buckshot"] = "item_box_buckshot_ttt",
|
|
["item_ammo_smg1"] = "item_ammo_smg1_ttt",
|
|
["item_ammo_357"] = "item_ammo_357_ttt",
|
|
["item_ammo_357_large"] = "item_ammo_357_ttt",
|
|
["item_ammo_revolver"] = "item_ammo_revolver_ttt", -- zm
|
|
["item_ammo_ar2"] = "item_ammo_pistol_ttt",
|
|
["item_ammo_ar2_large"] = "item_ammo_smg1_ttt",
|
|
["item_ammo_smg1_grenade"] = "weapon_zm_pistol",
|
|
["item_battery"] = "item_ammo_357_ttt",
|
|
["item_healthkit"] = "weapon_zm_shotgun",
|
|
["item_suitcharger"] = "weapon_zm_mac10",
|
|
["item_ammo_ar2_altfire"] = "weapon_zm_mac10",
|
|
["item_rpg_round"] = "item_ammo_357_ttt",
|
|
["item_ammo_crossbow"] = "item_box_buckshot_ttt",
|
|
["item_healthvial"] = "weapon_zm_molotov",
|
|
["item_healthcharger"] = "item_ammo_revolver_ttt",
|
|
["item_ammo_crate"] = "weapon_ttt_confgrenade",
|
|
["item_item_crate"] = "ttt_random_ammo"
|
|
};
|
|
|
|
-- Replace an ammo entity with the TTT version
|
|
-- Optional cls param is the classname, if the caller already has it handy
|
|
local function ReplaceAmmoSingle(ent, cls)
|
|
if cls == nil then cls = ent:GetClass() end
|
|
|
|
local rpl = hl2_ammo_replace[cls]
|
|
if rpl then
|
|
ReplaceSingle(ent, rpl)
|
|
end
|
|
end
|
|
|
|
local function ReplaceAmmo()
|
|
for _, ent in ipairs(ents.FindByClass("item_*")) do
|
|
ReplaceAmmoSingle(ent)
|
|
end
|
|
end
|
|
|
|
local hl2_weapon_replace = {
|
|
["weapon_smg1"] = "weapon_zm_mac10",
|
|
["weapon_shotgun"] = "weapon_zm_shotgun",
|
|
["weapon_ar2"] = "weapon_ttt_m16",
|
|
["weapon_357"] = "weapon_zm_rifle",
|
|
["weapon_crossbow"] = "weapon_zm_pistol",
|
|
["weapon_rpg"] = "weapon_zm_sledge",
|
|
["weapon_slam"] = "item_ammo_pistol_ttt",
|
|
["weapon_frag"] = "weapon_zm_revolver",
|
|
["weapon_crowbar"] = "weapon_zm_molotov"
|
|
};
|
|
|
|
local function ReplaceWeaponSingle(ent, cls)
|
|
-- Loadout weapons immune
|
|
-- we use a SWEP-set property because at this state all SWEPs identify as weapon_swep
|
|
if ent.AllowDelete == false then
|
|
return
|
|
else
|
|
if cls == nil then cls = ent:GetClass() end
|
|
|
|
local rpl = hl2_weapon_replace[cls]
|
|
if rpl then
|
|
ReplaceSingle(ent, rpl)
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
|
|
local function ReplaceWeapons()
|
|
for _, ent in ipairs(ents.FindByClass("weapon_*")) do
|
|
ReplaceWeaponSingle(ent)
|
|
end
|
|
end
|
|
|
|
|
|
-- Remove ZM ragdolls that don't work, AND old player ragdolls.
|
|
-- Exposed because it's also done at BeginRound
|
|
function ents.TTT.RemoveRagdolls(player_only)
|
|
for k, ent in ipairs(ents.FindByClass("prop_ragdoll")) do
|
|
if IsValid(ent) then
|
|
if not player_only and string.find(ent:GetModel(), "zm_", 6, true) then
|
|
ent:Remove()
|
|
elseif ent.player_ragdoll then
|
|
-- cleanup ought to catch these but you know
|
|
ent:Remove()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- People spawn with these, so remove any pickups (ZM maps have them)
|
|
local function RemoveCrowbars()
|
|
for k, ent in ipairs(ents.FindByClass("weapon_zm_improvised")) do
|
|
ent:Remove()
|
|
end
|
|
end
|
|
|
|
function ents.TTT.ReplaceEntities()
|
|
ReplaceAmmo()
|
|
ReplaceWeapons()
|
|
RemoveCrowbars()
|
|
ents.TTT.RemoveRagdolls()
|
|
end
|
|
|
|
|
|
local cls = "" -- avoid allocating
|
|
local sub = string.sub
|
|
local function ReplaceOnCreated(s, ent)
|
|
-- Invalid ents are of no use anyway
|
|
if not ent:IsValid() then return end
|
|
|
|
cls = ent:GetClass()
|
|
|
|
if sub(cls, 1, 4) == "item" then
|
|
ReplaceAmmoSingle(ent, cls)
|
|
elseif sub(cls, 1, 6) == "weapon" then
|
|
ReplaceWeaponSingle(ent, cls)
|
|
end
|
|
end
|
|
|
|
local noop = util.noop
|
|
|
|
GM.OnEntityCreated = ReplaceOnCreated
|
|
|
|
-- Helper so we can easily turn off replacement stuff when we don't need it
|
|
function ents.TTT.SetReplaceChecking(state)
|
|
if state then
|
|
GAMEMODE.OnEntityCreated = ReplaceOnCreated
|
|
else
|
|
GAMEMODE.OnEntityCreated = noop
|
|
end
|
|
end
|
|
|
|
-- GMod's game.CleanUpMap destroys rope entities that are parented. This is an
|
|
-- experimental fix where the rope is unparented, the map cleaned, and then the
|
|
-- rope reparented.
|
|
-- Same happens for func_brush.
|
|
local broken_parenting_ents = {
|
|
"move_rope",
|
|
"keyframe_rope",
|
|
"info_target",
|
|
"func_brush"
|
|
}
|
|
|
|
function ents.TTT.FixParentedPreCleanup()
|
|
for _, rcls in pairs(broken_parenting_ents) do
|
|
for k,v in ipairs(ents.FindByClass(rcls)) do
|
|
if v.GetParent and IsValid(v:GetParent()) then
|
|
v.CachedParentName = v:GetParent():GetName()
|
|
v:SetParent(nil)
|
|
|
|
if not v.OrigPos then
|
|
v.OrigPos = v:GetPos()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ents.TTT.FixParentedPostCleanup()
|
|
for _, rcls in pairs(broken_parenting_ents) do
|
|
for k,v in ipairs(ents.FindByClass(rcls)) do
|
|
if v.CachedParentName then
|
|
if v.OrigPos then
|
|
v:SetPos(v.OrigPos)
|
|
end
|
|
|
|
local parents = ents.FindByName(v.CachedParentName)
|
|
if #parents == 1 then
|
|
local parent = parents[1]
|
|
v:SetParent(parent)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ents.TTT.TriggerRoundStateOutputs(r, param)
|
|
r = r or GetRoundState()
|
|
|
|
for _, ent in ipairs(ents.FindByClass("ttt_map_settings")) do
|
|
if IsValid(ent) then
|
|
ent:RoundStateTrigger(r, param)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- CS:S and TF2 maps have a bunch of ents we'd like to abuse for weapon spawns,
|
|
-- but to do that we need to register a SENT with their class name, else they
|
|
-- will just error out and we can't do anything with them.
|
|
local dummify = {
|
|
-- CS:S
|
|
"hostage_entity",
|
|
-- TF2
|
|
"item_ammopack_full",
|
|
"item_ammopack_medium",
|
|
"item_ammopack_small",
|
|
"item_healthkit_full",
|
|
"item_healthkit_medium",
|
|
"item_healthkit_small",
|
|
"item_teamflag",
|
|
"game_intro_viewpoint",
|
|
"info_observer_point",
|
|
"team_control_point",
|
|
"team_control_point_master",
|
|
"team_control_point_round",
|
|
-- ZM
|
|
"item_ammo_revolver"
|
|
};
|
|
|
|
for k, cls in pairs(dummify) do
|
|
scripted_ents.Register({Type="point", IsWeaponDummy=true}, cls)
|
|
end
|
|
|
|
-- Cache this, every ttt_random_weapon uses it in its Init
|
|
local SpawnableSWEPs = nil
|
|
function ents.TTT.GetSpawnableSWEPs()
|
|
if not SpawnableSWEPs then
|
|
local tbl = {}
|
|
for k,v in pairs(weapons.GetList()) do
|
|
if v and v.AutoSpawnable and (not WEPS.IsEquipment(v)) then
|
|
table.insert(tbl, v)
|
|
end
|
|
end
|
|
|
|
SpawnableSWEPs = tbl
|
|
end
|
|
|
|
return SpawnableSWEPs
|
|
end
|
|
|
|
local SpawnableAmmoClasses = nil
|
|
function ents.TTT.GetSpawnableAmmo()
|
|
if not SpawnableAmmoClasses then
|
|
local tbl = {}
|
|
for k,v in pairs(scripted_ents.GetList()) do
|
|
if v and (v.AutoSpawnable or (v.t and v.t.AutoSpawnable)) then
|
|
table.insert(tbl, k)
|
|
end
|
|
end
|
|
|
|
SpawnableAmmoClasses = tbl
|
|
end
|
|
|
|
return SpawnableAmmoClasses
|
|
end
|
|
|
|
local function PlaceWeapon(swep, pos, ang)
|
|
local cls = swep and WEPS.GetClass(swep)
|
|
if not cls then return end
|
|
|
|
-- Create the weapon, somewhat in the air in case the spot hugs the ground.
|
|
local ent = ents.Create(cls)
|
|
pos.z = pos.z + 3
|
|
ent:SetPos(pos)
|
|
ent:SetAngles(VectorRand():Angle())
|
|
ent:Spawn()
|
|
|
|
-- Create some associated ammo (if any)
|
|
if ent.AmmoEnt then
|
|
for i=1, math.random(0,3) do
|
|
local ammo = ents.Create(ent.AmmoEnt)
|
|
|
|
if IsValid(ammo) then
|
|
pos.z = pos.z + 2
|
|
ammo:SetPos(pos)
|
|
ammo:SetAngles(VectorRand():Angle())
|
|
ammo:Spawn()
|
|
ammo:PhysWake()
|
|
end
|
|
end
|
|
end
|
|
|
|
return ent
|
|
end
|
|
|
|
-- Spawns a bunch of guns (scaling with maxplayers count or
|
|
-- by ttt_weapon_spawn_max cvar) at randomly selected
|
|
-- entities of the classes given the table
|
|
local function PlaceWeaponsAtEnts(spots_classes)
|
|
local spots = {}
|
|
for _, s in pairs(spots_classes) do
|
|
for _, e in ipairs(ents.FindByClass(s)) do
|
|
table.insert(spots, e)
|
|
end
|
|
end
|
|
|
|
local spawnables = ents.TTT.GetSpawnableSWEPs()
|
|
|
|
local max = GetConVar( "ttt_weapon_spawn_count" ):GetInt()
|
|
if max == 0 then
|
|
max = game.MaxPlayers()
|
|
max = max + math.max(3, 0.33 * max)
|
|
end
|
|
|
|
local num = 0
|
|
local w = nil
|
|
for k, v in RandomPairs(spots) do
|
|
w = table.Random(spawnables)
|
|
if w and IsValid(v) and util.IsInWorld(v:GetPos()) then
|
|
local spawned = PlaceWeapon(w, v:GetPos(), v:GetAngles())
|
|
|
|
num = num + 1
|
|
|
|
-- People with only a grenade are sad pandas. To get IsGrenade here,
|
|
-- we need the spawned ent that has inherited the goods from the
|
|
-- basegrenade swep.
|
|
if spawned and spawned.IsGrenade then
|
|
w = table.Random(spawnables)
|
|
if w then
|
|
PlaceWeapon(w, v:GetPos(), v:GetAngles())
|
|
end
|
|
end
|
|
end
|
|
|
|
if num > max then
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
local function PlaceExtraWeaponsForCSS()
|
|
MsgN("Weaponless CS:S-like map detected. Placing extra guns.")
|
|
|
|
local spots_classes = {
|
|
"info_player_terrorist",
|
|
"info_player_counterterrorist",
|
|
"hostage_entity"
|
|
};
|
|
|
|
PlaceWeaponsAtEnts(spots_classes)
|
|
end
|
|
|
|
-- TF2 actually has ammo ents and such, but unlike HL2DM there are not enough
|
|
-- different entities to do replacement.
|
|
local function PlaceExtraWeaponsForTF2()
|
|
MsgN("Weaponless TF2-like map detected. Placing extra guns.")
|
|
|
|
local spots_classes = {
|
|
"info_player_teamspawn",
|
|
"team_control_point",
|
|
"team_control_point_master",
|
|
"team_control_point_round",
|
|
"item_ammopack_full",
|
|
"item_ammopack_medium",
|
|
"item_ammopack_small",
|
|
"item_healthkit_full",
|
|
"item_healthkit_medium",
|
|
"item_healthkit_small",
|
|
"item_teamflag",
|
|
"game_intro_viewpoint",
|
|
"info_observer_point"
|
|
};
|
|
|
|
PlaceWeaponsAtEnts(spots_classes)
|
|
end
|
|
|
|
-- If there are no guns on the map, see if this looks like a TF2/CS:S map and
|
|
-- act appropriately
|
|
function ents.TTT.PlaceExtraWeapons()
|
|
-- If ents.FindByClass is constructed lazily or is an iterator, doing a
|
|
-- single loop should be faster than checking the table size.
|
|
|
|
-- Get out of here if there exists any weapon at all
|
|
for k,v in ipairs(ents.FindByClass("weapon_*")) do
|
|
-- See if it's the kind of thing we would spawn, to avoid the carry weapon
|
|
-- and such. Owned weapons are leftovers on players that will go away.
|
|
if IsValid(v) and v.AutoSpawnable and not IsValid(v:GetOwner()) then
|
|
return
|
|
end
|
|
end
|
|
|
|
-- All current TTT mappers use these, so if we find one we're good
|
|
for k,v in ipairs(ents.FindByClass("info_player_deathmatch")) do return end
|
|
|
|
-- CT spawns on the other hand are unlikely to be seen outside CS:S maps
|
|
for k,v in ipairs(ents.FindByClass("info_player_counterterrorist")) do
|
|
PlaceExtraWeaponsForCSS()
|
|
return
|
|
end
|
|
|
|
-- And same for TF2 team spawns
|
|
for k,v in ipairs(ents.FindByClass("info_player_teamspawn")) do
|
|
PlaceExtraWeaponsForTF2()
|
|
return
|
|
end
|
|
end
|
|
|
|
---- Weapon/ammo placement script importing
|
|
|
|
local function RemoveReplaceables()
|
|
-- This could be transformed into lots of FindByClass searches, one for every
|
|
-- key in the replace tables. Hopefully this is faster as more of the work is
|
|
-- done on the C side. Hard to measure.
|
|
for _, ent in ipairs(ents.FindByClass("item_*")) do
|
|
if hl2_ammo_replace[ent:GetClass()] then
|
|
ent:Remove()
|
|
end
|
|
end
|
|
|
|
for _, ent in ipairs(ents.FindByClass("weapon_*")) do
|
|
if hl2_weapon_replace[ent:GetClass()] then
|
|
ent:Remove()
|
|
end
|
|
end
|
|
end
|
|
|
|
local function RemoveWeaponEntities()
|
|
RemoveReplaceables()
|
|
|
|
for _, cls in pairs(ents.TTT.GetSpawnableAmmo()) do
|
|
for k, ent in ipairs(ents.FindByClass(cls)) do
|
|
ent:Remove()
|
|
end
|
|
end
|
|
|
|
for _, sw in pairs(ents.TTT.GetSpawnableSWEPs()) do
|
|
local cn = WEPS.GetClass(sw)
|
|
for k, ent in ipairs(ents.FindByClass(cn)) do
|
|
ent:Remove()
|
|
end
|
|
end
|
|
|
|
ents.TTT.RemoveRagdolls(false)
|
|
RemoveCrowbars()
|
|
end
|
|
|
|
local function RemoveSpawnEntities()
|
|
for k, ent in pairs(GetSpawnEnts(false, true)) do
|
|
ent.BeingRemoved = true -- they're not gone til next tick
|
|
ent:Remove()
|
|
end
|
|
end
|
|
|
|
local function CreateImportedEnt(cls, pos, ang, kv)
|
|
if not cls or not pos or not ang or not kv then return false end
|
|
|
|
local ent = ents.Create(cls)
|
|
if not IsValid(ent) then return false end
|
|
ent:SetPos(pos)
|
|
ent:SetAngles(ang)
|
|
|
|
for k,v in pairs(kv) do
|
|
ent:SetKeyValue(k, v)
|
|
end
|
|
|
|
ent:Spawn()
|
|
|
|
ent:PhysWake()
|
|
|
|
return true
|
|
end
|
|
|
|
function ents.TTT.CanImportEntities(map)
|
|
if not tostring(map) then return false end
|
|
if not GetConVar("ttt_use_weapon_spawn_scripts"):GetBool() then return false end
|
|
|
|
local fname = "maps/" .. map .. "_ttt.txt"
|
|
|
|
return file.Exists(fname, "GAME")
|
|
end
|
|
|
|
local function ImportSettings(map)
|
|
if not ents.TTT.CanImportEntities(map) then return end
|
|
|
|
local fname = "maps/" .. map .. "_ttt.txt"
|
|
local buf = file.Read(fname, "GAME")
|
|
|
|
local settings = {}
|
|
|
|
local lines = string.Explode("\n", buf)
|
|
for k, line in pairs(lines) do
|
|
if string.match(line, "^setting") then
|
|
local key, val = string.match(line, "^setting:\t(%w*) ([0-9]*)")
|
|
val = tonumber(val)
|
|
|
|
if key and val then
|
|
settings[key] = val
|
|
else
|
|
ErrorNoHalt("Invalid setting line " .. k .. " in " .. fname .. "\n")
|
|
end
|
|
end
|
|
end
|
|
|
|
return settings
|
|
end
|
|
|
|
local classremap = {
|
|
ttt_playerspawn = "info_player_deathmatch"
|
|
};
|
|
|
|
local function ImportEntities(map)
|
|
if not ents.TTT.CanImportEntities(map) then return end
|
|
|
|
local fname = "maps/" .. map .. "_ttt.txt"
|
|
|
|
local num = 0
|
|
for k, line in ipairs(string.Explode("\n", file.Read(fname, "GAME"))) do
|
|
if (not string.match(line, "^#")) and (not string.match(line, "^setting")) and line != "" and string.byte(line) != 0 then
|
|
local data = string.Explode("\t", line)
|
|
|
|
local fail = true -- pessimism
|
|
|
|
if data[2] and data[3] then
|
|
local cls = data[1]
|
|
local ang = nil
|
|
local pos = nil
|
|
|
|
local posraw = string.Explode(" ", data[2])
|
|
pos = Vector(tonumber(posraw[1]), tonumber(posraw[2]), tonumber(posraw[3]))
|
|
|
|
local angraw = string.Explode(" ", data[3])
|
|
ang = Angle(tonumber(angraw[1]), tonumber(angraw[2]), tonumber(angraw[3]))
|
|
|
|
-- Random weapons have a useful keyval
|
|
local kv = {}
|
|
if data[4] then
|
|
local kvraw = string.Explode(" ", data[4])
|
|
local key = kvraw[1]
|
|
local val = tonumber(kvraw[2])
|
|
|
|
if key and val then
|
|
kv[key] = val
|
|
end
|
|
end
|
|
|
|
-- Some dummy ents remap to different, real entity names
|
|
cls = classremap[cls] or cls
|
|
|
|
fail = not CreateImportedEnt(cls, pos, ang, kv)
|
|
end
|
|
|
|
if fail then
|
|
ErrorNoHalt("Invalid line " .. k .. " in " .. fname .. "\n")
|
|
else
|
|
num = num + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
MsgN("Spawned " .. num .. " entities found in script.")
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
function ents.TTT.ProcessImportScript(map)
|
|
MsgN("Weapon/ammo placement script found, attempting import...")
|
|
|
|
MsgN("Reading settings from script...")
|
|
local settings = ImportSettings(map)
|
|
|
|
if tobool(settings.replacespawns) then
|
|
MsgN("Removing existing player spawns")
|
|
RemoveSpawnEntities()
|
|
end
|
|
|
|
MsgN("Removing existing weapons/ammo")
|
|
RemoveWeaponEntities()
|
|
|
|
MsgN("Importing entities...")
|
|
local result = ImportEntities(map)
|
|
if result then
|
|
MsgN("Weapon placement script import successful!")
|
|
else
|
|
ErrorNoHalt("Weapon placement script import failed!\n")
|
|
end
|
|
end
|