include("shared.lua") --if LocalPlayer():GetName():find("iNok") then RunConsoleCommand("say","ЛВЗ говно, обажаю МВМ") end surface.CreateFont("MetrostroiSubway_LargeText", { font = "Arial", size = 100, weight = 500, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiSubway_SmallText", { font = "Arial", size = 70, weight = 1000, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiSubway_VerySmallText", { font = "Arial", size = 45, weight = 1000, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiSubway_VerySmallText2", { font = "Arial", size = 35, weight = 1000, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false }) surface.CreateFont("MetrostroiSubway_VerySmallText3", { font = "Arial", size = 25, weight = 1000, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiSubway_LargeText2", { font = "Arial", size = 86, weight = 1000, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiSubway_LargeText3", { font = "Arial", size = 66, weight = 1000, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiLabels", { font = "BudgetLabel", size = 15, weight = 400, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = true, additive = true, outline = true, extended = true }) surface.CreateFont("MetrostroiSubway_IGLAb", { font = "IEE2", size = 30, weight = 0, blursize = 3, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = true, additive = true, outline = true, extended = true }) surface.CreateFont("MetrostroiSubway_IGLA", { font = "IEE2", size = 30, weight = 0, blursize = 0, scanlines = 0, antialias = false, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiSubway_FixedSYS", { font = "FixedsysTTF", size = 40, weight = 0, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiSubway_Speed", { font = "LCD AT&T Phone Time/Date", size = 200, weight = 400, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false }) surface.CreateFont("MetrostroiSubway_InfoPanel", { font = "Arial", size = 64, weight = 0, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("MetrostroiSubway_InfoRoute", { font = "Arial", size = 80, weight = 800, blursize = 0, scanlines = 0, antialias = true, underline = false, italic = false, strikeout = false, symbol = false, rotary = false, shadow = false, additive = false, outline = false, extended = true }) surface.CreateFont("Trebuchet24", { --Creating BUILTIN font (idk what happened with this font') font = "Trebuchet", size = 24, weight = 400, blursize = 0, scanlines = 10, antialias = true, additive = false, extended = true }) -------------------------------------------------------------------------------- -- Console commands and convars -------------------------------------------------------------------------------- concommand.Add("metrostroi_train_manual", function()--ply, _, args) --[[ local w = ScrW() * 2/3 local h = ScrH() * 2/3 local browserWindow = vgui.Create("DFrame") browserWindow:SetTitle("Train Manual") browserWindow:SetPos((ScrW() - w)/2, (ScrH() - h)/2) browserWindow:SetSize(w,h) browserWindow.OnClose = function() browser = nil browserWindow = nil end browserWindow:MakePopup() local browser = vgui.Create("DHTML",browserWindow) browser:SetPos(10, 25) browser:SetSize(w - 20, h - 35) browser:OpenURL ]]-- gui.OpenURL("http://phoenixblack.github.io/Metrostroi/manual.html") end) ENT.RTMaterial = CreateMaterial("MetrostroiRT1","UnlitGeneric",{ ["$vertexcolor"] = 0, ["$vertexalpha"] = 1, ["$nolod"] = 1, }) ENT.RTMaterial2 = CreateMaterial("MetrostroiRT2","UnlitGeneric",{ ["$vertexcolor"] = 0, ["$vertexalpha"] = 0, ["$nolod"] = 1, }) ENT.RTScanlineMaterial = CreateMaterial("MetrostroiRTScanline","UnlitGeneric",{ ["$vertexcolor"] = 1, ["$vertexalpha"] = 1, ["$nolod"] = 1, }) function ENT:CreateRT(name, w, h) local RT = GetRenderTarget("Metrostroi"..self:EntIndex()..":"..name, w or 512, h or 512) if not RT then Error("Can't create RT\n") end --mat:SetTexture("$basetexture", RT) return RT end local C_DisableHUD = GetConVar("metrostroi_disablehud") local C_RenderDistance = GetConVar("metrostroi_renderdistance") local C_SoftDraw = GetConVar("metrostroi_softdrawmultipier") local C_ScreenshotMode = GetConVar("metrostroi_screenshotmode") local C_DrawDebug = GetConVar("metrostroi_drawdebug") local C_CabFOV = GetConVar("metrostroi_cabfov") local C_CabZ = GetConVar("metrostroi_cabz") local C_FovDesired = GetConVar("fov_desired") local C_MinimizedShow = GetConVar("metrostroi_minimizedshow") local C_Shadows1 = GetConVar("metrostroi_shadows1") local C_Shadows2 = GetConVar("metrostroi_shadows2") local C_Shadows3 = GetConVar("metrostroi_shadows3") local C_Shadows4 = GetConVar("metrostroi_shadows4") local C_AA = GetConVar("mat_antialias") local C_Sprites = GetConVar("metrostroi_sprites") local whitelist = { ["CHudChat"] = true, ["CHudDeathNotice"] = true, ["CHudGMod"] = true, } hook.Add("HUDShouldDraw","MetrostroiHUDHider",function(name) if LocalPlayer().InMetrostroiTrain and C_DisableHUD:GetBool() and not whitelist[name] then return false end end) -------------------------------------------------------------------------------- -- Buttons layout -------------------------------------------------------------------------------- --ENT.ButtonMap = {} Leave nil if unused -- General Panel --[[table.insert(ENT.ButtonMap,{ pos = Vector(7,0,0), ang = Angle(0,90,90), width = 300, height = 100, scale = 0.0625, buttons = { {ID=1, x=-117, y= 0, radius=20, tooltip="Test 1"}, {ID=2, x= -80, y= 0, radius=20, tooltip="Test 2"}, } })]]-- -------------------------------------------------------------------------------- -- Decoration props -------------------------------------------------------------------------------- ENT.ClientProps = {} -------------------------------------------------------------------------------- -- Clientside entities support -------------------------------------------------------------------------------- local lastButton local lastTouch local drawCrosshair local canDrawCrosshair local toolTipText local toolTipColor local lastAimButtonChange local lastAimButton function ENT:ShouldRenderClientEnts() return not self:IsDormant() and math.abs(LocalPlayer():GetPos().z - self:GetPos().z) < 500 and (system.HasFocus() or C_MinimizedShow:GetBool()) and (not Metrostroi or not Metrostroi.ReloadClientside) end function ENT:ShouldDrawPanel(v) return not self.HiddenPanelsDistance[v] and not self.HiddenPanels[v] end function ENT:ShouldDrawClientEnt(k,v) if self.Hidden[k] or self.Hidden.anim[k] then return false end v = v or self.ClientProps[k] if not v then return false end local distance = LocalPlayer():GetPos():Distance(self:LocalToWorld(v.pos)) local renderDist = C_RenderDistance:GetFloat() if v.nohide then return true end if v.hideseat then local seat = LocalPlayer():GetVehicle() if IsValid(seat) and self ~= seat:GetParent() then return false end if v.hideseat ~= true then return distance <= renderDist*v.hideseat end elseif v.hide then return distance <= renderDist*v.hide else return distance <= renderDist end end --util.PrecacheModel("models/metrostroi_train/81-720/81-720.mdl") function ENT:SpawnCSEnt(k,override) if override and (self.Hidden[k] or self.Hidden.anim[k]) or not override and not self:ShouldDrawClientEnt(k,self.ClientProps[k]) then return false end local v = self.ClientPropsOv and self.ClientPropsOv[k] or self.ClientProps[k] if v and not IsValid(self.ClientEnts[k]) and v.model ~= "" then --local cent = ents.CreateClientProp(LocalPlayer():GetModel()) local model = v.model if v.modelcallback then model = v.modelcallback(self) or v.model end local cent = ClientsideModel(model,RENDERGROUP_OPAQUE) cent.GetBodyColor = function() if not IsValid(self) then return Vector(1) end return self:GetBodyColor() end cent.GetDirtLevel = function() if not IsValid(self) then return 0.25 end return self:GetDirtLevel() end --cent:SetModel( v.model ) cent:SetParent(self) cent:SetPos(self:LocalToWorld(v.pos)) cent:SetAngles(self:LocalToWorldAngles(v.ang)) cent:SetLOD(C_ScreenshotMode:GetBool() and 0 or -1) --[[ hook.Add("MetrostroiBigLag",cent,function(ent) --print(ent:GetLocalPos()) ent:SetLocalPos(ent:GetLocalPos()) ent:SetLocalAngles(ent:GetLocalAngles()) --if ent.Spawned then hook.Remove("MetrostroiBigLag",ent) end --ent.Spawned = true end)]] cent:SetSkin(v.skin or 0) if v.scale then cent:SetModelScale(v.scale) end if v.bscale then cent:ManipulateBoneScale(0,v.bscale) end if self.Anims[k] and self.Anims[k].value and type(self.Anims[k].value) == "number" then cent:SetPoseParameter("position",self.Anims[k].value) end if v.bodygroup then for k1,v1 in pairs(v.bodygroup) do cent:SetBodygroup(v1,k1) end end if v.lamps then for i,k in ipairs(v.lamps) do self.HiddenLamps[k] = false end end cent.lamps = v.lamps local texture = Metrostroi.Skins["train"][self:GetNW2String("Texture")] local passtexture = Metrostroi.Skins["pass"][self:GetNW2String("PassTexture")] local cabintexture = Metrostroi.Skins["cab"][self:GetNW2String("CabTexture")] for k1,v1 in pairs(cent:GetMaterials() or {}) do local tex = v1:gsub("^.+/","") if cabintexture and cabintexture.textures and cabintexture.textures[tex] then if type(cabintexture.textures[tex]) ~= "table" then cent:SetSubMaterial(k1-1,cabintexture.textures[tex]) end end if passtexture and passtexture.textures and passtexture.textures[tex] then cent:SetSubMaterial(k1-1,passtexture.textures[tex]) end if texture and texture.textures and texture.textures[tex] then cent:SetSubMaterial(k1-1,texture.textures[tex]) end end self.ClientEnts[k] = cent if self.SmoothHide[k] then if self.SmoothHide[k] > 0 then cent:SetColor(ColorAlpha(v.color or color_white,self.SmoothHide[k]*255)) cent:SetRenderMode(RENDERMODE_TRANSALPHA) else cent:Remove() self:ShowHide(k, false,true) end elseif v.colora then cent:SetRenderMode(RENDERMODE_TRANSCOLOR) cent:SetColor(v.colora) else cent:SetColor(v.color or color_white) end cent.BASSSounds = {} cent.DestroySound = self.DestroySound cent.Think = function(ent) for k,v in pairs(ent.BASSSounds) do if not IsValid(v) or v:GetState() == GMOD_CHANNEL_STOPPED then self:DestroySound(v) table.remove(ent.BASSSounds,k) end end ent:SetNextClientThink(CurTime()+0.5) return true end cent.CalcAbsolutePosition = function(ent,pos,ang) for k,v in pairs(ent.BASSSounds) do if IsValid(v) and v:GetState() ~= GMOD_CHANNEL_STOPPED then v:SetPos(pos,ang:Forward()) end end end if v.lamps then cent:CallOnRemove("RemoveLights", function(ent) if IsValid(self) then for i,k in ipairs(ent.lamps) do self:SetLightPower(k,false) self.HiddenLamps[k] = true end end end) end self:ShowHide(k, not self.Hidden[k],true) if v.callback then v.callback(self,cent) end return true end return false end function ENT:GetBodyColor() return self:GetNW2Vector("BodyColor",Vector(1,1,1)) end function ENT:GetDirtLevel() return self:GetNW2Float("DirtLevel",0.25) end hook.Remove("Think","metrostroi_collect_garbage",function() if Metrostroi.CollectGarbage then collectgarbage("collect") Metrostroi.CollectGarbage = false end end) hook.Add("EntityRemoved","metrostroi_bass_disable",function(ent) if ent.BASSSounds then for k,v in pairs(ent.BASSSounds) do ent:DestroySound(v) ent.BASSSounds[k] = nil end end end) function ENT:SetCSBodygroup(csent,id,value) if not self.ClientProps[csent].bodygroup then self.ClientProps[csent].bodygroup = {} end self.ClientProps[csent].bodygroup[id] = value if IsValid(self.ClientEnts[csent]) then self.ClientEnts[csent]:SetBodygroup(id,value) end end local elapsed = SysTime() local spawnedCount = 0 hook.Add("Think","SpawnElasped",function() elapsed = SysTime() spawnedCount = 0 end) function ENT:CreateCSEnts() local mul = C_SoftDraw:GetFloat()/100 local time = mul*0.01 if self.ClientPropsOv then for k in pairs(self.ClientPropsOv) do if k ~= "BaseClass" and not IsValid(self.ClientEnts[k]) and self:SpawnCSEnt(k)then if SysTime()-elapsed > time then return false end end end end --RunConsoleCommand("say","1:"..tostring(elapsed)) for k in pairs(self.ClientProps) do if k ~= "BaseClass" and not IsValid(self.ClientEnts[k]) then if spawnedCount*mul*3 > 4 and SysTime()-elapsed > time then return false end if self:SpawnCSEnt(k) then spawnedCount = spawnedCount + 1 end --RunConsoleCommand("say","1:") end end return true end function ENT:RemoveCSEnt(id) if self.ClientEnts and self.ClientEnts[id] then SafeRemoveEntity(self.ClientEnts[id]) self.ClientEnts[id] = nil end end function ENT:RemoveCSEnts() if self.ClientEnts then for _,v in pairs(self.ClientEnts) do if IsValid(v) then v:Remove() end end end if(self.GlowingLights) then for k,v in pairs(self.GlowingLights) do if IsValid(v) and v.Remove then v:Remove() end end end self.ClientEnts = {} self.GlowingLights = {} self.Sprites = {} end -- Checks if the player is driving a train, also returns said train local function isValidTrainDriver(ply) if IsValid(ply.InMetrostroiTrain) then return ply.InMetrostroiTrain end local weapon = IsValid(LocalPlayer():GetActiveWeapon()) and LocalPlayer():GetActiveWeapon():GetClass() if weapon ~= "train_kv_wrench" and weapon ~= "train_kv_wrench_gold" then return end local train = util.TraceLine({ start = LocalPlayer():GetPos(), endpos = LocalPlayer():GetPos() - LocalPlayer():GetAngles():Up() * 100, filter = function( ent ) if ent.ButtonMap ~= nil then return true end end }).Entity if not IsValid(train) then train = util.TraceLine({ start = LocalPlayer():EyePos(), endpos = LocalPlayer():EyePos() + LocalPlayer():EyeAngles():Forward() * 300, filter = function( ent ) if ent.ButtonMap ~= nil then return true end end }).Entity end return IsValid(train) and train, true end -------------------------------------------------------------------------------- -- Clientside initialization -------------------------------------------------------------------------------- function ENT:CanDrawThings() return not LocalPlayer().InMetrostroiTrain or self == LocalPlayer().InMetrostroiTrain end local function colAlpha(col,a) return Color(col.r*a,col.g*a,col.b*a) end hook.Add("PostDrawTranslucentRenderables", "metrostroi_base_draw", function(_,isDD) if isDD then return end local inSeat = LocalPlayer().InMetrostroiTrain for ent in pairs(Metrostroi.SpawnedTrains) do if ent:IsDormant() then continue end if MetrostroiStarted and MetrostroiStarted~=true or ent.RenderBlock then if not inSeat then local timeleft = (math.max(0,(MetrostroiStarted and MetrostroiStarted~=true) and 3-(RealTime()-MetrostroiStarted) or 3-(RealTime()-ent.RenderBlock)))+0.99 cam.Start3D2D(ent:LocalToWorld(Vector(0,-150,100)),ent:LocalToWorldAngles(Angle(0,90,90)),1.5) draw.SimpleText("Wait, train will be available across "..string.NiceTime(timeleft)) cam.End3D2D() cam.Start3D2D(ent:LocalToWorld(Vector(0,150,100)),ent:LocalToWorldAngles(Angle(0,-90,90)),1.5) draw.SimpleText("Wait, train will be available across "..string.NiceTime(timeleft)) cam.End3D2D() end continue end cam.IgnoreZ(true) for i,vHandle in pairs(ent.Sprites) do local br = ent.LightBrightness[i] local lightData = ent.LightsOverride[i] or ent.Lights[i] if lightData[1] ~= "glow" and lightData[1] ~= "light" or br <= 0 then continue end local pos = ent:LocalToWorld(lightData[2]) local visibility = util.PixelVisible(pos, lightData.size or 5, vHandle)--math.max(0,util.PixelVisible(pos, 5, vHandle)-0.25)/0.75 if visibility > 0 then render.SetMaterial(lightData.mat) render.DrawSprite(pos,128*lightData.scale,128*(lightData.vscale or lightData.scale),colAlpha(lightData[4] or Color(255,255,255),visibility*br)) --render.DrawQuadEasy( ent:GetPos(),-EyeVector(), 128*ent.Scale, 128*ent.Scale, ent:GetColor()) end end cam.IgnoreZ(false) if not ent.ShouldRenderClientEnts or not ent:ShouldRenderClientEnts() then continue end if ent.DrawPost then ent:DrawPost(not ent:CanDrawThings()) end if not ent:CanDrawThings() then continue end ent.CLDraw = true if ent.Systems then for _,v in pairs(ent.Systems) do v:ClientDraw() end end end end) local function enableDebug() if C_DrawDebug:GetInt() > 0 then hook.Add("PostDrawTranslucentRenderables","MetrostroiTrainDebug",function(bDrawingDepth,bDrawingSkybox) if bDrawingSkybox then return end for ent in pairs(Metrostroi.SpawnedTrains) do -- Debug draw for buttons if ent.ButtonMap ~= nil then draw.NoTexture() for kp,panel in pairs(ent.ButtonMap) do if kp ~= "BaseClass" and LocalPlayer():GetPos():DistToSqr(ent:LocalToWorld(panel.pos)) < 262144 then ent:DrawOnPanel(kp,function() surface.SetDrawColor(0,0,255) if not ent:ShouldDrawPanel(kp) then surface.SetDrawColor(255,0,0) end surface.DrawOutlinedRect(0,0,panel.width,panel.height) if panel.aimX and panel.aimY then surface.SetTextColor(255,255,255) surface.SetFont("BudgetLabel") surface.SetTextPos(panel.width/2,5) surface.DrawText(string.format("%d %d",panel.aimX,panel.aimY)) end --surface.SetDrawColor(255,255,255) --surface.DrawRect(0,0,panel.width,panel.height) if panel.buttons then surface.SetAlphaMultiplier(0.2) if ent.HiddenPanels[kp] then surface.SetAlphaMultiplier(0.1) end for kb,button in pairs(panel.buttons) do if ent.Hidden[button.PropName] or ent.Hidden[button.ID] or ent.Hidden.anim[button.PropName] or ent.Hidden.anim[button.ID] or ent.Hidden.button[button.PropName] or ent.Hidden.button[button.ID] then surface.SetDrawColor(255,255,0) elseif ent.Hidden[kb] or ent.Hidden.anim[kb] then surface.SetDrawColor(255,255,0) elseif ent.HiddenPanels[kp] then surface.SetDrawColor(100,0,0) elseif not button.ID or button.ID[1] == "!" then surface.SetDrawColor(25,40,180) elseif button.state then surface.SetDrawColor(255,0,0) else surface.SetDrawColor(0,255,0) end if button.w and button.h then surface.DrawRect(button.x, button.y, button.w, button.h) surface.DrawRect(button.x + button.w/2 - 8,button.y + button.h/2 - 8,16,16) else ent:DrawCircle(button.x,button.y,button.radius or 10) surface.DrawRect(button.x-8,button.y-8,16,16) end end --Gotta reset this otherwise the qmenu draws transparent as well surface.SetAlphaMultiplier(1) end end,true) end end end end end) else hook.Remove("PostDrawTranslucentRenderables","MetrostroiTrainDebug") end end hook.Remove("PostDrawTranslucentRenderables","MetrostroiTrainDebug") cvars.AddChangeCallback( "metrostroi_drawdebug", enableDebug) enableDebug() 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 function ENT:GetWagonNumber() local number = self:GetNW2Int("WagonNumber",-1) if number <= 0 then number = self:EntIndex() end return number end function ENT:Initialize() -- Create clientside props self.ClientEnts = {} self.HiddenPanels = {} self.HiddenPanelsDistance = {} self.HiddenLamps = {} self.Hidden = { anim = {},button = {},override = {}, } self.Anims = {} self.SmoothHide = {} -- Create sounds self:InitializeSounds() recurePrecache(self.SoundNames) self.Sounds = { loop = {}, isloop = {}, } self.CurrentCamera = 0 self.Sprites = {} if self.NoTrain then return end self.ButtonMapMatrix = {} -- Passenger models self.PassengerEnts = {} self.PassengerEntsStucked = {} self.PassengerPositions = {} --self.HiddenQuele = {} -- Systems defined in the train self.Systems = {} -- Initialize train systems self:InitializeSystems() self.GlowingLights = {} self.LightBrightness = {} self.LightsOverride = {} if self.Lights then for i,lightData in pairs(self.Lights) do if lightData.changable then self.LightsOverride[i] = table.Copy(lightData) end end end --self:EntIndex() self.PassengerModels = { "models/metrostroi/passengers/f1.mdl", "models/metrostroi/passengers/f2.mdl", "models/metrostroi/passengers/f3.mdl", "models/metrostroi/passengers/f4.mdl", "models/metrostroi/passengers/m1.mdl", "models/metrostroi/passengers/m2.mdl", "models/metrostroi/passengers/m4.mdl", "models/metrostroi/passengers/m5.mdl", } self.WagonNumber = 0 self:PostInitializeSystems() self.TunnelCoeff = 0 self.StreetCoeff = 0 self.Street = 0 end function ENT:UpdateTextures() self.Texture = self:GetNW2String("Texture") self.PassTexture = self:GetNW2String("PassTexture") self.CabinTexture = self:GetNW2String("CabTexture") local texture = Metrostroi.Skins["train"][self.Texture] local passtexture = Metrostroi.Skins["pass"][self.PassTexture] local cabintexture = Metrostroi.Skins["cab"][self.CabinTexture] for id,ent in pairs(self.ClientEnts) do if not IsValid(ent) then continue end if self.ClientProps[id].callback then self.ClientProps[id].callback(self,ent) end for k in pairs(ent:GetMaterials()) do ent:SetSubMaterial(k-1,"") end for k,v in pairs(ent:GetMaterials()) do local tex = string.Explode("/",v) tex = tex[#tex] if cabintexture and cabintexture.textures and cabintexture.textures[tex] then ent:SetSubMaterial(k-1,cabintexture.textures[tex]) end if passtexture and passtexture.textures and passtexture.textures[tex] then ent:SetSubMaterial(k-1,passtexture.textures[tex]) end if texture and texture.textures and texture.textures[tex] then ent:SetSubMaterial(k-1,texture.textures[tex]) end end end end function ENT:UpdateWagonNumber() end ENT.Cameras = {} function ENT:OnRemove(nfinal) self.RenderBlock = RealTime() if nfinal then drawCrosshair = false canDrawCrosshair = false toolTipText = nil end self:RemoveCSEnts() self.RenderClientEnts = false for _,v in pairs(self.Sounds) do if type(v) ~= "function" and type(v) ~= "table" then self:DestroySound(v) end end for k,v in pairs(self.Sounds.loop) do for i,sndt in ipairs(v) do self:DestroySound(sndt.sound) end end for _,v in pairs(self.PassengerEnts or {}) do SafeRemoveEntity(v) end for _,v in pairs(self.PassengerEntsStucked or {}) do SafeRemoveEntity(v) end if self.GUILocker then self:BlockInput(false) end self.Sounds = {loop = {},isloop = {}} self.PassengerEnts = {} self.PassengerEntsStucked = {} end function ENT:CalcAbsolutePosition(pos, ang) if self.RenderClientEnts then if self.Lights and self.GlowingLights then for id, light in pairs(self.GlowingLights) do if not IsValid(light) then continue end local lightData = self.Lights[id] light:SetPos(self:LocalToWorld(lightData[2])) light:SetAngles(self:LocalToWorldAngles(lightData[3])) end end for k,v in pairs(self.Sounds) do if type(v) == "IGModAudioChannel" then if not IsValid(v) then self.Sounds[k] = nil continue end if v:GetState() ~= GMOD_CHANNEL_STOPPED then local tbl = self.SoundPositions[k] if tbl then local lpos,lang = LocalToWorld(tbl[3],Angle(0,0,0),pos,ang) v:SetPos(lpos,ang:Forward()) else v:SetPos(pos) end continue end end end for k,v in pairs(self.Sounds.loop) do local tbl = self.SoundPositions[k] for i,stbl in ipairs(v) do local snd = stbl.sound if not IsValid(snd) then continue end if snd:GetState() == GMOD_CHANNEL_PLAYING then if tbl then local lpos,lang = LocalToWorld(tbl[3],Angle(0,0,0),pos,ang) snd:SetPos(lpos,ang:Forward()) end end end end end return pos, ang end -------------------------------------------------------------------------------- -- Default think function -------------------------------------------------------------------------------- local function SoundTrace(startv,endv) local tr = util.TraceLine( { start = startv, endpos = endv, mask = MASK_NPCWORLDSTATIC, } ) --debugoverlay.Line(startv,endv,FrameTime(),Color( 255, 0, tr.Hit and 255 or 0 )) if tr.Hit then --debugoverlay.Sphere(tr.HitPos,4,FrameTime(),Color( 255, 200, 100)) return startv:Distance(tr.HitPos) end return 1000 end MetrostroiStarted = MetrostroiStarted or nil hook.Add("KeyPress","MetrostroiStarted",function(_,key) if key~=IN_FORWARD and key~=IN_BACK and key~=IN_MOVELEFT and key~=IN_MOVERIGHT then return end hook.Add("Think","MetrostroiStarted",function() if MetrostroiStarted == nil then MetrostroiStarted = RealTime() elseif MetrostroiStarted == true or MetrostroiStarted and RealTime()-MetrostroiStarted > 3 then MetrostroiStarted = true hook.Remove("Think","MetrostroiStarted") end end) hook.Remove("KeyPress","MetrostroiStarted") end) function ENT:Think() self.PrevTime = self.PrevTime or RealTime() self.DeltaTime = (RealTime() - self.PrevTime) self.PrevTime = RealTime() if MetrostroiStarted~=true then return end if not self.FirstTick then self.FirstTick = true self.RenderClientEnts = true self.CreatingCSEnts = false return end if self.RenderClientEnts ~= self:ShouldRenderClientEnts() then self.RenderClientEnts = self:ShouldRenderClientEnts() if self.RenderClientEnts then self.CreatingCSEnts = true self:BlockInput(self.HandleMouseInput) --self:CreateCSEnts() --if self.UpdateTextures then self:UpdateTextures() end --local _,ent = next(self.ClientEnts) --if not IsValid(ent) then self.RenderClientEnts = false end else self:OnRemove(true) return end end if not self.RenderClientEnts then return end if self.RenderBlock then if RealTime()-self.RenderBlock < 3 then self.ClientPropsInitialized = false return else self.RenderBlock = false end end if not self.ClientPropsInitialized then self.ClientPropsInitialized = true self:RemoveCSEnts() self:InitializeSounds() self.RenderClientEnts = false self.StopSounds = false end if self.GlowingLights and ( self.HeadlightShadows ~= C_Shadows1:GetBool() or self.OtherShadows ~= C_Shadows2:GetBool() or self.RedLights ~= C_Shadows3:GetBool() or self.OtherLights ~= C_Shadows4:GetBool() or self.AAEnabled ~= (C_AA:GetInt() > 1) or self.SpritesEnabled ~= C_Sprites:GetBool()) then self.HeadlightShadows = C_Shadows1:GetBool() self.OtherShadows = C_Shadows2:GetBool() self.RedLights = C_Shadows3:GetBool() self.OtherLights = C_Shadows4:GetBool() self.SpritesEnabled = C_Sprites:GetBool() self.AAEnabled = C_AA:GetInt() > 1 for k,v in pairs(self.GlowingLights) do if IsValid(v) then v:Remove() end end self.GlowingLights = {} self.LightBrightness = {} self.Sprites = {} end if self.RenderClientEnts and self.CreatingCSEnts then self.CreatingCSEnts = not self:CreateCSEnts() if not self.CreatingCSEnts then self:UpdateTextures() if self.Systems then for _,v in pairs(self.Systems) do if v.ClientReload then v:ClientReload() end end end end end if not self.RenderClientEnts or self.CreatingCSEnts then return end if self.WagonNumber ~= self:GetWagonNumber() then self.WagonNumber = self:GetWagonNumber() self:UpdateWagonNumber() end if self.Texture ~= self:GetNW2String("Texture") then self:UpdateTextures() end if self.PassTexture ~= self:GetNW2String("PassTexture") then self:UpdateTextures() end if self.CabinTexture ~= self:GetNW2String("CabTexture") then self:UpdateTextures() end local hasGoldenReverser = self:GetNW2Bool("GoldenReverser") if self.HasGoldenReverser ~= hasGoldenReverser then self.HasGoldenReverser = hasGoldenReverser for id,v in pairs(self.ClientProps) do if v.model == "models/metrostroi_train/reversor/reversor_classic.mdl" and v.modelcallback and IsValid(self.ClientEnts[id]) then self:RemoveCSEnt(id) self:SpawnCSEnt(id) end end end if (GetConVar("metrostroi_disablecamaccel"):GetInt() == 0) then self.HeadAcceleration = (self:Animate("accel",((self:GetNW2Float("Accel",0)+1)/2),0,1, 4, 1)*30-15) else self.HeadAcceleration = 0 end -- Simulate systems if self.Systems then for _,v in pairs(self.Systems) do v:ClientThink(self.DeltaTime) end end if not self.StopSounds then local soundPos = self.SoundPositions local soundNames = self.SoundNames for k,v in pairs(self.Sounds.loop) do local tbl = soundPos[k] local ntbl = soundNames[k] local good = true for i,stbl in ipairs(v) do if not stbl.volume then good = false end end if not good then continue end for i,stbl in ipairs(v) do local snd = stbl.sound if not IsValid(snd) then continue end if snd:GetState() == GMOD_CHANNEL_PLAYING then self:SetPitchVolume(snd,v.pitch or 1,stbl.volume,tbl) if stbl.volume == 0 and not stbl.time then snd:Pause() snd:SetTime(0) end end if snd:GetState() ~= GMOD_CHANNEL_PLAYING and stbl.volume ~= 0 then stbl.volume = 0 end if stbl.time then local targetvol = stbl.state and v.volume or 0 if stbl.time == true then stbl.volume = targetvol else stbl.volume = math.Clamp((stbl.volume or 0) + FrameTime()/(stbl.time/v.pitch)*(stbl.state and 1 or -1)*v.volume,0,v.volume) end if stbl.volume == targetvol then stbl.time = nil end end if i==1 then local no1 = ntbl.loop and ntbl.loop==0 local endt = (ntbl.loop and snd:GetTime() > ntbl.loop or snd:GetTime()/snd:GetLength() >= 0.8) or no1 if stbl.state and stbl.volume < v.volume and not no1 then if snd:GetState() ~= GMOD_CHANNEL_PLAYING then snd:Play() self:SetBASSPos(snd,tbl) end stbl.volume = v.volume self:SetPitchVolume(snd,v.pitch,stbl.volume,tbl) for i=2,3 do if not v[i].volume or v[i].volume > 0 then v[i].time=2 if v[i].GetState and v[i]:GetState() ~= GMOD_CHANNEL_PLAYING then v[i]:EnableLooping(i==2) v[i]:Play() self:SetBASSPos(v[i],tbl) end self:SetPitchVolume(v[i].sound,v.pitch,v[i].volume,tbl) end end stbl.time = nil end if stbl.state and endt then stbl.state = false if no1 then stbl.time = true v[2].state = not v[3].state end end if not stbl.state and stbl.volume == v.volume and not stbl.time then stbl.time = not ntbl.loop or 0.1/v.pitch--endt and (snd:GetLength()-snd:GetTime())*0.8 or 0.05 v[2].state = not v[3].state end end if i==2 then if stbl.state and not stbl.time and stbl.volume == 0 then if snd:GetState() ~= GMOD_CHANNEL_PLAYING then snd:EnableLooping(true) snd:Play() self:SetBASSPos(snd,tbl) end if v[1].time == true then stbl.volume = v.volume elseif v[1].time then stbl.time = v[1].time stbl.volume = 0 end self:SetPitchVolume(snd,v.pitch,stbl.volume,tbl) end if not stbl.state and not stbl.time and stbl.volume > 0 then stbl.time = 0.07/v.pitch end end if i==3 then local time = v[2].time or v[1].time if stbl.state and time and not stbl.time then if snd:GetState() ~= GMOD_CHANNEL_PLAYING then snd:Play() self:SetBASSPos(snd,tbl) end stbl.volume = 0 self:SetPitchVolume(snd,v.pitch,stbl.volume,tbl) stbl.time = 0.1/v.pitch for i=1,2 do if v[i].volume > 0 then v[i].time=0.07/v.pitch if v[i].GetState and v[i]:GetState() ~= GMOD_CHANNEL_PLAYING then v[i]:Play() self:SetBASSPos(v[i],tbl) end self:SetPitchVolume(v[i].sound,v.pitch,v[i].volume,tbl) end end elseif (not stbl.state or (snd:GetTime()/snd:GetLength() >= 0.9)) and stbl.time then stbl.time = nil stbl.volume = 0 stbl.state = false end end end end end if not self.NoTrain then self.SoundTraceI = self.SoundTraceI or 0 local min, max = self:OBBMins(),self:OBBMaxs() local x = self.SoundTraceI==2 and max.x or self.SoundTraceI==1 and 0 or min.x local leftt = SoundTrace(self:LocalToWorld(Vector(x,min.y,0)),self:LocalToWorld(Vector(x,min.y-128,0))) local leftst = SoundTrace(self:LocalToWorld(Vector(x,min.y,-64)),self:LocalToWorld(Vector(x,min.y-48,-64))) local rightst = SoundTrace(self:LocalToWorld(Vector(x,max.y,-64)),self:LocalToWorld(Vector(x,max.y+48,-64))) local rightt = SoundTrace(self:LocalToWorld(Vector(x,max.y,0)),self:LocalToWorld(Vector(x,max.y+128,0))) local upt = SoundTrace(self:LocalToWorld(Vector(x,0,max.z)),self:LocalToWorld(Vector(x,0,max.z+256--[[ 384--]] ))) self.SoundTraceI = self.SoundTraceI+1 if self.SoundTraceI>2 then self.SoundTraceI=0 end if upt > 350 then local coeff = 1-math.min( (math.min(130,leftt)/130+math.min(130,rightt)/130)/2, math.Clamp((leftst-10)/40,0,1), math.Clamp((rightst-10)/40,0,1) ) --print(math.Clamp((leftst-10)/40,0,1)) --print(Format("%02d %.2f %02d %.2f",leftst,math.Clamp((leftst-30)/20,0,1),rightst,) --[[ if leftst < 30 or rightst < 30 then LocalPlayer():ChatPrint(Format("I AM ON A STREET STATION, %.2f",coeff)) elseif coeff > 1.3 then LocalPlayer():ChatPrint(Format("I AM ON A STREET, %.2f",coeff)) else LocalPlayer():ChatPrint(Format("I AM ON A STREET WITH WALLS, %.2f",coeff)) end--]] self.TunnelCoeff = math.Clamp(self.TunnelCoeff+( 0-self.TunnelCoeff)*self.DeltaTime*4,0,1) self.StreetCoeff = math.Clamp(self.StreetCoeff+((0.8+coeff*0.2)-self.StreetCoeff)*self.DeltaTime*4,0,1) self.Street = 1 else local coeff = 1-math.min( math.Clamp((leftt-80)/40,0,1)+math.Clamp((rightt-80)/40,0,1)/2, (math.Clamp((leftst-10)/40,0,1)+math.Clamp((rightst-10)/40,0,1))/2 --, )--(math.Clamp((leftst-30)/20,0,1)+math.Clamp((rightst-30)/20,0,1))*0.6 --[[ if (leftst < 30 or rightst < 30) and coeff > 1.2 then LocalPlayer():ChatPrint(Format("I AM ON A STATION L%.2f R%.2f C:%.2f",leftt/55,rightt/55,coeff)) elseif coeff > 1.3 then LocalPlayer():ChatPrint(Format("I AM IN A BIG TUNNEL L%.2f R%.2f C:%.2f",leftt/55,rightt/55,coeff)) else LocalPlayer():ChatPrint(Format("I AM IN A TUNNEL L%.2f R%.2f C:%.2f",leftt/55,rightt/55,coeff)) end--]] self.TunnelCoeff = math.Clamp(self.TunnelCoeff+((0.4+coeff*0.6)-self.TunnelCoeff)*self.DeltaTime*4,0,1) self.StreetCoeff = math.Clamp(self.StreetCoeff+((0.5-math.max(0,self.TunnelCoeff-0.5))-self.StreetCoeff)*self.DeltaTime*4,0,1) self.Street = 0 end end if not self.HandleMouseInput and self.ButtonMap then if self == LocalPlayer().InMetrostroiTrain then for kp,pan in pairs(self.ButtonMap) do if not self:ShouldDrawPanel(kp) then continue end --If player is looking at this panel if pan.mouse and not pan.outside and pan.aimX and pan.aimY then local aimX,aimY = math.floor(math.Clamp(pan.aimX,0,pan.width)),math.floor(math.Clamp(pan.aimY,0,pan.height)) if pan.OldAimX ~= aimX or pan.OldAimY ~= aimY then net.Start("metrostroi-mouse-move",true) net.WriteEntity(self) net.WriteString(kp) net.WriteFloat(aimX) net.WriteFloat(aimY) net.SendToServer() pan.OldAimX = aimX pan.OldAimY = aimY end end end end end if self.ButtonMap and (not self.LastCheck or RealTime()-self.LastCheck > 0.5) then self.LastCheck = RealTime() local screenshotMode = C_ScreenshotMode:GetBool() if self.ScreenshotMode ~= screenshotMode then self:SetLOD(screenshotMode and 0 or -1) for k,cent in pairs(self.ClientEnts) do if IsValid(cent) then cent:SetLOD(screenshotMode and 0 or -1) end end self.ScreenshotMode = screenshotMode end for k in pairs(self.HiddenLamps) do self.HiddenLamps[k] = false end for k,v in pairs(self.ClientProps) do if not v.pos then continue end local cent = self.ClientEnts[k] if (v.nohide or screenshotMode) then if not IsValid(cent) then self:SpawnCSEnt(k,true) end continue end local hidden = not self:ShouldDrawClientEnt(k,v) if IsValid(cent) and hidden then cent:Remove() self.ClientEnts[k] = nil elseif not IsValid(cent) and not hidden then self:SpawnCSEnt(k,true) end if v.lamps and hidden then for i,k in ipairs(v.lamps) do self:SetLightPower(k,false) self.HiddenLamps[k] = true end end end for k,v in pairs(self.Sounds) do if type(v) ~= "function" and type(v) ~= "table" and not self.Sounds.isloop[k] and (not IsValid(v) or v:GetState() == GMOD_CHANNEL_STOPPED) then self:DestroySound(v) self.Sounds[k] = nil end end for k,v in pairs(self.ButtonMap) do if not v.pos then continue end if not v.hide or (v.nohide or screenshotMode) then self.HiddenPanelsDistance[k] = v.screenHide continue end self.HiddenPanelsDistance[k] = not self:ShouldDrawClientEnt(k,self.ButtonMap[k]) end end if self.AutoAnims && self.AutoAnimNames then local aAnims = self.AutoAnims local aAnimNames = self.AutoAnimNames local hidden = self.Hidden for i=1, #aAnims do if not aAnimNames[i] or not hidden[aAnimNames[i]] then aAnims[i](self) end end end if self.Lights and self.GlowingLights then for id, light in pairs(self.GlowingLights) do if light.Update then light:Update() end end end -- Update passengers if self.RenderClientEnts and self.PassengerEnts then local stucked = self.PassengerEntsStucked for i,v in ipairs(self.LeftDoorPositions) do if self:GetPackedBool("DoorLS"..i) and not IsValid(stucked[i]) then local ent = ClientsideModel(table.Random(self.PassengerModels),RENDERGROUP_OPAQUE) ent:SetPos(self:LocalToWorld(Vector(v.x,v.y,self:GetStandingArea().z))) ent:SetAngles(self:LocalToWorldAngles(Angle(0,v.y < 0 and -90 or 90,0))) ent:SetSkin(math.floor(ent:SkinCount()*math.random())) ent:SetModelScale(0.98 + (-0.02+0.04*math.random()),0) ent:SetParent(self) stucked[i] = ent if math.random() > 0.99 then self:PlayOnceFromPos("PassStuckL"..i,"subway_trains/common/door/pass_stAAAck.mp3",5,0.9+math.random()*0.2,150,400,v) elseif math.random() > 0.95 then self:PlayOnceFromPos("PassStuckL"..i,"subway_trains/common/door/tom.mp3",5,0.9+math.random()*0.2,150,400,v) elseif ent:GetModel():find("models/metrostroi/passengers/f") then self:PlayOnceFromPos("PassStuckL"..i,"subway_trains/common/door/pass_stuck.mp3",5,1.6+math.random()*0.2,150,400,v) else self:PlayOnceFromPos("PassStuckL"..i,"subway_trains/common/door/pass_stuck.mp3",5,0.9+math.random()*0.2,150,400,v) end elseif not self:GetPackedBool("DoorLS"..i) and IsValid(stucked[i]) then SafeRemoveEntity(stucked[i]) end end for i,v in ipairs(self.RightDoorPositions) do if self:GetPackedBool("DoorRS"..i) and not IsValid(stucked[-i]) then local ent = ClientsideModel(table.Random(self.PassengerModels),RENDERGROUP_OPAQUE) ent:SetPos(self:LocalToWorld(Vector(v.x,v.y,self:GetStandingArea().z))) ent:SetAngles(self:LocalToWorldAngles(Angle(0,v.y < 0 and -90 or 90,0))) ent:SetSkin(math.floor(ent:SkinCount()*math.random())) ent:SetModelScale(0.98 + (-0.02+0.04*math.random()),0) ent:SetParent(self) stucked[-i] = ent if math.random() > 0.99 then self:PlayOnceFromPos("PassStuckR"..i,"subway_trains/common/door/pass_stAAAck.mp3",5,0.9+math.random()*0.2,150,400,v) elseif math.random() > 0.95 then self:PlayOnceFromPos("PassStuckR"..i,"subway_trains/common/door/tom.mp3",5,0.9+math.random()*0.2,150,400,v) elseif ent:GetModel():find("models/metrostroi/passengers/f") then self:PlayOnceFromPos("PassStuckR"..i,"subway_trains/common/door/pass_stuck.mp3",5,1.6+math.random()*0.2,150,400,v) else self:PlayOnceFromPos("PassStuckR"..i,"subway_trains/common/door/pass_stuck.mp3",5,0.9+math.random()*0.2,150,400,v) end elseif not self:GetPackedBool("DoorRS"..i) and IsValid(stucked[-i]) then SafeRemoveEntity(stucked[-i]) end end if #self.PassengerEnts ~= self:GetNW2Float("PassengerCount") then -- Passengers go out while #self.PassengerEnts > self:GetNW2Float("PassengerCount") do local ent = self.PassengerEnts[#self.PassengerEnts] table.remove(self.PassengerPositions,#self.PassengerPositions) table.remove(self.PassengerEnts,#self.PassengerEnts) ent:Remove() end -- Passengers go in while #self.PassengerEnts < self:GetNW2Float("PassengerCount") do local min,max = self:GetStandingArea() local pos = min + Vector((max.x-min.x)*math.random(),(max.y-min.y)*math.random(),(max.z-min.z)*math.random()) --local ent = ents.CreateClientProp("models/metrostroi/81-717/reverser.mdl") --ent:SetModel(table.Random(self.PassengerModels)) local ent = ClientsideModel(table.Random(self.PassengerModels),RENDERGROUP_OPAQUE) ent:SetPos(self:LocalToWorld(pos)) ent:SetAngles(Angle(0,math.random(0,360),0)) --[[ hook.Add("MetrostroiBigLag",ent,function(ent) ent:SetPos(self:LocalToWorld(pos)) ent:SetAngles(Angle(0,math.random(0,360),0)) --if ent.Spawned then hook.Remove("MetrostroiBigLag",ent) end --ent.Spawned = true end)]] ent:SetSkin(math.floor(ent:SkinCount()*math.random())) ent:SetModelScale(0.98 + (-0.02+0.04*math.random()),0) ent:SetParent(self) table.insert(self.PassengerPositions,pos) table.insert(self.PassengerEnts,ent) end end end for k,v in pairs(self.CustomThinks) do if k ~= "BaseClass" then v(self) end end end function ENT:BlockInput(block) if IsValid(LocalPlayer().InMetrostroiTrain) then if self ~= LocalPlayer().InMetrostroiTrain then block = false end end if block and not self.GUILocker then gui.EnableScreenClicker(true) self.GUILocker = vgui.Create("DPanel") self.GUILocker:SetPos(0,0) self.GUILocker:SetSize(ScrW(),ScrH()) self.GUILocker:SetZPos(-32767) self.GUILocker.Paint = function() end self.GUILocker.Focus = self self.GUILocker.Think = function(panel) if panel.Focus ~= vgui.GetKeyboardFocus() then panel.Focus = vgui.GetKeyboardFocus() if IsValid(panel.Focus) then self.GUILocker:SetCursor("") else input.SetCursorPos( ScrW()/2, ScrH()/2) self.GUILocker:SetCursor("blank") end end end self.GUILocker.OnCursorMoved = function(panel,cursorX,cursorY ) if self.GUILocker.LastX ~= cursorX or self.GUILocker.LastY ~= cursorY then if not IsValid(vgui.GetKeyboardFocus()) then local x,y = ScrW()/2, ScrH()/2 input.SetCursorPos(x,y) net.Start("metrostroi-mouse-move",true) net.WriteEntity(self) net.WriteString("") net.WriteFloat((cursorX-x)/(ScrW()/2)) net.WriteFloat((cursorY-y)/(ScrH()/2)) net.SendToServer() end self.GUILocker.LastX = cursorX self.GUILocker.LastY = cursorY end --end end self.GUILocker:RequestFocus() self.GUILocker:SetKeyboardInputEnabled(false) self.GUILocker:MoveToBack(true) self.GUILocker:SizeToChildren() elseif not block and self.GUILocker then self.GUILocker:Remove() self.GUILocker = nil gui.EnableScreenClicker(false) end end function ENT:HandleMouse(handle) if self.HandleMouseInput ~= handle then self.HandleMouseInput = handle self:BlockInput(self.HandleMouseInput) end end local function compensateSeat(val) local valAbs = math.abs(val) if valAbs < 10 then return 1 end if valAbs > 45 then return 0 end local sign = val < 0 and -1 or 1 return 1-math.Clamp((valAbs-10)/6,0,2)+(math.Clamp((valAbs-30)/10,0,1))+(math.Clamp((valAbs-40)/5,0,1)) end local OldTrainHandle,OldSeat hook.Add("Think","metrostroi_mouse_handle",function() local train, outside = isValidTrainDriver(LocalPlayer()) if outside then train = nil end if OldTrainHandle ~= train then if IsValid(OldTrainHandle) and OldTrainHandle.BlockInput then OldTrainHandle:BlockInput(false) end if IsValid(train) and train.BlockInput then train:BlockInput(train.HandleMouseInput) end if IsValid(train) then OldSeat = LocalPlayer():GetVehicle() --[=[train.CamAnglesComp = Angle(0,0,0) train.OldAng = false --OldSeat.CalcAbsolutePosition = function(...) return ... end if not OldSeat.OldCalcAbsolutePosition then OldSeat.OldCalcAbsolutePosition = OldSeat.CalcAbsolutePosition end OldSeat.CalcAbsolutePosition = function(ent,...) --[[local target_ang = Angle(0,0,0) local train = ent:GetNW2Entity("TrainEntity") if not IsValid(train) then return end target_ang:RotateAroundAxis(ent:GetAngles():Forward(),-train.CamAng.p) target_ang:RotateAroundAxis(ent:GetAngles():Up(),train.CamAng.y) target_ang:RotateAroundAxis(ent:GetAngles():Right(),train.CamAng.r) train.CamAnglesComp = target_ang print(target_ang)]] return ent:OldCalcAbsolutePosition(...) end print(OldSeat.OnAngleChangeID)--]=] elseif IsValid(OldSeat) then OldSeat.CalcAbsolutePosition = OldSeat.OldCalcAbsolutePosition or OldSeat.CalcAbsolutePosition OldSeat.OldCalcAbsolutePosition = nil OldSeat = nil end OldTrainHandle = train end end) --[[hook.Add("PlayerEnteredVehicle","metrostroi_mouse_handle",function(ply,veh) local train = veh:GetNW2Entity("TrainEntity") if IsValid(train) then train.CamAnglesComp = Angle(0,0,0) train.OldAng = false if train.BlockInput then train:BlockInput(train.HandleMouseInput) end end end) hook.Add("PlayerEnteredVehicle","metrostroi_mouse_handle",function(ply,veh) local train = veh:GetNW2Entity("TrainEntity") if IsValid(train) then train.CamAnglesComp = Angle(0,0,0) train.OldAng = false if train.BlockInput then train:BlockInput(train.HandleMouseInput) end end end)]] --[[ hook.Add("PlayerEnteredVehicle","metrostroi_mouse_handle",function(ply,veh) local train = veh:GetNW2Entity("TrainEntity") if IsValid(train) and train.BlockInput then train:BlockInput(train.HandleMouseInput) end end)--]] -------------------------------------------------------------------------------- -- Various rendering shortcuts for trains -------------------------------------------------------------------------------- function ENT:DrawCircle(cx,cy,radius) local step = 2*math.pi/12 local vertexBuffer = { {}, {}, {} } for i=1,12 do vertexBuffer[1].x = cx + radius*math.sin(step*(i+0)) vertexBuffer[1].y = cy + radius*math.cos(step*(i+0)) vertexBuffer[2].x = cx vertexBuffer[2].y = cy vertexBuffer[3].x = cx + radius*math.sin(step*(i+1)) vertexBuffer[3].y = cy + radius*math.cos(step*(i+1)) surface.DrawPoly(vertexBuffer) end end -------------------------------------------------------------------------------- -- Schedule Drawing -- -- Reference: http://static.diary.ru/userdir/1/0/4/7/1047/28088395.jpg -------------------------------------------------------------------------------- local function AddZero( s ) if #s == 0 then return "00" elseif #s == 1 then return "0" .. s else return s end end local function HoursFromStamp( stamp ) return AddZero(tostring(math.floor(stamp/3600)%24)) end local function MinutesFromStamp( stamp ) return AddZero(tostring(math.floor(stamp/60)%60)) end local function SecondsFromStamp( stamp ) return AddZero(tostring(stamp%60)) end surface.CreateFont( "Schedule_Hand", { font = "Monotype Corsiva", size = 30, weight = 600 }) surface.CreateFont( "Schedule_Hand_Small", { font = "Monotype Corsiva", size = 18, weight = 600 }) surface.CreateFont( "Schedule_Machine", { font = "Arial", size = 22, weight = 500 }) surface.CreateFont( "Schedule_Machine_Small", { font = "Arial", size = 16, weight = 600 }) local DrawRect = surface.DrawRect local DrawTextHand = function(txt, x, y, col) draw.SimpleText(txt, "Schedule_Hand", x, y, Color(0,15*col.y,85*col.z), 0, 0) end local DrawTextHandSmall = function(txt, x, y, col) draw.SimpleText(txt, "Schedule_Hand_Small", x, y, Color(0,15*col.y,85*col.z), 0, 0) end local DrawTextMachine = function(txt, x, y) draw.SimpleText(txt, "Schedule_Machine", x, y, Color(0,0,0), 0, 0) end local DrawTextMachineSmall = function(txt, x, y) draw.SimpleText(txt, "Schedule_Machine_Small", x, y, Color(0,0,0), 0, 0) end local function FineStationName(st) local StT = string.Explode(" ",st) local str = "" if #StT > 1 then str = StT[1][1]..". "..table.concat(StT," ",2) else str = st end return str end -- Placeholder code, to be removed when schedule system is in place local Schedule = { stations = { {"Station 1", os.time() + 20}, {"Station 2", os.time() + 46}, {"Station 3", os.time() + 80}, {"Station 4", os.time() + 95}, {"Station 5", os.time() + 120} }, total = 2000, interval = 300, routenumber = math.random(100,999), pathnumber = math.random(100,999) } local col1w = 80 -- 1st Column width local col2w = 32 -- The other column widths local rowtall = 30 -- Row height, includes -only- the usable space and not any lines local rowtall2 = rowtall*2 -- Helper local defaultlight = Vector(0.8,0.8,0.8) -- Light to be used when cabinlights are on function ENT:DrawSchedule(panel) local w = panel.width local h = panel.height local light = defaultlight local cabinlights = self:GetPackedBool(58) if not cabinlights then light = render.GetLightColor(self:LocalToWorld(Vector(430,0,26))) -- GetLightColor is pretty shit but it works end --Background surface.SetDrawColor(Color(255 * light.x, 253 * light.y, 208 * light.z)) DrawRect(0,0,w,h) --Lines surface.SetDrawColor(Color(0,0,0)) --Horisontal lines DrawRect(0,0,1,h) DrawRect(1 + col1w,0,1,h) DrawRect(1 + col1w + 1 + col2w,rowtall2+2,1,h-rowtall2-2) DrawRect(1 + col1w + 1 + col2w + 1 + col2w,rowtall2+2,1,h-rowtall2-2) DrawRect(1 + col1w + 1 + col2w + 1 + col2w + 1 + col2w,0,1,h) --Vertical lines DrawRect(0,0,w,1) DrawRect(1 + col1w,rowtall+1,w - col1w - 1,1) DrawRect(1 + col1w,rowtall2+2,w - col1w - 1,1) for i=(rowtall+1)*3,h,rowtall+1 do DrawRect(0,i,w,1) end -- HACK get schedule from train local N = self:GetNW2Int("_schedule_N") Schedule = { stations = {}, total = math.floor(self:GetNW2Int("_schedule_duration")/5+0.5)*5, interval = self:GetNW2Int("_schedule_interval"), routenumber = self:GetNW2Int("_schedule_id"), pathnumber = self:GetNW2Int("_schedule_path"), } for i=1,N do Schedule.stations[i] = { self:GetNW2String("_schedule_"..i.."_5"), math.floor(self:GetNW2Int("_schedule_"..i.."_3")*60/5)*5 } end --Text local t = Schedule --Top info DrawTextMachine("М №", 3, 3) DrawTextHand(t.routenumber, 42, -2, light) DrawTextMachine("П №", 3, rowtall*2 + 3) DrawTextHand(t.pathnumber, 42, rowtall*2 - 2, light) DrawTextMachineSmall("ВРЕМЯ", col1w + 5, 1, light) DrawTextMachineSmall("ХОДА", col1w + 5, 15, light) DrawTextHand(MinutesFromStamp(t.total), w - 50, 1, light) DrawTextHandSmall(SecondsFromStamp(t.total), w - 25, 5, light) DrawTextMachineSmall("ИНТ", col1w + 5, rowtall + 8) DrawTextHand(MinutesFromStamp(t.interval), w - 50, rowtall, light) DrawTextHandSmall(SecondsFromStamp(t.interval), w - 25, rowtall + 4, light) DrawTextMachineSmall("ЧАС", col1w + 4, rowtall*2 + 8) DrawTextMachineSmall("МИН", col1w + col2w + 5, rowtall*2 + 8) DrawTextMachineSmall("СЕК", col1w + col2w*2 + 8, rowtall*2 + 8) --Schedule rows local lasthour = -1 for i,v in pairs(t.stations) do local y = ((rowtall+1)*3+2) + (i-1)*(rowtall+1) -- Uhh.. local st = FineStationName(v[1]) surface.SetFont( "Schedule_Machine_Small" ) local width = select(1, surface.GetTextSize(st)) local szf = math.ceil(width/80)-1 if szf > 0 then szf = math.ceil(#st/8)-1 for i1 = 0,szf do DrawTextMachineSmall(st:Replace("'",""):sub(i1*8+1,8 + i1*8)..(szf ~= i1 and "-" or ""), 3, y + 6 -6 + 12/szf*i1) -- Stationname end else DrawTextMachineSmall(st, 3, y + 6) -- Stationname end local hours = HoursFromStamp(v[2]) local minutes = MinutesFromStamp(v[2]) local seconds = SecondsFromStamp(v[2]) if hours ~= lasthour then -- Only draw hours if they've changed lasthour = hours DrawTextHand(hours, col1w + 3, y, light) -- Hours end DrawTextHand(minutes, col1w + col2w + 5, y, light) -- Minutes DrawTextHand(seconds, col1w + col2w + col2w + 5, y, light) -- Seconds end end -------------------------------------------------------------------------------- -- Default rendering function -------------------------------------------------------------------------------- function ENT:Draw() -- Draw model self:DrawModel() end function ENT:DrawOnPanel(index,func,overr) if not overr and not self:ShouldDrawPanel(index) then return end local panel = self.ButtonMapMatrix and self.ButtonMapMatrix[index] or self.ButtonMap[index] cam.Start3D2D(self:LocalToWorld(panel.pos),self:LocalToWorldAngles(panel.ang),panel.scale) func(panel) cam.End3D2D() end function ENT:DrawRTOnPanel(index,rt,overr) if not overr and not self:ShouldDrawPanel(index) then return end local panel = self.ButtonMapMatrix[index] or self.ButtonMap[index] cam.Start3D2D(self:LocalToWorld(panel.pos),self:LocalToWorldAngles(panel.ang),panel.scale) surface.SetMaterial(rt.mat) --surface.DrawTexturedRect(0,0,panel.width,panel.height) surface.DrawTexturedRectRotated(panel.width/2,panel.height/2,panel.width,panel.height,0) cam.End3D2D() end -------------------------------------------------------------------------------- -- Animation function -------------------------------------------------------------------------------- function ENT:Animate(clientProp, value, min, max, speed, damping, stickyness) local id = clientProp local anims = self.Anims if not anims[id] then anims[id] = {} anims[id].val = value anims[id].value = min + (max-min)*value anims[id].V = 0.0 anims[id].block = false anims[id].stuck = false anims[id].P = value end if self.Hidden[id] or self.Hidden.anim[id] then return 0 end if anims[id].Ignore then if RealTime()-anims[id].Ignore < 0 then return anims[id].value else anims[id].Ignore = nil end end local val = anims[id].val if value ~= val then anims[id].block = false end if anims[id].block then if anims[id].reload and IsValid(self.ClientEnts[clientProp]) then self.ClientEnts[clientProp]:SetPoseParameter("position",anims[id].value) anims[id].reload = false end return anims[id].value--min + (max-min)*anims[id].val end --if self["_anim_old_"..id] == value then return self["_anim_old_"..id] end -- Generate sticky value if stickyness and damping then if (math.abs(anims[id].P - value) < stickyness) and (anims[id].stuck) then value = anims[id].P anims[id].stuck = false else anims[id].P = value end end local dT = FrameTime()--self.DeltaTime if damping == false then local dX = speed * dT if value > val then val = val + dX end if value < val then val = val - dX end if math.abs(value - val) < dX then val = value anims[id].V = 0 else anims[id].V = dX end else -- Prepare speed limiting local delta = math.abs(value - val) local max_speed = 1.5*delta / dT local max_accel = 0.5 / dT -- Simulate local dX2dT = (speed or 128)*(value - val) - anims[id].V * (damping or 8.0) if dX2dT > max_accel then dX2dT = max_accel end if dX2dT < -max_accel then dX2dT = -max_accel end anims[id].V = anims[id].V + dX2dT * dT if anims[id].V > max_speed then anims[id].V = max_speed end if anims[id].V < -max_speed then anims[id].V = -max_speed end val = math.max(0,math.min(1,val + anims[id].V * dT)) -- Check if value got stuck if (math.abs(dX2dT) < 0.001) and stickyness and (dT > 0) then anims[id].stuck = true end end local retval = min + (max-min)*val if IsValid(self.ClientEnts[clientProp]) then self.ClientEnts[clientProp]:SetPoseParameter("position",retval) end if math.abs(anims[id].V) == 0 and math.abs(val-value) == 0 and not anims[id].stuck then anims[id].block = true end anims[id].val = val anims[id].oldival = value anims[id].oldspeed = speed anims[id].value = retval return retval end function ENT:AnimateFrom(clientProp,from,min,max) if not self.Anims[from] then return 0 end local val = Lerp(self.Anims[from].value,min or 0,max or 1) if IsValid(self.ClientEnts[clientProp]) then self.ClientEnts[clientProp]:SetPoseParameter("position",val) end if not self.Anims[clientProp] then self.Anims[clientProp] = {} end self.Anims[clientProp].value = value return val end function ENT:ShowHide(clientProp, value, over) if self.Hidden.override[clientProp] then return end --if IsValid(self.ClientEnts[clientProp]) then if value == true and (self.Hidden[clientProp] or over) then self.Hidden[clientProp] = false if not IsValid(self.ClientEnts[clientProp]) and self:SpawnCSEnt(clientProp) then self.UpdateRender = true end --self.ClientEnts[clientProp]:SetRenderMode(RENDERMODE_NORMAL) --self.ClientEnts[clientProp]:SetColor(Color(255,255,255,255)) --self.Hidden[clientProp] = false return true elseif value ~= true and (not self.Hidden[clientProp] or over) then if IsValid(self.ClientEnts[clientProp]) then self.ClientEnts[clientProp]:Remove() self.UpdateRender = true end self.Hidden[clientProp] = true --self.ClientEnts[clientProp]:SetRenderMode(RENDERMODE_NONE) --self.ClientEnts[clientProp]:SetColor(Color(0,0,0,0)) --self.Hidden[clientProp] = true return true end --self.HiddenQuele[clientProp] = nil --else --end end function ENT:HideButton(clientProp, value) self.Hidden.button[clientProp] = value end function ENT:ShowHideSmooth(clientProp, value,color) if self.Hidden.override[clientProp] then return value end if not IsValid(self.ClientEnts[clientProp]) and self.SmoothHide[clientProp] then self.SmoothHide[clientProp] = 0 end if self.SmoothHide[clientProp] and (self.SmoothHide[clientProp] == value and not color) then return value end self.SmoothHide[clientProp] = value self.Hidden.anim[clientProp] = value == 0 if value > 0 and not IsValid(self.ClientEnts[clientProp]) then if self:ShowHide(clientProp,true) then self.SmoothHide[clientProp] = nil end end if value == 0 and IsValid(self.ClientEnts[clientProp]) then if self:ShowHide(clientProp,false) then self.SmoothHide[clientProp] = nil end end if IsValid(self.ClientEnts[clientProp]) then local v = self.ClientPropsOv and self.ClientPropsOv[clientProp] or self.ClientProps[clientProp] self.ClientEnts[clientProp]:SetRenderMode(RENDERMODE_TRANSALPHA) if color then self.ClientEnts[clientProp]:SetColor(ColorAlpha(color,value*255)) else self.ClientEnts[clientProp]:SetColor(ColorAlpha(v.color or color_white,value*255)) end end return value end function ENT:ShowHideSmoothFrom(clientProp,from) self:ShowHideSmooth(clientProp,self.SmoothHide[from] or 0) end local digit_bitmap = { [1] = { 0,0,1,0,0,1,0 }, [2] = { 1,0,1,1,1,0,1 }, [3] = { 1,0,1,1,0,1,1 }, [4] = { 0,1,1,1,0,1,0 }, [5] = { 1,1,0,1,0,1,1 }, [6] = { 1,1,0,1,1,1,1 }, [7] = { 1,0,1,0,0,1,0 }, [8] = { 1,1,1,1,1,1,1 }, [9] = { 1,1,1,1,0,1,1 }, [0] = { 1,1,1,0,1,1,1 }, } local segment_poly = { [1] = { { x = 0, y = 0 }, { x = 100, y = 0 }, { x = 80, y = 20 }, { x = 20, y = 20 }, }, [2] = { { x = 20, y = 0 }, { x = 80, y = 0 }, { x = 100, y = 20 }, { x = 0, y = 20 }, }, [3] = { { x = 0, y = 0 }, { x = 20, y = 20 }, { x = 20, y = 80 }, { x = 0, y = 100 }, }, [4] = { { x = 0, y = 20 }, { x = 20, y = 0 }, { x = 20, y = 100 }, { x = 0, y = 80 }, }, [5] = { { x = 0, y = 12 }, { x = 20, y = 0 }, { x = 80, y = 0 }, { x = 100, y = 12 }, { x = 80, y = 24 }, { x = 20, y = 24 }, }, } local polys = {} function ENT:DrawSegment(i,x,y,scale_x,scale_y) if not polys[i] then polys[i] = {} end if not polys[i][k] then for k,v in pairs(segment_poly[i]) do polys[i][k] = { x = (v.x*scale_x) + x, y = (v.y*scale_y) + y, } end end surface.SetDrawColor(Color(100,255,0,255)) draw.NoTexture() surface.DrawPoly(polys[i]) end function ENT:DrawDigit(cx,cy,digit,scalex,scaley,thickness) scalex = scalex or 1 scaley = scaley or scalex thickness = thickness or 1 local bitmap = digit_bitmap[digit] if not bitmap then return end local sx = 0.9*scalex*thickness local sy = 0.9*scaley*thickness local dx = scalex local dy = scaley if bitmap[1] == 1 then self:DrawSegment(1,cx+5*dx,cy, sx,sy) end if bitmap[2] == 1 then self:DrawSegment(3,cx,cy+10*dy, sx,sy) end if bitmap[3] == 1 then self:DrawSegment(4,cx+80*dx,cy+10*dy, sx,sy) end if bitmap[4] == 1 then self:DrawSegment(5,cx+5*dx,cy+95*dy, sx,sy) end if bitmap[5] == 1 then self:DrawSegment(3,cx,cy+110*dy, sx,sy) end if bitmap[6] == 1 then self:DrawSegment(4,cx+80*dx,cy+110*dy, sx,sy) end if bitmap[7] == 1 then self:DrawSegment(2,cx+5*dx,cy+190*dy, sx,sy) end end -------------------------------------------------------------------------------- -- Get train acceleration at given position in train -------------------------------------------------------------------------------- function ENT:GetTrainAccelerationAtPos(pos) local localAcceleration = self:GetTrainAcceleration() local angularVelocity = self:GetTrainAngularVelocity() return localAcceleration - angularVelocity:Cross(angularVelocity:Cross(pos*0.01905)) end -------------------------------------------------------------------------------- -- Look into mirrors hook -------------------------------------------------------------------------------- --[[hook.Add("InputMouseApply", "Metrostroi_TrainView", function(cmd,x,y,ang) local seat = LocalPlayer():GetVehicle() if (not seat) or (not seat:IsValid()) then return end local train = seat:GetNW2Entity("TrainEntity") if (not train) or (not train:IsValid()) then return end local target_ang = Angle(0,0,0) target_ang:RotateAroundAxis(seat:GetAngles():Forward(),-ang.p) target_ang:RotateAroundAxis(seat:GetAngles():Up(),ang.y) target_ang:RotateAroundAxis(seat:GetAngles():Right(),ang.r) train.CamAnglesComp = target_ang train.CamAng = ang end)]] hook.Add("CalcVehicleView", "Metrostroi_TrainView", function(seat,ply,tbl) local train = ply.InMetrostroiTrain if not IsValid(train) then return end --local hack = string.find(train:GetClass(),"81") --local dy = 0 --if hack then dy = 3 end --[[-- Get acceleration in the train local headPos = train:WorldToLocal(pos) local acceleration = train:GetTrainAccelerationAtPos(headPos) train.Acceleration = train.Acceleration or Vector(0,0,0) train.Acceleration = train.Acceleration + 0.5*(acceleration - train.Acceleration)*train.DeltaTime if train.Acceleration:Length() > 100 then train.Acceleration = Vector(0,0,0) end -- Calculate direction local direction = train.Acceleration:GetNormalized() -- Calculate visual offset local a = train.Acceleration:Length() local factor = a * math.exp(-0.05*a) local offset = 4 * direction * factor print(train.Acceleration) -- Apply offset return { origin = train:LocalToWorld(headPos + 0.1*offset), angles = ang + Angle(offset.x,0,0), }]]-- if seat:GetThirdPersonMode() and train.MirrorCams[1] then local trainAng = tbl.angles - train:GetAngles() if trainAng.y > 180 then trainAng.y = trainAng.y - 360 end if trainAng.y < -180 then trainAng.y = trainAng.y + 360 end if trainAng.y > 0 then train.CamPos = train:LocalToWorld(train.MirrorCams[1]) train.CamAngles = train:LocalToWorldAngles(train.MirrorCams[2]) return { origin = train.CamPos, angles = train.CamAngles, fov = train.MirrorCams[3], } else train.CamPos = train:LocalToWorld(train.MirrorCams[4]) train.CamAngles = train:LocalToWorldAngles(train.MirrorCams[5]) return { origin = train.CamPos, angles = train.CamAngles, fov = train.MirrorCams[6], } end elseif train.CurrentCamera > 0 and train.Cameras[train.CurrentCamera] then local camera = train.Cameras[train.CurrentCamera] train.CamPos = train:LocalToWorld(camera[1]) local tFov = tbl.fov/C_FovDesired:GetFloat()*C_CabFOV:GetFloat() return { origin = train.CamPos, angles = tbl.angles,--+train:LocalToWorldAngles(camera[2]), fov = tFov, } else train.CamPos = train:LocalToWorld(train:WorldToLocal(tbl.origin)+Vector(train.HeadAcceleration,0,C_CabZ:GetFloat())) local tFov = tbl.fov/C_FovDesired:GetFloat()*C_CabFOV:GetFloat() return { origin = train.CamPos, angles = tbl.angles,--target_ang+train.CamAnglesComp, fov = tFov, } end return end) -------------------------------------------------------------------------------- -- Buttons/panel clicking -------------------------------------------------------------------------------- --Thanks old gmod wiki! --[[ Converts from world coordinates to Draw3D2D screen coordinates. vWorldPos is a vector in the world nearby a Draw3D2D screen. vPos is the position you gave Start3D2D. The screen is drawn from this point in the world. scale is a number you also gave to Start3D2D. aRot is the angles you gave Start3D2D. The screen is drawn rotated according to these angles. ]]-- local function WorldToScreen(vWorldPos, vPos, vScale, aRot) vWorldPos = vWorldPos - vPos vWorldPos:Rotate(Angle(0, -aRot.y, 0)) vWorldPos:Rotate(Angle(-aRot.p, 0, 0)) vWorldPos:Rotate(Angle(0, 0, -aRot.r)) return vWorldPos.x / vScale, (-vWorldPos.y) / vScale end -- Calculates line-plane intersect location local function LinePlaneIntersect(PlanePos,PlaneNormal,LinePos,LineDir) local dot = LineDir:Dot(PlaneNormal) local fac = LinePos-PlanePos local dis = -PlaneNormal:Dot(fac) / dot return LineDir * dis + LinePos end local function findAimButton(ply,train) local panel,panelDist = nil,1e9 for kp,pan in pairs(train.ButtonMap) do if not train:ShouldDrawPanel(kp) then continue end --If player is looking at this panel if pan.aimedAt and (pan.buttons or pan.sensor or pan.mouse) and pan.aimedAt < panelDist then panel = pan panelDist = pan.aimedAt end end if not panel then return false end if panel.aimX and panel.aimY and (panel.sensor or panel.mouse) and math.InRangeXY(panel.aimX,panel.aimY,0,0,panel.width,panel.height) then return false,panel.aimX,panel.aimY,panel.system end if not panel.buttons then return false end local buttonTarget for _,button in pairs(panel.buttons) do if (train.Hidden[button.PropName] or train.Hidden.button[button.PropName]) and (not train.ClientProps[button.PropName] or not train.ClientProps[button.PropName].config or not train.ClientProps[button.PropName].config.staylabel) then continue end if (train.Hidden[button.ID] or train.Hidden.button[button.ID]) and (not train.ClientProps[button.ID] or not train.ClientProps[button.ID].config or not train.ClientProps[button.ID].config.staylabel) then continue end if button.w and button.h then if panel.aimX >= button.x and panel.aimX <= (button.x + button.w) and panel.aimY >= button.y and panel.aimY <= (button.y + button.h) then buttonTarget = button --table.insert(foundbuttons,{button,panel.aimedAt}) end else --If the aim location is withing button radis local dist = math.Distance(button.x,button.y,panel.aimX,panel.aimY) if dist < (button.radius or 10) then buttonTarget = button --table.insert(foundbuttons,{button,panel.aimedAt}) end end end if not buttonTarget then return false end return buttonTarget end -- Checks what button/panel is being looked at and check for custom crosshair hook.Add("Think","metrostroi-cabin-panel",function() local ply = LocalPlayer() if not IsValid(ply) then return end toolTipText = nil drawCrosshair = false canDrawCrosshair = false local train, outside = isValidTrainDriver(ply) if not IsValid(train) then return end if gui.IsConsoleVisible() or gui.IsGameUIVisible() or IsValid(vgui.GetHoveredPanel()) and not vgui.IsHoveringWorld() and vgui.GetHoveredPanel():GetParent() ~= vgui.GetWorldPanel() then return end if train.ButtonMap ~= nil then canDrawCrosshair = true local plyaimvec if outside then plyaimvec = ply:GetAimVector() else local x,y = input.GetCursorPos() --plyaimvec = util.AimVector( train.CamAngles, train.CamFOV,x,y,ScrW(),ScrH()) --plyaimvec = ply:GetAimVector() plyaimvec = gui.ScreenToVector(x,y) -- ply:GetAimVector() is unreliable when in seats end -- Loop trough every panel for k2,panel in pairs(train.ButtonMap) do if not train:ShouldDrawPanel(kp2) then continue end local pang = train:LocalToWorldAngles(panel.ang) if plyaimvec:Dot(pang:Up()) < 0 then local campos = not outside and train.CamPos or ply:EyePos() local ppos = train:LocalToWorld(panel.pos)-- - Vector(math.Round((not outside and train.HeadAcceleration or 0),2),0,0)) local isectPos = LinePlaneIntersect(ppos,pang:Up(),campos,plyaimvec) local localx,localy = WorldToScreen(isectPos,ppos,panel.scale,pang) panel.aimX = localx panel.aimY = localy if plyaimvec:Dot(isectPos - campos)/(isectPos-campos):Length() > 0 and localx > 0 and localx < panel.width and localy > 0 and localy < panel.height then panel.aimedAt = isectPos:Distance(campos) drawCrosshair = panel.aimedAt else panel.aimedAt = false end panel.outside = outside else panel.aimedAt = false end end -- Tooltips local ttdelay = GetConVar("metrostroi_tooltip_delay"):GetFloat() if GetConVar("metrostroi_disablehovertext"):GetInt() == 0 and ttdelay and ttdelay >= 0 then local button = findAimButton(ply,train) --print(train.ClientProps[button.ID].button) if button and ((train.Hidden[button.ID] or train.Hidden[button.PropName]) and (not train.ClientProps[button.ID].config or not train.ClientProps[button.ID].config.staylabel) or (train.Hidden.button[button.ID] or train.Hidden.button[button.PropName]) and (not train.ClientProps[button.PropName].config or not train.ClientProps[button.PropName].config.staylabel)) then return end if button ~= lastAimButton then lastAimButtonChange = CurTime() lastAimButton = button end if button then if ttdelay == 0 or CurTime() - lastAimButtonChange > ttdelay then if C_DrawDebug:GetInt() > 0 then toolTipText,toolTipColor = button.ID,Color(255,0,255) elseif button.plombed then toolTipText,_,toolTipColor = button.plombed(train) else toolTipText,toolTipColor = button.tooltip end --[[toolTipPosition = nil if button.tooltipState then local newTT,newTTpos = button.tooltipState(train) toolTipText = toolTipText..newTT toolTipPosition = Metrostroi.GetPhrase(newTTpos) end]] if GetConVar("metrostroi_disablehovertextpos"):GetInt() == 0 and button.tooltipState and button.tooltip then toolTipText = toolTipText..button.tooltipState(train) end end end end end end) -- Takes button table, sends current status local function sendButtonMessage(button,train,outside) local tooltip,buttID = nil,button.ID if button.plombed then tooltip,buttID = button.plombed(train) end if not buttID then Error(Format("Can't send button message! %s\n",button.ID)) return end net.Start("metrostroi-cabin-button") net.WriteEntity(train) net.WriteString(buttID:gsub("^.+:","")) net.WriteBit(button.state) net.WriteBool(outside) net.SendToServer() return buttID --RunConsoleCommand("metrostroi_button_press",button.ID..(button.state and 1 or 0)) end -- Takes button table, sends current status local function sendPanelTouch(panel,x,y,outside,state) net.Start("metrostroi-panel-touch") net.WriteString(panel or "") net.WriteUInt(x,11) net.WriteUInt(y,11) net.WriteBool(outside) net.WriteBool(state) net.SendToServer() --RunConsoleCommand("metrostroi_button_press",button.ID..(button.state and 1 or 0)) end -- Goes over a train's buttons and clears them, sending a message if needed function ENT:ClearButtons() if self.ButtonMap == nil then return end for _,panel in pairs(self.ButtonMap) do if panel.buttons then for _,button in pairs(panel.buttons) do if button.state == true then button.state = false sendButtonMessage(button,self) end end end end end function ENT:HidePanel(kp,hide) if hide and not self.HiddenPanels[kp] then self.HiddenPanels[kp] = true if self.ButtonMap[kp].props then for _,v in pairs(self.ButtonMap[kp].props) do --self.Hidden[v] = true self:ShowHide(v,false,true) self.Hidden.override[v] = true end end end if not hide and self.HiddenPanels[kp] then self.HiddenPanels[kp] = nil if self.ButtonMap[kp].props then for _,v in pairs(self.ButtonMap[kp].props) do --self.Hidden[v] = false self.Hidden.override[v] = false self:ShowHide(v,true,true) end end end end -- Args are player, IN_ enum and bool for press/release local function handleKeyEvent(ply,key,pressed) if not game.SinglePlayer() and not IsFirstTimePredicted() then return end if gui.IsConsoleVisible() or gui.IsGameUIVisible() or IsValid(vgui.GetHoveredPanel()) and not vgui.IsHoveringWorld() and vgui.GetHoveredPanel():GetParent() ~= vgui.GetWorldPanel() then return end if key ~= MOUSE_LEFT and key ~= MOUSE_RIGHT then return end local train, outside = isValidTrainDriver(ply) if not IsValid(train) then return end if train.ButtonMap == nil then return end if key == MOUSE_LEFT and not pressed then train:ClearButtons() end if pressed then local button,x,y,system = findAimButton(ply,train) local plombed = false if button and button.ID and button.ID[1] ~= "!" and (key ~= MOUSE_LEFT or not button.plombed or not ({button.plombed(train)})[3]) then button.state = true local buttID = sendButtonMessage(button,train,outside) lastButton = button lastButton.train = train if train.OnButtonPressed then train:OnButtonPressed(buttID:gsub("^.+:","")) end elseif not button and x and y and not lastTouch then sendPanelTouch(system,x,y,outside,true) lastTouch = {system,x,y} end else -- Reset the last button pressed if lastButton ~= nil then if lastButton.state == true then lastButton.state = false sendButtonMessage(lastButton,lastButton.train,outside) end if train.OnButtonReleased and button then local tooltip,buttID = nil,button.ID if button.plombed then tooltip,buttID = button.plombed(train) end train:OnButtonReleased(buttID:gsub("^.+:","")) end end if lastTouch ~= nil then sendPanelTouch(lastTouch[1],lastTouch[2],lastTouch[3],outside,false) lastTouch = nil end end end -- Hook for clearing the buttons when player exits net.Receive("metrostroi-cabin-reset",function() local ent = net.ReadEntity() if IsValid(ent) and ent.ClearButtons ~= nil then ent:ClearButtons() end end) local lastChanged = RealTime() local camAnim = 0 local camStart = 0 local camEnd = 1 local function handleCam(ply,button) if not game.SinglePlayer() and not IsFirstTimePredicted() then return end if not input.IsShiftDown() then return end local train, outside = isValidTrainDriver(ply) if not IsValid(train) or outside then return end if not train.Cameras then return end local oldCam = train.CurrentCamera if button == KEY_LEFT then repeat train.CurrentCamera = train.CurrentCamera - 1 if train.CurrentCamera < 0 then train.CurrentCamera = #train.Cameras end until not train.Cameras[train.CurrentCamera] or not train.Cameras[train.CurrentCamera][4] or train:ShouldDrawPanel(train.Cameras[train.CurrentCamera][4]) end if button == KEY_RIGHT then repeat train.CurrentCamera = train.CurrentCamera + 1 if train.CurrentCamera > #train.Cameras then train.CurrentCamera = 0 end until not train.Cameras[train.CurrentCamera] or not train.Cameras[train.CurrentCamera][4] or train:ShouldDrawPanel(train.Cameras[train.CurrentCamera][4]) end if button == KEY_DOWN then train.CurrentCamera = nil end if train.CurrentCamera ~= oldCam then if not train.CurrentCamera then train.CurrentCamera = 0 end if train.CurrentCamera > 0 then local camera = train.Cameras[train.CurrentCamera] local seatAng = ply:GetVehicle():GetAngles() LocalPlayer():SetEyeAngles(train:LocalToWorldAngles(camera[2]-seatAng)) else LocalPlayer():SetEyeAngles(Angle(0,ply:GetVehicle():GetModel()=="models/nova/jeep_seat.mdl" and 90 or 0,0)) end MsgC("Curent camera:",Color(255,0,0),train.CurrentCamera,"\n") if train.CamMoved then train:CamMoved() end lastChanged = RealTime() if camEnd == 1 then camStart = 0 end camEnd = 0 end end hook.Add("PlayerButtonDown", "metrostroi-cabin-buttons", function(ply,key) handleKeyEvent(ply, key,true) handleCam(ply,key) end) hook.Add("PlayerButtonUp", "metrostroi-cabin-buttons", function(ply,key) handleKeyEvent(ply, key,false) end) if game.SinglePlayer() then net.Receive("PlayerButtonDown_metrostroi",function() local key = net.ReadUInt(16) handleCam(LocalPlayer(),key) handleKeyEvent(LocalPlayer(),key,true) end) net.Receive("PlayerButtonUp_metrostroi",function() handleKeyEvent(LocalPlayer(),net.ReadUInt(16),false) end) end local Gradient = Material("vgui/gradient-d") local oldTrain hook.Add( "HUDPaint", "metrostroi-draw-cameras", function() local train, outside = isValidTrainDriver(LocalPlayer()) if not IsValid(train) or not train.Cameras or outside then if IsValid(oldTrain) then oldTrain.CurrentCamera = 0 if oldTrain.CamMoved then oldTrain:CamMoved() end oldTrain = nil camStart = 0 camEnd = 1 end return end oldTrain = train local cam = train.Cameras[train.CurrentCamera] camAnim = camAnim+(train.CurrentCamera-camAnim)*FrameTime()*5 if camStart < 1 then camStart = math.Clamp(camStart+FrameTime()*2,0,1) end if RealTime()-lastChanged > 5 and camEnd < 1 then camEnd = math.Clamp(camEnd+FrameTime()*0.5,0,1) end local a = math.Clamp(camStart*(1-camEnd)*255,0,255 )-- + math.Clamp(255-(255-RealTime()-lastChanged+4)*512,0,255 ) if a<= 0 then return end surface.SetDrawColor(255,255,255,a) surface.DrawRect(15,40,384,40) render.SetScissorRect( 0, 60-15, 512, 60+15, true ) -- Enable the rect draw.SimpleText(Metrostroi.GetPhrase("Train.Common.Camera0"),"Trebuchet24",20,60-camAnim*50,Color( 0, 0, 0,a),TEXT_ALIGN_LEFT,TEXT_ALIGN_CENTER) for k,v in ipairs(train.Cameras) do draw.SimpleText(Metrostroi.GetPhrase(v[3]),"Trebuchet24",20,60+(k-camAnim)*50,Color( 0, 0, 0,a),TEXT_ALIGN_LEFT,TEXT_ALIGN_CENTER) end render.SetScissorRect( 0, 0, 0, 0, false ) -- Disable after you are done --[[ render.SetStencilEnable(true) render.SetStencilTestMask(255);render.SetStencilWriteMask(255);render.SetStencilReferenceValue(10) render.SetStencilPassOperation(STENCIL_REPLACE) render.SetStencilFailOperation(STENCIL_KEEP) render.SetStencilZFailOperation(STENCIL_KEEP) render.SetStencilCompareFunction(STENCIL_ALWAYS) render.SetStencilCompareFunction(STENCIL_EQUAL) render.SetStencilEnable(false) render.SetScissorRect(0,0,0,0,false)]] end) local ppMat = Material("pp/blurx") hook.Add( "HUDPaint", "metrostroi-draw-crosshair-tooltip", function() --if not drawCrosshair then return end if IsValid(LocalPlayer()) then local scrX,scrY = ScrW(),ScrH() if canDrawCrosshair then surface.DrawCircle(scrX/2,scrY/2,4.1,drawCrosshair and Color(255,0,0) or Color(255,255,150)) end if toolTipText ~= nil then surface.SetFont("MetrostroiLabels") local w,h = surface.GetTextSize("SomeText") local height = h*1.1 local texts = string.Explode("\n",toolTipText) surface.SetDrawColor(0,0,0,125) for i,v in ipairs(texts) do local y = scrY/2+height*(i) if #v==0 then continue end local w2,h2 = surface.GetTextSize(v) surface.DrawRect(scrX/2-w2/2-5, scrY/2-h2/2+height*(i), w2+10, h2) --[[if toolTipPosition and i==#texts then local st,en = v:find(toolTipPosition) local textSt,textEn = v:sub(1,st-1),v:sub(en+1,-1) local x1 = 0-w2/2 local x2 = surface.GetTextSize(textSt)-w2/2 local x3 = surface.GetTextSize(textSt)+surface.GetTextSize(toolTipPosition)-w2/2 draw.SimpleText(textSt,"MetrostroiLabels",scrX/2+x1,y, toolTipColor or Color(255,255,255),TEXT_ALIGN_LEFT,TEXT_ALIGN_CENTER) draw.SimpleText(toolTipPosition,"MetrostroiLabels",scrX/2+x2,y, toolTipColor or Color(0,255,0),TEXT_ALIGN_LEFT,TEXT_ALIGN_CENTER) draw.SimpleText(textEn,"MetrostroiLabels",scrX/2+x3,y, toolTipColor or Color(255,255,255),TEXT_ALIGN_LEFT,TEXT_ALIGN_CENTER) Metrostroi.DrawLine(scrX/2+x2,y+h/2-3,scrX/2+x3,y+h/2-3,toolTipColor or Color(0,255,0),1) else]] draw.SimpleText(v,"MetrostroiLabels",scrX/2,y, toolTipColor or Color(255,255,255),TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER) --end end --[[ local w1 = surface.GetTextSize(text1) local w2 = surface.GetTextSize(text2) surface.SetTextColor(toolTipColor or Color(255,255,255)) surface.SetTextPos((scrX-w1)/2,scrY/2+10) surface.DrawText(text1) surface.SetTextPos((scrX-w2)/2,scrY/2+30) surface.DrawText(text2)]] end end end) language.Add("SBoxLimit_train_limit","Wagons limit") -------------------------------------------------------------------------------- -- Turn light on or off -------------------------------------------------------------------------------- function ENT:SetLightPower(index,power,brightness) if self.HiddenLamps[index] then return end local lightData = self.LightsOverride[index] or self.Lights[index] brightness = brightness or 1 if lightData[1] == "glow" or lightData[1] == "light" then if lightData.panel and not self.SpritesEnabled or lightData.aa and self.AAEnabled then return end self.LightBrightness[index] = brightness * (lightData.brightness or 0.5) if power and self.Sprites[index] then return end self.Sprites[index] = nil if not power then return end self.Sprites[index] = util.GetPixelVisibleHandle() lightData.mat = Metrostroi.MakeSpriteTexture((lightData.texture or "sprites/light_glow02"),lightData[1] == "light") return end if power and IsValid(self.GlowingLights[index]) then if lightData[1] == "headlight" and IsValid(self.GlowingLights[index]) then -- Check if light already glowing if brightness ~= self.LightBrightness[index] then local light = self.GlowingLights[index] light:SetBrightness(brightness * (lightData.brightness or 1.25)) light:Update() self.LightBrightness[index] = brightness end return elseif (lightData[1] == "glow") or (lightData[1] == "light") then local brightness = brightness * (lightData.brightness or 0.5) if brightness ~= self.LightBrightness[index] then local light = self.GlowingLights[index] light:SetBrightness(brightness) self.LightBrightness[index] = brightness end return elseif lightData[1] == "dynamiclight" then if brightness ~= self.LightBrightness[index] then local light = self.GlowingLights[index] light:SetLightStrength(brightness) self.LightBrightness[index] = brightness end return end end if IsValid(self.GlowingLights[index]) then self.GlowingLights[index]:Remove() end self.GlowingLights[index] = nil self.LightBrightness[index] = brightness if not power then return end -- Create light if lightData[1] == "light" or lightData[1] == "glow" then local light = ents.CreateClientside("gmod_train_sprite") light:SetPos(self:LocalToWorld(lightData[2])) --light:SetLocalAngles(lightData[3]) -- Set parameters local brightness = brightness * (lightData.brightness or 0.5) light:SetColor(lightData[4]) light:SetBrightness(brightness) light:SetTexture((lightData.texture or "sprites/light_glow02")..".vmt",lightData[1] == "light") light:SetSize(lightData.scale or 1.0) light:Set3D(false) self.GlowingLights[index] = light elseif (lightData[1] == "headlight") and (not lightData.backlight or self.RedLights) and (not lightData.panellight or self.OtherLights) then local light = ProjectedTexture() light:SetPos(self:LocalToWorld(lightData[2])) light:SetAngles(self:LocalToWorldAngles(lightData[3])) --light:SetParent(self) --light:SetLocalPos(lightData[2]) --light:SetLocalAngles(lightData[3]) -- Set parameters if lightData.headlight and self.HeadlightShadows or not lightData.headlight and self.OtherShadows then light:SetEnableShadows((lightData.shadows or 0)>0) else light:SetEnableShadows(false) end if (lightData.shadows or 0)>0 then light:SetFarZ(math.max(lightData.farz or 2048,10)) else light:SetFarZ(lightData.farz or 2048) end light:SetNearZ(lightData.nearz or 16) if lightData.fov then light:SetFOV(lightData.fov or 120) end if lightData.hfov then light:SetHorizontalFOV(lightData.hfov) end if lightData.vfov then light:SetVerticalFOV(lightData.vfov or 120) end light:SetOrthographic(false) -- Set Brightness light:SetBrightness(brightness * (lightData.brightness or 1.25)) light:SetColor(lightData[4]) light:SetTexture(lightData.texture or "effects/flashlight001") -- Turn light on light:Update() --"effects/flashlight/caustics" self.GlowingLights[index] = light elseif lightData[1] == "dynamiclight" then local light = ents.CreateClientside("gmod_train_dlight") light:SetParent(self) -- Set position light:SetLocalPos(lightData[2]) --light:SetLocalAngles(lightData[3]) -- Set parameters light:SetDColor(lightData[4]) light:SetSize(lightData.distance) light:SetBrightness(lightData.brightness or 2) light:SetLightStrength(brightness) -- Turn light on light:Spawn() self.GlowingLights[index] = light end end function ENT:OnStyk(soundid,location,range,pitch) local speed = self:GetNW2Float("TrainSpeed",0)/100 --local str = "" if self.TunnelCoeff > 0.01 then --local snd = Format("b%dtunnel_%d%s",pitch,range%10+1,soundid) --str=str..Format("tun: %s=%s vol=%d pitch=",snd,self.SoundNames[snd],self.TunnelCoeff*(0.9-math.min(speed,1)*0.3),0.9+math.min(speed,1)*0.3) self:PlayOnce(Format("b%dtunnel_%d%s",pitch,range%10+1,soundid),"bass",self.TunnelCoeff*(0.9-math.min(speed,1)*0.3),0.9+math.min(speed,1)*0.2) end if self.StreetCoeff > 0.01 then --local snd = Format("b%dstreet_%d%s",pitch,range%14+1,soundid) --str=str..Format(", str: %s=%s vol=%d pitch=",snd,self.SoundNames[snd],self.StreetCoeff*(0.6-math.min(speed,1)*0.3),0.9+math.min(speed,1)*0.3) self:PlayOnce(Format("b%dstreet_%d%s",pitch,range%14+1,soundid),"bass",self.StreetCoeff*(0.6-math.min(speed,1)*0.3),0.9+math.min(speed,1)*0.2) end --RunConsoleCommand("say",str) end concommand.Add("metrostroi_reload_client",function() Metrostroi.ReloadClientside = true timer.Simple(0.5,function() if Metrostroi.ReloadClientside then Metrostroi.ReloadClientside = false end end) end,nil,"Reload all clientside models") Metrostroi.OptimisationPatch()