--------------------------------------------------------------------------------
-- Clientside utility functions
--------------------------------------------------------------------------------
local bitmap_font_1 = {
    [10] = {
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0},
    ["."] = {
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,0,0,
        0,0,1,0},
    [1] = {
        0,0,0,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1},
    [2] = {
        1,1,1,1,
        0,0,0,1,
        0,0,0,1,
        1,1,1,1,
        1,0,0,0,
        1,0,0,0,
        1,1,1,1},
    [3] = {
        1,1,1,1,
        0,0,0,1,
        0,0,0,1,
        1,1,1,1,
        0,0,0,1,
        0,0,0,1,
        1,1,1,1},
    [4] = {
        1,0,0,1,
        1,0,0,1,
        1,0,0,1,
        1,1,1,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1},
    [5] = {
        1,1,1,1,
        1,0,0,0,
        1,0,0,0,
        1,1,1,1,
        0,0,0,1,
        0,0,0,1,
        1,1,1,1},
    [6] = {
        1,1,1,1,
        1,0,0,0,
        1,0,0,0,
        1,1,1,1,
        1,0,0,1,
        1,0,0,1,
        1,1,1,1},
    [7] = {
        1,1,1,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1},
    [8] = {
        1,1,1,1,
        1,0,0,1,
        1,0,0,1,
        1,1,1,1,
        1,0,0,1,
        1,0,0,1,
        1,1,1,1},
    [9] = {
        1,1,1,1,
        1,0,0,1,
        1,0,0,1,
        1,1,1,1,
        0,0,0,1,
        0,0,0,1,
        0,0,0,1},
    [0] = {
        1,1,1,1,
        1,0,0,1,
        1,0,0,1,
        1,0,0,1,
        1,0,0,1,
        1,0,0,1,
        1,1,1,1},
}



--------------------------------------------------------------------------------
-- Draw bitmap digit
function Metrostroi.DrawClockDigit(cx,cy,scale,digit)
    local bitmap = bitmap_font_1[digit]
    if not bitmap then return end

    local w=12*scale
    local p=8*scale
    for i=1,4*7 do
        local x = (i-1)%4
        local y = math.floor((i-1)/4)
        if bitmap[i] == 1 then
            for z=1,6,1 do
                surface.SetDrawColor(Color(255,60,0,math.max(0,30-1*z*z)))
                surface.DrawRect(cx+x*w-z*scale, cy+y*w-z*scale, p+2*z*scale, p+2*z*scale)
            end

            surface.SetDrawColor(Color(255,240,0,255))
            surface.DrawRect(cx+x*w, cy+y*w, p, p)
        end
    end
end





function Metrostroi.PositionFromPanel(panel,button_id_or_vec,z,x,y,train)
    local self = train or ENT
    local panel = self.ButtonMap[panel]
    if not panel then return Vector(0,0,0) end
    if not panel.buttons then return Vector(0,0,0) end

    -- Find button or read position
    local vec
    if type(button_id_or_vec) == "string" then
        local button
        for k,v in pairs(panel.buttons) do
            if v.ID == button_id_or_vec then
                button = v
                break
            end
        end
        vec = Vector(button.x + (button.radius and 0 or (button.w or 0)/2)+(x or 0),button.y + (button.radius and 0 or (button.h or 0)/2)+(y or 0),z or 0)
    else
        vec = button_id_or_vec
    end

    -- Convert to global coords
    vec.y = -vec.y
    vec:Rotate(panel.ang)
    return panel.pos + vec * panel.scale
end

function Metrostroi.AngleFromPanel(panel,ang,train)
    local self = train or ENT
    local panel = self.ButtonMap[panel]
    if not panel then return Vector(0,0,0) end
    local true_ang = panel.ang + Angle(0,0,0)
    if type(ang) == "Angle" then
        true_ang:RotateAroundAxis(panel.ang:Forward(),ang.pitch)
        true_ang:RotateAroundAxis(panel.ang:Right(),ang.yaw)
        true_ang:RotateAroundAxis(panel.ang:Up(),ang.roll)
    else
        true_ang:RotateAroundAxis(panel.ang:Up(),ang or -90)
    end
    return true_ang
end

Metrostroi.PrecacheModels = Metrostroi.PrecacheModels or {}
function Metrostroi.GenerateClientProps()
    local self = ENT
    if not self.AutoAnimNames then self.AutoAnimNames = {} end
    local ret = "self.table = {\n"
    --local reti = 0
    for id, panel in pairs(self.ButtonMap) do
        if not panel.buttons then continue end
        if not panel.props then panel.props = {} end
        for name, buttons in pairs(panel.buttons) do
            --if reti > 8 then reti=0; ret=ret.."\n" end

            if buttons.tooltipFunc then
                local func = buttons.tooltipFunc
                buttons.tooltipState = function(ent)
                    local str = func(ent)
                    if not str then return "" end
                    return "\n["..str:gsub("\n","]\n[").."]"
                end
            elseif buttons.varTooltip then
                local states = buttons.states or {"Train.Buttons.Off","Train.Buttons.On"}
                local count = (#states-1)
                buttons.tooltipState = function(ent)
                    return Format("\n[%s]",Metrostroi.GetPhrase(states[math.floor(buttons.varTooltip(ent)*count+0.5)+1]):gsub("\n","]\n["))
                end
            elseif buttons.var then
                local var = buttons.var
                local st1,st2 = "Train.Buttons.Off","Train.Buttons.On"
                if buttons.states then
                    st1 = buttons.states[1]
                    st2 = buttons.states[2]
                end
                buttons.tooltipState = function(ent)
                    return Format("\n[%s]",Metrostroi.GetPhrase(ent:GetPackedBool(var) and st2 or st1):gsub("\n","]\n["))
                end
            end
            if buttons.model then
                local config = buttons.model
                local name = config.name or buttons.ID

                if config.tooltipFunc then
                    local func = config.tooltipFunc
                    buttons.tooltipState = function(ent)
                        local str = func(ent)
                        if not str then return "" end
                        return "\n["..str:gsub("\n","]\n[").."]"
                    end
                elseif config.varTooltip then
                    local states = config.states or {"Train.Buttons.Off","Train.Buttons.On"}
                    local count = (#states-1)
                    local func = config.getfunc
                    if config.varTooltip == true and func then
                        buttons.tooltipState = function(ent)
                            return Format("\n[%s]",Metrostroi.GetPhrase(states[math.floor(config.func(ent)*count+0.5)+1]):gsub("\n","]\n["))
                        end
                    elseif config.varTooltip ~= true then
                        buttons.tooltipState = function(ent)
                            return Format("\n[%s]",Metrostroi.GetPhrase(states[math.floor(config.varTooltip(ent)*count+0.5)+1]):gsub("\n","]\n["))
                        end
                    end
                elseif (config.var and (not config.noTooltip and not buttons.ID:find("Set") or config.noTooltip==false)) then
                    local var = config.var
                    local st1,st2 = "Train.Buttons.Off","Train.Buttons.On"
                    if config.states then
                        st1 = config.states[1]
                        st2 = config.states[2]
                    end
                    buttons.tooltipState = function(ent)
                        return Format("\n[%s]",Metrostroi.GetPhrase(ent:GetPackedBool(var) and st2 or st1))
                    end
                end
                if config.model then
                    table.insert(panel.props,name)
                    self.ClientProps[name] = {
                        model = config.model or "models/metrostroi/81-717/button07.mdl",
                        pos = Metrostroi.PositionFromPanel(id,config.pos or buttons.ID,(config.z or 0.2),(config.x or 0),(config.y or 0)),
                        ang = Metrostroi.AngleFromPanel(id,config.ang),
                        color = config.color,
                        colora = config.colora,
                        skin = config.skin or 0,
                        config = config,
                        cabin = config.cabin,
                        hide = panel.hide or config.hide,
                        hideseat = panel.hideseat or config.hideseat,
                        bscale = config.bscale,
                        scale = config.scale,
                    }
                    --[[if config.varTooltip then
                        local states = config.states or {"Train.Buttons.On","Train.Buttons.Off"}
                        local count = (#states-1)
                        buttons.tooltipState = function(ent)
                            local text = "\n["
                            for i,t in ipairs(states) do
                                if i == #states then
                                    text = text..Metrostroi.GetPhrase(t)
                                else
                                    text = text..Metrostroi.GetPhrase(t).."|"
                                end
                            end
                            text = text.."]"
                            return text,states[math.floor(config.varTooltip(ent)+0.5)*count+1]
                        end
                    elseif (config.var and not config.noTooltip) then
                        local var = config.var
                        local st1,st2 = "Train.Buttons.On","Train.Buttons.Off"
                        if config.states then
                            st1 = config.states[1]
                            st2 = config.states[2]
                        end
                        buttons.tooltipState = function(ent)
                            return Format("\n[%s|%s]",Metrostroi.GetPhrase(st1),Metrostroi.GetPhrase(st2)),ent:GetPackedBool(var) and st1 or st2
                        end
                    end]]
                    if config.var then
                        local var = config.var
                        local vmin, vmax = config.vmin or 0,config.vmax or 1
                        local min,max = config.min or 0,config.max or 1
                        local speed,damping,stickyness = config.speed or 16,config.damping or false,config.stickyness or nil
                        local func = config.getfunc
                        local i
                        if func then
                            if config.disable then
                                i = table.insert(self.AutoAnims, function(ent)
                                    ent:Animate(name,func(ent,vmin,vmax,var),min,max,speed,damping,stickyness)
                                    ent:HideButton(config.disable,ent:GetPackedBool(var))
                                end)
                            elseif config.disableinv then
                                i = table.insert(self.AutoAnims, function(ent)
                                    ent:Animate(name,func(ent,vmin,vmax,var),min,max,speed,damping,stickyness)
                                    ent:HideButton(config.disableinv,not ent:GetPackedBool(var))
                                end)
                            elseif config.disableoff and config.disableon then
                                i = table.insert(self.AutoAnims, function(ent)
                                    ent:Animate(name,func(ent,vmin,vmax,var),min,max,speed,damping,stickyness)
                                    ent:HideButton(config.disableoff,ent:GetPackedBool(var))
                                    ent:HideButton(config.disableon,not ent:GetPackedBool(var))
                                end)
                            elseif config.disablevar then
                                i = table.insert(self.AutoAnims, function(ent)
                                    ent:HideButton(name,ent:GetPackedBool(config.disablevar))
                                    ent:Animate(name,func(ent,vmin,vmax,var),min,max,speed,damping,stickyness)
                                end)
                            else
                                i = table.insert(self.AutoAnims, function(ent) ent:Animate(name,func(ent,vmin,vmax),min,max,speed,damping,stickyness) end)
                            end
                        else
                            if config.disable then
                                i = table.insert(self.AutoAnims, function(ent)
                                    ent:Animate(name,ent:GetPackedBool(var) and vmax or vmin,min,max,speed,damping,stickyness)
                                    ent:HideButton(config.disable,ent:GetPackedBool(var))
                                end)
                            elseif config.disableinv then
                                i = table.insert(self.AutoAnims, function(ent)
                                    ent:Animate(name,ent:GetPackedBool(var) and vmax or vmin,min,max,speed,damping,stickyness)
                                    ent:HideButton(config.disableinv,not ent:GetPackedBool(var))
                                end)
                            elseif config.disableoff and config.disableon then
                                i = table.insert(self.AutoAnims, function(ent)
                                    ent:Animate(name,ent:GetPackedBool(var) and vmax or vmin,min,max,speed,damping,stickyness)
                                    ent:HideButton(config.disableoff,ent:GetPackedBool(var))
                                    ent:HideButton(config.disableon,not ent:GetPackedBool(var))
                                end)
                            elseif config.disablevar then
                                i = table.insert(self.AutoAnims, function(ent)
                                    ent:HideButton(name,ent:GetPackedBool(config.disablevar))
                                    ent:Animate(name,ent:GetPackedBool(var) and vmax or vmin,min,max,speed,damping,stickyness)
                                end)
                            else
                                i = table.insert(self.AutoAnims, function(ent) ent:Animate(name,ent:GetPackedBool(var) and vmax or vmin,min,max,speed,damping,stickyness) end)
                            end
                        end
                        self.AutoAnimNames[i] = name
                    end
                end
                if config.sound or config.sndvol and config.var then
                    local id = config.sound or config.var
                    local sndid = config.sndid or buttons.ID
                    local vol,pitch,min,max = config.sndvol, config.sndpitch,config.sndmin,config.sndmax
                    local func,snd = config.getfunc, config.snd
                    local vmin, vmax = config.vmin or 0,config.vmax or 1
                    local var = config.var
                    local ang = config.sndang
                    --if func then
                        --self.ClientSounds[id] = {sndid,function(ent,var) return snd(func(ent,vmin,vmax),var) end,vol or 1,pitch or 1,min or 100,max or 1000,ang or Angle(0,0,0)}
                    --else
                    if not self.ClientSounds[id] then self.ClientSounds[id] = {} end
                    table.insert(self.ClientSounds[id],{sndid,function(ent,var) return snd(var > 0,var) end,vol or 1,pitch or 1,min or 100,max or 1000,ang or Angle(0,0,0)})
                    --end
                end
                if config.plomb then
                    local pconfig = config.plomb
                    local pname = name.."_pl"
                    if pconfig.model then
                        table.insert(panel.props,pname)
                        self.ClientProps[pname] = {
                            model = pconfig.model,
                            pos = Metrostroi.PositionFromPanel(id,config.pos or buttons.ID,(config.z or 0.2)+(pconfig.z or 0.2),(config.x or 0)+(pconfig.x or 0),(config.y or 0)+(pconfig.y or 0)),
                            ang = Metrostroi.AngleFromPanel(id,pconfig.ang or config.ang),
                            color = pconfig.color or pconfig.color,
                            skin = pconfig.skin or config.skin or 0,
                            config = pconfig,
                            cabin = pconfig.cabin,
                            hide = panel.hide or config.hide,
                            hideseat = panel.hideseat or config.hideseat,
                            bscale = pconfig.bscale or config.bscale,
                            scale = pconfig.scale or config.scale,
                        }
                    end
                    if pconfig.var then
                        local var = pconfig.var
                        if pconfig.model then
                            local i = table.insert(self.AutoAnims, function(ent)
                                ent:SetCSBodygroup(pname,1,ent:GetPackedBool(var) and 0 or 1)
                            end)
                            self.AutoAnimNames[i] = pname
                        end
                        local id,tooltip = buttons.ID,buttons.tooltip
                        local pid,ptooltip = pconfig.ID,pconfig.tooltip
                        buttons.plombed = function(ent)
                            if ent:GetPackedBool(var) then
                                return Format("%s\n%s",buttons.tooltip,Metrostroi.GetPhrase("Train.Buttons.Sealed") or "Plombed"),pid,Color(255,150,150),true
                            else
                                return buttons.tooltip,id,false
                            end
                        end
                    end
                end
                if config.lamp then
                    local lconfig = config.lamp
                    local lname = name.."_lamp"
                    table.insert(panel.props,lname)
                    self.ClientProps[lname] = {
                        model = lconfig.model or "models/metrostroi/81-717/button07.mdl",
                        pos = Metrostroi.PositionFromPanel(id,config.pos or buttons.ID,(config.z or 0.2)+(lconfig.z or 0.2),(config.x or 0)+(lconfig.x or 0),(config.y or 0)+(lconfig.y or 0)),
                        ang = Metrostroi.AngleFromPanel(id,lconfig.ang or config.ang),
                        color = lconfig.color or config.color,
                        skin = lconfig.skin or config.skin or 0,
                        config = lconfig,
                        cabin = lconfig.cabin,
                        igrorepanel = true,
                        hide = panel.hide or config.hide,
                        hideseat = panel.hideseat or config.hideseat,
                        bscale = lconfig.bscale or config.bscale,
                        scale = lconfig.scale or config.scale,
                    }
                    if lconfig.anim then
                        local i = table.insert(self.AutoAnims, function(ent)
                            ent:AnimateFrom(lname,name)
                        end)
                        self.AutoAnimNames[i] = lname
                    end

                    if lconfig.lcolor then
                        self.Lights[lname] = { "headlight",
                            Metrostroi.PositionFromPanel(id,config.pos or buttons.ID,(config.z or 0.2)+(lconfig.z or 0.2)+(lconfig.lz or 0.2),(config.x or 0)+(lconfig.x or 0)+(lconfig.lx or 0),(config.y or 0)+(lconfig.y or 0)+(lconfig.ly or 0)),
                            Metrostroi.AngleFromPanel(id,lconfig.lang or lconfig.ang or config.ang)+Angle(90,0,0),
                            lconfig.lcolor,farz = lconfig.lfar or 8,nearz = lconfig.lnear or 1,shadows = lconfig.lshadows or 1,brightness = lconfig.lbright or 1,fov = lconfig.lfov,texture=lconfig.ltex or "effects/flashlight/soft",panellight=true,
                            hidden = lname,
                        }
                        --[[self.ClientProps[lname.."TEST"] = {
                            model = "models/metrostroi_train/81-703/cabin_cran_334.mdl",
                            pos = Metrostroi.PositionFromPanel(id,config.pos or buttons.ID,(config.z or 0.2)+(lconfig.z or 0.2)+(lconfig.lz or 0.2),(config.x or 0)+(lconfig.x or 0)+(lconfig.lx or 0),(config.y or 0)+(lconfig.y or 0)+(lconfig.ly or 0)),
                            ang = Metrostroi.AngleFromPanel(id,lconfig.lang or lconfig.ang or config.ang)+Angle(-180,180,0),
                            scale=0.1,
                        }]]
                        --[[table.insert(self.AutoAnims, function(ent)
                            ent:AnimateFrom(lname,name)
                        end)]]
                    end
                    if lconfig.var then
                        --ret=ret.."\""..lconfig.var.."\","
                        --reti = reti + 1
                        local var,animvar = lconfig.var,lname.."_anim"
                        local min,max = lconfig.min or 0,lconfig.max or 1
                        local speed = lconfig.speed or 10
                        local func = lconfig.getfunc
                        local light = lconfig.lcolor
                        if func then
                            table.insert(self.AutoAnims, function(ent)
                                local val = ent:Animate(animvar,func(ent,min,max,var),0,1,speed,false)
                                ent:ShowHideSmooth(lname,val)
                                if light then ent:SetLightPower(lname,val>0,val) end
                            end)
                        else
                            local i = table.insert(self.AutoAnims, function(ent)
                                --print(lname,ent.SmoothHide[lname])
                                local val = ent:Animate(animvar,ent:GetPackedBool(var) and max or min,0,1,speed,false)
                                ent:ShowHideSmooth(lname,val)
                                if light then ent:SetLightPower(lname,val>0,val) end
                            end)
                        end
                    end
                end
                if config.lamps then
                    for k,lconfig in ipairs(config.lamps) do
                        local lname = name.."_lamp"..k
                        table.insert(panel.props,lname)
                        self.ClientProps[lname] = {
                            model = lconfig.model or "models/metrostroi/81-717/button07.mdl",
                            pos = Metrostroi.PositionFromPanel(id,config.pos or buttons.ID,(config.z or 0.2)+(lconfig.z or 0.2),(config.x or 0)+(lconfig.x or 0),(config.y or 0)+(lconfig.y or 0)),
                            ang = Metrostroi.AngleFromPanel(id,lconfig.ang or config.ang),
                            color = lconfig.color or config.color,
                            skin = lconfig.skin or config.skin or 0,
                            config = lconfig,
                            cabin = lconfig.cabin,
                            igrorepanel = true,
                            hide = panel.hide or config.hide,
                            hideseat = panel.hideseat or config.hideseat,
                            bscale = lconfig.bscale or config.bscale,
                            scale = lconfig.scale or config.scale,
                        }
                        if lconfig.var then
                            --ret=ret.."\""..lconfig.var.."\","
                            --reti = reti + 1
                            local var,animvar = lconfig.var,lname.."_anim"
                            local min,max = lconfig.min or 0,lconfig.max or 1
                            local speed = lconfig.speed or 10
                            local func = lconfig.getfunc
                            if func then
                                table.insert(self.AutoAnims, function(ent)
                                    local val = ent:Animate(animvar,func(ent,min,max,var),0,1,speed,false)
                                    ent:ShowHideSmooth(lname,val)
                                end)
                            else
                                table.insert(self.AutoAnims, function(ent)
                                    --print(lname,ent.SmoothHide[lname])
                                    local val = ent:Animate(animvar,ent:GetPackedBool(var) and max or min,0,1,speed,false)
                                    ent:ShowHideSmooth(lname,val)
                                end)
                            end
                        end
                    end
                end
                if config.sprite then
                    local sconfig = config.sprite

                    local hideName = sconfig.hidden or config.lamp and name.."_lamp" or name
                    self.Lights[sconfig.lamp or name] = { sconfig.glow and "glow" or "light",
                        Metrostroi.PositionFromPanel(id,config.pos or buttons.ID,(config.z or 0.5)+(sconfig.z or 0.2),(config.x or 0)+(sconfig.x or 0),(config.y or 0)+(sconfig.y or 0)),
                        Metrostroi.AngleFromPanel(id, sconfig.ang or config.ang)+Angle(90,0,0),
                        sconfig.color or sconfig.color,
                        brightness = sconfig.bright,texture=sconfig.texture or "sprites/light_glow02",scale=sconfig.scale or 0.02,vscale=sconfig.vscale,
                        size = sconfig.size,
                        hidden = hideName,
                        aa = sconfig.aa,panel = sconfig.panel ~= false,
                    }
                    local i
                    if sconfig.getfunc then
                        local func = sconfig.getfunc
                        i = table.insert(self.AutoAnims, function(ent)
                            local val = func(ent)
                            ent:SetLightPower(name,not ent.Hidden[hideName] and val>0,val)
                        end)
                    elseif sconfig.var then
                        --ret=ret.."\""..lconfig.var.."\","
                        --reti = reti + 1
                        local var,animvar = sconfig.var,name.."_sanim"
                        local speed = sconfig.speed or 10
                        i = table.insert(self.AutoAnims, function(ent)
                            local val = ent:Animate(animvar,ent:GetPackedBool(var) and 1 or 0,0,1,speed,false)
                            ent:SetLightPower(name,val>0,val)
                        end)
                    elseif sconfig.lamp then
                        local lightName = sconfig.lamp
                        i = table.insert(self.AutoAnims, function(ent)
                            local val = ent.Anims[lightName] and ent.Anims[lightName].value or 0
                            ent:SetLightPower(lightName,val>0,val)
                        end)
                    elseif config.lamp and config.lamp.var then
                        local lname = name.."_lamp"
                        local lightName = lname.."_anim"
                        i = table.insert(self.AutoAnims, function(ent)
                            local val = ent.Anims[lightName] and ent.Anims[lightName].value or 0
                            ent:SetLightPower(name,val>0,val)
                        end)
                    end
                    if not i then
                        ErrorNoHalt("Bad sprite "..name.."/"..hideName..", no controlable function...\n")
                    else
                        self.AutoAnimNames[i] = hideName
                    end
                end
                if config.labels then
                    for k,aconfig in ipairs(config.labels) do
                        local aname = name.."_label"..k
                        table.insert(panel.props,aname)
                        self.ClientProps[aname] = {
                            model = aconfig.model or "models/metrostroi/81-717/button07.mdl",
                            pos = Metrostroi.PositionFromPanel(id,config.pos or buttons.ID,(config.z or 0.2)+(aconfig.z or 0.2),(config.x or 0)+(aconfig.x or 0),(config.y or 0)+(aconfig.y or 0)),
                            ang = Metrostroi.AngleFromPanel(id,aconfig.ang or config.ang),
                            color = aconfig.color or config.color,
                            colora = aconfig.colora or config.colora,
                            skin = aconfig.skin or config.skin or 0,
                            config = aconfig,
                            cabin = aconfig.cabin,
                            igrorepanel = true,
                            hide = panel.hide or config.hide,
                            hideseat = panel.hideseat or config.hideseat,
                            bscale = aconfig.bscale or config.bscale,
                            scale = aconfig.scale or config.scale,
                        }
                    end
                end
                buttons.model = nil
            end
        end
    end
    for k,v in pairs(self.ClientProps) do
        if not v.model then continue end
        Metrostroi.PrecacheModels[v.model] = true
    end
    for k,v in pairs(self.Lights or {}) do
        if not v.hidden then continue end
        local cP = self.ClientProps[v.hidden]
        if not cP then ErrorNoHalt("No clientProp "..v.hidden.." in entity "..self.Folder.."\n") continue end
        if not cP.lamps then cP.lamps = {} end
        table.insert(cP.lamps,k)
    end
    --ret = ret.."\n}"
    --SetClipboardText(ret)
end
--[[
timer.Simple(1,function()
    for k,ent in pairs(Metrostroi.PrecacheModels) do
        if ent==true or not IsValid(ent) then
            local model = ClientsideModel(k)
            model:SetPos(LocalPlayer():GetPos())
            Metrostroi.PrecacheModels[k] = model
        end
    end
    timer.Simple(2,function()
        for k,ent in pairs(Metrostroi.PrecacheModels) do
            if IsValid(ent) then
                SafeRemoveEntity(ent)
            end
        end
    end)
end)]]

function Metrostroi.InsertHide(panel,prop_name)
    local self = ENT
    if self.ButtonMap[panel] then
        if not self.ButtonMap[panel].props then self.ButtonMap[panel].props = {} end
        table.insert(self.ButtonMap[panel].props,prop_name)
    end
end






--------------------------------------------------------------------------------
-- Training markers
--------------------------------------------------------------------------------
local prevV = 0
local A = 0
local D1true = 0
local D2true = 0
local prevTime
hook.Add("PostDrawOpaqueRenderables", "metrostroi-draw-stopmarker",function()
    prevTime = prevTime or RealTime()
    local dT = math.max(0.001,RealTime() - prevTime)
    prevTime = RealTime()

    -- Skip if disabled
    if GetConVar("metrostroi_stop_helper"):GetInt() ~= 1 then return end

    -- Get train
    local train = LocalPlayer().InMetrostroiTrain
    if not IsValid(train) then return end

    -- Calculate acceleration
    local V = train:GetNW2Float("V",train:GetVelocity():Length()*0.01905)*0.277778
    local newA = (V - prevV)/dT
    prevV = V

    -- Calculate marker position
    A = train:GetNW2Float("A",A + (newA - A)*1.0*dT)
    local T1 = math.abs(V/(A+1e-8))
    local T2 = math.abs(V/(1.2+1e-8))
    local D1 = T1*V + (T1^2)*A/2
    local D2 = T2*V + (T2^2)*A/2

    -- Smooth out D
    D1 = math.min(200,math.max(0,D1))*0.65
    D2 = math.min(200,math.max(0,D2))*0.70
    D1true = D1true + (D1 - D1true)*12.0*dT
    D2true = D2true + (D2 - D2true)*12.0*dT
    local offset1 = D1true/0.01905
    local offset2 = D2true/0.01905

    -- Draw marker
    if A > -0.1 then return end
--  if D1 > 195 then return end
    if D2 > 195 then return end
    local base_pos1 = train:LocalToWorld(Vector(500+offset1,80,10))
    cam.Start3D2D(base_pos1,train:LocalToWorldAngles(Angle(0,-90,90)),1.0)
        surface.SetDrawColor(255,255,255)
        surface.DrawRect(-1,-1,8*20+2,4+2)
        for i=0,19 do
            surface.SetDrawColor(240,200,40)
            surface.DrawRect(8*i+0,0,4,4)
            surface.SetDrawColor(0,0,0)
            surface.DrawRect(8*i+4,0,4,4)
        end
        surface.SetDrawColor(255,255,255)
        surface.DrawRect(-1,-96,2,192)
        surface.DrawRect(8*20,-96,2,192)

--      surface.SetTextColor(255,255,255)
--      surface.SetFont("Trebuchet24")
--      surface.SetTextPos(64-128,-30)
--      surface.DrawText(Format("%.1f m  %.1f m/s %.1f m/s2",D,V,A))
--      surface.SetTextPos(64,-30)
--      surface.DrawText(Format("%.1f m %.0f sec",D,T))
    cam.End3D2D()

    local base_pos2 = train:LocalToWorld(Vector(500+offset2,80,10))
    cam.Start3D2D(base_pos2,train:LocalToWorldAngles(Angle(0,-90,90)),1.0)
        surface.SetDrawColor(240,40,40)
        surface.DrawRect(-1,-1,8*20+2,4+2)
        for i=0,19 do
            surface.SetDrawColor(0,0,0)
            surface.DrawRect(8*i+0,0,4,4)
            surface.SetDrawColor(240,40,40)
            surface.DrawRect(8*i+4,0,4,4)
        end

        surface.SetDrawColor(240,40,40)
        surface.DrawRect(-1,-1+110,8*20+2,16+2)
        for i=0,19 do
            surface.SetDrawColor(0,0,0)
            surface.DrawRect(8*i+0,110,4,16)
            surface.SetDrawColor(240,40,40)
            surface.DrawRect(8*i+4,110,4,16)
        end

        surface.SetDrawColor(240,40,40)
        surface.DrawRect(-6,-96,6,192)
        surface.DrawRect(8*20,-96,4,192)
    cam.End3D2D()
end)




--------------------------------------------------------------------------------
-- Fix for gm_metrostroi 3D sky
--------------------------------------------------------------------------------
local player_state = {}
if string.sub(game.GetMap(),1,13) == "gm_metrostroi" then
    timer.Create("Metrostroi_3DSkyFix",1.0,0,function()
        local player = LocalPlayer()
        if not IsValid(player) then return end

        RunConsoleCommand("r_3dsky", (player:GetPos().z < -1024) and "0" or "1")
    end)
end

timer.Simple(0,function()
    net.Start("metrostroi_cam_update") net.SendToServer()
end)
net.Receive("metrostroi_cam_update",function()
    local ent = Entity(net.ReadUInt(16))
    Metrostroi.RTCamera = ent
end)

local CamRT = surface.GetTextureID( "pp/rt" )
local CamWork = GetConVar("metrostroi_drawcams")
Metrostroi.CamTimers = Metrostroi.CamTimers or {}
Metrostroi.CamQueue = Metrostroi.CamQueue or {}
function Metrostroi.RenderCamOnRT(train,cpos,name,time,RT,post,pos,ang,x,y,scale,xmin,ymin)
    if not CamWork then  CamWork = GetConVar("metrostroi_drawcams") return end
    if not CamWork:GetBool() then return end
    name = train:EntIndex()..name
    --print(name,Metrostroi.CamQueue[name])
    if (not Metrostroi.CamTimers[name] or RealTime()-Metrostroi.CamTimers[name] > time) and not Metrostroi.CamQueue[name] then
        Metrostroi.CamQueue[name] = table.insert(Metrostroi.CamQueue,{train,cpos,name,time,RT,post,pos,ang,x,y,scale,xmin,ymin})
    end
end
function Metrostroi.SetCamPosAng(pos,ang)
    if IsValid(Metrostroi.RTCamera) then
        Metrostroi.RTCamera:SetPos(pos)
        Metrostroi.RTCamera:SetAngles(ang)
    end
end
hook.Add("Think","metrostroi_camera_move",function()
    if IsValid(Metrostroi.RTCamera) then
        Metrostroi.RTCamera:SetPos(Vector(0,0,-2^16))
        Metrostroi.RTCamera:SetAngles(Angle(90,0,0))
    end
    if Metrostroi.RenderCam and Metrostroi.RenderedCam ~= RealTime() then
        local camera = Metrostroi.RenderCam
        Metrostroi.RenderCam = nil
        if IsValid(camera[1]) then
            local distance = camera[1]:LocalToWorld(camera[2]):Distance(LocalPlayer():GetPos())
            if distance > 256 then return end
            local x,y = camera[9],camera[10]
            local scale = camera[11] or 1
            local xmin,ymin = camera[12] or 0,camera[13] or 0
            render.PushRenderTarget(camera[5],0,0,x, y)
            render.Clear(0, 0, 0, 0)
            cam.Start2D()
                surface.SetTexture( CamRT )
                surface.SetDrawColor( 255, 255, 255, 255 )
                surface.DrawTexturedRectRotated((x/2-xmin)*scale,(y/2-ymin)*scale,x*scale,y*scale,0)
            cam.End2D()
            render.PopRenderTarget()
        else
        end
    end
    if #Metrostroi.CamQueue > 0 and not Metrostroi.RenderCam then
        local cam = table.remove(Metrostroi.CamQueue,1)
        Metrostroi.CamQueue[cam[3]] = nil
        local name,time,post,pos,ang = cam[3],cam[4],cam[6],cam[7],cam[8]
        if IsValid(post) then
            debugoverlay.Sphere(post:LocalToWorld(pos),1,time,Color( 150, 105, 200 ),true)
            debugoverlay.Text(post:LocalToWorld(pos),name,time,Color( 150, 105, 200 ),true)
            debugoverlay.Line(post:LocalToWorld(pos),post:LocalToWorld(pos)+post:LocalToWorldAngles(ang):Forward()*25,time,Color( 150, 105, 200 ),true)
            Metrostroi.RenderCam = cam
            Metrostroi.SetCamPosAng(post:LocalToWorld(cam[7]),post:LocalToWorldAngles(cam[8]))
            Metrostroi.CamTimers[cam[3]] = RealTime()
        end
    end
end)

local function rect_ol(x,y,w,h,c)
    Metrostroi.DrawLine(x-1,y,x+w,y,c)
    Metrostroi.DrawLine(x+w,y,x+w,y+h,c)
    Metrostroi.DrawLine(x,y+h,x+w,y+h,c)
    Metrostroi.DrawLine(x,y,x,y+h,c)
end

function Metrostroi.DrawLine(x1,y1,x2,y2,col,sz)
    surface.SetDrawColor(col)
    if x1 == x2 then
        -- vertical line
        local wid =  (sz or 1) / 2
        surface.DrawRect(x1-wid, y1, wid*2, y2-y1)
    elseif y1 == y2 then
        -- horizontal line
        local wid =  (sz or 1) / 2
        surface.DrawRect(x1, y1-wid, x2-x1, wid*2)
    else
        -- other lines
        local x3 = (x1 + x2) / 2
        local y3 = (y1 + y2) / 2
        local wx = math.sqrt((x2-x1) ^ 2 + (y2-y1) ^ 2)
        local angle = math.deg(math.atan2(y1-y2, x2-x1))
        draw.NoTexture()
        surface.DrawTexturedRectRotated(x3, y3, wx, (sz or 1), angle)
    end
end
function Metrostroi.DrawRectOutline(x,y,w,h,col,sz)
    local wid = sz or 1
    if wid < 0 then
        for i=0, wid+1, -1 do
            rect_ol(x+i, y+i, w-2*i, h-2*i, col)
        end
    elseif wid > 0 then
        for i=0, wid-1 do
            rect_ol(x+i, y+i, w-2*i, h-2*i, col)
        end
    end
end

function Metrostroi.DrawRectOL(x,y,w,h,col,sz,col1)
    local wid = sz or 1
    if wid < 0 then
        for i=0, wid+1, -1 do
            rect_ol(x+i, y+i, w-2*i, h-2*i, col)
        end
    elseif wid > 0 then
        for i=0, wid-1 do
            rect_ol(x+i, y+i, w-2*i, h-2*i, col)
        end
    end
    surface.SetDrawColor(col1)
    surface.DrawRect(x+math.max(0,sz-1),y+math.max(0,sz-1),w-math.max(0,(sz-0.5)*2),h-math.max(0,(sz-0.5)*1.5))
end

function Metrostroi.DrawTextRect(x,y,w,h,col,mat)
    surface.SetDrawColor(col)
    surface.SetMaterial(mat)
    surface.DrawTexturedRect(x,y,w,h)
end

function Metrostroi.DrawTextRectOL(x,y,w,h,col,mat,sz,col1)
    local wid = sz or 1
    if wid < 0 then
        for i=0, wid+1, -1 do
            rect_ol(x+i, y+i, w-2*i, h-2*i, col1)
        end
    elseif wid > 0 then
        for i=0, wid-1 do
            rect_ol(x+i, y+i, w-2*i, h-2*i, col1)
        end
    end
    surface.SetDrawColor(col)
    surface.DrawRect(x+math.max(0,sz-1),y+math.max(0,sz-1),w-math.max(0,(sz-0.5)*2),h-math.max(0,(sz-0.5)*1.5))
    surface.SetDrawColor(Color(col.r - 40,col.g - 40,col.b - 40))
    surface.SetMaterial(mat)
    surface.DrawTexturedRect(x+math.max(0,sz-1),y+math.max(0,sz-1),w-math.max(0,(sz-0.5)*2),h-math.max(0,(sz-0.5)*2))
end


local function recurePrecache(sound)
    if type(sound) == "table" then
        for k,snd in pairs(sound) do recurePrecache(snd) end
    elseif type(sound) == "string" then
        util.PrecacheSound(sound)
    end
end
local function util_PrecacheSound(dir)
    local files,dirs = file.Find(dir.."/*","GAME")
    for _, fdir in pairs(dirs) do
        util_PrecacheSound(dir.."/"..fdir)
    end

    for _,v in pairs(files) do
        util.PrecacheSound(dir.."/"..v)
    end
end
--util_PrecacheSound("sound/subway_trains")
matproxy.Add{
    name = "TrainBodyColor",
    init = function( self, mat, values )
        -- Store the name of the variable we want to set
        if values then self._MATresultVarC = values.resultvar end
    end,
    bind = function( self, mat, ent )
        if ( self._MATresultVarC and ent.GetBodyColor ) then
            mat:SetVector( self._MATresultVarC, ent:GetBodyColor() )
        end
    end
}
matproxy.Add{
    name = "TrainBodyDecal",
    init = function( self, mat, values )
        -- Store the name of the variable we want to set
        if values then self._MATresultVarD = values.resultvar end
    end,
    bind = function( self, mat, ent )
        if ( self._MATresultVarD and ent.GetDirtLevel ) then
            mat:SetFloat( self._MATresultVarD, ent:GetDirtLevel() )
        end
    end
}

Metrostroi.RenderTargetNames = {
    "ASNP","IGLA","PUAV","PAM","Vityaz","Tickers","Sarmat","Cam1","Cam2","Cam3","Cam4",
}
function Metrostroi.AddCaptureRTName(name)
    if not table.HasValue(Metrostroi.RenderTargetNames,name) then
        table.insert(Metrostroi.RenderTargetNames,name)
    end
end

concommand.Add("metrostroi_capture_rt",function(_,_,args)
    local RTs = #args > 0 and args or Metrostroi.RenderTargetNames

    local train = LocalPlayer().InMetrostroiTrain
    if not IsValid(train) then return end

    local oldRt = render.GetRenderTarget() -- we'll save the old screen and draw on a new one!
    file.CreateDir("rt_captures")
    for i,v in ipairs(RTs) do
        local RT = train[v]
        if RT and type(RT) == "ITexture" then

            render.SetRenderTarget( train[v] )

            local data = render.Capture( { format = "png", quality = 100, x = 0, y = 0, h = RT:Height(), w = RT:Width() } )

            local pictureFile = file.Open("rt_captures/"..train:EntIndex().." "..v.." "..os.date("!%d%m%y_%H%M%S",os.time())..".png", "wb", "DATA" )
            pictureFile:Write( data )
            pictureFile:Close()
        end
    end

    render.SetRenderTarget( oldRt )
end)

--------------------------------------------------------------------------------
-- Player meta table magic
-- Author: HunterNL
--------------------------------------------------------------------------------
local Player = FindMetaTable("Player")

function Player:GetTrain()
    local seat = self:GetVehicle()
    if IsValid(seat) then
        return seat:GetNW2Entity("TrainEntity"),seat
    end
end
hook.Add("Think","MetrostroiGetTrain",function()
    local ply = LocalPlayer()
    local train = ply:GetTrain()
    if IsValid(train) then
        ply.InMetrostroiTrain =  train
    else
        ply.InMetrostroiTrain = false
    end
end)

RunConsoleCommand("r_rootlod",0) -- Train models only visible with High model quality

local matSprite = {
    ["$basetexture"] = "",
    ["$spriteorientation"] = "vp_parallel",
    ["$spriteorigin"] = "[ 0.50 0.50 ]",
    ["$illumfactor"] = 7,
    ["$spriterendermode"] = 3,
}
local matUnlit = {
    ["$basetexture"] = "",
    ["$translucent"]= 1,
    ["$additive"] = 1,
    ["$vertexcolor"] = 1,
    --["$vertexalpha"] = 1,
}
Metrostroi.SpriteCache1 = Metrostroi.SpriteCache1 or {}
Metrostroi.SpriteCache2 = Metrostroi.SpriteCache2 or {}
function Metrostroi.MakeSpriteTexture(path,isSprite)
    if isSprite then
        if Metrostroi.SpriteCache1[path] then return Metrostroi.SpriteCache1[path] end
        matSprite["$basetexture"] = path
        Metrostroi.SpriteCache1[path] = CreateMaterial(path..":sprite","Sprite",matSprite)
        return Metrostroi.SpriteCache1[path]
    else
        if Metrostroi.SpriteCache1[path] then return Metrostroi.SpriteCache1[path] end
        matUnlit["$basetexture"] = path
        Metrostroi.SpriteCache2[path] = CreateMaterial(path..":spriteug","UnlitGeneric",matUnlit)
        return Metrostroi.SpriteCache2[path]
    end
end