This commit is contained in:
lifestorm
2024-08-04 22:55:00 +03:00
parent 0e770b2b49
commit 94063e4369
7342 changed files with 1718932 additions and 14 deletions

View 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()

View 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)

View 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)

View 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)

View 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

View 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

View 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

View 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

View 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)

View 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