mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 13:53:45 +03:00
Upload
This commit is contained in:
377
lua/pac3/core/shared/entity_mutator.lua
Normal file
377
lua/pac3/core/shared/entity_mutator.lua
Normal file
@@ -0,0 +1,377 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- rate limit?
|
||||
|
||||
local CLIENT = CLIENT
|
||||
local SERVER = SERVER
|
||||
|
||||
if pac.emut then
|
||||
for _, ent in ipairs(ents.GetAll()) do
|
||||
if ent.pac_mutations then
|
||||
for _, mutator in pairs(ent.pac_mutations) do
|
||||
xpcall(pac.emut.RestoreMutations, function() end, mutator.Owner, mutator.ClassName, mutator.Entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local emut = {}
|
||||
|
||||
pac.emut = emut
|
||||
|
||||
emut.registered_mutators = {}
|
||||
|
||||
do
|
||||
-- TOOD: use list instead of hash map
|
||||
emut.active_mutators = {}
|
||||
|
||||
function emut.AddMutator(mutator)
|
||||
emut.active_mutators[mutator] = mutator
|
||||
end
|
||||
|
||||
function emut.RemoveMutator(mutator)
|
||||
emut.active_mutators[mutator] = nil
|
||||
end
|
||||
|
||||
function emut.RemoveMutatorsOwnedByEntity(ent)
|
||||
if not ent.pac_mutations then return end
|
||||
|
||||
for class_name, mutator in pairs(ent.pac_mutations) do
|
||||
emut.RemoveMutator(mutator)
|
||||
ent.pac_mutations[class_name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function emut.GetAllMutators()
|
||||
local out = {}
|
||||
|
||||
for _, mutator in pairs(emut.active_mutators) do
|
||||
table.insert(out, mutator)
|
||||
end
|
||||
|
||||
return out
|
||||
end
|
||||
end
|
||||
|
||||
local function on_error(msg)
|
||||
print(debug.traceback(msg))
|
||||
ErrorNoHalt(msg)
|
||||
end
|
||||
|
||||
local suppress_send_to_server = false
|
||||
local override_enabled = false
|
||||
|
||||
function emut.MutateEntity(owner, class_name, ent, ...)
|
||||
if not IsValid(owner) then owner = game.GetWorld() end
|
||||
assert(emut.registered_mutators[class_name], "invalid mutator " .. class_name)
|
||||
if not IsValid(ent) then ErrorNoHalt("entity is invalid") return end
|
||||
|
||||
if hook.Run("PACMutateEntity", owner, ent, class_name, ...) == false then
|
||||
return
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
if pace.IsBanned(owner) then return end
|
||||
|
||||
if override_enabled and owner:IsPlayer() and not emut.registered_mutators[class_name].cvar:GetBool() then
|
||||
pac.Message(owner, "tried to set size when it's disabled")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
ent.pac_mutations = ent.pac_mutations or {}
|
||||
|
||||
local mutator = ent.pac_mutations[class_name]
|
||||
|
||||
if not mutator then
|
||||
mutator = setmetatable({Entity = ent, Owner = owner}, emut.registered_mutators[class_name])
|
||||
mutator.original_state = {mutator:StoreState()}
|
||||
ent.pac_mutations[class_name] = mutator
|
||||
emut.AddMutator(mutator)
|
||||
end
|
||||
|
||||
-- notify about owner change?
|
||||
mutator.Owner = owner
|
||||
mutator.current_state = {...}
|
||||
|
||||
if not xpcall(mutator.Mutate, on_error, mutator, ...) then
|
||||
mutator.current_state = nil
|
||||
emut.RestoreMutations(owner, class_name, ent)
|
||||
return
|
||||
end
|
||||
|
||||
if CLIENT and not emut.registered_mutators[class_name].cvar:GetBool() then
|
||||
return false
|
||||
end
|
||||
|
||||
if CLIENT and owner == LocalPlayer() and not suppress_send_to_server then
|
||||
net.Start("pac_entity_mutator")
|
||||
net.WriteString(class_name)
|
||||
net.WriteEntity(ent)
|
||||
net.WriteBool(false)
|
||||
mutator:WriteArguments(...)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
net.Start("pac_entity_mutator")
|
||||
net.WriteEntity(owner)
|
||||
net.WriteString(class_name)
|
||||
net.WriteEntity(ent)
|
||||
net.WriteBool(false)
|
||||
mutator:WriteArguments(...)
|
||||
net.SendPVS(owner:GetPos())
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function emut.RestoreMutations(owner, class_name, ent)
|
||||
if not IsValid(owner) then owner = game.GetWorld() end
|
||||
assert(emut.registered_mutators[class_name], "invalid mutator " .. class_name)
|
||||
if not IsValid(ent) then ErrorNoHalt("entity is invalid") return end
|
||||
|
||||
if SERVER then
|
||||
if not override_enabled then
|
||||
if not emut.registered_mutators[class_name].cvar:GetBool() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local mutator = ent.pac_mutations and ent.pac_mutations[class_name]
|
||||
|
||||
if mutator then
|
||||
xpcall(mutator.Mutate, on_error, mutator, unpack(mutator.original_state))
|
||||
ent.pac_mutations[class_name] = nil
|
||||
emut.RemoveMutator(mutator)
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
if not emut.registered_mutators[class_name].cvar:GetBool() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if CLIENT and owner == LocalPlayer() and not suppress_send_to_server then
|
||||
net.Start("pac_entity_mutator")
|
||||
net.WriteString(class_name)
|
||||
net.WriteEntity(ent)
|
||||
net.WriteBool(true)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
net.Start("pac_entity_mutator")
|
||||
net.WriteEntity(owner)
|
||||
net.WriteString(class_name)
|
||||
net.WriteEntity(ent)
|
||||
net.WriteBool(true)
|
||||
net.SendPVS(owner:GetPos())
|
||||
-- we also include the player who made the mutations, in case the server wants the arguments to be something else
|
||||
end
|
||||
end
|
||||
|
||||
function emut.Register(meta)
|
||||
|
||||
if Entity(1):IsValid() then
|
||||
for _, ent in ipairs(ents.GetAll()) do
|
||||
if ent.pac_mutations then
|
||||
for class_name, mutator in pairs(ent.pac_mutations) do
|
||||
if class_name == meta.ClassName then
|
||||
xpcall(emut.RestoreMutations, function() end, mutator.Owner, mutator.ClassName, mutator.Entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
meta.Mutate = meta.Mutate or function() end
|
||||
meta.StoreState = meta.StoreState or function() end
|
||||
|
||||
function meta:Disable()
|
||||
if self.disabled_state then return end
|
||||
|
||||
local state = {xpcall(self.StoreState, on_error, self)}
|
||||
if state[1] then
|
||||
table.remove(state, 1)
|
||||
self.disabled_state = state
|
||||
override_enabled = true
|
||||
xpcall(emut.MutateEntity, on_error, self.Owner, self.ClassName, self.Entity, unpack(self.original_state))
|
||||
override_enabled = false
|
||||
end
|
||||
end
|
||||
|
||||
function meta:Enable()
|
||||
if not self.disabled_state then return end
|
||||
|
||||
xpcall(emut.MutateEntity, on_error, self.Owner, self.ClassName, self.Entity, unpack(self.disabled_state))
|
||||
|
||||
self.disabled_state = nil
|
||||
end
|
||||
|
||||
function meta:__tostring()
|
||||
return "mutator[" .. self.ClassName .. "]" .. "[" .. tostring(self.Owner) .. "]" .. "[" .. tostring(self.Entity) .. "]"
|
||||
end
|
||||
|
||||
meta.__index = meta
|
||||
emut.registered_mutators[meta.ClassName] = meta
|
||||
|
||||
do
|
||||
local name = "pac_modifier_" .. meta.ClassName
|
||||
|
||||
local default = 1
|
||||
|
||||
if GAMEMODE and GAMEMODE.FolderName and not GAMEMODE.FolderName:lower():find("sandbox") then
|
||||
default = 0
|
||||
end
|
||||
|
||||
meta.cvar = CreateConVar(name, default, CLIENT and {FCVAR_REPLICATED} or {FCVAR_ARCHIVE, FCVAR_REPLICATED})
|
||||
|
||||
if SERVER then
|
||||
cvars.AddChangeCallback(name, function()
|
||||
local enable = meta.cvar:GetBool()
|
||||
|
||||
if enable then
|
||||
pac.Message("entity modifier ", name, " is now enabled")
|
||||
emut.EnableMutator()
|
||||
else
|
||||
pac.Message("entity modifier ", name, " is now disabled")
|
||||
emut.DisableMutator()
|
||||
end
|
||||
|
||||
end, name .. "_change")
|
||||
end
|
||||
end
|
||||
|
||||
if meta.Update then
|
||||
timer.Create("pac_entity_mutator_" .. meta.ClassName, meta.UpdateRate, 0, function()
|
||||
|
||||
if not meta.cvar:GetBool() then return end
|
||||
|
||||
for _, mutator in ipairs(emut.GetAllMutators()) do
|
||||
if mutator.ClassName == meta.ClassName then
|
||||
if not xpcall(mutator.Update, on_error, mutator, unpack(mutator.current_state)) then
|
||||
xpcall(emut.RestoreMutations, on_error, mutator.Owner, mutator.ClassName, mutator.Entity)
|
||||
emut.RemoveMutator(mutator)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
util.AddNetworkString("pac_entity_mutator")
|
||||
net.Receive("pac_entity_mutator", function(len, ply)
|
||||
local class_name = net.ReadString()
|
||||
if not emut.registered_mutators[class_name] then return end
|
||||
|
||||
local ent = net.ReadEntity()
|
||||
if not ent:IsValid() then return end
|
||||
|
||||
if net.ReadBool() then
|
||||
emut.RestoreMutations(ply, class_name, ent)
|
||||
else
|
||||
if not pace.CanPlayerModify(ply, ent) then return end
|
||||
|
||||
emut.MutateEntity(ply, class_name, ent, emut.registered_mutators[class_name].ReadArguments())
|
||||
end
|
||||
end)
|
||||
|
||||
function emut.EnableMutator()
|
||||
for _, mutator in ipairs(emut.GetAllMutators()) do
|
||||
mutator:Enable()
|
||||
end
|
||||
end
|
||||
|
||||
function emut.DisableMutator()
|
||||
for _, mutator in ipairs(emut.GetAllMutators()) do
|
||||
mutator:Disable()
|
||||
end
|
||||
end
|
||||
|
||||
function emut.ReplicateMutatorsForPlayer(ply)
|
||||
for _, mutator in ipairs(emut.GetAllMutators()) do
|
||||
net.Start("pac_entity_mutator")
|
||||
net.WriteEntity(mutator.Owner)
|
||||
net.WriteString(mutator.ClassName)
|
||||
net.WriteEntity(mutator.Entity)
|
||||
net.WriteBool(false)
|
||||
mutator:WriteArguments(unpack(mutator.current_state))
|
||||
net.Send(ply)
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add("PlayerInitialSpawn", "pac_entity_mutators_spawn", function(ply)
|
||||
local id = "pac_entity_mutators_spawn" .. ply:UniqueID()
|
||||
hook.Add( "SetupMove", id, function(movingPly, _, cmd)
|
||||
if not ply:IsValid() then
|
||||
hook.Remove("SetupMove", id)
|
||||
elseif movingPly == ply and not cmd:IsForced() then
|
||||
emut.ReplicateMutatorsForPlayer(ply)
|
||||
|
||||
hook.Remove("SetupMove", id)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function emut.RemoveMutationsForPlayer(ply)
|
||||
for _, mutator in ipairs(emut.GetAllMutators()) do
|
||||
if mutator.Owner == ply then
|
||||
emut.RestoreMutations(mutator.Owner, mutator.ClassName, mutator.Entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add("EntityRemoved", "pac_entity_mutators_left", function(ent)
|
||||
if not IsValid(ent) then return end
|
||||
if ent:IsPlayer() then
|
||||
if Player(ent:UserID()) == NULL then
|
||||
emut.RemoveMutationsForPlayer(ent)
|
||||
end
|
||||
else
|
||||
emut.RemoveMutatorsOwnedByEntity(ent)
|
||||
end
|
||||
end)
|
||||
|
||||
if CLIENT then
|
||||
net.Receive("pac_entity_mutator", function(len)
|
||||
local ply = net.ReadEntity()
|
||||
if not ply:IsValid() then return end
|
||||
local class_name = net.ReadString()
|
||||
local ent = net.ReadEntity()
|
||||
if not ent:IsValid() then return end
|
||||
|
||||
suppress_send_to_server = true
|
||||
|
||||
xpcall(function()
|
||||
if net.ReadBool() then
|
||||
emut.RestoreMutations(ply, class_name, ent)
|
||||
else
|
||||
emut.MutateEntity(ply, class_name, ent, emut.registered_mutators[class_name].ReadArguments())
|
||||
end
|
||||
end, on_error)
|
||||
|
||||
suppress_send_to_server = false
|
||||
end)
|
||||
end
|
||||
|
||||
function emut.LoadMutators()
|
||||
local files = file.Find("pac3/core/shared/entity_mutators/*.lua", "LUA")
|
||||
|
||||
for _, name in pairs(files) do
|
||||
include("pac3/core/shared/entity_mutators/" .. name)
|
||||
end
|
||||
end
|
||||
|
||||
emut.LoadMutators()
|
||||
34
lua/pac3/core/shared/entity_mutators/blood_color.lua
Normal file
34
lua/pac3/core/shared/entity_mutators/blood_color.lua
Normal file
@@ -0,0 +1,34 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
local MUTATOR = {}
|
||||
|
||||
MUTATOR.ClassName = "blood_color"
|
||||
|
||||
function MUTATOR:WriteArguments(enum)
|
||||
assert(enum >= -1 and enum <= 6, "invalid blood color")
|
||||
net.WriteInt(enum, 8)
|
||||
end
|
||||
|
||||
function MUTATOR:ReadArguments()
|
||||
return net.ReadInt(8)
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
function MUTATOR:StoreState()
|
||||
return self.Entity:GetBloodColor()
|
||||
end
|
||||
|
||||
function MUTATOR:Mutate(enum)
|
||||
self.Entity:SetBloodColor(enum)
|
||||
end
|
||||
end
|
||||
|
||||
pac.emut.Register(MUTATOR)
|
||||
81
lua/pac3/core/shared/entity_mutators/model.lua
Normal file
81
lua/pac3/core/shared/entity_mutators/model.lua
Normal file
@@ -0,0 +1,81 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
local MUTATOR = {}
|
||||
|
||||
MUTATOR.ClassName = "model"
|
||||
MUTATOR.UpdateRate = 0.25
|
||||
|
||||
function MUTATOR:WriteArguments(path)
|
||||
assert(isstring(path), "path must be a string")
|
||||
|
||||
net.WriteString(path)
|
||||
end
|
||||
|
||||
function MUTATOR:ReadArguments()
|
||||
return net.ReadString()
|
||||
end
|
||||
|
||||
function MUTATOR:Update(val)
|
||||
if not self.actual_model or not IsValid(self.Entity) then return end
|
||||
|
||||
if self.Entity:GetModel():lower() ~= self.actual_model:lower() then
|
||||
self.Entity:SetModel(self.actual_model)
|
||||
end
|
||||
end
|
||||
|
||||
function MUTATOR:StoreState()
|
||||
return self.Entity:GetModel()
|
||||
end
|
||||
|
||||
function MUTATOR:Mutate(path)
|
||||
if path:find("^http") then
|
||||
if SERVER and pac.debug then
|
||||
if self.Owner:IsPlayer() then
|
||||
pac.Message(self.Owner, " wants to use ", path, " as model on ", ent)
|
||||
end
|
||||
end
|
||||
|
||||
local ent_str = tostring(self.Entity)
|
||||
|
||||
pac.DownloadMDL(path, function(mdl_path)
|
||||
if not self.Entity:IsValid() then
|
||||
pac.Message("cannot set model ", mdl_path, " on ", ent_str ,': entity became invalid')
|
||||
return
|
||||
end
|
||||
|
||||
if SERVER and pac.debug then
|
||||
pac.Message(mdl_path, " downloaded for ", ent, ': ', path)
|
||||
end
|
||||
|
||||
self.Entity:SetModel(mdl_path)
|
||||
self.actual_model = mdl_path
|
||||
|
||||
end, function(err)
|
||||
pac.Message(err)
|
||||
end, self.Owner)
|
||||
else
|
||||
if path:EndsWith(".mdl") then
|
||||
self.Entity:SetModel(path)
|
||||
|
||||
if self.Owner:IsPlayer() and path:lower() ~= self.Entity:GetModel():lower() then
|
||||
self.Owner:ChatPrint('[PAC3] ERROR: ' .. path .. " is not a valid model on the server.")
|
||||
else
|
||||
self.actual_model = path
|
||||
end
|
||||
else
|
||||
local translated = player_manager.TranslatePlayerModel(path)
|
||||
self.Entity:SetModel(translated)
|
||||
self.actual_model = translated
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pac.emut.Register(MUTATOR)
|
||||
131
lua/pac3/core/shared/entity_mutators/size.lua
Normal file
131
lua/pac3/core/shared/entity_mutators/size.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
local MUTATOR = {}
|
||||
|
||||
MUTATOR.ClassName = "size"
|
||||
|
||||
function MUTATOR:WriteArguments(multiplier, other)
|
||||
net.WriteFloat(multiplier)
|
||||
if other then
|
||||
net.WriteBool(true)
|
||||
net.WriteFloat(other.StandingHullHeight)
|
||||
net.WriteFloat(other.CrouchingHullHeight)
|
||||
net.WriteFloat(other.HullWidth)
|
||||
else
|
||||
net.WriteBool(false)
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
local hidden_state = self.original_state[3]
|
||||
if hidden_state then
|
||||
net.WriteBool(true)
|
||||
net.WriteTable(hidden_state)
|
||||
else
|
||||
net.WriteBool(false)
|
||||
end
|
||||
else
|
||||
net.WriteBool(false)
|
||||
end
|
||||
end
|
||||
|
||||
function MUTATOR:ReadArguments()
|
||||
local multiplier = math.Clamp(net.ReadFloat(), 0.1, 10)
|
||||
local other = false
|
||||
local hidden_state
|
||||
|
||||
if net.ReadBool() then
|
||||
other = {}
|
||||
other.StandingHullHeight = net.ReadFloat()
|
||||
other.CrouchingHullHeight = net.ReadFloat()
|
||||
other.HullWidth = net.ReadFloat()
|
||||
end
|
||||
|
||||
if net.ReadBool() then
|
||||
hidden_state = net.ReadTable()
|
||||
end
|
||||
|
||||
return multiplier, other, hidden_state
|
||||
end
|
||||
|
||||
function MUTATOR:StoreState()
|
||||
local ent = self.Entity
|
||||
|
||||
return
|
||||
1, --ent:GetModelScale(),
|
||||
false, -- we will just ent:ResetHull()
|
||||
{
|
||||
ViewOffset = ent.GetViewOffset and ent:GetViewOffset() or nil,
|
||||
ViewOffsetDucked = ent.GetViewOffsetDucked and ent:GetViewOffsetDucked() or nil,
|
||||
StepSize = ent.GetStepSize and ent:GetStepSize() or nil,
|
||||
}
|
||||
end
|
||||
|
||||
local functions = {
|
||||
"ViewOffset",
|
||||
"ViewOffsetDucked",
|
||||
"StepSize",
|
||||
}
|
||||
|
||||
function MUTATOR:Mutate(multiplier, other, hidden_state)
|
||||
local ent = self.Entity
|
||||
|
||||
if ent:GetModelScale() ~= multiplier then
|
||||
ent:SetModelScale(multiplier)
|
||||
end
|
||||
|
||||
-- hmmm
|
||||
hidden_state = hidden_state or self.original_state[3]
|
||||
|
||||
if hidden_state then
|
||||
for _, key in ipairs(functions) do
|
||||
local original = hidden_state[key]
|
||||
if original then
|
||||
local setter = ent["Set" .. key]
|
||||
|
||||
if setter then
|
||||
setter(ent, original * multiplier)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if ent.SetHull and ent.SetHullDuck and ent.ResetHull then
|
||||
if other then
|
||||
local smin, smax = Vector(), Vector()
|
||||
local cmin, cmax = Vector(), Vector()
|
||||
|
||||
local w = math.Clamp(other.HullWidth or 32, 1, 4096)
|
||||
|
||||
smin.x = -w / 2
|
||||
smax.x = w / 2
|
||||
smin.y = -w / 2
|
||||
smax.y = w / 2
|
||||
|
||||
cmin.x = -w / 2
|
||||
cmax.x = w / 2
|
||||
cmin.y = -w / 2
|
||||
cmax.y = w / 2
|
||||
|
||||
smin.z = 0
|
||||
smax.z = math.Clamp(other.StandingHullHeight or 72, 1, 4096)
|
||||
|
||||
cmin.z = 0
|
||||
cmax.z = math.Clamp(other.CrouchingHullHeight or 36, 1, 4096)
|
||||
|
||||
ent:SetHull(smin, smax)
|
||||
ent:SetHullDuck(cmin, cmax)
|
||||
else
|
||||
ent:ResetHull()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pac.emut.Register(MUTATOR)
|
||||
64
lua/pac3/core/shared/footsteps_fix.lua
Normal file
64
lua/pac3/core/shared/footsteps_fix.lua
Normal file
@@ -0,0 +1,64 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
if game.SinglePlayer() then
|
||||
if SERVER then
|
||||
util.AddNetworkString('pac_footstep')
|
||||
util.AddNetworkString('pac_footstep_request_state_update')
|
||||
util.AddNetworkString('pac_signal_mute_footstep')
|
||||
|
||||
hook.Add("PlayerFootstep", "footstep_fix", function(ply, pos, _, snd, vol)
|
||||
net.Start("pac_footstep_request_state_update")
|
||||
net.Send(ply)
|
||||
|
||||
net.Start("pac_footstep")
|
||||
net.WriteEntity(ply)
|
||||
net.WriteVector(pos)
|
||||
net.WriteString(snd)
|
||||
net.WriteFloat(vol)
|
||||
net.Broadcast()
|
||||
end)
|
||||
|
||||
net.Receive("pac_signal_mute_footstep", function(len,ply)
|
||||
local b = net.ReadBool()
|
||||
ply.pac_mute_footsteps = b
|
||||
if ply.pac_mute_footsteps then
|
||||
hook.Add("PlayerFootstep", "pac_footstep_silence", function()
|
||||
return b
|
||||
end)
|
||||
else hook.Remove("PlayerFootstep", "pac_footstep_silence") end
|
||||
end)
|
||||
|
||||
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
net.Receive("pac_footstep", function(len)
|
||||
local ply = net.ReadEntity()
|
||||
local pos = net.ReadVector()
|
||||
local snd = net.ReadString()
|
||||
local vol = net.ReadFloat()
|
||||
|
||||
if ply:IsValid() then
|
||||
hook.Run("pac_PlayerFootstep", ply, pos, snd, vol)
|
||||
end
|
||||
end)
|
||||
net.Receive("pac_footstep_request_state_update", function()
|
||||
net.Start("pac_signal_mute_footstep")
|
||||
net.WriteBool(LocalPlayer().pac_mute_footsteps)
|
||||
net.SendToServer()
|
||||
end)
|
||||
end
|
||||
else
|
||||
hook.Add("PlayerFootstep", "footstep_fix", function(ply, pos, _, snd, vol)
|
||||
return hook.Run("pac_PlayerFootstep", ply, pos, snd, vol)
|
||||
end)
|
||||
end
|
||||
77
lua/pac3/core/shared/hash.lua
Normal file
77
lua/pac3/core/shared/hash.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
local string_hash = util.SHA256
|
||||
local is_singleplayer = game.SinglePlayer()
|
||||
local is_bot = FindMetaTable("Player").IsBot
|
||||
local steamid64 = FindMetaTable("Player").SteamID64
|
||||
|
||||
function pac.Hash(obj)
|
||||
local t = type(obj)
|
||||
|
||||
if t == "string" then
|
||||
return string_hash(obj)
|
||||
elseif t == "Player" then
|
||||
if is_singleplayer then
|
||||
return "SinglePlayer"
|
||||
end
|
||||
|
||||
-- Player(s) can never be next bots (?)
|
||||
-- IsNextBot is present in Entity and NextBot metatables (and those functions are different)
|
||||
-- but not in Player's one
|
||||
if obj:IsNextBot() then
|
||||
return "nextbot " .. tostring(obj:EntIndex())
|
||||
end
|
||||
|
||||
if is_bot(obj) then
|
||||
return "bot " .. tostring(obj:EntIndex())
|
||||
end
|
||||
|
||||
local hash = steamid64(obj)
|
||||
if not hash then
|
||||
if pac.debug then
|
||||
ErrorNoHaltWithStack( "FIXME: Did not get a steamid64 for a player object " .. tostring(obj) .. ', valid=' .. tostring(IsValid(obj)) .. ', steamid=' .. tostring(obj:SteamID()) .. '\n' )
|
||||
end
|
||||
hash = "0"
|
||||
end
|
||||
return hash
|
||||
elseif t == "number" then
|
||||
return string_hash(tostring(t))
|
||||
elseif t == "table" then
|
||||
return string_hash(("%p"):format(obj))
|
||||
elseif t == "nil" then
|
||||
return string_hash(SysTime() .. ' ' .. os.time() .. ' ' .. RealTime())
|
||||
elseif IsEntity(obj) then
|
||||
return tostring(obj:EntIndex())
|
||||
else
|
||||
error("NYI " .. t)
|
||||
end
|
||||
end
|
||||
|
||||
function pac.ReverseHash(str, t)
|
||||
if t == "Player" then
|
||||
if is_singleplayer then
|
||||
return Entity(1)
|
||||
end
|
||||
|
||||
if str:StartWith("nextbot ") then
|
||||
return pac.ReverseHash(str:sub(#"nextbot " + 1), "Entity")
|
||||
elseif str:StartWith("bot ") then
|
||||
return pac.ReverseHash(str:sub(#"bot " + 1), "Entity")
|
||||
end
|
||||
|
||||
return player.GetBySteamID64(str) or NULL
|
||||
elseif t == "Entity" then
|
||||
return ents.GetByIndex(tonumber(str))
|
||||
else
|
||||
error("NYI " .. t)
|
||||
end
|
||||
end
|
||||
201
lua/pac3/core/shared/http.lua
Normal file
201
lua/pac3/core/shared/http.lua
Normal file
@@ -0,0 +1,201 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
local CL_LIMIT, CL_LIMIT_OVERRIDE, CL_NO_CLENGTH
|
||||
|
||||
if CLIENT then
|
||||
CL_LIMIT = CreateConVar("pac_webcontent_limit", "-1", {FCVAR_ARCHIVE}, "webcontent limit in kb, -1 = unlimited")
|
||||
CL_NO_CLENGTH = CreateConVar("pac_webcontent_allow_no_content_length", "0", {FCVAR_ARCHIVE}, "allow downloads with no content length")
|
||||
CL_LIMIT_OVERRIDE = CreateConVar("pac_webcontent_limit_force", "0", {FCVAR_ARCHIVE}, "Override serverside setting")
|
||||
end
|
||||
|
||||
local SV_LIMIT = CreateConVar("sv_pac_webcontent_limit", "-1", CLIENT and {FCVAR_REPLICATED} or {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "webcontent limit in kb, -1 = unlimited")
|
||||
local SV_NO_CLENGTH = CreateConVar("sv_pac_webcontent_allow_no_content_length", "-1", CLIENT and {FCVAR_REPLICATED} or {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "allow downloads with no content length")
|
||||
|
||||
function pac.FixGMODUrl(url)
|
||||
-- to avoid "invalid url" errors
|
||||
-- gmod does not allow urls containing "10.", "172.16.", "192.168.", "127." or "://localhost"
|
||||
-- we escape 10. and 127. can occur (mydomain.com/model10.zip) and assume the server supports
|
||||
-- the escaped request
|
||||
return url:Replace("10.", "%31%30%2e"):Replace("127.", "%31%32%37%2e")
|
||||
end
|
||||
|
||||
local function http(method, url, headers, cb, failcb)
|
||||
|
||||
url = pac.FixGMODUrl(url)
|
||||
|
||||
return HTTP({
|
||||
method = method,
|
||||
url = url,
|
||||
headers = headers,
|
||||
success = function(code, data, headers)
|
||||
if code < 400 then
|
||||
cb(data, #data, headers)
|
||||
else
|
||||
local header = {}
|
||||
for k,v in pairs(headers) do
|
||||
table.insert(header, tostring(k) .. ": " .. tostring(v))
|
||||
end
|
||||
|
||||
local err = "server returned code " .. code .. ":\n\n"
|
||||
err = err .. "url: "..url.."\n"
|
||||
err = err .. "================\n"
|
||||
|
||||
err = err .. "HEADER:\n"
|
||||
err = err .. table.concat(header, "\n") .. "\n"
|
||||
|
||||
err = err .. "================\n"
|
||||
|
||||
err = err .. "BODY:\n"
|
||||
err = err .. data .. "\n"
|
||||
|
||||
err = err .. "================\n"
|
||||
failcb(err, code >= 400, code)
|
||||
end
|
||||
end,
|
||||
failed = function(err)
|
||||
if failcb then
|
||||
failcb("_G.HTTP error: " .. err)
|
||||
else
|
||||
pac.Message("_G.HTTP error: " .. err)
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function pac.FixUrl(url)
|
||||
url = url:Trim()
|
||||
url = url:gsub("[\"'<>\n\\]+", "")
|
||||
|
||||
if url:find("dropbox", 1, true) then
|
||||
url = url:gsub([[^http%://dl%.dropboxusercontent%.com/]], [[https://dl.dropboxusercontent.com/]])
|
||||
url = url:gsub([[^https?://dl.dropbox.com/]], [[https://www.dropbox.com/]])
|
||||
url = url:gsub([[^https?://www.dropbox.com/s/(.+)%?dl%=[01]$]], [[https://dl.dropboxusercontent.com/s/%1]])
|
||||
url = url:gsub([[^https?://www.dropbox.com/s/(.+)$]], [[https://dl.dropboxusercontent.com/s/%1]])
|
||||
url = url:gsub([[^https?://www.dropbox.com/scl/(.+)$]], [[https://dl.dropboxusercontent.com/scl/%1]]) --Fix for new dropbox format.
|
||||
return url
|
||||
end
|
||||
|
||||
if url:find("drive.google.com", 1, true) and not url:find("export=download", 1, true) then
|
||||
local id =
|
||||
url:match("https://drive.google.com/file/d/(.-)/") or
|
||||
url:match("https://drive.google.com/file/d/(.-)$") or
|
||||
url:match("https://drive.google.com/open%?id=(.-)$")
|
||||
|
||||
if id then
|
||||
return "https://drive.google.com/uc?export=download&id=" .. id
|
||||
end
|
||||
return url
|
||||
end
|
||||
|
||||
if url:find("gitlab.com", 1, true) then
|
||||
return url:gsub("^(https?://.-/.-/.-/)blob", "%1raw")
|
||||
end
|
||||
|
||||
url = url:gsub([[^http%://onedrive%.live%.com/redir?]],[[https://onedrive.live.com/download?]])
|
||||
url = url:gsub("pastebin.com/([a-zA-Z0-9]*)$", "pastebin.com/raw.php?i=%1")
|
||||
url = url:gsub("github.com/([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)/blob/", "github.com/%1/%2/raw/")
|
||||
|
||||
return url
|
||||
end
|
||||
|
||||
function pac.getContentLength(url, cb, failcb)
|
||||
return http("HEAD", url, {["Accept-Encoding"] = "none"}, function(_, _, headers)
|
||||
local length
|
||||
|
||||
-- server have rights to send headers in any case
|
||||
for key, value in pairs(headers) do
|
||||
if string.lower(key) == "content-length" then
|
||||
length = tonumber(value)
|
||||
|
||||
if not length or math.floor(length) ~= length then
|
||||
return failcb(string.format("malformed server reply with header content-length (got %q, expected valid integer number)", value), true)
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if length then return cb(length) end
|
||||
|
||||
return pac.contentLengthFallback(url, cb, failcb)
|
||||
end, function(err, over400, code)
|
||||
if code == 405 then
|
||||
return pac.contentLengthFallback(url, cb, failcb)
|
||||
end
|
||||
|
||||
return failcb(err, over400)
|
||||
end )
|
||||
end
|
||||
|
||||
-- Performs a GET but requests 0 bytes
|
||||
-- We can then read the response headers to determine the content size.
|
||||
-- This allows Google Drive and other hosts to work with PAC even with content-length limits set
|
||||
-- (They typically block HEAD requests)
|
||||
function pac.contentLengthFallback(url, cb, failcb)
|
||||
local function fail()
|
||||
return failcb("unable to determine content length", true)
|
||||
end
|
||||
|
||||
return http("GET", url, {["Range"] = "bytes=0-0"}, function(data, data_length, headers)
|
||||
-- e.g. "bytes 0-0/11784402"
|
||||
local contentRange = headers["Content-Range"]
|
||||
if not contentRange then return fail() end
|
||||
|
||||
local spl = string.Split(contentRange, "/")
|
||||
local contentLength = spl[2]
|
||||
|
||||
if contentLength then return cb(tonumber(contentLength)) end
|
||||
|
||||
return fail()
|
||||
end )
|
||||
end
|
||||
|
||||
function pac.HTTPGet(url, cb, failcb)
|
||||
if not url or url:len() < 4 then
|
||||
failcb("url length is less than 4 (" .. tostring(url) .. ")", true)
|
||||
return
|
||||
end
|
||||
|
||||
url = pac.FixUrl(url)
|
||||
|
||||
local limit = SV_LIMIT:GetInt()
|
||||
|
||||
if CLIENT and (CL_LIMIT_OVERRIDE:GetBool() or limit == -1) then
|
||||
limit = CL_LIMIT:GetInt()
|
||||
end
|
||||
|
||||
if limit == -1 then
|
||||
return http("GET", url, nil, cb, failcb)
|
||||
end
|
||||
|
||||
return pac.getContentLength(url, function(length)
|
||||
if length then
|
||||
if length <= (limit * 1024) then
|
||||
http("GET", url, nil, cb, failcb)
|
||||
else
|
||||
failcb("download is too big (" .. string.NiceSize(length) .. ")", true)
|
||||
end
|
||||
else
|
||||
local allow_no_contentlength = SV_NO_CLENGTH:GetInt()
|
||||
|
||||
if CLIENT and (CL_LIMIT_OVERRIDE:GetBool() or allow_no_contentlength < 0) then
|
||||
allow_no_contentlength = CL_NO_CLENGTH:GetInt()
|
||||
end
|
||||
|
||||
if allow_no_contentlength > 0 then
|
||||
http("GET", url, nil, cb, failcb)
|
||||
else
|
||||
failcb("unknown file size when allow_no_contentlength is " .. allow_no_contentlength, true)
|
||||
end
|
||||
end
|
||||
end, failcb)
|
||||
end
|
||||
74
lua/pac3/core/shared/init.lua
Normal file
74
lua/pac3/core/shared/init.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
include("util.lua")
|
||||
|
||||
include("footsteps_fix.lua")
|
||||
include("http.lua")
|
||||
include("movement.lua")
|
||||
include("entity_mutator.lua")
|
||||
include("hash.lua")
|
||||
|
||||
pac.StringStream = include("pac3/libraries/string_stream.lua")
|
||||
|
||||
CreateConVar("pac_sv_draw_distance", 0, CLIENT and FCVAR_REPLICATED or bit.bor(FCVAR_REPLICATED, FCVAR_ARCHIVE))
|
||||
|
||||
do
|
||||
local tohash = {
|
||||
-- Crash
|
||||
'weapon_unusual_isotope.pcf',
|
||||
|
||||
-- Invalid
|
||||
'blood_fx.pcf',
|
||||
'boomer_fx.pcf',
|
||||
'charger_fx.pcf',
|
||||
'default.pcf',
|
||||
'electrical_fx.pcf',
|
||||
'environmental_fx.pcf',
|
||||
'fire_01l4d.pcf',
|
||||
'fire_fx.pcf',
|
||||
'fire_infected_fx.pcf',
|
||||
'firework_crate_fx.pcf',
|
||||
'fireworks_fx.pcf',
|
||||
'footstep_fx.pcf',
|
||||
'gen_dest_fx.pcf',
|
||||
'hunter_fx.pcf',
|
||||
'infected_fx.pcf',
|
||||
'insect_fx.pcf',
|
||||
'item_fx.pcf',
|
||||
'locator_fx.pcf',
|
||||
'military_artillery_impacts.pcf',
|
||||
'rain_fx.pcf',
|
||||
'rain_storm_fx.pcf',
|
||||
'rope_fx.pcf',
|
||||
'screen_fx.pcf',
|
||||
'smoker_fx.pcf',
|
||||
'speechbubbles.pcf',
|
||||
'spitter_fx.pcf',
|
||||
'steam_fx.pcf',
|
||||
'steamworks.pcf',
|
||||
'survivor_fx.pcf',
|
||||
'tank_fx.pcf',
|
||||
'tanker_explosion.pcf',
|
||||
'test_collision.pcf',
|
||||
'test_distancealpha.pcf',
|
||||
'ui_fx.pcf',
|
||||
'vehicle_fx.pcf',
|
||||
'water_fx.pcf',
|
||||
'weapon_fx.pcf',
|
||||
'witch_fx.pcf'
|
||||
}
|
||||
|
||||
pac.BlacklistedParticleSystems = {}
|
||||
|
||||
for i, val in ipairs(tohash) do
|
||||
pac.BlacklistedParticleSystems[val] = true
|
||||
end
|
||||
end
|
||||
324
lua/pac3/core/shared/movement.lua
Normal file
324
lua/pac3/core/shared/movement.lua
Normal file
@@ -0,0 +1,324 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
local movementConvar = CreateConVar("pac_free_movement", -1, CLIENT and {FCVAR_REPLICATED} or {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "allow players to modify movement. -1 apply only allow when noclip is allowed, 1 allow for all gamemodes, 0 to disable")
|
||||
|
||||
local default = {
|
||||
JumpHeight = 200,
|
||||
StickToGround = true,
|
||||
GroundFriction = 0.12,
|
||||
AirFriction = 0.01,
|
||||
Gravity = Vector(0,0,-600),
|
||||
Noclip = false,
|
||||
MaxGroundSpeed = 750,
|
||||
MaxAirSpeed = 1,
|
||||
AllowZVelocity = false,
|
||||
ReversePitch = false,
|
||||
UnlockPitch = false,
|
||||
VelocityToViewAngles = 0,
|
||||
RollAmount = 0,
|
||||
|
||||
SprintSpeed = 750,
|
||||
RunSpeed = 300,
|
||||
WalkSpeed = 100,
|
||||
DuckSpeed = 25,
|
||||
|
||||
FinEfficiency = 0,
|
||||
FinLiftMode = "normal",
|
||||
FinCline = false
|
||||
}
|
||||
|
||||
if SERVER then
|
||||
util.AddNetworkString("pac_modify_movement")
|
||||
|
||||
net.Receive("pac_modify_movement", function(len, ply)
|
||||
local cvar = movementConvar:GetInt()
|
||||
if cvar == 0 or (cvar == -1 and hook.Run("PlayerNoClip", ply, true)==false) then return end
|
||||
|
||||
local str = net.ReadString()
|
||||
if str == "disable" then
|
||||
ply.pac_movement = nil
|
||||
else
|
||||
if default[str] ~= nil then
|
||||
local val = net.ReadType()
|
||||
if type(val) == type(default[str]) then
|
||||
ply.pac_movement = ply.pac_movement or table.Copy(default)
|
||||
ply.pac_movement[str] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
local sensitivityConvar = GetConVar("sensitivity")
|
||||
pac.AddHook("InputMouseApply", "custom_movement", function(cmd, x,y, ang)
|
||||
local ply = pac.LocalPlayer
|
||||
local self = ply.pac_movement
|
||||
if not self then return end
|
||||
|
||||
if ply:GetMoveType() == MOVETYPE_NOCLIP then
|
||||
if ply.pac_movement_viewang then
|
||||
ang.r = 0
|
||||
cmd:SetViewAngles(ang)
|
||||
ply.pac_movement_viewang = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if self.UnlockPitch then
|
||||
ply.pac_movement_viewang = ply.pac_movement_viewang or ang
|
||||
ang = ply.pac_movement_viewang
|
||||
|
||||
local sens = sensitivityConvar:GetFloat() * 20
|
||||
x = x / sens
|
||||
y = y / sens
|
||||
|
||||
if ang.p > 89 or ang.p < -89 then
|
||||
x = -x
|
||||
end
|
||||
|
||||
ang.p = math.NormalizeAngle(ang.p + y)
|
||||
ang.y = math.NormalizeAngle(ang.y + -x)
|
||||
end
|
||||
|
||||
if self.ReversePitch then
|
||||
ang.p = -ang.p
|
||||
end
|
||||
|
||||
local vel = ply:GetVelocity()
|
||||
|
||||
local roll = math.Clamp(vel:Dot(-ang:Right()) * self.RollAmount, -89, 89)
|
||||
if not vel:IsZero() then
|
||||
if vel:Dot(ang:Forward()) < 0 then
|
||||
vel = -vel
|
||||
end
|
||||
ang = LerpAngle(self.VelocityToViewAngles, ang, vel:Angle())
|
||||
end
|
||||
ang.r = roll
|
||||
|
||||
cmd:SetViewAngles(ang)
|
||||
|
||||
if self.UnlockPitch then
|
||||
return true
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function badMovetype(ply)
|
||||
local mvtype = ply:GetMoveType()
|
||||
|
||||
return mvtype == MOVETYPE_OBSERVER
|
||||
or mvtype == MOVETYPE_NOCLIP
|
||||
or mvtype == MOVETYPE_LADDER
|
||||
or mvtype == MOVETYPE_CUSTOM
|
||||
or mvtype == MOVETYPE_ISOMETRIC
|
||||
end
|
||||
|
||||
local frictionConvar = GetConVar("sv_friction")
|
||||
pac.AddHook("Move", "custom_movement", function(ply, mv)
|
||||
local self = ply.pac_movement
|
||||
|
||||
if not self then
|
||||
if not ply.pac_custom_movement_reset then
|
||||
if not badMovetype(ply) then
|
||||
ply:SetGravity(1)
|
||||
ply:SetMoveType(MOVETYPE_WALK)
|
||||
|
||||
if ply.pac_custom_movement_jump_height then
|
||||
ply:SetJumpPower(ply.pac_custom_movement_jump_height)
|
||||
ply.pac_custom_movement_jump_height = nil
|
||||
end
|
||||
end
|
||||
|
||||
ply.pac_custom_movement_reset = true
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
ply.pac_custom_movement_reset = nil
|
||||
ply.pac_custom_movement_jump_height = ply.pac_custom_movement_jump_height or ply:GetJumpPower()
|
||||
|
||||
if badMovetype(ply) then return end
|
||||
|
||||
mv:SetForwardSpeed(0)
|
||||
mv:SetSideSpeed(0)
|
||||
mv:SetUpSpeed(0)
|
||||
|
||||
ply:SetJumpPower(self.JumpHeight)
|
||||
|
||||
if self.Noclip then
|
||||
ply:SetMoveType(MOVETYPE_NONE)
|
||||
else
|
||||
ply:SetMoveType(MOVETYPE_WALK)
|
||||
end
|
||||
|
||||
ply:SetGravity(0.00000000000000001)
|
||||
|
||||
local on_ground = ply:IsOnGround()
|
||||
|
||||
if not self.StickToGround then
|
||||
ply:SetGroundEntity(NULL)
|
||||
end
|
||||
|
||||
local speed = self.RunSpeed
|
||||
|
||||
if mv:KeyDown(IN_SPEED) then
|
||||
speed = self.SprintSpeed
|
||||
end
|
||||
|
||||
if mv:KeyDown(IN_WALK) then
|
||||
speed = self.WalkSpeed
|
||||
end
|
||||
|
||||
if mv:KeyDown(IN_DUCK) then
|
||||
speed = self.DuckSpeed
|
||||
end
|
||||
|
||||
-- speed = speed * FrameTime()
|
||||
|
||||
local ang = mv:GetAngles()
|
||||
local vel = Vector()
|
||||
|
||||
if on_ground and self.StickToGround then
|
||||
ang.p = 0
|
||||
end
|
||||
|
||||
if mv:KeyDown(IN_FORWARD) then
|
||||
vel = vel + ang:Forward()
|
||||
elseif mv:KeyDown(IN_BACK) then
|
||||
vel = vel - ang:Forward()
|
||||
end
|
||||
|
||||
if mv:KeyDown(IN_MOVERIGHT) then
|
||||
vel = vel + ang:Right()
|
||||
elseif mv:KeyDown(IN_MOVELEFT) then
|
||||
vel = vel - ang:Right()
|
||||
end
|
||||
|
||||
vel = vel:GetNormalized() * speed
|
||||
|
||||
if self.AllowZVelocity then
|
||||
if mv:KeyDown(IN_JUMP) then
|
||||
vel = vel + ang:Up() * speed
|
||||
elseif mv:KeyDown(IN_DUCK) then
|
||||
vel = vel - ang:Up() * speed
|
||||
end
|
||||
end
|
||||
|
||||
if not self.AllowZVelocity then
|
||||
vel.z = 0
|
||||
end
|
||||
|
||||
local speed = vel
|
||||
|
||||
local vel = mv:GetVelocity()
|
||||
|
||||
if on_ground and not self.Noclip and self.StickToGround then -- work against ground friction
|
||||
local sv_friction = frictionConvar:GetInt()
|
||||
|
||||
if sv_friction > 0 then
|
||||
sv_friction = 1 - (sv_friction * 15) / 1000
|
||||
vel = vel / sv_friction
|
||||
end
|
||||
end
|
||||
|
||||
vel = vel + self.Gravity * 0
|
||||
|
||||
-- todo: don't allow adding more velocity to existing velocity if it exceeds
|
||||
-- but allow decreasing
|
||||
if not on_ground then
|
||||
local friction = self.AirFriction
|
||||
friction = -(friction) + 1
|
||||
|
||||
vel = vel * friction
|
||||
|
||||
vel = vel + self.Gravity * 0.015
|
||||
speed = speed:GetNormalized() * math.Clamp(speed:Length(), 0, self.MaxAirSpeed)
|
||||
vel = vel + (speed * FrameTime()*(66.666*(-friction+1)))
|
||||
else
|
||||
local friction = self.GroundFriction
|
||||
friction = -(friction) + 1
|
||||
|
||||
vel = vel * friction
|
||||
|
||||
speed = speed:GetNormalized() * math.min(speed:Length(), self.MaxGroundSpeed)
|
||||
vel = vel + (speed * FrameTime()*(75.77*(-friction+1)))
|
||||
vel = vel + self.Gravity * 0.015
|
||||
end
|
||||
|
||||
if self.FinEfficiency > 0 then -- fin
|
||||
local curvel = vel
|
||||
local curup = ang:Forward()
|
||||
|
||||
local vec1 = curvel
|
||||
local vec2 = curup
|
||||
vec1 = vec1 - 2*(vec1:Dot(vec2))*vec2
|
||||
local sped = vec1:Length()
|
||||
|
||||
local finalvec = curvel
|
||||
local modf = math.abs(curup:Dot(curvel:GetNormalized()))
|
||||
local nvec = (curup:Dot(curvel:GetNormalized()))
|
||||
|
||||
if (self.pln == 1) then
|
||||
|
||||
if nvec > 0 then
|
||||
vec1 = vec1 + (curup * 10)
|
||||
else
|
||||
vec1 = vec1 + (curup * -10)
|
||||
end
|
||||
|
||||
finalvec = vec1:GetNormalized() * (math.pow(sped, modf) - 1)
|
||||
finalvec = finalvec:GetNormalized()
|
||||
finalvec = (finalvec * self.FinEfficiency) + curvel
|
||||
end
|
||||
|
||||
if (self.FinLiftMode ~= "none") then
|
||||
if (self.FinLiftMode == "normal") then
|
||||
local liftmul = 1 - math.abs(nvec)
|
||||
finalvec = finalvec + (curup * liftmul * curvel:Length() * self.FinEfficiency) / 700
|
||||
else
|
||||
local liftmul = (nvec / math.abs(nvec)) - nvec
|
||||
finalvec = finalvec + (curup * curvel:Length() * self.FinEfficiency * liftmul) / 700
|
||||
end
|
||||
end
|
||||
|
||||
finalvec = finalvec:GetNormalized()
|
||||
finalvec = finalvec * curvel:Length()
|
||||
|
||||
if self.FinCline then
|
||||
local trace = {
|
||||
start = mv:GetOrigin(),
|
||||
endpos = mv:GetOrigin() + Vector(0, 0, -1000000),
|
||||
mask = 131083
|
||||
}
|
||||
local trc = util.TraceLine(trace)
|
||||
|
||||
local MatType = trc.MatType
|
||||
|
||||
if (MatType == 67 or MatType == 77) then
|
||||
local heatvec = Vector(0, 0, 100)
|
||||
local cline = ((2 * (heatvec:Dot(curup)) * curup - heatvec)) * (math.abs(heatvec:Dot(curup)) / 1000)
|
||||
finalvec = finalvec + (cline * (self.FinEfficiency / 50))
|
||||
end
|
||||
end
|
||||
|
||||
vel = finalvec
|
||||
end
|
||||
|
||||
mv:SetVelocity(vel)
|
||||
|
||||
if self.Noclip then
|
||||
mv:SetOrigin(mv:GetOrigin() + vel * 0.01)
|
||||
end
|
||||
|
||||
return false
|
||||
end)
|
||||
784
lua/pac3/core/shared/util.lua
Normal file
784
lua/pac3/core/shared/util.lua
Normal file
@@ -0,0 +1,784 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
|
||||
local PREFIX = '[PAC3] '
|
||||
local PREFIX_COLOR = Color(255, 255, 0)
|
||||
|
||||
local DEFAULT_TEXT_COLOR = Color(200, 200, 200)
|
||||
local BOOLEAN_COLOR = Color(33, 83, 226)
|
||||
local NUMBER_COLOR = Color(245, 199, 64)
|
||||
local STEAMID_COLOR = Color(255, 255, 255)
|
||||
local ENTITY_COLOR = Color(180, 232, 180)
|
||||
local FUNCTION_COLOR = Color(62, 106, 255)
|
||||
local TABLE_COLOR = Color(107, 200, 224)
|
||||
local URL_COLOR = Color(174, 124, 192)
|
||||
|
||||
function pac.RepackMessage(strIn)
|
||||
local output = {}
|
||||
|
||||
for line in string.gmatch(strIn, '([^ ]+)') do
|
||||
if #output ~= 0 then
|
||||
table.insert(output, ' ')
|
||||
end
|
||||
|
||||
table.insert(output, line)
|
||||
end
|
||||
|
||||
return output
|
||||
end
|
||||
|
||||
local function FormatMessage(tabIn)
|
||||
local prevColor = DEFAULT_TEXT_COLOR
|
||||
local output = {prevColor}
|
||||
|
||||
for i, val in ipairs(tabIn) do
|
||||
local valType = type(val)
|
||||
|
||||
if valType == 'number' then
|
||||
table.insert(output, NUMBER_COLOR)
|
||||
table.insert(output, tostring(val))
|
||||
table.insert(output, prevColor)
|
||||
elseif valType == 'string' then
|
||||
if val:find('^https?://') then
|
||||
table.insert(output, URL_COLOR)
|
||||
table.insert(output, val)
|
||||
table.insert(output, prevColor)
|
||||
else
|
||||
table.insert(output, val)
|
||||
end
|
||||
elseif valType == 'Player' then
|
||||
if team then
|
||||
table.insert(output, team.GetColor(val:Team()) or ENTITY_COLOR)
|
||||
else
|
||||
table.insert(output, ENTITY_COLOR)
|
||||
end
|
||||
|
||||
table.insert(output, val:Nick())
|
||||
|
||||
if val.SteamName and val:SteamName() ~= val:Nick() then
|
||||
table.insert(output, ' (' .. val:SteamName() .. ')')
|
||||
end
|
||||
|
||||
table.insert(output, '<')
|
||||
table.insert(output, val:SteamID())
|
||||
table.insert(output, '>')
|
||||
table.insert(output, prevColor)
|
||||
elseif valType == 'Entity' or valType == 'NPC' or valType == 'Vehicle' then
|
||||
table.insert(output, ENTITY_COLOR)
|
||||
table.insert(output, tostring(val))
|
||||
table.insert(output, prevColor)
|
||||
elseif IsColor(val) then
|
||||
table.insert(output, val)
|
||||
prevColor = val
|
||||
elseif valType == 'table' then
|
||||
table.insert(output, TABLE_COLOR)
|
||||
table.insert(output, tostring(val))
|
||||
table.insert(output, prevColor)
|
||||
elseif valType == 'function' then
|
||||
table.insert(output, FUNCTION_COLOR)
|
||||
table.insert(output, string.format('function - %p', val))
|
||||
table.insert(output, prevColor)
|
||||
elseif valType == 'boolean' then
|
||||
table.insert(output, BOOLEAN_COLOR)
|
||||
table.insert(output, tostring(val))
|
||||
table.insert(output, prevColor)
|
||||
else
|
||||
table.insert(output, tostring(val))
|
||||
end
|
||||
end
|
||||
|
||||
return output
|
||||
end
|
||||
|
||||
pac.FormatMessage = FormatMessage
|
||||
|
||||
function pac.Message(...)
|
||||
local formatted = FormatMessage({...})
|
||||
MsgC(PREFIX_COLOR, PREFIX, unpack(formatted))
|
||||
MsgC('\n')
|
||||
return formatted
|
||||
end
|
||||
|
||||
function pac.dprint(fmt, ...)
|
||||
if not pac.debug then return end
|
||||
MsgN("\n")
|
||||
MsgN(">>>PAC3>>>")
|
||||
MsgN(fmt:format(...))
|
||||
if pac.debug_trace then
|
||||
MsgN("==TRACE==")
|
||||
debug.Trace()
|
||||
MsgN("==TRACE==")
|
||||
end
|
||||
MsgN("<<<PAC3<<<")
|
||||
MsgN("\n")
|
||||
end
|
||||
|
||||
local DEBUG_MDL = false
|
||||
local VERBOSE = false
|
||||
|
||||
local shader_params = include("pac3/libraries/shader_params.lua")
|
||||
local texture_keys = {}
|
||||
|
||||
for _, shader in pairs(shader_params.shaders) do
|
||||
for _, params in pairs(shader) do
|
||||
for key, info in pairs(params) do
|
||||
if info.type == "texture" then
|
||||
texture_keys[key] = key
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, params in pairs(shader_params.base) do
|
||||
for key, info in pairs(params) do
|
||||
if info.type == "texture" then
|
||||
texture_keys[key] = key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
texture_keys["include"] = "include"
|
||||
|
||||
-- for pac_restart
|
||||
PAC_MDL_SALT = PAC_MDL_SALT or 0
|
||||
|
||||
local cached_paths = {}
|
||||
|
||||
function pac.DownloadMDL(url, callback, onfail, ply)
|
||||
local skip_cache = false
|
||||
|
||||
if url:StartWith("_") then
|
||||
skip_cache = true
|
||||
url = url:sub(2)
|
||||
end
|
||||
|
||||
if not skip_cache and cached_paths[url] then
|
||||
callback(cached_paths[url])
|
||||
return
|
||||
end
|
||||
|
||||
return pac.resource.Download(url, function(path)
|
||||
if ply:IsPlayer() and not ply:IsValid() then
|
||||
pac.Message(Color(255, 50, 50), "player is no longer valid")
|
||||
file.Delete(path)
|
||||
return
|
||||
end
|
||||
|
||||
local file_content = file.Read(path)
|
||||
|
||||
if not file_content then
|
||||
pac.Message(Color(255, 50, 50), "content is empty")
|
||||
file.Delete(path)
|
||||
return
|
||||
end
|
||||
|
||||
local id = util.CRC(url .. file_content)
|
||||
|
||||
if skip_cache then
|
||||
id = util.CRC(id .. os.clock())
|
||||
end
|
||||
|
||||
if skip_cache or not file.Exists("pac3_cache/downloads/"..id..".dat", "DATA") then
|
||||
|
||||
local dir = "pac3_cache/" .. id .. "/"
|
||||
|
||||
local f = file.Open(path, "rb", "DATA")
|
||||
|
||||
local files = {}
|
||||
|
||||
local ok, err = pcall(function()
|
||||
for i = 1, 128 do
|
||||
local pos = f:Tell()
|
||||
|
||||
local sig = f:ReadLong()
|
||||
|
||||
if sig == 0x02014b50 then break end
|
||||
|
||||
assert(sig == 0x04034b50, "bad zip signature (file is not a zip?)")
|
||||
|
||||
f:Seek(pos+6) local bitflag = f:ReadShort()
|
||||
f:Seek(pos+8) local compression_method = f:ReadShort()
|
||||
f:Seek(pos+14) local crc = f:ReadShort()
|
||||
f:Seek(pos+18) local size2 = f:ReadLong()
|
||||
f:Seek(pos+22) local size = f:ReadLong()
|
||||
f:Seek(pos+26) local file_name_length = f:ReadShort()
|
||||
local extra_field_length = f:ReadShort()
|
||||
|
||||
local name = f:Read(file_name_length):lower()
|
||||
local file_path = name
|
||||
|
||||
if compression_method ~= 0 then
|
||||
error("the file " .. name .. " is compressed! (use compression method 0 / store, or maybe you drag dropped files into the archive)")
|
||||
end
|
||||
|
||||
f:Skip(extra_field_length)
|
||||
|
||||
local buffer = f:Read(size)
|
||||
|
||||
name = name:match(".+/(.+)") or name
|
||||
|
||||
if not buffer then
|
||||
if not file_path:EndsWith("/") then
|
||||
pac.Message(Color(255, 50,50), file_path .. " is empty")
|
||||
end
|
||||
else
|
||||
local ok = true
|
||||
for i,v in ipairs(files) do
|
||||
if v.file_name == name then
|
||||
if ply == pac.LocalPlayer then
|
||||
pac.Message(Color(255, 50,50), file_path .. " is already a file at " .. v.file_path)
|
||||
end
|
||||
ok = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if ok then
|
||||
table.insert(files, {file_name = name, buffer = buffer, crc = crc, file_path = file_path})
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
f:Close()
|
||||
|
||||
local count = 0
|
||||
local model_found = false
|
||||
local other_models = {}
|
||||
|
||||
table.sort(files, function(a, b) return #a.buffer > #b.buffer end)
|
||||
|
||||
for i, v in ipairs(files) do
|
||||
if v.file_name:EndsWith(".mdl") then
|
||||
local name = v.file_name:match("(.+)%.mdl")
|
||||
for _, v2 in ipairs(files) do
|
||||
if v2.file_name:EndsWith(name .. ".ani") then
|
||||
v.ani = v2
|
||||
break
|
||||
end
|
||||
end
|
||||
if v.ani then
|
||||
v.file_name = v.file_name:gsub(".-(%..+)", "i"..count.."%1"):lower()
|
||||
v.ani.file_name = v.ani.file_name:gsub(".-(%..+)", "i"..count.."%1"):lower()
|
||||
count = count + 1
|
||||
else
|
||||
if not model_found or v.file_name:StartWith(model_found) then
|
||||
model_found = v.file_name:match("(.-)%.")
|
||||
v.file_name = v.file_name:gsub(".-(%..+)", "model%1"):lower()
|
||||
else
|
||||
table.insert(other_models, v.file_name)
|
||||
end
|
||||
end
|
||||
elseif v.file_name:EndsWith(".vtx") or v.file_name:EndsWith(".vvd") or v.file_name:EndsWith(".phy") then
|
||||
if not model_found or v.file_name:StartWith(model_found) then
|
||||
model_found = v.file_name:match("(.-)%.")
|
||||
v.file_name = v.file_name:gsub(".-(%..+)", "model%1"):lower()
|
||||
else
|
||||
table.insert(other_models, v.file_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if other_models[1] and ply == pac.LocalPlayer then
|
||||
pac.Message(Color(255, 200, 50), url, ": the archive contains more than one model.")
|
||||
pac.Message(Color(255, 200, 50), url, ": " .. model_found .. " was selected.")
|
||||
pac.Message(Color(255, 200, 50), url, ": these are ignored:")
|
||||
PrintTable(other_models)
|
||||
end
|
||||
|
||||
if VERBOSE then
|
||||
print("FILES:")
|
||||
for i, v in ipairs(files) do
|
||||
print(v.file_name)
|
||||
end
|
||||
end
|
||||
|
||||
if not ok then
|
||||
onfail(err)
|
||||
local str = file.Read(path)
|
||||
file.Delete(path)
|
||||
|
||||
pac.Message(Color(255, 50,50), err)
|
||||
pac.Message(Color(255, 50,50), "the zip archive downloaded (", string.NiceSize(#str) ,") could not be parsed")
|
||||
|
||||
local is_binary = false
|
||||
for i = 1, #str do
|
||||
local b = str:byte(i)
|
||||
if b == 0 then
|
||||
is_binary = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not is_binary then
|
||||
pac.Message(Color(255, 50,50), "the url isn't a binary zip archive. Is it a html website? here's the content:")
|
||||
print(str)
|
||||
elseif ply == pac.LocalPlayer then
|
||||
file.Write("pac3_cache/failed_zip_download.dat", str)
|
||||
pac.Message("the zip archive was stored to garrysmod/data/pac3_cache/failed_zip_download.dat (rename extension to .zip) if you want to inspect it")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local required = {
|
||||
".mdl",
|
||||
".vvd",
|
||||
".dx90.vtx",
|
||||
}
|
||||
local found = {}
|
||||
for k,v in pairs(files) do
|
||||
for _, ext in ipairs(required) do
|
||||
if v.file_name:EndsWith(ext) then
|
||||
table.insert(found, ext)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #found < #required then
|
||||
local str = {}
|
||||
|
||||
for _, ext in ipairs(required) do
|
||||
if not table.HasValue(found, ext) then
|
||||
table.insert(str, ext)
|
||||
end
|
||||
end
|
||||
|
||||
onfail("could not find " .. table.concat(str, " or ") .. " in zip archive")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
do -- hex models
|
||||
local found_vmt_directories = {}
|
||||
|
||||
for i, data in ipairs(files) do
|
||||
if data.file_name:EndsWith(".mdl") then
|
||||
local found_materials = {}
|
||||
local found_materialdirs = {}
|
||||
local found_mdl_includes = {}
|
||||
|
||||
local vtf_dir_offset
|
||||
local vtf_dir_count
|
||||
|
||||
local material_offset
|
||||
local material_count
|
||||
|
||||
local include_mdl_dir_offset
|
||||
local include_mdl_dir_count
|
||||
|
||||
if DEBUG_MDL then
|
||||
file.Write(data.file_name..".debug.old.dat", data.buffer)
|
||||
end
|
||||
|
||||
local f = pac.StringStream(data.buffer)
|
||||
local id = f:read(4)
|
||||
local version = f:readUInt32()
|
||||
local checksum = f:readUInt32()
|
||||
|
||||
local name_offset = f:tell()
|
||||
local name = f:read(64)
|
||||
local size_offset = f:tell()
|
||||
local size = f:readUInt32()
|
||||
|
||||
f:skip(12 * 6) -- skips over all the vec3 stuff
|
||||
|
||||
f:skip(4) -- flags
|
||||
f:skip(8) -- bone
|
||||
f:skip(8) -- bone controller
|
||||
f:skip(8) -- hitbox
|
||||
f:skip(8) -- local anim
|
||||
f:skip(8) -- sequences
|
||||
f:skip(8) -- activitylistversion + eventsindexed
|
||||
|
||||
do
|
||||
material_count = f:readUInt32()
|
||||
material_offset = f:readUInt32() + 1 -- +1 to convert 0 indexed to 1 indexed
|
||||
|
||||
local old_pos = f:tell()
|
||||
f:seek(material_offset)
|
||||
|
||||
for i = 1, material_count do
|
||||
local material_start = f:tell()
|
||||
local material_name_offset = f:readInt32()
|
||||
f:skip(60)
|
||||
local material_end = f:tell()
|
||||
|
||||
local material_name_pos = material_start + material_name_offset
|
||||
f:seek(material_name_pos)
|
||||
|
||||
local material_name = (f:readString() .. ".vmt"):lower()
|
||||
local found = false
|
||||
|
||||
for i, v in pairs(files) do
|
||||
if v.file_name == material_name then
|
||||
found = v.file_path
|
||||
break
|
||||
elseif v.file_path == ("materials/" .. material_name) then
|
||||
v.file_name = material_name
|
||||
found = v.file_path
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
for i, v in pairs(files) do
|
||||
if string.find(v.file_path, material_name, 1, true) or string.find(material_name, v.file_name, 1, true) then
|
||||
table.insert(files, {file_name = material_name, buffer = v.buffer, crc = v.crc, file_path = v.file_path})
|
||||
found = v.file_path
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
if ply == pac.LocalPlayer then
|
||||
pac.Message(Color(255, 50,50), url, " the model wants to find ", material_name , " but it was not found in the zip archive")
|
||||
end
|
||||
local dummy = "VertexLitGeneric\n{\n\t$basetexture \"error\"\n}"
|
||||
table.insert(files, {file_name = material_name, buffer = dummy, crc = util.CRC(dummy), file_path = material_name})
|
||||
end
|
||||
|
||||
table.insert(found_materials, {name = material_name, offset = material_name_pos})
|
||||
f:seek(material_end)
|
||||
end
|
||||
|
||||
if ply == pac.LocalPlayer and #found_materials == 0 then
|
||||
pac.Message(Color(255, 200, 50), url, ": could not find any materials in this model")
|
||||
end
|
||||
|
||||
f:seek(old_pos)
|
||||
end
|
||||
|
||||
|
||||
do
|
||||
vtf_dir_count = f:readUInt32()
|
||||
vtf_dir_offset = f:readUInt32() + 1 -- +1 to convert 0 indexed to 1 indexed
|
||||
|
||||
local old_pos = f:tell()
|
||||
f:seek(vtf_dir_offset)
|
||||
for i = 1, vtf_dir_count do
|
||||
local offset_pos = f:tell()
|
||||
local offset = f:readUInt32() + 1 -- +1 to convert 0 indexed to 1 indexed
|
||||
|
||||
local old_pos = f:tell()
|
||||
f:seek(offset)
|
||||
local dir = f:readString()
|
||||
table.insert(found_materialdirs, {offset_pos = offset_pos, offset = offset, dir = dir})
|
||||
table.insert(found_vmt_directories, {dir = dir})
|
||||
f:seek(old_pos)
|
||||
end
|
||||
table.sort(found_vmt_directories, function(a,b) return #a.dir>#b.dir end)
|
||||
f:seek(old_pos)
|
||||
end
|
||||
|
||||
f:skip(4 + 8) -- skin
|
||||
f:skip(8) -- bodypart
|
||||
f:skip(8) -- attachment
|
||||
f:skip(4 + 8) -- localnode
|
||||
f:skip(8) -- flex
|
||||
f:skip(8) -- flex rules
|
||||
f:skip(8) -- ik
|
||||
f:skip(8) -- mouth
|
||||
f:skip(8) -- localpose
|
||||
f:skip(4) -- render2dprop
|
||||
f:skip(8) -- keyvalues
|
||||
f:skip(8) -- iklock
|
||||
f:skip(12) -- mass
|
||||
f:skip(4) -- contents
|
||||
|
||||
do
|
||||
include_mdl_dir_count = f:readUInt32()
|
||||
include_mdl_dir_offset = f:readUInt32() + 1 -- +1 to convert 0 indexed to 1 indexed
|
||||
|
||||
local old_pos = f:tell()
|
||||
|
||||
f:seek(include_mdl_dir_offset)
|
||||
for i = 1, include_mdl_dir_count do
|
||||
local base_pos = f:tell()
|
||||
|
||||
f:skip(4)
|
||||
|
||||
local file_name_offset = f:readUInt32()
|
||||
local old_pos = f:tell()
|
||||
f:seek(base_pos + file_name_offset)
|
||||
table.insert(found_mdl_includes, {base_pos = base_pos, path = f:readString()})
|
||||
f:seek(old_pos)
|
||||
end
|
||||
|
||||
f:seek(old_pos)
|
||||
end
|
||||
|
||||
f:skip(4) -- virtual pointer
|
||||
|
||||
local anim_name_offset_pos = f:tell()
|
||||
|
||||
if VERBOSE or DEBUG_MDL then
|
||||
print(data.file_name, "MATERIAL DIRECTORIES:")
|
||||
PrintTable(found_materialdirs)
|
||||
print("============")
|
||||
print(data.file_name, "MATERIALS:")
|
||||
PrintTable(found_materials)
|
||||
print("============")
|
||||
print(data.file_name, "MDL_INCLUDES:")
|
||||
PrintTable(found_mdl_includes)
|
||||
print("============")
|
||||
end
|
||||
|
||||
do -- replace the mdl name (max size is 64 bytes)
|
||||
local newname = string.sub(dir .. data.file_name:lower(), 1, 63)
|
||||
f:seek(name_offset)
|
||||
f:write(newname .. string.rep("\0", 64-#newname))
|
||||
end
|
||||
|
||||
for i,v in ipairs(found_mdl_includes) do
|
||||
local file_name = (v.path:match(".+/(.+)") or v.path)
|
||||
local found = false
|
||||
|
||||
for _, info in ipairs(files) do
|
||||
if info.file_path == file_name then
|
||||
file_name = info.file_name
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
local path = "models/" .. dir .. file_name
|
||||
local newoffset = f:size() + 1
|
||||
f:seek(newoffset)
|
||||
f:writeString(path)
|
||||
f:seek(v.base_pos + 4)
|
||||
f:writeInt32(newoffset - v.base_pos)
|
||||
elseif ply == pac.LocalPlayer and not file.Exists(v.path, "GAME") then
|
||||
pac.Message(Color(255, 50, 50), "the model want to include ", v.path, " but it doesn't exist")
|
||||
end
|
||||
end
|
||||
|
||||
-- if we extend the mdl file with vmt directories we don't have to change any offsets cause nothing else comes after it
|
||||
if data.file_name == "model.mdl" then
|
||||
for i,v in ipairs(found_materialdirs) do
|
||||
local newoffset = f:size() + 1
|
||||
f:seek(newoffset)
|
||||
f:writeString(dir)
|
||||
f:seek(v.offset_pos)
|
||||
f:writeInt32(newoffset - 1) -- -1 to convert 1 indexed to 0 indexed
|
||||
end
|
||||
else
|
||||
local new_name = "models/" .. dir .. data.file_name:gsub("mdl$", "ani")
|
||||
local newoffset = f:size() + 1
|
||||
f:seek(newoffset)
|
||||
f:writeString(new_name)
|
||||
f:seek(anim_name_offset_pos)
|
||||
f:writeInt32(newoffset - 1) -- -1 to convert 1 indexed to 0 indexed
|
||||
end
|
||||
|
||||
local cursize = f:size()
|
||||
|
||||
-- Add nulls to align to 4 bytes
|
||||
local padding = 4-cursize%4
|
||||
if padding<4 then
|
||||
f:seek(cursize+1)
|
||||
f:write(string.rep("\0",padding))
|
||||
cursize = cursize + padding
|
||||
end
|
||||
|
||||
f:seek(size_offset)
|
||||
f:writeInt32(cursize)
|
||||
|
||||
data.buffer = f:getString()
|
||||
|
||||
if DEBUG_MDL then
|
||||
file.Write(data.file_name..".debug.new.dat", data.buffer)
|
||||
end
|
||||
|
||||
local crc = pac.StringStream()
|
||||
crc:writeInt32(tonumber(util.CRC(data.buffer)))
|
||||
data.crc = crc:getString()
|
||||
end
|
||||
end
|
||||
|
||||
for i, data in ipairs(files) do
|
||||
if data.file_name:EndsWith(".vmt") then
|
||||
local proxies = data.buffer:match('("?%f[%w_]P?p?roxies%f[^%w_]"?%s*%b{})')
|
||||
data.buffer = data.buffer:lower():gsub("\\", "/")
|
||||
|
||||
if proxies then
|
||||
data.buffer = data.buffer:gsub('("?%f[%w_]proxies%f[^%w_]"?%s*%b{})', proxies)
|
||||
end
|
||||
|
||||
if DEBUG_MDL or VERBOSE then
|
||||
print(data.file_name .. ":")
|
||||
end
|
||||
|
||||
for shader_param in pairs(texture_keys) do
|
||||
data.buffer = data.buffer:gsub('("?%$?%f[%w_]' .. shader_param .. '%f[^%w_]"?%s+"?)([^"%c]+)("?%s?)', function(l, vtf_path, r)
|
||||
if vtf_path == "env_cubemap" then
|
||||
return
|
||||
end
|
||||
|
||||
local new_path
|
||||
for _, info in ipairs(found_vmt_directories) do
|
||||
if info.dir == "" then continue end
|
||||
|
||||
new_path, count = vtf_path:gsub("^" .. info.dir:gsub("\\", "/"):lower(), dir)
|
||||
if count == 0 then
|
||||
new_path = nil
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not new_path then
|
||||
for _, info in ipairs(files) do
|
||||
local vtf_name = (vtf_path:match(".+/(.+)") or vtf_path)
|
||||
if info.file_name:EndsWith(".vtf") then
|
||||
if info.file_name == vtf_name .. ".vtf" or info.file_name == vtf_name then
|
||||
new_path = dir .. vtf_name
|
||||
break
|
||||
end
|
||||
elseif (info.file_name:EndsWith(".vmt") and l:StartWith("include")) then
|
||||
if info.file_name == vtf_name then
|
||||
new_path = "materials/" .. dir .. vtf_name
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not new_path then
|
||||
if not file.Exists("materials/" .. vtf_path .. ".vtf", "GAME") then
|
||||
if ply == pac.LocalPlayer then
|
||||
pac.Message(Color(255, 50, 50), "vmt ", data.file_name, " wants to find texture materials/", vtf_path, ".vtf for $", shader_param ," but it doesn't exist")
|
||||
print(data.buffer)
|
||||
end
|
||||
end
|
||||
new_path = vtf_path -- maybe it's a special texture? in that case i need to it
|
||||
end
|
||||
|
||||
if DEBUG_MDL or VERBOSE then
|
||||
print("\t" .. vtf_path .. " >> " .. new_path)
|
||||
end
|
||||
|
||||
return l .. new_path .. r
|
||||
end)
|
||||
end
|
||||
|
||||
local crc = pac.StringStream()
|
||||
crc:writeInt32(tonumber(util.CRC(data.buffer)))
|
||||
data.crc = crc:getString()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if skip_cache then
|
||||
id = id .. "_temp"
|
||||
end
|
||||
|
||||
local path = "pac3_cache/downloads/" .. id .. ".dat"
|
||||
local f = file.Open(path, "wb", "DATA")
|
||||
|
||||
if not f then
|
||||
onfail("unable to open file " .. path .. " for writing")
|
||||
|
||||
pac.Message(Color(255, 50, 50), "unable to write to ", path, " for some reason")
|
||||
if file.Exists(path, "DATA") then
|
||||
pac.Message(Color(255, 50, 50), "the file exists and its size is ", string.NiceSize(file.Size(path, "DATA")))
|
||||
pac.Message(Color(255, 50, 50), "is it locked or in use by something else?")
|
||||
else
|
||||
pac.Message(Color(255, 50, 50), "the file does not exist")
|
||||
pac.Message(Color(255, 50, 50), "are you out of disk space?")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
f:Write("GMAD")
|
||||
f:WriteByte(3)
|
||||
f:WriteLong(0)f:WriteLong(0)
|
||||
f:WriteLong(0)f:WriteLong(0)
|
||||
f:WriteByte(0)
|
||||
f:Write("name here")f:WriteByte(0)
|
||||
f:Write("description here")f:WriteByte(0)
|
||||
f:Write("author here")f:WriteByte(0)
|
||||
f:WriteLong(1)
|
||||
|
||||
for i, data in ipairs(files) do
|
||||
f:WriteLong(i)
|
||||
if data.file_name:EndsWith(".vtf") or data.file_name:EndsWith(".vmt") then
|
||||
f:Write("materials/" .. dir .. data.file_name:lower())f:WriteByte(0)
|
||||
else
|
||||
f:Write("models/" .. dir .. data.file_name:lower())f:WriteByte(0)
|
||||
end
|
||||
f:WriteLong(#data.buffer)f:WriteLong(0)
|
||||
f:WriteLong(data.crc)
|
||||
end
|
||||
|
||||
f:WriteLong(0)
|
||||
|
||||
for i, data in ipairs(files) do
|
||||
f:Write(data.buffer)
|
||||
end
|
||||
|
||||
f:Flush()
|
||||
|
||||
local content = file.Read("pac3_cache/downloads/" .. id .. ".dat", "DATA")
|
||||
f:Write(util.CRC(content))
|
||||
f:Close()
|
||||
end
|
||||
|
||||
local ok, tbl = game.MountGMA("data/pac3_cache/downloads/" .. id .. ".dat")
|
||||
|
||||
if not ok then
|
||||
onfail("failed to mount gma mdl")
|
||||
return
|
||||
end
|
||||
|
||||
for k,v in pairs(tbl) do
|
||||
if v:EndsWith("model.mdl") then
|
||||
if VERBOSE and not DEBUG_MDL then
|
||||
print("util.IsValidModel: ", tostring(util.IsValidModel(v)))
|
||||
|
||||
local dev = GetConVar("developer"):GetFloat()
|
||||
if dev == 0 then
|
||||
RunConsoleCommand("developer", "3")
|
||||
|
||||
timer.Simple(0.1, function()
|
||||
if CLIENT then
|
||||
ClientsideModel(v):Remove()
|
||||
else
|
||||
local ent = ents.Create("prop_dynamic")
|
||||
ent:SetModel(v)
|
||||
ent:Spawn()
|
||||
ent:Remove()
|
||||
end
|
||||
print("created and removed model")
|
||||
RunConsoleCommand("developer", "0")
|
||||
end)
|
||||
else
|
||||
if CLIENT then
|
||||
ClientsideModel(v):Remove()
|
||||
else
|
||||
local ent = ents.Create("prop_dynamic")
|
||||
ent:SetModel(v)
|
||||
ent:Spawn()
|
||||
ent:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cached_paths[url] = v
|
||||
callback(v)
|
||||
|
||||
file.Delete("pac3_cache/downloads/" .. id .. ".dat")
|
||||
break
|
||||
end
|
||||
end
|
||||
end, onfail)
|
||||
end
|
||||
Reference in New Issue
Block a user