1
0
mirror of https://github.com/metrostroi-repo/MetrostroiAddon.git synced 2026-05-02 00:42:29 +00:00
This commit is contained in:
g_brzhezinskiy
2021-01-02 12:51:45 +03:00
commit 1d05caf866
613 changed files with 337020 additions and 0 deletions

View File

@@ -0,0 +1,409 @@
include("shared.lua")
--------------------------------------------------------------------------------
-- Random number generation
--------------------------------------------------------------------------------
local random = math.random
local sqrt = math.sqrt
local ln = math.log
local cos = math.cos
local sin = math.sin
local pi2 = 2*math.pi
-- Generate at least 60 random bits
local function rand60()
return random() + random()/(2^15) + random()/(2^30) + random()/(2^45)
end
-- Generate random gaussian-distributed value
local function gauss_random(x0,sigma)
local u,v = rand60(),rand60()
if u == 0.0 then return gauss_random(x0,sigma) end -- Remove singularity
local r = sqrt(-2 * ln(u))
local x,y = r * cos(pi2*v)
return x*(sigma or 0.5) + (x0 or 0)
end
--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
local passengerModels = { -- Common passenger models
"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",
}
local rareModels = { -- Less common special models
"models/metrostroi/passengers/m3.mdl",
"models/metrostroi/passengers/f5.mdl",
}
function ENT:Initialize()
self.PassengerSounds = CreateSound(self,Sound( "subway_stations/announces/peoples.wav" ))
self.NonPassengerSounds = CreateSound(self,Sound( "ambient/levels/canals/tunnel_wind_loop1.wav" ))
self.ClientModels = {}
self.CleanupModels = {}
self:DrawShadow(false)
end
function ENT:OnRemove()
self.PassengerSounds:Stop()
self.NonPassengerSounds:Stop()
for k,v in pairs(self.ClientModels) do SafeRemoveEntity(v) end
self.ClientModels = {}
self.ClientsideModels = {}
self.CleanupModels = {}
self.Pool = nil
self.PassengersLeft = nil
end
--------------------------------------------------------------------------------
-- Is position in the world free or blocked by world
--------------------------------------------------------------------------------
local trace = {}
local function isPositionFree(pos)
local ped_size = 16
local ped_legs = 8
local ped_height = 90
trace.start = pos+Vector(0,0,ped_legs)
trace.endpos = pos+Vector(0,0,ped_height)
trace.mask = -1
local result = util.TraceLine(trace)
if result.Hit then return false end
trace.start = pos+Vector(-ped_size,0,ped_legs)
trace.endpos = pos+Vector(-ped_size,0,ped_height)
trace.mask = -1
local result = util.TraceLine(trace)
if result.Hit then return false end
trace.start = pos+Vector(ped_size,0,ped_legs)
trace.endpos = pos+Vector(ped_size,0,ped_height)
trace.mask = -1
local result = util.TraceLine(trace)
if result.Hit then return false end
trace.start = pos+Vector(0,-ped_size,ped_legs)
trace.endpos = pos+Vector(0,-ped_size,ped_height)
trace.mask = -1
local result = util.TraceLine(trace)
if result.Hit then return false end
trace.start = pos+Vector(0,ped_size,ped_legs)
trace.endpos = pos+Vector(0,ped_size,ped_height)
trace.mask = -1
local result = util.TraceLine(trace)
if result.Hit then return false end
return true
end
--------------------------------------------------------------------------------
-- Populate platform with pedestrians
--------------------------------------------------------------------------------
function ENT:PopulatePlatform(platformStart,platformEnd,stationCenter)
-- Define platform
local platformDir = platformEnd-platformStart
local platformN = (platformDir:Angle()+Angle(0,90,0)):Forward()
local platformD = platformDir:GetNormalized()
local platformWidth = ((platformStart-stationCenter) - ((platformStart-stationCenter):Dot(platformD))*platformD):Length()
-- Create pool
self.Pool = self.Pool or {}
-- Fill pool
math.randomseed(self:Seed() + #self.Pool)
local N = math.min(self:PoolSize() - #self.Pool,32)
for i=1,N do
local pedestrian = {}
local iterations = 1
while iterations <= 16 do
-- Generate random constants
local a = -1
while (a < 0) or (a > 1) do a = gauss_random(self:GetNW2Float("X0"),self:GetNW2Float("Sigma")) end
local b = math.abs(gauss_random(0.00,0.20))
-- Create random position
pedestrian.distance = b*platformWidth
pedestrian.pos = platformStart + platformDir*a + platformN*pedestrian.distance
-- Check if pedestrian is not standing in a building
if isPositionFree(pedestrian.pos) then break end
iterations = iterations + 1
end
-- Random other parameters
pedestrian.ang = platformN:Angle() + Angle(0,math.random(-50,50),0)
pedestrian.skin = math.random()
pedestrian.scale = 0.98 + gauss_random(0,0.03)
pedestrian.model = table.Random(passengerModels)
-- Add to pool
table.insert(self.Pool,pedestrian)
end
end
--------------------------------------------------------------------------------
-- Think loop that manages clientside models
--------------------------------------------------------------------------------
function ENT:Think()
--self.TestSound = CreateSound(self,Sound( "subway_stations_test1/orange_1.mp3" ))
--self.TestSound:SetDSP(117)
--self.TestSound:SetSoundLevel(105)
--self.TestSound:SetDSP(58)
--self.TestSound:Stop()
--self.TestSound:Play()
--self.TestSound:ChangeVolume(0.7)
self.PrevTime = self.PrevTime or CurTime()
self.DeltaTime = (CurTime() - self.PrevTime)
self.PrevTime = CurTime()
if self:IsDormant() then
if self.Pool then
self:OnRemove()
end
return
end
if self:GetNW2Bool("MustPlayAnnounces") then
self.PassengerSounds:SetSoundLevel(105)
self.PassengerSounds:Play()
self.PassengerSounds:SetSoundLevel(105)
self.PassengerSounds:ChangeVolume(0.3)
else
if self.PassengerSounds:IsPlaying() then
self.PassengerSounds:Stop()
end
end
if self:GetNW2Bool("MustPlaySpooky") then
self.NonPassengerSounds:SetSoundLevel(105)
self.NonPassengerSounds:Play()
self.NonPassengerSounds:SetSoundLevel(105)
self.NonPassengerSounds:ChangeVolume(1)
else
if self.NonPassengerSounds:IsPlaying() then
self.NonPassengerSounds:Stop()
end
end
-- Platform parameters
local platformStart = self:GetNW2Vector("PlatformStart",false)
local platformEnd = self:GetNW2Vector("PlatformEnd",false)
local stationCenter = self:GetPos()--self:GetNW2Vector("StationCenter",false)
if not platformStart or not platformEnd or not stationCenter or not self:GetNW2Float("X0",false) or not self:GetNW2Float("Sigma",false) then return end
-- Platforms with tracks in middle
local dot = (stationCenter - platformStart):Cross(platformEnd - platformStart)
if dot.z > 0.0 then
local a,b = platformStart,platformEnd
platformStart,platformEnd = b,a
end
-- If platform is defined and pool is not
--print(self:GetNW2Vector("StationCenter"))
--print(entStart,entEnd,self.Pool)
local dataReady = (self:GetNW2Float("X0",-1) >= 0) and (self:GetNW2Float("Sigma",-1) > 0)
local poolReady = (self.Pool) and (#self.Pool == self:PoolSize())
if (not poolReady) and (stationCenter:Length() > 0.0) then
self:PopulatePlatform(platformStart,platformEnd,stationCenter)
end
local modelCount = 0
-- Check if set of models changed
if (CurTime() - (self.ModelCheckTimer or 0) > 1.0) and poolReady then
self.ModelCheckTimer = CurTime()
local WindowStart = self:GetNW2Int("WindowStart")
local WindowEnd = self:GetNW2Int("WindowEnd")
for i=1,self:PoolSize() do
local in_bounds = false
if WindowStart <= WindowEnd then in_bounds = (i >= WindowStart) and (i < WindowEnd) end
if WindowStart > WindowEnd then in_bounds = (i >= WindowStart) or (i <= WindowEnd) end
if in_bounds then
-- Model in window
if not self.ClientModels[i] then
--self.ClientModels[i] = ents.CreateClientProp("models/metrostroi/81-717/reverser.mdl")
--self.ClientModels[i]:SetModel(self.Pool[i].model)
--hook.Add("MetrostroiBigLag",self.ClientModels[i],function(ent)
-- if not self.Pool then return end
-- ent:SetPos(self.Pool[i].pos)
-- ent:SetAngles(self.Pool[i].ang)
-- --if ent.Spawned then hook.Remove("MetrostroiBigLag",ent) end
-- --ent.Spawned = true
--end)
self.ClientModels[i] = ClientsideModel(self.Pool[i].model,RENDERGROUP_OPAQUE)
self.ClientModels[i]:SetPos(self.Pool[i].pos)
self.ClientModels[i]:SetAngles(self.Pool[i].ang)
self.ClientModels[i]:SetSkin(math.floor(self.ClientModels[i]:SkinCount()*self.Pool[i].skin))
self.ClientModels[i]:SetModelScale(self.Pool[i].scale,0)
self.ClientModels[i]:DestroyShadow()
self.ClientModels[i]:DrawShadow(false)
modelCount = modelCount + 1
if modelCount > 15 then poolReady = false self.ModelCheckTimer = self.ModelCheckTimer - 0.9 break end
end
else
-- Model found that is not in window
if IsValid(self.ClientModels[i]) then
-- Get nearest door
local count = self:GetNW2Int("TrainDoorCount",0)
local distance = 1e9
local target = Vector(0,0,0)
for j=1,count do
local vec = self:GetNW2Vector("TrainDoor"..j,Vector(0,0,0))
local d = vec:Distance(self.ClientModels[i]:GetPos())
if d < distance then
target = vec
distance = d
end
end
-- Add to list of cleanups
table.insert(self.CleanupModels,{
ent = self.ClientModels[i],
target = target,
})
self.ClientModels[i] = nil
end
end
end
end
-- Add models for cleanup of people who left trains
self.PassengersLeft = self.PassengersLeft or self:GetNW2Int("PassengersLeft")
while poolReady and (self.PassengersLeft < self:GetNW2Int("PassengersLeft")) do
-- Get random door
local count = self:GetNW2Int("TrainDoorCount",0)
local i = math.max(1,math.min(count,1+math.floor((count-1)*math.random() + 0.5)))
local pos = self:GetNW2Vector("TrainDoor"..i,Vector(0,0,0))
pos.z = self:GetPos().z
-- Create clientside model
local i = math.max(1,math.min(self:PoolSize(),1+math.floor(math.random()*self:PoolSize() + 0.5)))
--local ent = ents.CreateClientProp("models/metrostroi/81-717/reverser.mdl")
--ent:SetModel(self.Pool[i].model)
--hook.Add("MetrostroiBigLag",ent,function(ent)
-- ent:SetPos(pos)
-- --if ent.Spawned then hook.Remove("MetrostroiBigLag",ent) end
-- --ent.Spawned = true
--end)
local ent= ClientsideModel(self.Pool[i].model,RENDERGROUP_OPAQUE)
ent:SetPos(pos)
ent:SetSkin(math.floor(ent:SkinCount()*self.Pool[i].skin))
ent:SetModelScale(self.Pool[i].scale,0)
-- Generate target pos
local platformDir = platformEnd-platformStart
local platformN = (platformDir:Angle()+Angle(0,90,0)):Forward()
local platformD = platformDir:GetNormalized()
local platformWidth = ((platformStart-stationCenter) - ((platformStart-stationCenter):Dot(platformD))*platformD):Length()
local target = pos + platformN*platformWidth
pos = pos - platformN * 4.0 * math.random()
pos = pos + platformD * 16.0 * math.random()
target = target + platformD * 128.0 * math.random()
-- Add to list of cleanups
table.insert(self.CleanupModels,{
ent = ent,
target = target,
})
-- Add passenger
self.PassengersLeft = self.PassengersLeft + 1
end
-- Animate models for cleanup
for k,v in pairs(self.CleanupModels) do
-- if not v or not IsValid(v) then self.CleanupModels[k] = nil return end
if not IsValid(v.ent) then
self.CleanupModels[k] = nil
continue
end
-- Get pos and target in XY plane
local pos = v.ent:GetPos()
local target = v.target
pos.z = 0
target.z = 0
local distance = pos:DistToSqr(target)
local count = self:GetNW2Int("TrainDoorCount",0)
-- Delete if reached the target point
if distance < 2*256--[[threshold]] or math.abs(LocalPlayer():GetPos().z - v.ent:GetPos().z) > 256 or count == 0 then
v.ent:Remove()
self.CleanupModels[k] = nil
continue
end
-- Find direction in which pedestrians must walk
local targetDir = (target - pos):GetNormalized()
-- Make it go along the platform if too far
local threshold = 16
if distance > 36864--[[192]] then
local platformDir = (platformEnd-platformStart):GetNormalized()
local projection = targetDir:Dot(platformDir)
if math.abs(projection) > 0.1 then
targetDir = (platformDir * projection):GetNormalized()
end
end
-- Move pedestrian
local speed = 1024
if distance > 1048576--[[1024]] then speed = 2048 end
v.ent:SetPos(v.ent:GetPos() + targetDir*math.min(threshold,speed*self.DeltaTime))
-- Rotate pedestrian
v.ent:SetAngles(targetDir:Angle() + Angle(0,180,0))
--[[ Check if door can be reached at all (it still exists)
local distance = 1e9
local new_target = target
for j=1,count do
local vec = self:GetNW2Vector("TrainDoor"..j,Vector(0,0,0))
local d = vec:DistToSqr(v.target)
if d < distance then
new_target = vec
distance = d
end
end
--if distance > 32
--then v.target = self:GetPos()
--else v.target = new_target
--end]]
end
end
--------------------------------------------------------------------------------
-- Make sure entity is not drawn
--------------------------------------------------------------------------------
function ENT:Draw()
if GetConVarNumber("metrostroi_drawsignaldebug") ~= 1 then return end
local platformStart = self:GetNW2Vector("PlatformStart",false)
local platformEnd = self:GetNW2Vector("PlatformEnd",false)
local pos = self:GetPos()+Vector(0,0,50)
--[[ if platformStart and platformEnd then
pos = platformStart + (platformEnd-platformStart)/2+Vector(0,0,50)
end--]]
--print(2)
local ang = self:LocalToWorldAngles(Angle(0,180,90))
cam.Start3D2D(pos, ang, 0.25)
surface.SetDrawColor(125, 125, 0, 255)
surface.DrawRect(0, 0, 160, 24)
draw.DrawText(Format("[%d]/%d",self:GetNWInt("StationIndex"),self:GetNWInt("PlatformIndex")),"Trebuchet24",5,0,Color(0,0,0,255))
cam.End3D2D()
local ang = self:LocalToWorldAngles(Angle(0,0,90))
cam.Start3D2D(pos, ang, 0.25)
surface.SetDrawColor(125, 125, 0, 255)
surface.DrawRect(0, 0, 160, 24)
draw.DrawText(Format("[%d]/%d",self:GetNWInt("StationIndex"),self:GetNWInt("PlatformIndex")),"Trebuchet24",5,0,Color(0,0,0,255))
cam.End3D2D()
end

View File

@@ -0,0 +1,617 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
ENT.TriTone = Sound( "subway_stations/tritone.mp3" )
function ENT:PlayAnnounce(arriving,Ann)
if not arriving then
if self.MustPlayAnnounces then
local tbl = Metrostroi.StationSound
if not tbl and not Ann then return end
local snd = Ann or table.Random(tbl)
if not snd.sound then snd.sound = Sound(snd[1]) end
sound.Play(snd.sound,self:LocalToWorld(Vector(0,-1200,200)),90,100,1)
sound.Play(snd.sound,self:LocalToWorld(Vector(0,1200,200)),90,100,1)
timer.Adjust( "metrostroi_station_announce_"..self:EntIndex(), snd[2]+math.random(10,30),0,function() self:PlayAnnounce() end)
if self.SyncAnnounces and not Ann then
local ent = Metrostroi.Stations[self.StationIndex][self.PlatformIndex == 2 and 1 or 2].ent
if IsValid(ent) then ent:PlayAnnounce(nil,snd) end
end
else
timer.Remove("metrostroi_station_announce_"..self:EntIndex())
end
elseif arriving == 1 then
sound.Play(self.TriTone,self:LocalToWorld(Vector(0,-1200,200)),120,self.TritonePitch,1)
sound.Play(self.TriTone,self:LocalToWorld(Vector(0,1200,200)),120,self.TritonePitch,1)
elseif arriving == 2 then
sound.Play(Ann,self:LocalToWorld(Vector(-1200,0,50)),120,100,0.4)
sound.Play(Ann,self:LocalToWorld(Vector(1200,0,50)),120,100,0.4)
end
end
--------------------------------------------------------------------------------
-- Initialize the platform data
--------------------------------------------------------------------------------
function ENT:Initialize()
-- Get platform parameters
self.VMF = self.VMF or {}
self.PlatformStart = ents.FindByName(self.VMF.PlatformStart or "")[1]
self.SignOff = tonumber(self.VMF.Sign or self.VMF.NoSign or "0") == 1
self.PlatformEnd = ents.FindByName(self.VMF.PlatformEnd or "")[1]
self.StationIndex = tonumber(self.VMF.StationIndex) or 100
self.PlatformIndex = tonumber(self.VMF.PlatformIndex) or 1
self:SetNWInt("StationIndex",self.StationIndex)
self:SetNWInt("PlatformIndex",self.PlatformIndex)
self.PopularityIndex = self.VMF.PopularityIndex or 1.0
self.PlatformLast = (self.VMF.PlatformLast == "yes")
self.PlatformX0 = self.VMF.PlatformX0 or 0.80
self.PlatformSigma = self.VMF.PlatformSigma or 0.25
self.HorliftStation = tonumber(self.VMF.HorliftStation) or 0
self.HorliftHaveSignal = (tonumber(self.VMF.HorliftHaveSignal) or 0) > 0
self.TritonePitch = math.random(90,110)
if not self.PlatformStart then
self.VMF.PlatformStart = "station"..self.StationIndex.."_"..(self.VMF.PlatformStart or "")
self.PlatformStart = ents.FindByName(self.VMF.PlatformStart or "")[1]
end
if not self.PlatformEnd then
self.VMF.PlatformEnd = "station"..self.StationIndex.."_"..(self.VMF.PlatformEnd or "")
self.PlatformEnd = ents.FindByName(self.VMF.PlatformEnd or "")[1]
end
-- Drop to floor
self:DropToFloor()
if IsValid(self.PlatformStart) then self.PlatformStart:DropToFloor() end
if IsValid(self.PlatformEnd) then self.PlatformEnd:DropToFloor() end
-- Positions
if IsValid(self.PlatformStart) then
self.PlatformStart = self.PlatformStart:GetPos()
else
self.PlatformStart = Vector(0,0,0)
end
if IsValid(self.PlatformEnd) then
self.PlatformEnd = self.PlatformEnd:GetPos()
else
self.PlatformEnd = Vector(0,0,0)
end
self.PlatformDir = self.PlatformEnd-self.PlatformStart
self.PlatformNorm = self.PlatformDir:GetNormalized()
-- Platforms with tracks in middle
local dot = (self:GetPos() - self.PlatformStart):Cross(self.PlatformEnd - self.PlatformStart)
self.InvertSides = dot.z > 0.0
-- Initial platform pool configuration
self.WindowStart = 0 -- Increases when people board train
self.WindowEnd = 0 -- Increases naturally over time
self.PassengersLeft = 0 -- Number of passengers that left trains
-- Send things to client
self:SetNW2Float("X0",self.PlatformX0)
self:SetNW2Float("Sigma",self.PlatformSigma)
self:SetNW2Int("WindowStart",self.WindowStart)
self:SetNW2Int("WindowEnd",self.WindowEnd)
self:SetNW2Int("PassengersLeft",self.PassengersLeft)
self:SetNW2Vector("PlatformStart",self.PlatformStart)
self:SetNW2Vector("PlatformEnd",self.PlatformEnd)
self:SetNW2Vector("StationCenter",self:GetPos())
-- FIXME make this nicer
for i=1,32 do self:SetNW2Vector("TrainDoor"..i,Vector(0,0,0)) end
self:SetNW2Int("TrainDoorCount",0)
local ars_ents = ents.FindInSphere(self.PlatformEnd,768)
for k,v in pairs(ars_ents) do
local delta_z = math.abs(self.PlatformEnd.z-v:GetPos().z)
if (v:GetClass() == "gmod_track_pui") and (delta_z < 160) then
self.PUI = v
v.Horlift = self.HorliftStation > 0
end
end
--[[
self.NoEntry = {}
local position = Metrostroi.StationData and Metrostroi.StationData[self.StationIndex] and "outside" or "inside"
if Metrostroi.StationAnnouncesToEnd then
for i,route in ipairs(Metrostroi.WorkingStations) do
for k, station in ipairs(route) do
if station == self.StationIndex then
local endst = route[self.PlatformIndex == 1 and #route or 1]
if Metrostroi.StationAnnouncesToEnd[endst] then
self.NoEntry.arr = Sound(Metrostroi.StationAnnouncesToEnd[endst][1]:Replace("inside",position))
self.NoEntry.dep = Sound(Metrostroi.StationAnnouncesToEnd[endst][2]:Replace("inside",position))
end
break
end
if self.NoEntry.arr then break end
end
end
end
if not self.NoEntry.arr then
ErrorNoHalt("Metrostroi: Station "..self.VMF.PlatformStart.." can't choose no_entry sound!\n")
self.NoEntry.arr = Sound( "subway_stations/"..position.."/no_entry_arr.mp3" )
self.NoEntry.dep = Sound( "subway_stations/"..position.."/no_entry_dep.mp3" )
end
self.NoEntry.depot = Sound( "subway_stations/"..position.."/no_entry_depot.mp3" )
if Metrostroi.StationAnnouncesTo[self.StationIndex] and Metrostroi.StationAnnouncesTo[self.StationIndex][1] == self.PlatformIndex then
self.NoEntry.specarr = Sound(Metrostroi.StationAnnouncesTo[self.StationIndex][3])
self.NoEntry.specdep = Sound(Metrostroi.StationAnnouncesTo[self.StationIndex][4])
end]]
self:DrawShadow(false)
end
function ENT:OnRemove()
timer.Destroy("metrostroi_station_announce_"..self:EntIndex())
end
--------------------------------------------------------------------------------
-- Load key-values defined in VMF
--------------------------------------------------------------------------------
function ENT:KeyValue(key, value)
self.VMF = self.VMF or {}
self.VMF[key] = value
end
--------------------------------------------------------------------------------
-- Process platform logic
--------------------------------------------------------------------------------
function erf(x)
local a1 = 0.254829592
local a2 = -0.284496736
local a3 = 1.421413741
local a4 = -1.453152027
local a5 = 1.061405429
local p = 0.3275911
-- Save the sign of x
sign = 1
if x < 0 then sign = -1 end
x = math.abs(x)
-- A&S formula 7.1.26
t = 1.0/(1.0 + p*x)
y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*math.exp(-x*x)
return sign*y
end
local function CDF(x,x0,sigma) return 0.5 * (1 + erf((x - x0)/math.sqrt(2*sigma^2))) end
local function merge(t1,t2) for k,v in pairs(t2) do table.insert(t1,v) end end
function ENT:PopulationCount()
local totalCount = self.WindowEnd - self.WindowStart
if self.WindowStart > self.WindowEnd then totalCount = (self:PoolSize() - self.WindowStart) + self.WindowEnd end
return totalCount
end
local empty_checked = {}
local function getTrainDriver(train,checked)
if not checked then
for k,v in pairs(empty_checked) do empty_checked[k] = nil end
checked = empty_checked
end
if not IsValid(train) then return end
if checked[train] then return end
checked[train] = true
local ply = train:GetDriver()
if IsValid(ply) then -- and (train.KV.ReverserPosition ~= 0)
return ply
end
return getTrainDriver(train.RearTrain,checked) or getTrainDriver(train.FrontTrain,checked)
end
function ENT:FireHorliftDoors(input)
if not self.Doors then
self.Doors = {}
local doors = ents.FindByName("station"..self.StationIndex.."_platform"..self.PlatformIndex.."_door")
for _,v in pairs(doors) do
table.insert(self.Doors,v)
end
end
for _,v in pairs(self.Doors) do
v:Fire(input,"","1")
end
end
function ENT:GetDoorState()
if not self.Doors then
self.Doors = {}
local doors = ents.FindByName("station"..self.StationIndex.."_platform"..self.PlatformIndex.."_door")
for _,v in pairs(doors) do
table.insert(self.Doors,v)
end
end
for _,v in pairs(self.Doors) do
if v:GetSaveTable().m_toggle_state ~= 1 then return true end
end
return false
end
local function getPassengerRate(passCount)
if passCount < 80 then
return 1-((passCount/80)^3)*0.2
else
return 1-math.min(1, (((passCount-80)/220)^0.6)*0.85+0.2)
end
end
ENT.TESTTEST = false
local dT = 0.25
local trains = {}
function ENT:Think()
if not Metrostroi.Stations[self.StationIndex] then return end
-- Send update to client
self:SetNW2Int("WindowStart",self.WindowStart)
self:SetNW2Int("WindowEnd",self.WindowEnd)
self:SetNW2Int("PassengersLeft",self.PassengersLeft)
-- Check if any trains are at the platform
if Metrostroi.Stations[self.StationIndex] then
self.MustPlayAnnounces = (not Metrostroi.Stations[self.StationIndex][self.PlatformIndex == 2 and 1 or 2] or self.PlatformIndex == 1)
self:SetNW2Bool("MustPlaySpooky",(not Metrostroi.Stations[self.StationIndex][self.PlatformIndex == 2 and 1 or 2]) and self.PlatformIndex == 1)
if self:GetNW2Bool("MustPlaySpooky") then
end
if not timer.Exists("metrostroi_station_announce_"..self:EntIndex()) and self.MustPlayAnnounces then
timer.Create("metrostroi_station_announce_"..self:EntIndex(),0,0,function() self:PlayAnnounce() end)
end
self.SyncAnnounces = self.InvertSides and Metrostroi.Stations[self.StationIndex][self.PlatformIndex == 2 and 1 or 2]
self:SetNW2Bool("MustPlayAnnounces",self.MustPlayAnnounces or self.InvertSides)
end
local boardingDoorList = {}
local CurrentTrain
local TrainArrivedDist
local PeopleGoing = false
local boarding = false
local BoardTime = 8+7*self.HorliftStation
for k,v in pairs(ents.FindByClass("gmod_subway_*")) do
if v.Base ~= "gmod_subway_base" and v:GetClass() ~= "gmod_subway_base" then continue end
if not IsValid(v) or v:GetPos():Distance(self:GetPos()) > self.PlatformStart:Distance(self.PlatformEnd) then continue end
local platform_distance = ((self.PlatformStart-v:GetPos()) - ((self.PlatformStart-v:GetPos()):Dot(self.PlatformNorm))*self.PlatformNorm):Length()
local vertical_distance = math.abs(v:GetPos().z - self.PlatformStart.z)
if vertical_distance >= 192 or platform_distance >= 256 then continue end
local minb,maxb = v:LocalToWorld(Vector(-480,0,0)),v:LocalToWorld(Vector(480,0,0)) --FIXME
--[[
local minb,maxb = v:WorldSpaceAABB() --FIXME
if (self:GetAngles()-v:GetAngles()):Forward().y > 0 then
local temp = maxb
maxb = minb
minb = temp
end
--local train_start = (v:LocalToWorld(Vector(480,0,0)) - self.PlatformStart):Dot(self.PlatformDir) / (self.PlatformDir:Length()^2)
--local train_end = (v:LocalToWorld(Vector(-480,0,0)) - self.PlatformStart):Dot(self.PlatformDir) / (self.PlatformDir:Length()^2)]]
local train_start = (maxb - self.PlatformStart):Dot(self.PlatformDir) / (self.PlatformDir:Length()^2)
local train_end = (minb - self.PlatformStart):Dot(self.PlatformDir) / (self.PlatformDir:Length()^2)
local left_side = train_start > train_end
if self.InvertSides then left_side = not left_side end
local doors_open = left_side and v.LeftDoorsOpen or not left_side and v.RightDoorsOpen
if (train_start < 0) and (train_end < 0) then doors_open = false end
if (train_start > 1) and (train_end > 1) then doors_open = false end
if -0.2 < train_start and train_start < 1.2 then
v.BoardTime = self.Timer and CurTime()-self.Timer
v.Horlift = self.HorliftStation > 0
end
if 0 < train_start and train_start < 1 and (not TrainArrivedDist or TrainArrivedDist < train_start) then
TrainArrivedDist = train_start
CurrentTrain = v
end
-- Check horizontal lift station logic
local passengers_can_board = false
if self.HorliftStation > 0 then
-- Check fine stop
local stopped_fine = false
for i=0,4 do
local x_s = 0.99086 - i*0.1929
local x_e = 0.97668 - i*0.1929
stopped_fine = stopped_fine or ((train_start < x_s) and (train_start > x_e))
end
-- Open doors on station
if stopped_fine and (v.SOSD or self.OldOpened and not self.OpenedBySOSD) then
self.OpenedBySOSD = v.SOSD
self.HorliftTimer1 = self.HorliftTimer1 or CurTime()
if ((CurTime() - self.HorliftTimer1) > 0.5) then
if not self.HorliftTimer2 then self:FireHorliftDoors("Open") end
self.HorliftTimer2 = CurTime()
end
end
-- Allow boarding
if self.HorliftTimer2 and self:GetDoorState() then passengers_can_board = doors_open end
else
passengers_can_board = doors_open
end
-- Board passengers
if passengers_can_board then
-- Find player of the train
local driver = getTrainDriver(v)
-- Limit train to platform
train_start = math.max(0,math.min(1,train_start))
train_end = math.max(0,math.min(1,train_end))
-- Check if this was the last stop
if (v.LastPlatform ~= self) then
v.LastPlatform = self
if v.AnnouncementToLeaveWagonAcknowledged then v.AnnouncementToLeaveWagonAcknowledged = nil end
-- How many passengers must leave on this station
local proportion = math.random() * math.max(0,1.0 + math.log(self.PopularityIndex))
if self.PlatformLast then proportion = 1 end
if (v.AnnouncementToLeaveWagon == true) then proportion = 1 end
-- Total count
v.PassengersToLeave = math.floor(proportion * v:GetNW2Float("PassengerCount") + 0.5)
end
-- Check for announcement
if v.AnnouncementToLeaveWagon and not v.AnnouncementToLeaveWagonAcknowledged then
v.AnnouncementToLeaveWagonAcknowledged = true
end
-- Calculate number of passengers near the train
local passenger_density = math.abs(CDF(train_start,self.PlatformX0,self.PlatformSigma) - CDF(train_end,self.PlatformX0,self.PlatformSigma))
local passenger_count = passenger_density * self:PopulationCount()
-- Get number of doors
local door_count = #v.LeftDoorPositions
if not left_side then door_count = #v.RightDoorPositions end
-- Get maximum boarding rate for normal russian subway train doors
local max_boarding_rate = getPassengerRate(v:GetNW2Int("PassengerCount")) * 1.4 * door_count * dT
--print(Format("R:%.2f\tS:%.2f\tP:% 3d",max_boarding_rate,getPassengerRate(v:GetNW2Int("PassengerCount")),v:GetNW2Int("PassengerCount")))
-- Get boarding rate based on passenger density
local boarding_rate = math.min(max_boarding_rate,passenger_count)
if self.PlatformLast then boarding_rate = 0 end
-- Get rate of leaving
local leaving_rate = 1.4 * door_count * dT
if v.PassengersToLeave == 0 and not v.AnnouncementToLeaveWagonAcknowledged then leaving_rate = 0 end
-- Board these passengers into train
local speedLimit = math.max(0,1-math.abs(v.Speed/5))
local boarded,left,count
--if v.AnnouncementToLeaveWagonAcknowledged then
if v.AnnouncementToLeaveWagonAcknowledged then
boarded = 0
left = math.ceil(math.min(math.max(2,leaving_rate + 0.5),v:GetNW2Int("PassengerCount"))*speedLimit*1.5)
count = v:GetNW2Int("PassengerCount")
else
count = self:PopulationCount() + v.PassengersToLeave
boarded = math.ceil(math.min(math.max(2,boarding_rate+0.5),self:PopulationCount())*speedLimit)
left = math.ceil(math.min(math.max(2,leaving_rate +0.5),v.PassengersToLeave)*speedLimit)
end
if (v.PrevLeftDoorsOpening ~= v.LeftDoorsOpening) then
v.CanStuckPassengerLeft = not v.LeftDoorsOpening and ((boarded > 0 or left > 0) and math.min(1,count/100) or math.min(1,count/400))
v.PrevLeftDoorsOpening = v.LeftDoorsOpening
if v.LeftDoorsOpening then v.LastPlatform = nil end
end
if (v.PrevRightDoorsOpening ~= v.RightDoorsOpening) then
v.CanStuckPassengerRight = not v.RightDoorsOpening and ((boarded > 0 or left > 0) and math.min(1,count/100) or math.min(1,count/400))
v.PrevRightDoorsOpening = v.RightDoorsOpening
if v.RightDoorsOpening then v.LastPlatform = nil end
end
if math.random() <= math.Clamp(17-passenger_count,0,17)/17*0.5 then boarded = 0 end
if math.random() <= math.Clamp(17-v.PassengersToLeave,0,17)/17*0.5 then left = 0 end
local passenger_delta = boarded - left
-- People board from platform
if boarded > 0 then
PeopleGoing = true
self.WindowStart = (self.WindowStart + boarded) % self:PoolSize()
end
-- People leave to
if left > 0 then
PeopleGoing = true
if IsValid(driver) then
driver:AddFrags(left)
driver.MTransportedPassengers = (driver.MTransportedPassengers or 0) + left
end
-- Move passengers
v.PassengersToLeave = v.PassengersToLeave - left
self.PassengersLeft = self.PassengersLeft + left
if v.AnnouncementToLeaveWagonAcknowledged and not self.PlatformLast then
if math.random() > 0.3 then
self.WindowStart = (self.WindowStart - left) % self:PoolSize()
end
elseif not self.PlatformLast and math.random() > 0.9 then
self.WindowStart = (self.WindowStart - left) % self:PoolSize()
end
end
--[[ People boarded train
if boarded > 0 then
if IsValid(driver) then
driver:AddDeaths(boarded)
end
end]]
-- Change number of people in train
v:BoardPassengers(passenger_delta)
-- Keep list of door positions
if left_side then
for k, vec in ipairs(v.LeftDoorPositions) do
table.insert(boardingDoorList, v:LocalToWorld(vec))
end
else
for k, vec in ipairs(v.RightDoorPositions) do
table.insert(boardingDoorList, v:LocalToWorld(vec))
end
end
if v.AnnouncementToLeaveWagonAcknowledged then
BoardTime = math.max(BoardTime,8+7*self.HorliftStation+(v.PassengersToLeave or 0)*dT*0.6)
else
BoardTime = math.max(BoardTime,8+7*self.HorliftStation+math.max((v.PassengersToLeave or 0)*dT,self:PopulationCount()*dT)*0.5)
end
-- Add doors to boarding list
--print("BOARDING",boarding_rate,"DELTA = "..passenger_delta,self.PlatformLast,v:GetNW2Int("PassengerCount"))
end
if v.UPO then v.UPO.AnnouncerPlay = self.AnnouncerPlay end
v.BoardTimer = self.BoardTimer
boarding = boarding or passengers_can_board
end
--if not boarding then CurrentTrain = nil end
self.BoardTime = BoardTime
if CurrentTrain and not self.CurrentTrain then
self.CurrentTrain = CurrentTrain
self:PlayAnnounce(1)
elseif not CurrentTrain and self.CurrentTrain then
self.CurrentTrain = nil
end
--PUI Timer
if boarding and not self.Timer then self.Timer = math.max(CurTime()+20,CurTime()+self.BoardTime) end
if not self.CurrentTrain and self.Timer then self.Timer = nil end
if self.Timer then
self.BoardTimer = -(CurTime()-self.Timer)
self.AnnouncerPlay = self.BoardTimer < 8+7*self.HorliftStation+0.2
--print(self.PlatformIndex,self.BoardTimer,self.AnnouncerPlay)
else
self.BoardTimer = 20
self.AnnouncerPlay = false
end
if IsValid(self.PUI) then
local train = self.CurrentTrain
if IsValid(train) and Metrostroi.EndStations and Metrostroi.EndStations[1] and type(train.SignsIndex) == "number" then
local id = Metrostroi.EndStations[1][1]
for k,v in pairs(train.SignsList or {}) do
if v == train.SignsIndex then id = k; break end
end
local ends = false
for k,v in pairs(Metrostroi.EndStations) do
if id == v[1] or id == v[#v] then
ends = true
break
end
end
if ends then
self.PUI.Last = 0
else
self.PUI.Last = id
end
else
self.PUI.Last = 0
end
if IsValid(self.CurrentTrain) and IsValid(self.PUI) and self.Timer then
if not self.PUI.Work then self.PUI.Work = true end
self.PUI.BoardTime = self.BoardTimer
local time = 8+7*self.HorliftStation
self.PUI.Lamp = time-0.2 < self.BoardTimer and self.BoardTimer < time+0.3
else
self.PUIStartGoing = false
self.PUI.Work = false
self.PUI.Lamp = false
end
end
--[[
if self.CurrentTrain and not self.SignOff then
if not self.TritonePlayed then
if false and self.CurrentTrain.SignsList and (self.CurrentTrain.SignsList[self.CurrentTrain.SignsIndex] == "" or self.CurrentTrain.SignsList[self.CurrentTrain.SignsIndex] and self.CurrentTrain.SignsList[self.CurrentTrain.SignsIndex][3]) then
self:PlayAnnounce(2,self.NoEntry.arr)
timer.Simple(20,function()
if not IsValid(self.CurrentTrain) then return end
self:PlayAnnounce(2,self.NoEntry.dep)
if self.CurrentTrain.SignsIndex == #self.CurrentTrain.SignsList-3 then
timer.Simple(15,function() self:PlayAnnounce(2,self.NoEntry.depot) end)
end
end)
else
local spec = false
if self.NoEntry.specarr then
for k,v in pairs( self.CurrentTrain.SignsList or {}) do
if v == self.CurrentTrain.SignsIndex then
if Metrostroi.StationAnnouncesTo[self.StationIndex][2] == k then
spec = true
break
end
end
end
end
if spec then
self:PlayAnnounce(2,self.NoEntry.specarr)
timer.Simple(20,function()
if not IsValid(self.CurrentTrain) then return end
self:PlayAnnounce(2,self.NoEntry.specdep)
end)
else
self:PlayAnnounce(1)
end
end
self.TritonePlayed = true
end
else
self.TritonePlayed = nil
end]]
-- Add passengers
if (not self.PlatformLast) and (#boardingDoorList == 0) then
local target = (Metrostroi.PassengersScale or 50)*self.PopularityIndex --300
-- then target = target*0.1 end
if target <= 0 then
self.WindowEnd = self.WindowStart
else
local growthDelta = math.max(0,(target-self:PopulationCount())*0.005)
if growthDelta < 1.0 then -- Accumulate fractional rate
self.GrowthAccumulation = (self.GrowthAccumulation or 0) + growthDelta
if self.GrowthAccumulation > 1.0 then
growthDelta = 1
self.GrowthAccumulation = self.GrowthAccumulation - 1.0
end
end
self.WindowEnd = (self.WindowEnd + math.floor(growthDelta+0.5)) % self:PoolSize()
end
end
if self.HorliftStation > 0 then
if self.HorliftTimer2 then
if (CurTime() - self.HorliftTimer2) > 1 then
self:FireHorliftDoors("Close")
self.HorliftTimer1 = nil
self.HorliftTimer2 = nil
self.HorliftTimer3 = CurTime()
end
end
if self.HorliftTimer3 and (CurTime() - self.HorliftTimer3) > 2.5 then
self.HorliftTimer3 = nil
end
end
if self.OldOpened ~= self:GetDoorState() or self.OldPeopleGoing ~= PeopleGoing then
self.ARSOverride = true
self.OldOpened = self:GetDoorState()
self.OldPeopleGoing = PeopleGoing
if not self.OldOpened then self.OpenedBySOSD = false end
end
-- Block local ARS sections
if self.ARSOverride ~= nil then
-- Signal override to all signals
local ars_ents = ents.FindInSphere(self.PlatformEnd,768)
for k,v in pairs(ars_ents) do
local delta_z = math.abs(self.PlatformEnd.z-v:GetPos().z)
if (v:GetClass() == "gmod_track_signal") and (delta_z < 128) then
v.OverrideTrackOccupied = self:GetDoorState()
end
if (v:GetClass() == "gmod_track_horlift_signal") and (delta_z < 90 and v:GetNWInt("Type") == 0 or v:GetNWInt("Type") == 1) then
v.WhiteSignal = self:GetDoorState()
v.YellowSignal = not self:GetDoorState()
v.PeopleGoing = PeopleGoing
end
end
-- Finish override
self.ARSOverride = nil
end
if self.BoardingDoorListLength ~= #boardingDoorList then
-- Send boarding list FIXME make this nicer
for k,v in ipairs(boardingDoorList) do
self:SetNW2Vector("TrainDoor"..k,v)
end
self:SetNW2Int("TrainDoorCount",#boardingDoorList)
end
self.BoardingDoorListLength = #boardingDoorList
self:NextThink(CurTime() + dT)
return true
end

View File

@@ -0,0 +1,14 @@
-- FIXME: this should be point, but for some reason it won't call init/think callbacks that way
--ENT.Type = "point"
ENT.Type = "anim"
ENT.PrintName = "Train Platform"
ENT.Spawnable = false
ENT.AdminSpawnable = false
function ENT:PoolSize()
return 1024
end
function ENT:Seed()
return self:EntIndex()
end