--[[ | 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 shader_params = include("pac3/libraries/shader_params.lua") local mat_hdr_level = GetConVar("mat_hdr_level") local material_flags = { debug = bit.lshift(1, 0), no_debug_override = bit.lshift(1, 1), no_draw = bit.lshift(1, 2), use_in_fillrate_mode = bit.lshift(1, 3), vertexcolor = bit.lshift(1, 4), vertexalpha = bit.lshift(1, 5), selfillum = bit.lshift(1, 6), additive = bit.lshift(1, 7), alphatest = bit.lshift(1, 8), multipass = bit.lshift(1, 9), znearer = bit.lshift(1, 10), model = bit.lshift(1, 11), flat = bit.lshift(1, 12), nocull = bit.lshift(1, 13), nofog = bit.lshift(1, 14), ignorez = bit.lshift(1, 15), decal = bit.lshift(1, 16), envmapsphere = bit.lshift(1, 17), noalphamod = bit.lshift(1, 18), envmapcameraspace = bit.lshift(1, 19), basealphaenvmapmask = bit.lshift(1, 20), translucent = bit.lshift(1, 21), normalmapalphaenvmapmask = bit.lshift(1, 22), needs_software_skinning = bit.lshift(1, 23), opaquetexture = bit.lshift(1, 24), envmapmode = bit.lshift(1, 25), suppress_decals = bit.lshift(1, 26), halflambert = bit.lshift(1, 27), wireframe = bit.lshift(1, 28), allowalphatocoverage = bit.lshift(1, 29), ignore_alpha_modulation = bit.lshift(1, 30), } local function TableToFlags(flags, valid_flags) if isstring(flags) then flags = {flags} end local out = 0 for k, v in pairs(flags) do if v then local flag = valid_flags[v] or valid_flags[k] if not flag then error("invalid flag", 2) end out = bit.bor(out, tonumber(flag)) end end return out end local function FlagsToTable(flags, valid_flags) if not flags then return valid_flags.default_valid_flag end local out = {} for k, v in pairs(valid_flags) do if bit.band(flags, v) > 0 then out[k] = true end end return out end local shader_name_translate = { vertexlitgeneric = "3d", unlitgeneric = "2d", eyerefract = "eye refract", } for shader_name, groups in pairs(shader_params.shaders) do for group_name, base_group in pairs(shader_params.base) do if groups[group_name] then for k,v in pairs(base_group) do if not groups[group_name][k] then groups[group_name][k] = v end end else groups[group_name] = base_group end end end for shader_name, groups in pairs(shader_params.shaders) do local temp = CreateMaterial(tostring({}), shader_name, {}) local BUILDER, PART = pac.PartTemplate("base") PART.ClassName = "material_" .. (shader_name_translate[shader_name] or shader_name) PART.Description = shader_name PART.ProperColorRange = true if shader_name == "vertexlitgeneric" then PART.FriendlyName = "material" PART.Group = {'modifiers', 'model', 'entity'} else PART.FriendlyName = "material " .. shader_name PART.Group = "advanced" end PART.Icon = "icon16/paintcan.png" BUILDER:StartStorableVars() BUILDER:SetPropertyGroup("generic") -- move this to tools or something BUILDER:GetSet("LoadVmt", "", {editor_panel = "material"}) function PART:SetLoadVmt(path) if not path or path == "" then return end local str = file.Read("materials/" .. path .. ".vmt", "GAME") if not str then return end local vmt = util.KeyValuesToTable(str) local shader = str:match("^(.-)%{"):gsub("%p", ""):Trim() for k,v in pairs(self:GetVars()) do local param = PART.ShaderParams[k] if param and param.default ~= nil then self["Set" .. k](self, param.default) end if param and param.type == "texture" then self["Set" .. k](self, "") end end print(str) print("======") PrintTable(vmt) print("======") for k,v in pairs(vmt) do if k:StartWith("$") then k = k:sub(2) end local func = self["Set" .. k] if func then local info = PART.ShaderParams[k] if isstring(v) then if v:find("[", nil, true) then v = Vector(v:gsub("[%[%]]", ""):gsub("%s+", " "):Trim()) if isnumber(info.default) then v = v.x end elseif v:find("{", nil, true) then v = Vector(v:gsub("[%{%}]", ""):gsub("%s+", " "):Trim()) if info.type == "color" then v = v / 255 end end end if isnumber(v) then if info.type == "bool" or info.is_flag then v = v == 1 end end func(self, v) else pac.Message("cannot convert material parameter " .. k) end end end BUILDER:GetSet("MaterialOverride", "all", {enums = function(self, str) local materials = {} if pace.current_part:GetOwner():IsValid() then materials = pace.current_part:GetOwner():GetMaterials() end table.insert(materials, "all") local tbl = {} for _, v in ipairs(materials) do v = v:match(".+/(.+)") or v tbl[v] = v:lower() end return tbl end}) local function update_submaterial(self, remove, parent) pac.RunNextFrameSimple(function() if not IsValid(self) and not remove then return end local name = self:GetName() for _, part in ipairs(self:GetRootPart():GetChildrenList()) do if part.GetMaterials then for _, path in ipairs(part.Materials:Split(";")) do if path == name then part:SetMaterials(part.Materials) break end end end end local str = self.MaterialOverride parent = parent or self:GetParent() local num = 0 if parent:IsValid() then if tonumber(str) then num = tonumber(str) elseif str ~= "all" and parent:GetOwner():IsValid() then for i, v in ipairs(parent:GetOwner():GetMaterials()) do if (v:match(".+/(.+)") or v):lower() == str:lower() then num = i break end end end parent.material_override = parent.material_override or {} parent.material_override[num] = parent.material_override[num] or {} for _, stack in pairs(parent.material_override) do for i, v in ipairs(stack) do if v == self then table.remove(stack, i) break end end end if not remove then table.insert(parent.material_override[num], self) end end end) end function PART:Initialize() self.translation_vector = Vector() self.rotation_angle = Angle(0, 0, 0) end function PART:GetNiceName() local path = "" if shader_name == "refract" then path = self:Getnormalmap() elseif shader_name == "eyerefract" then path = self:Getiris() else path = self:Getbasetexture() end path = path:gsub("%%(..)", function(char) local num = tonumber("0x" .. char) if num then return string.char(num) end end) local name = ("/".. path):match(".+/(.-)%.") or ("/".. path):match(".+/(.+)") local nice_name = (pac.PrettifyName(name) or "no texture") .. " | " .. shader_name return nice_name end function PART:SetMaterialOverride(num) self.MaterialOverride = num update_submaterial(self) end function PART:OnThink() if self:GetOwner():IsValid() then local materials = self:GetOwner():GetMaterials() if materials and #materials ~= self.last_material_count then update_submaterial(self) self.last_material_count = #materials end end end PART.ShaderParams = {} PART.TransformVars = {} local sorted_groups = {} for k, v in pairs(groups) do table.insert(sorted_groups, {k = k, v = v}) end table.sort(sorted_groups, function(a, b) return a.k:lower() < b.k:lower() end) for _, v in ipairs(sorted_groups) do local group, params = v.k, v.v local sorted_params = {} for k, v in pairs(params) do table.insert(sorted_params, {k = k, v = v}) end table.sort(sorted_params, function(a, b) return a.k:lower() < b.k:lower() end) for _, v in ipairs(sorted_params) do local key, info = v.k, v.v PART.ShaderParams[key] = info if info.is_flag and group == "generic" then BUILDER:SetPropertyGroup("flags") else BUILDER:SetPropertyGroup(group) end if info.default == nil then if info.type == "vec3" then info.default = Vector(0,0,0) elseif info.type == "color" then info.default = Vector(1,1,1) elseif info.type == "float" then info.default = 0 elseif info.type == "vec2" then info.default = Vector(0, 0) end end local property_name = key local description = (info.description or "") .. " ($" .. key .. ")" if info.type == "matrix" then local position_key = property_name .. "Position" local scale_key = property_name .. "Scale" local angle_key = property_name .. "Angle" local angle_center_key = property_name .. "AngleCenter" local friendly_name = info.friendly:gsub("Transform", "") BUILDER:GetSet(position_key, Vector(0, 0, 0), {editor_friendly = friendly_name .. "Position", description = description}) BUILDER:GetSet(scale_key, Vector(1, 1, 1), {editor_friendly = friendly_name .. "Scale", description = description}) BUILDER:GetSet(angle_key, 0, {editor_panel = "number", editor_friendly = friendly_name .. "Angle", description = description}) BUILDER:GetSet(angle_center_key, Vector(0.5, 0.5, 0), {editor_friendly = friendly_name .. "AngleCenter", description = description}) PART.TransformVars[position_key] = true PART.TransformVars[scale_key] = true PART.TransformVars[angle_key] = true PART.TransformVars[angle_center_key] = true local shader_key = "$" .. key local function setup_matrix(self) self.matrix = self.matrix or Matrix() self.matrix:Identity() self.matrix:Translate(self.translation_vector) self.matrix:Translate(self[angle_center_key]) self.matrix:Rotate(self.rotation_angle) self.matrix:Translate(-self[angle_center_key]) self.matrix:SetScale(self[scale_key]) end PART["Set" .. position_key] = function(self, vec) self[position_key] = vec self.translation_vector.x = self[position_key].x self.translation_vector.y = self[position_key].y setup_matrix(self) self:GetRawMaterial():SetMatrix(shader_key, self.matrix) end PART["Set" .. scale_key] = function(self, vec) self[scale_key] = vec setup_matrix(self) self:GetRawMaterial():SetMatrix(shader_key, self.matrix) end PART["Set" .. angle_key] = function(self, num) self[angle_key] = num self.rotation_angle.y = self[angle_key]*360 setup_matrix(self) self:GetRawMaterial():SetMatrix(shader_key, self.matrix) end PART["Set" .. angle_center_key] = function(self, vec) self[angle_center_key] = vec setup_matrix(self) self:GetRawMaterial():SetMatrix(shader_key, self.matrix) end elseif info.type == "texture" then local getnohdr = "Get" .. property_name .. "NoHDR" if info.partial_hdr then BUILDER:GetSet(property_name .. "NoHDR", false, { editor_friendly = info.friendly .. " No HDR", description = "Disables bound param when HDR is enabled", }) end info.default = info.default or "" BUILDER:GetSet(property_name, info.default, { editor_panel = "textures", editor_friendly = info.friendly, description = description, shader_param_info = info, }) local key = "$" .. key PART["Set" .. property_name .. "NoHDR"] = function(self, val) self[property_name .. "NoHDR"] = val PART["Set" .. property_name](self, self[property_name]) end PART["Set" .. property_name] = function(self, val) self[property_name] = val if val == "" or info.partial_hdr and mat_hdr_level:GetInt() > 0 and self[getnohdr](self) then self:GetRawMaterial():SetUndefined(key) self:GetRawMaterial():Recompute() else if not pac.resource.DownloadTexture(val, function(tex, frames) if frames then self.vtf_frame_limit = self.vtf_frame_limit or {} self.vtf_frame_limit[property_name] = frames end self:GetRawMaterial():SetTexture(key, tex) end, self:GetPlayerOwner()) then self:GetRawMaterial():SetTexture(key, val) local texture = self:GetRawMaterial():GetTexture(key) if texture then self.vtf_frame_limit = self.vtf_frame_limit or {} self.vtf_frame_limit[property_name] = texture:GetNumAnimationFrames() end end end end else BUILDER:GetSet(property_name, info.default, { editor_friendly = info.friendly, enums = info.enums, description = description, editor_sensitivity = (info.type == "vec3" or info.type == "color") and 0.25 or nil, editor_panel = (info.type == "color" and "color2") or (property_name == "model" and "boolean") or nil, editor_round = info.type == "integer", }) local flag_key = key local key = "$" .. key if isnumber(info.default) then PART["Set" .. property_name] = function(self, val) self[property_name] = val local mat = self:GetRawMaterial() mat:SetFloat(key, val) if info.recompute then mat:Recompute() end end if property_name:lower():find("frame") then PART["Set" .. property_name] = function(self, val) self[property_name] = val if self.vtf_frame_limit and info.linked and self.vtf_frame_limit[info.linked] then self:GetRawMaterial():SetInt(key, math.abs(val)%self.vtf_frame_limit[info.linked]) else self:GetRawMaterial():SetInt(key, val) end end end elseif isbool(info.default) then if info.is_flag then PART["Set" .. property_name] = function(self, val) self[property_name] = val local mat = self:GetRawMaterial() local tbl = FlagsToTable(mat:GetInt("$flags"), material_flags) tbl[flag_key] = val mat:SetInt("$flags", TableToFlags(tbl, material_flags)) mat:Recompute() end else PART["Set" .. property_name] = function(self, val) if isvector(val) then val = (val == Vector(1,1,1)) and true or false end self[property_name] = val local mat = self:GetRawMaterial() mat:SetInt(key, val and 1 or 0) if info.recompute then mat:Recompute() end end end elseif isvector(info.default) or info.type == "vec3" or info.type == "vec2" then PART["Set" .. property_name] = function(self, val) if isstring(val) then val = Vector() end self[property_name] = val local mat = self:GetRawMaterial() mat:SetVector(key, val) if info.recompute then mat:Recompute() end end elseif info.type == "vec4" then -- need vec4 type PART["Set" .. property_name] = function(self, val) local x,y,z,w if isstring(val) then x,y,z,w = unpack(val:Split(" ")) x = tonumber(x) or 0 y = tonumber(y) or 0 z = tonumber(z) or 0 w = tonumber(w) or 0 elseif isvector(val) then x,y,z = val.x, val.y, val.z w = 0 else x, y, z, w = 0, 0, 0, 0 end self[property_name] = ("%f %f %f %f"):format(x, y, z, w) local mat = self:GetRawMaterial() mat:SetString(key, ("[%f %f %f %f]"):format(x,y,z,w)) if info.recompute then mat:Recompute() end end end end end end BUILDER:EndStorableVars() function PART:GetRawMaterial() if not self.Materialm then self.material_name = tostring({}) local mat = pac.CreateMaterial(self.material_name, shader_name, {}) self.Materialm = mat for k,v in pairs(self:GetVars()) do if PART.ShaderParams[k] and PART.ShaderParams[k].default ~= nil then self["Set" .. k](self, v) end end end return self.Materialm end function PART:OnParent(parent) update_submaterial(self) end function PART:OnRemove() update_submaterial(self, true) end function PART:OnUnParent(parent) update_submaterial(self, true, parent) end function PART:OnHide() update_submaterial(self, true) end function PART:OnShow() update_submaterial(self) end BUILDER:Register() end