mirror of
https://github.com/metrostroi-repo/MetrostroiAddon.git
synced 2026-05-02 00:42:29 +00:00
2608 lines
95 KiB
Lua
2608 lines
95 KiB
Lua
AddCSLuaFile("cl_init.lua")
|
|
AddCSLuaFile("shared.lua")
|
|
include("shared.lua")
|
|
-----------------------------------DUPLICATOR----------------------------------
|
|
|
|
function ENT:PreEntityCopy()
|
|
local BaseDupe = {}
|
|
local Tbl = {}
|
|
if IsValid(self.FrontBogey) then
|
|
Tbl[1] = {
|
|
self.FrontBogey:EntIndex(),
|
|
self.FrontBogey.NoPhysics,
|
|
self.FrontBogey:GetAngles(),
|
|
}
|
|
end
|
|
if IsValid(self.FrontJoin) then
|
|
Tbl[1][4] = self.FrontJoin:EntIndex()
|
|
end
|
|
if IsValid(self.RearBogey) then
|
|
Tbl[2] = {
|
|
self.RearBogey:EntIndex(),
|
|
self.RearBogey.NoPhysics,
|
|
self.RearBogey:GetAngles(),
|
|
}
|
|
end
|
|
if IsValid(self.RearJoin) then
|
|
Tbl[2][4] = self.RearJoin:EntIndex()
|
|
end
|
|
|
|
BaseDupe.Tbl = Tbl
|
|
duplicator.StoreEntityModifier(self, "BaseDupe", BaseDupe)
|
|
end
|
|
duplicator.RegisterEntityModifier( "BaseDupe" , function() end)
|
|
|
|
function ENT:PostEntityPaste(ply,ent,createdEntities)
|
|
local BaseDupe = ent.EntityMods.BaseDupe
|
|
local Tbl = BaseDupe.Tbl
|
|
for k,v in pairs(Tbl) do
|
|
BaseDupe.Tbl[k][1] = createdEntities[BaseDupe.Tbl[k][1]] or nil
|
|
BaseDupe.Tbl[k][4] = createdEntities[BaseDupe.Tbl[k][4]] or nil
|
|
end
|
|
if IsValid(self.FrontBogey) and IsValid(BaseDupe.Tbl[1][1]) then self.FrontBogey:Remove() end
|
|
if IsValid(self.RearBogey) and IsValid(BaseDupe.Tbl[2][1]) then self.RearBogey:Remove() end
|
|
if IsValid(self.FrontJoin) and IsValid(BaseDupe.Tbl[1][4]) then self.FrontJoin:Remove() end
|
|
if IsValid(self.RearJoin) and IsValid(BaseDupe.Tbl[2][4]) then self.RearJoin:Remove() end
|
|
if IsValid(self.FrontBogey) and IsValid(self.RearBogey) and not self.IgnoreEngine then
|
|
for i = 1,#self.TrainEntities do
|
|
if IsValid(self.TrainEntities[i]) and self.TrainEntities[i]:GetClass() == "gmod_train_bogey" then
|
|
table.remove(self.TrainEntities,i)
|
|
end
|
|
end
|
|
end
|
|
self.FrontBogey = Tbl[1][1] or nil
|
|
self.RearBogey = Tbl[2][1] or nil
|
|
for k,v in pairs(Tbl) do
|
|
if IsValid(v[1]) then
|
|
v[1].NoPhysics = v[2] or nil
|
|
|
|
-- Assign ownership
|
|
if IsValid(self:GetPlayer()) then v[1]:SetPlayer(self:GetPlayer()) end
|
|
if CPPI and IsValid(self:CPPIGetOwner()) then v[1]:CPPISetOwner(self:CPPIGetOwner()) end
|
|
|
|
-- Some shared general information about the bogey
|
|
self.SquealSound = self.SquealSound or math.floor(4*math.random())
|
|
self.SquealSensitivity = self.SquealSensitivity or math.random()
|
|
v[1].SquealSensitivity = self.SquealSensitivity
|
|
v[1]:SetNW2Int("SquealSound",self.SquealSound)
|
|
v[1]:SetNW2Bool("IsForwardBogey", k == 1)
|
|
v[1]:SetNW2Entity("TrainEntity", self)
|
|
|
|
-- Constraint bogey to the train
|
|
if self.NoPhysics then
|
|
v[1]:SetParent(self)
|
|
else
|
|
constraint.Axis(v[1],self,0,0,
|
|
Vector(0,0,0),Vector(0,0,0),
|
|
0,0,0,1,Vector(0,0,1),false)
|
|
end
|
|
|
|
if self.SubwayTrain.Type == "Tatra" then
|
|
v[1]:SetAngles(self:GetAngles() + Angle(0,(1-k)*180,0))
|
|
end
|
|
table.insert(self.TrainEntities,v[1])
|
|
end
|
|
end
|
|
self.Owner = ply
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
local C_MaxWagons = GetConVar("metrostroi_maxwagons")
|
|
local C_MaxTrains = GetConVar("metrostroi_maxtrains")
|
|
local C_MaxTrainsOnPly = GetConVar("metrostroi_maxtrains_onplayer")
|
|
|
|
function ENT:Initialize()
|
|
self.Joints = {}
|
|
self.JointPositions = {}
|
|
if self:GetModel() == "models/error.mdl" then
|
|
self:SetModel("models/props_lab/reciever01a.mdl")
|
|
end
|
|
if not self.NoPhysics then
|
|
self:PhysicsInit(SOLID_VPHYSICS)
|
|
self:SetMoveType(MOVETYPE_VPHYSICS)
|
|
self:SetSolid(SOLID_VPHYSICS)
|
|
else
|
|
self:SetSolid(SOLID_VPHYSICS)
|
|
end
|
|
self:SetUseType(SIMPLE_USE)
|
|
-- Prop-protection related
|
|
if IsValid(self.Owner) then
|
|
self:SetPlayer(self.Owner)
|
|
if CPPI then self:CPPISetOwner(self.Owner) end
|
|
end
|
|
-- Entities that belong to train and must be cleaned up later
|
|
self.TrainEntities = {}
|
|
-- All the sitting positions in train
|
|
self.Seats = {}
|
|
-- List of headlights, dynamic lights, sprite lights
|
|
self.Lights = {}
|
|
-- Load sounds
|
|
self:InitializeSounds()
|
|
if self.NoTrain then return end
|
|
|
|
-- Possible number of train wires
|
|
self.TrainWireCount = self.TrainWireCount or 36
|
|
-- Train wires
|
|
self:ResetTrainWires()
|
|
self:UpdateWagonList()
|
|
self:ChooseTrainWireLeader()
|
|
self:GenerateWagonNumber()
|
|
-- Systems defined in the train
|
|
self.Systems = {}
|
|
-- Initialize train systems
|
|
self:InitializeSystems()
|
|
-- Initialize highspeed interface
|
|
self:InitializeHighspeedLayout()
|
|
-- Add telemetry recording module if required
|
|
if GetConVar("metrostroi_write_telemetry"):GetInt() == 1 then
|
|
self:LoadSystem("Telemetry")
|
|
end
|
|
self:LoadSystem("FailSim")
|
|
|
|
|
|
if Wire_CreateInputs then
|
|
-- Initialize wire interface
|
|
self.WireIOSystems = self.WireIOSystems or { "KV", "ALSCoil", "Pneumatic"}
|
|
self.WireIOIgnoreList = self.WireIOIgnoreList or {
|
|
"ALSCoilEnabled", "ALSCoilRealF5"
|
|
}
|
|
local inputs = {}
|
|
local outputs = {}
|
|
local inputTypes = {}
|
|
local outputTypes = {}
|
|
local ignoreOutputs = {}
|
|
for k,v in pairs(self.WireIOIgnoreList) do
|
|
ignoreOutputs[v] = true
|
|
end
|
|
for _,name in pairs(self.WireIOSystems) do
|
|
local v = self.Systems[name]
|
|
if v then
|
|
local i = v:Inputs()
|
|
local o = v:Outputs()
|
|
|
|
for _,v2 in pairs(i) do
|
|
if type(v2) == "string" then
|
|
local name = (v.Name or "")..v2
|
|
if not ignoreOutputs[name] then
|
|
table.insert(inputs,name)
|
|
table.insert(inputTypes,"NORMAL")
|
|
end
|
|
elseif type(v2) == "table" then
|
|
local name = (v.Name or "")..v2[1]
|
|
if not ignoreOutputs[name] then
|
|
table.insert(inputs,name)
|
|
table.insert(inputTypes,v2[2])
|
|
end
|
|
else
|
|
ErrorNoHalt("Invalid wire input for metrostroi subway entity")
|
|
end
|
|
end
|
|
|
|
for _,v2 in pairs(o) do
|
|
if type(v2) == "string" then
|
|
local name = (v.Name or "")..v2
|
|
if not ignoreOutputs[name] then
|
|
table.insert(outputs,(v.Name or "")..v2)
|
|
table.insert(outputTypes,"NORMAL")
|
|
end
|
|
elseif type(v2) == "table" then
|
|
local name = (v.Name or "")..v2[1]
|
|
if not ignoreOutputs[name] then
|
|
table.insert(outputs,(v.Name or "")..v2[1])
|
|
table.insert(outputTypes,v2[2])
|
|
end
|
|
else
|
|
ErrorNoHalt("Invalid wire output for metrostroi subway entity")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Add input for a custom driver seat
|
|
table.insert(inputs,"DriverSeat")
|
|
table.insert(inputTypes,"ENTITY")
|
|
|
|
-- Add input for wrench
|
|
table.insert(inputs,"DriversWrenchPresent")
|
|
table.insert(inputTypes,"NORMAL")
|
|
|
|
-- Add I/O for train wires
|
|
if self.SubwayTrain then
|
|
--for i=1,self.TrainWireCount do
|
|
for i=1,20 do
|
|
table.insert(inputs,"TrainWire"..i)
|
|
table.insert(inputTypes,"NORMAL")
|
|
table.insert(outputs,"TrainWire"..i)
|
|
table.insert(outputTypes,"NORMAL")
|
|
end
|
|
end
|
|
|
|
self.Inputs = WireLib.CreateSpecialInputs(self,inputs,inputTypes)
|
|
self.Outputs = WireLib.CreateSpecialOutputs(self,outputs,outputTypes)
|
|
else
|
|
self.WireIOSystems = {}
|
|
self.WireIOIgnoreList = {}
|
|
end
|
|
|
|
-- Setup drivers controls
|
|
self.ButtonBuffer = {}
|
|
self.KeyBuffer = {}
|
|
self.KeyMap = {}
|
|
|
|
-- Override for if drivers wrench is present
|
|
self.DriversWrenchPresent = false
|
|
|
|
-- External interaction areas
|
|
self.InteractionAreas = {}
|
|
|
|
-- Joystick module support
|
|
if joystick then
|
|
self.JoystickBuffer = {}
|
|
end
|
|
self.DebugVars = {}
|
|
|
|
|
|
-- Cross-connections in train wires
|
|
self.TrainWireCrossConnections = {}
|
|
|
|
self.TrainWireInverts = {}
|
|
self.TrainWireWritersID = {}
|
|
self.TrainWireTurbostroi = {}
|
|
-- Overrides for train wire values from wiremod interface and special concommand
|
|
self.TrainWireOverrides = {}
|
|
self.TrainWireOutside = {}
|
|
self.TrainWireOutsideFrom = {}
|
|
|
|
|
|
-- Is this train 'odd' or 'even' in coupled set
|
|
self.TrainCoupledIndex = 0
|
|
|
|
-- Speed and acceleration of train
|
|
self.Speed = 0
|
|
self.SpeedSign = 0
|
|
self.Acceleration = 0
|
|
|
|
-- Initialize train
|
|
if Turbostroi and (not self.NoPhysics) and not self.DontAccelerateSimulation then
|
|
Turbostroi.InitializeTrain(self)
|
|
end
|
|
if not Turbostroi or self.DontAccelerateSimulation then self.DataCache = {} end
|
|
|
|
-- Passenger related data (must be set by derived trains to allow boarding)
|
|
self.LeftDoorsOpen = false
|
|
--self.LeftDoorsBlocked = false
|
|
self.PrevLeftDoorsOpening = false
|
|
self.LeftDoorsOpening = false
|
|
self.RightDoorsOpen = false
|
|
--self.RightDoorsBlocked = false
|
|
self.PrevRightDoorsOpening = false
|
|
self.RightDoorsOpening = false
|
|
|
|
-- Get default train mass
|
|
if IsValid(self:GetPhysicsObject()) then
|
|
self.NormalMass = self:GetPhysicsObject():GetMass()
|
|
end
|
|
|
|
SetGlobalInt("metrostroi_train_count",Metrostroi.TrainCount())
|
|
net.Start("MetrostroiTrainCount") net.Broadcast()
|
|
|
|
--[[GRAVHULL
|
|
if GravHull then
|
|
if !(IsValid(self) and self:GetMoveType() == MOVETYPE_VPHYSICS and !GravHull.HULLS[self]) then return false end
|
|
GravHull.RegisterHull(self,-2,100)
|
|
GravHull.UpdateHull(self)
|
|
end
|
|
]]
|
|
|
|
self.FailSim:TriggerInput("TrainWires",self.TrainWireCount)
|
|
self:FindFineSkin()
|
|
-- Initialize train systems
|
|
self:PostInitializeSystems()
|
|
for k,v in pairs(self.CustomSpawnerUpdates) do if k ~= "BaseClass" then v(self) end end
|
|
end
|
|
function ENT:GetWagonNumber()
|
|
return self.WagonNumber or self:EntIndex()
|
|
end
|
|
-- Remove entity
|
|
function ENT:OnRemove()
|
|
-- Remove FailSim objects
|
|
for k,v in pairs(self.Systems) do
|
|
if FailSim.Objects[v] then FailSim.Objects[v] = nil end
|
|
end
|
|
|
|
-- Remove all linked objects
|
|
constraint.RemoveAll(self)
|
|
if self.TrainEntities then
|
|
for k,v in pairs(self.TrainEntities) do
|
|
SafeRemoveEntity(v)
|
|
end
|
|
end
|
|
|
|
-- Deinitialize train
|
|
if Turbostroi then
|
|
Turbostroi.DeinitializeTrain(self)
|
|
end
|
|
SetGlobalInt("metrostroi_train_count",Metrostroi.TrainCount())
|
|
net.Start("MetrostroiTrainCount") net.Broadcast()
|
|
end
|
|
|
|
function ENT:GetDriverName()
|
|
local drv = self:GetDriver()
|
|
local name = tostring(self)
|
|
if IsValid(drv) then
|
|
name = drv:GetName().."(sit in driver place)"
|
|
elseif IsValid(self.Owner) then
|
|
name = self.Owner:GetName().."(owner)"
|
|
end
|
|
return name
|
|
end
|
|
|
|
function ENT:GetDriverPly()
|
|
local drv = self:GetDriver()
|
|
if IsValid(drv) then
|
|
return drv,true
|
|
elseif IsValid(self.Owner) then
|
|
return self.Owner,false
|
|
else
|
|
return self,nil
|
|
end
|
|
end
|
|
|
|
-- Interaction zones
|
|
function ENT:Use(ply)
|
|
local tr = ply:GetEyeTrace()
|
|
if not tr.Hit then return end
|
|
local hitpos = self:WorldToLocal(tr.HitPos)
|
|
--print(hitpos)
|
|
if self.InteractionZones and ply:GetPos():Distance(tr.HitPos) < 100 then
|
|
for k,v in pairs(self.InteractionZones) do
|
|
if hitpos:Distance(v.Pos) < v.Radius then
|
|
self:ButtonEvent(v.ID,nil,ply)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Trigger output
|
|
function ENT:TriggerOutput(name,value)
|
|
if Wire_TriggerOutput then
|
|
Wire_TriggerOutput(self,name,tonumber(value) or 0)
|
|
end
|
|
end
|
|
|
|
-- Trigger input
|
|
function ENT:TriggerInput(name, value)
|
|
-- Custom seat
|
|
if name == "DriverSeat" then
|
|
if IsValid(value) and value:IsVehicle() then
|
|
self.DriverSeat = value
|
|
else
|
|
self.DriverSeat = nil
|
|
end
|
|
end
|
|
|
|
-- Train wire input
|
|
if string.sub(name,1,9) == "TrainWire" then
|
|
local id = tonumber(string.sub(name,10))
|
|
self.TrainWireOverrides[id] = value
|
|
end
|
|
|
|
-- Drivers wrench present
|
|
if name == "DriversWrenchPresent" then
|
|
self.DriversWrenchPresent = (value > 0.5)
|
|
end
|
|
|
|
-- Propagate inputs to relevant systems
|
|
for k,v in pairs(self.Systems) do
|
|
if v.IsInput[name] then
|
|
v:TriggerInput(name,value)
|
|
elseif v.Name and (string.sub(name,1,#v.Name) == v.Name) then
|
|
local subname = string.sub(name,#v.Name+1)
|
|
if v.IsInput[subname] then
|
|
v:TriggerInput(subname,value)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- The debugger will call this
|
|
function ENT:GetDebugVars()
|
|
-- Train wires
|
|
for i=1,99 do
|
|
self.DebugVars["TW"..i] = self:ReadTrainWire(i)
|
|
end
|
|
|
|
-- System variables
|
|
for k,v in pairs(self.Systems) do
|
|
for _,output in pairs(v.OutputsList or {}) do
|
|
self.DebugVars[(v.Name or "")..output] = v[output] or 0
|
|
end
|
|
end
|
|
|
|
-- Speed/acceleration
|
|
self.DebugVars["Speed"] = self.Speed
|
|
self.DebugVars["Acceleration"] = self.Acceleration
|
|
return self.DebugVars
|
|
end
|
|
|
|
--Debugging function, call via the console or something
|
|
function ENT:ShowInteractionZones()
|
|
for k,v in pairs(self.InteractionZones) do
|
|
debugoverlay.Sphere(self:LocalToWorld(v.Pos),v.Radius,15,Color(255,185,0),true)
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Highspeed interface
|
|
--------------------------------------------------------------------------------
|
|
-- Initialize highspeed layout
|
|
function ENT:InitializeHighspeedLayout()
|
|
--local layout = ""
|
|
self.HighspeedLayout = {}
|
|
for k,v in pairs(Metrostroi.TrainHighspeedInterface) do
|
|
local offset = v[1] + 128
|
|
if self.Systems[v[2] ] then
|
|
self.HighspeedLayout[offset] = function(value)
|
|
if value then
|
|
self.Systems[v[2] ]:TriggerInput(v[3],value)
|
|
else
|
|
return (self.Systems[v[2] ][v[3] ] or 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
--layout = layout.."["..offset.."]\t"..v[2].."."..v[3].."\r\n"
|
|
end
|
|
--file.Write("hs_layout.txt",layout)
|
|
|
|
--[[local str = ""
|
|
local offset = 0
|
|
for k,v in SortedPairs(self.Systems) do
|
|
for i=1,#v.InputsList do
|
|
str = str.."{ "..offset..", \""..k.."\", \""..v.InputsList[i].."\" },\r\n"
|
|
offset = offset + 1
|
|
end
|
|
for i=1,#v.OutputsList do
|
|
str = str.."{ "..offset..", \""..k.."\", \""..v.OutputsList[i].."\" },\r\n"
|
|
offset = offset + 1
|
|
end
|
|
str = str..k.."\r\n"
|
|
end
|
|
file.Write("hs_layout3.txt",str)]]--
|
|
end
|
|
|
|
function ENT:ChooseTrainWireLeader()
|
|
local key = math.random( 1, #self.WagonList )
|
|
for k,v in ipairs(self.WagonList) do
|
|
v.TrainWireLeader = key==k
|
|
end
|
|
end
|
|
|
|
function ENT:ElectricConnected(train,isRear)
|
|
if not IsValid(train) then return end
|
|
local conf = self.SubwayTrain
|
|
|
|
if isRear then
|
|
local rT = self.RearTrain
|
|
local rC = rT.SubwayTrain
|
|
|
|
if not IsValid(rT) then return end
|
|
local rTIsFront = rT.FrontTrain==train
|
|
|
|
if rTIsFront and rC and rC.NoFrontEKK then return end
|
|
if rC and conf and conf.EKKType ~= rC.EKKType then return end
|
|
if rTIsFront and rT.FrontCoupledBogeyDisconnect or not rTIsFront and rT.RearCoupledBogeyDisconnect or self.RearCoupledBogeyDisconnect then return end
|
|
if not IsValid(self.RearCouple) or self.RearCouple:ElectricDisconnected() or (
|
|
rT.FrontTrain == self and (not IsValid(rT.FrontCouple) or rT.FrontCouple:ElectricDisconnected())
|
|
or rT.RearTrain == self and (not IsValid(rT.RearCouple) or rT.RearCouple:ElectricDisconnected())) then return end
|
|
else
|
|
local fT = self.FrontTrain
|
|
local fC = fT.SubwayTrain
|
|
|
|
if not IsValid(fT) then return end
|
|
local fTIsFront = fT.FrontTrain==train
|
|
|
|
if conf.NoFrontEKK or fTIsFront and fC and fC.NoFrontEKK then return end
|
|
if fC and conf and conf.EKKType ~= fC.EKKType then return end
|
|
if fTIsFront and fT.FrontCoupledBogeyDisconnect or not fTIsFront and fT.RearCoupledBogeyDisconnect or self.FrontCoupledBogeyDisconnect then return end
|
|
if IsValid(self.FrontCouple) and self.FrontCouple:ElectricDisconnected() or (
|
|
fT.FrontTrain == self and (not IsValid(fT.FrontCouple) or fT.FrontCouple:ElectricDisconnected())
|
|
or fT.RearTrain == self and (not IsValid(fT.RearCouple) or fT.RearCouple:ElectricDisconnected())) then return end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ENT:UpdateWagonList(selfupdate)
|
|
if self.LastWagonListUpdate == CurTime() then return end
|
|
self.LastUpdate = CurTime()
|
|
|
|
-- Populate list of wagons
|
|
self.WagonList = {}
|
|
self.WagonListIDs = {}
|
|
local function populateList(train,checked)
|
|
if IsValid(train) then
|
|
if checked[train] then return end
|
|
checked[train] = true
|
|
|
|
self.WagonListIDs[train] = table.insert(self.WagonList,train)
|
|
local conf = train.SubwayTrain
|
|
local fT = train.FrontTrain
|
|
if IsValid(fT) then
|
|
local fC = fT.SubwayTrain
|
|
--if not conf.NoFrontEKK and (fT.FrontTrain~=train or not fC.NoFrontEKK) and not train.FrontCoupledBogeyDisconnect and conf.EKKType == fC.EKKType then
|
|
if train:ElectricConnected(fT,false) then
|
|
populateList(fT,checked)
|
|
end
|
|
end
|
|
local rT = train.RearTrain
|
|
if IsValid(rT) then
|
|
local rC = rT.SubwayTrain
|
|
--if (rT.FrontTrain~=train or not rC.NoFrontEKK) and not train.RearCoupledBogeyDisconnect and conf.EKKType == rC.EKKType then
|
|
if train:ElectricConnected(rT,true) then
|
|
populateList(rT,checked)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
populateList(self,{})
|
|
if selfupdate then return end
|
|
for _,v in pairs(self.WagonList) do
|
|
if v ~= self then v:UpdateWagonList(true) end
|
|
end
|
|
end
|
|
|
|
function ENT:GetWagonCount()
|
|
return #self.WagonList
|
|
end
|
|
|
|
function ENT:ReadCell(Address)
|
|
if Address < 0 then return nil end
|
|
if Address == 0 then
|
|
return 1
|
|
end
|
|
if (Address > 0) and (Address < 128) then
|
|
return self:ReadTrainWire(Address)
|
|
end
|
|
if self.HighspeedLayout[Address] then
|
|
return self.HighspeedLayout[Address]()
|
|
end
|
|
if (Address >= 49152) and (Address < 49152+8192) then
|
|
local x = (Address - (49152+64))
|
|
local entryID = math.floor(x/4)
|
|
local varID = x%4
|
|
|
|
if self.Schedule then
|
|
if (entryID >= 0) then
|
|
local entry = self.Schedule[entryID+1]
|
|
if entry then
|
|
if varID >= 2 then
|
|
return (entry[varID+1] or 0)*60
|
|
else
|
|
return entry[varID+1] or 0
|
|
end
|
|
end
|
|
end
|
|
if Address == 49152 then return #self.Schedule end
|
|
if Address == 49153 then return self.Schedule.ScheduleID end
|
|
if Address == 49154 then return self.Schedule.Interval end
|
|
if Address == 49155 then return self.Schedule.Duration end
|
|
if Address == 49156 then return self.Schedule.StartStation end
|
|
if Address == 49157 then return self.Schedule.EndStation end
|
|
if Address == 49158 then return self.Schedule.StartTime*60 end
|
|
if Address == 49159 then return self.Schedule.EndTime*60 end
|
|
end
|
|
|
|
local pos = Metrostroi.TrainPositions[self]
|
|
if (Address >= 49160) and (Address <= 49171) and pos and pos[1] then
|
|
pos = pos[1]
|
|
|
|
-- Get stations
|
|
local current,next,prev = 0,0,0
|
|
local cPlatID,nPlatID,pPlatID = 0,0,0
|
|
local x1,x2,x3 = 1e9,0,1e9
|
|
for stationID,stationData in pairs(Metrostroi.Stations) do
|
|
for platformID,platformData in pairs(stationData) do
|
|
if (platformData.node_start.path == pos.path) and
|
|
(platformData.x_start < pos.x) and
|
|
(platformData.x_end > pos.x) then
|
|
current = stationID
|
|
cPlatID = platformID
|
|
end
|
|
if (platformData.node_start.path == pos.path) and
|
|
(platformData.x_start > pos.x) then
|
|
if platformData.x_start < x1 then
|
|
x1 = platformData.x_start
|
|
next = stationID
|
|
nPlatID = platformID
|
|
end
|
|
end
|
|
if (platformData.node_start.path == pos.path) and
|
|
(platformData.x_start < pos.x) then
|
|
if platformData.x_start > x2 then
|
|
x2 = platformData.x_start
|
|
prev = stationID
|
|
pPlatID = platformID
|
|
end
|
|
end
|
|
if (platformData.node_start.path == pos.path) and
|
|
(platformData.x_end > pos.x) then
|
|
if platformData.x_end < x3 then
|
|
x3 = platformData.x_end
|
|
next = stationID
|
|
nPlatID = platformID
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if Address == 49160 then return current end
|
|
if Address == 49161 then return next end
|
|
if Address == 49162 then return prev end
|
|
if Address == 49163 then return x1 - pos.x end
|
|
if Address == 49165 then return x3 - pos.x end
|
|
if Address == 49166 then return cPlatID end
|
|
if Address == 49167 then return nPlatID end
|
|
if Address == 49168 then return pPlatID end
|
|
|
|
if Address == 49169 then return current > 0 and current or next end
|
|
if Address == 49170 then return cPlatID > 0 and cPlatID or nPlatID end
|
|
if Address == 49171 then return x2 - pos.x end
|
|
end
|
|
return 0
|
|
end
|
|
|
|
if (Address >= 57344) and (Address < 57344+4096) then
|
|
local x = (Address - 57344)
|
|
local lineID = math.floor(x/800)
|
|
local stationID = math.floor((x - lineID*800)/8)
|
|
local platformID = math.floor((x - lineID*800 - stationID*8)/4)
|
|
local varID = x - lineID*800 - stationID*8 - platformID*4
|
|
|
|
local station = Metrostroi.Stations[(lineID+1)*100 + stationID]
|
|
if station then
|
|
local platform = station[platformID]
|
|
if platform then
|
|
if varID == 0 then return platform.x_start end
|
|
if varID == 1 then return platform.x_end end
|
|
if varID == 2 then return platform.node_start.path.id end
|
|
if varID == 3 then return 0 end
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
if (Address >= 65504) and (Address <= 65510) then
|
|
local pos = Metrostroi.TrainPositions[self]
|
|
if pos and pos[1] then
|
|
pos = pos[1]
|
|
if Address == 65504 then return pos.x end
|
|
if Address == 65505 then return pos.y end
|
|
if Address == 65506 then return pos.z end
|
|
if Address == 65507 then return pos.distance end
|
|
if Address == 65508 then return pos.forward and 1 or 0 end
|
|
if Address == 65509 then return pos.node.id end
|
|
if Address == 65510 then return pos.path.id end
|
|
end
|
|
return 0
|
|
end
|
|
if Address == 65535 then
|
|
---self:UpdateWagonList()
|
|
return #self.WagonList
|
|
end
|
|
if Address >= 65536 then
|
|
local wagonIndex = 1+math.floor(Address/65536)
|
|
local variableAddress = Address % 65536
|
|
---self:UpdateWagonList()
|
|
|
|
if self.WagonList[wagonIndex] and IsValid(self.WagonList[wagonIndex]) then
|
|
return self.WagonList[wagonIndex]:ReadCell(variableAddress)
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
end
|
|
|
|
function ENT:WriteCell(Address, value)
|
|
if Address < 0 then return false end
|
|
if Address == 0 then return true end
|
|
if (Address >= 1) and (Address < 128) then
|
|
self.TrainWireOverrides[Address] = value > 0 and 1 or nil
|
|
return true
|
|
end
|
|
if self.HighspeedLayout[Address] then
|
|
self.HighspeedLayout[Address](value)
|
|
return true
|
|
end
|
|
if (Address >= 32768) and (Address < (32768+32*24)) then
|
|
local stringID = math.floor((Address-32768)/32)
|
|
local charID = (Address-32768)%32
|
|
local prevStr = self:GetNW2String("CustomStr"..stringID)
|
|
local newStr = ""
|
|
for i=0,31 do
|
|
local ch = string.byte(prevStr,i+1) or 32
|
|
if i == charID then ch = value end
|
|
newStr = newStr..(string.char(ch) or "?")
|
|
end
|
|
self:SetNW2String("CustomStr"..stringID,newStr)
|
|
end
|
|
if Address == 49164 then
|
|
if self.Announcer then
|
|
self.Announcer:Queue(value)
|
|
end
|
|
end
|
|
if Address >= 65536 then
|
|
local wagonIndex = 1+math.floor(Address/65536)
|
|
local variableAddress = Address % 65536
|
|
---self:UpdateWagonList()
|
|
|
|
if self.WagonList[wagonIndex] and IsValid(self.WagonList[wagonIndex]) then
|
|
return self.WagonList[wagonIndex]:WriteCell(variableAddress,value)
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
|
|
function ENT:SpawnButton(model,pos,ang,min,max,soundtbl)
|
|
local ent = ents.Create("gmod_train_button")
|
|
ent:SetPos(self:LocalToWorld(pos))
|
|
ent:SetAngles(self:LocalToWorldAngles(ang ))
|
|
ent:SetParent(self)
|
|
ent.Owner = ply
|
|
ent.Model = model
|
|
ent.Sounds = soundtbl
|
|
ent:Spawn()
|
|
ent:Activate()
|
|
return ent
|
|
end
|
|
function ENT:SpawnSwitch(model,pos,ang,min,max,soundtbl)
|
|
local ent = ents.Create("gmod_train_swtich")
|
|
ent:SetPos(self:LocalToWorld(pos))
|
|
ent:SetAngles(self:LocalToWorldAngles(ang ))
|
|
ent:SetParent(self)
|
|
ent.Owner = ply
|
|
ent.Model = model
|
|
ent.Min = min
|
|
ent.Max = max
|
|
ent.Sounds = soundtbl
|
|
ent:Spawn()
|
|
ent:Activate()
|
|
return ent
|
|
end
|
|
|
|
function ENT:CANWrite(source,sourceid,target,targetid,textdata,numdata,checked)
|
|
for i=1,#self.WagonList do
|
|
local train = self.WagonList[i]
|
|
if not targetid or targetid == train:GetWagonNumber() then
|
|
local sys = train[target]
|
|
if sys and sys.CANReceive then
|
|
sys:CANReceive(source,sourceid,target,targetid,textdata,numdata)
|
|
end
|
|
if targetid then return end
|
|
end
|
|
end
|
|
--[[
|
|
--print(self,"CANWrite called")
|
|
if not checked then
|
|
checked = {}
|
|
end
|
|
checked[self] = true
|
|
|
|
if self.RearTrain and not checked[self.RearTrain] and not self.RearCoupledBogeyDisconnect then
|
|
self.RearTrain:CANWrite(source,sourceid,target,targetid,textdata,numdata,checked)
|
|
end
|
|
if self.FrontTrain and not checked[self.FrontTrain] and not self.FrontCoupledBogeyDisconnect then
|
|
self.FrontTrain:CANWrite(source,sourceid,target,targetid,textdata,numdata,checked)
|
|
end]]
|
|
end
|
|
--[[
|
|
function ENT:CANWrite(src,dest,textdata,numdata)
|
|
self:CANReceive(src,dest,textdata,numdata)
|
|
end]]
|
|
--(train,3,"","",36,train:ReadTrainWire(36))
|
|
--------------------------------------------------------------------------------
|
|
-- Train wire I/O
|
|
--------------------------------------------------------------------------------
|
|
function ENT:LeaderReadTrainWire(id)
|
|
if self.TrainWireOverrides[id] then return self.TrainWireOverrides[id] end
|
|
if self.TrainWireOutside[id] then
|
|
return (self.TrainWireOutsideFrom[id] and (self.TrainWireTurbostroi[self.TrainWireOutsideFrom[id]] or 0) or 1)*self.TrainWireOutside[id]
|
|
end
|
|
return (self.TrainWireTurbostroi[id] or 0)+(self.TrainWireWriters[id] or 0)
|
|
end
|
|
|
|
function ENT:WriteTrainWire(k,v,ignore)
|
|
if not self.TrainWireWritersID[k] then self.TrainWireWritersID[k] = true end
|
|
self.TrainWireWriters[k] = v
|
|
end
|
|
|
|
function ENT:ReadTrainWire(k)
|
|
return self.TrainWires[k] or 0
|
|
end
|
|
function ENT:ResetTrainWires()
|
|
-- Remember old train wires reference
|
|
local trainWires = self.TrainWires
|
|
-- Create new train wires
|
|
self.TrainWires = {}
|
|
self.TrainWireWriters = {}
|
|
|
|
-- Initialize train wires to zero values
|
|
for i=1,128 do self.TrainWires[i] = 0 end
|
|
end
|
|
|
|
function ENT:GetTrainWire18()
|
|
---self:UpdateWagonList()
|
|
--[[
|
|
-- Total resistance
|
|
local Rtotal = 0.0
|
|
for i,train in ipairs(self.WagonList) do
|
|
if train.Electric and train.LK4 then
|
|
local RLK4 = train.Electric.RPSignalResistor + train.LK4.Value*1e9
|
|
local RRP = (1-train.RPvozvrat.Value)*1e9
|
|
local Rtrain = ((RRP^-1) + (RLK4^-1))^-1
|
|
Rtotal = Rtotal + (Rtrain^-1)
|
|
end
|
|
end]]
|
|
local Rtotal = 0.0
|
|
for i,train in ipairs(self.WagonList) do
|
|
if train.Electric and train.LK4 then
|
|
local RLK4 = 1-train.LK4.Value
|
|
local RRP = train.RPvozvrat.Value
|
|
Rtotal = Rtotal + RLK4+RRP*100
|
|
end
|
|
end
|
|
|
|
-- Mask for panel RP light info
|
|
--local Mask = (self.Panel["RedRP"] > 0.25) and 0 or 1e9
|
|
return Rtotal/#self.WagonList
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Coupling logic
|
|
--------------------------------------------------------------------------------
|
|
function ENT:UpdateIndexes()
|
|
local function updateIndexes(train,checked,newIndex)
|
|
if not train then return end
|
|
if checked[train] then return end
|
|
checked[train] = true
|
|
|
|
train.TrainCoupledIndex = newIndex
|
|
|
|
local conf = train.SubwayTrain
|
|
local fT = train.FrontTrain
|
|
if IsValid(fT) then
|
|
local fC = fT.SubwayTrain
|
|
--if not conf.NoFrontEKK and (fT.FrontTrain~=train or not fC.NoFrontEKK) and not train.FrontCoupledBogeyDisconnect and conf.EKKType == fC.EKKType then
|
|
if train:ElectricConnected(fT,false) then
|
|
if fT and (fT.FrontTrain == train) then
|
|
updateIndexes(fT,checked,1-newIndex)
|
|
else
|
|
updateIndexes(fT,checked,newIndex)
|
|
end
|
|
end
|
|
end
|
|
local rT = train.RearTrain
|
|
if IsValid(rT) then
|
|
local rC = rT.SubwayTrain
|
|
--if (rT.FrontTrain~=train or not rC.NoFrontEKK) and not train.RearCoupledBogeyDisconnect and conf.EKKType == rC.EKKType then
|
|
if train:ElectricConnected(rT,true) then
|
|
if rT and (rT.RearTrain == train) then
|
|
updateIndexes(rT,checked,1-newIndex)
|
|
else
|
|
updateIndexes(rT,checked,newIndex)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
updateIndexes(self,{},0)
|
|
end
|
|
|
|
function ENT:OnConnectDisconnect()
|
|
self:UpdateCoupledTrains()
|
|
|
|
--if ((train.FrontTrain == self) or (train.RearTrain == self)) then
|
|
self:UpdateIndexes()
|
|
--end
|
|
|
|
self:UpdateWagonList()
|
|
self:ChooseTrainWireLeader()
|
|
end
|
|
|
|
function ENT:OnCouple(bogey,isfront)
|
|
if isfront then
|
|
self.FrontCoupledBogey = bogey
|
|
else
|
|
self.RearCoupledBogey = bogey
|
|
end
|
|
|
|
local train = bogey:GetNW2Entity("TrainEntity")
|
|
if not IsValid(train) then return end
|
|
hook.Run("MetrostroiCoupled",self,train)
|
|
--print(Format("%s(%05d) coupled with %s(%05d)",self,self:GetWagonNumber(),train,train:GetWagonNumber()))
|
|
--Don't update train wires when there's no parent train
|
|
|
|
self:OnConnectDisconnect()
|
|
if self.OnCoupled then self:OnCoupled() end
|
|
|
|
-- Update train wires
|
|
--[[ if (isfront and self.FrontCoupledBogeyDisconnect) or (not isfront and self.RearCoupledBogeyDisconnect) then
|
|
return
|
|
end--]]
|
|
--[[GRAVHULL
|
|
if GravHull then
|
|
if IsValid(ent) and self:GetMoveType() == MOVETYPE_VPHYSICS then
|
|
if !GravHull.SHIPS[self] then
|
|
self = self.MyShip or (self.Ghost and self.Ghost.MyShip)
|
|
end
|
|
end
|
|
if IsValid(self) then
|
|
self:GetOwner():ChatPrint("Removed a local physics system.")
|
|
GravHull.UnHull(self)
|
|
end
|
|
|
|
if !(IsValid(self) and self:GetMoveType() == MOVETYPE_VPHYSICS and !GravHull.HULLS[self]) then return false end
|
|
GravHull.RegisterHull(self,-2,100)
|
|
GravHull.UpdateHull(self)
|
|
end
|
|
]]
|
|
end
|
|
|
|
function ENT:OnDecouple(isfront)
|
|
--print(self,"Disconnected from front?:" ,isfront)
|
|
if isfront then
|
|
self.FrontCoupledBogey = nil
|
|
else
|
|
self.RearCoupledBogey = nil
|
|
end
|
|
|
|
self:OnConnectDisconnect()
|
|
if self.OnDecoupled then self:OnDecoupled() end
|
|
--[[GRAVHULL
|
|
if GravHull then
|
|
if IsValid(ent) and self:GetMoveType() == MOVETYPE_VPHYSICS then
|
|
if !GravHull.SHIPS[self] then
|
|
self = self.MyShip or (self.Ghost and self.Ghost.MyShip)
|
|
end
|
|
end
|
|
if IsValid(self) then
|
|
self:GetOwner():ChatPrint("Removed a local physics system.")
|
|
GravHull.UnHull(self)
|
|
end
|
|
|
|
if !(IsValid(self) and self:GetMoveType() == MOVETYPE_VPHYSICS and !GravHull.HULLS[self]) then return false end
|
|
GravHull.RegisterHull(self,-2,100)
|
|
GravHull.UpdateHull(self)
|
|
end
|
|
]]
|
|
end
|
|
|
|
function ENT:OnBogeyDisconnect(bogey,isfront)
|
|
if isfront then
|
|
self.FrontCoupledBogeyDisconnect = true
|
|
else
|
|
self.RearCoupledBogeyDisconnect = true
|
|
end
|
|
|
|
self:OnConnectDisconnect()
|
|
end
|
|
|
|
function ENT:OnBogeyConnect(bogey,isfront)
|
|
--print(self,"Coupled with ",bogey," at ",isfront)
|
|
if isfront then
|
|
self.FrontCoupledBogeyDisconnect = false
|
|
else
|
|
self.RearCoupledBogeyDisconnect = false
|
|
end
|
|
|
|
local train = bogey:GetNW2Entity("TrainEntity")
|
|
if not IsValid(train) then return end
|
|
--Don't update train wires when there's no parent train
|
|
|
|
self:OnConnectDisconnect()
|
|
end
|
|
|
|
function ENT:UpdateCoupledTrains()
|
|
if self.FrontCoupledBogey then
|
|
self.FrontTrain = self.FrontCoupledBogey:GetNW2Entity("TrainEntity")
|
|
else
|
|
self.FrontTrain = nil
|
|
end
|
|
|
|
if self.RearCoupledBogey then
|
|
self.RearTrain = self.RearCoupledBogey:GetNW2Entity("TrainEntity")
|
|
else
|
|
self.RearTrain = nil
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Create a bogey for the train
|
|
--------------------------------------------------------------------------------
|
|
function ENT:CreateBogey(pos,ang,forward,typ)
|
|
-- Create bogey entity
|
|
local bogey = ents.Create("gmod_train_bogey")
|
|
bogey:SetPos(self:LocalToWorld(pos))
|
|
bogey:SetAngles(self:GetAngles() + ang)
|
|
bogey.BogeyType = typ
|
|
bogey.NoPhysics = self.NoPhysics
|
|
bogey:Spawn()
|
|
|
|
-- Assign ownership
|
|
if IsValid(self:GetPlayer()) then bogey:SetPlayer(self:GetPlayer()) end
|
|
if CPPI and IsValid(self:CPPIGetOwner()) then bogey:CPPISetOwner(self:CPPIGetOwner()) end
|
|
|
|
-- Some shared general information about the bogey
|
|
self.SquealSound = self.SquealSound or math.floor(4*math.random())
|
|
self.SquealSensitivity = self.SquealSensitivity or math.random()
|
|
bogey.SquealSensitivity = self.SquealSensitivity
|
|
bogey:SetNW2Int("SquealSound",self.SquealSound)
|
|
bogey:SetNW2Bool("IsForwardBogey", forward)
|
|
bogey:SetNW2Entity("TrainEntity", self)
|
|
bogey.SpawnPos = pos
|
|
bogey.SpawnAng = ang
|
|
local index=1
|
|
for i,v in ipairs(self.JointPositions) do
|
|
if v>pos.x then index=i+1 else break end
|
|
end
|
|
table.insert(self.JointPositions,index,pos.x+53.6)
|
|
table.insert(self.JointPositions,index+1,pos.x-53.6)
|
|
-- Constraint bogey to the train
|
|
if self.NoPhysics then
|
|
bogey:SetParent(self)
|
|
else
|
|
constraint.Axis(bogey,self,0,0,
|
|
Vector(0,0,0),Vector(0,0,0),
|
|
0,0,0,1,Vector(0,0,1),false)
|
|
if forward and IsValid(self.FrontCouple) then
|
|
constraint.NoCollide(bogey,self.FrontCouple,0,0)
|
|
elseif not forward and IsValid(self.RearCouple) then
|
|
constraint.NoCollide(bogeyself.RearCouple,0,0)
|
|
end
|
|
end
|
|
|
|
-- Add to cleanup list
|
|
table.insert(self.TrainEntities,bogey)
|
|
return bogey
|
|
end
|
|
function ENT:AddLightSensor(pos,ang,model)
|
|
local sensor = ents.Create("gmod_train_autodrive_coil")
|
|
if IsValid(self:GetPlayer()) then sensor:SetPlayer(self:GetPlayer()) end
|
|
if CPPI and IsValid(self:CPPIGetOwner()) then sensor:CPPISetOwner(self:CPPIGetOwner()) end
|
|
sensor:SetPos(self:LocalToWorld(pos))
|
|
sensor:SetAngles(self:LocalToWorldAngles(ang))
|
|
sensor:SetParent(self)
|
|
sensor.Model = model or "models/props_c17/display_cooler01a.mdl"
|
|
sensor.IsSensor = true
|
|
sensor.Train = self
|
|
sensor:Spawn()
|
|
return sensor
|
|
end
|
|
function ENT:AddAutodriveCoil(bogey,right)
|
|
-- Create bogey entity
|
|
local coil = right and bogey.CoilR or not right and bogey.CoilL
|
|
if not IsValid(coil) then
|
|
coil = ents.Create("gmod_train_autodrive_coil")
|
|
coil:Spawn()
|
|
if right then
|
|
bogey.CoilR = coil
|
|
else
|
|
bogey.CoilL = coil
|
|
end
|
|
-- Assign ownership
|
|
if IsValid(self:GetPlayer()) then coil:SetPlayer(self:GetPlayer()) end
|
|
if CPPI and IsValid(self:CPPIGetOwner()) then coil:CPPISetOwner(self:CPPIGetOwner()) end
|
|
end
|
|
if right then
|
|
coil:SetPos(bogey:LocalToWorld(Vector(-54,70,-30)))
|
|
else
|
|
coil:SetPos(bogey:LocalToWorld(Vector(-54,-70,-30)))
|
|
end
|
|
coil:SetAngles(bogey:GetAngles())
|
|
|
|
coil:SetParent(bogey)
|
|
|
|
return coil
|
|
end
|
|
--------------------------------------------------------------------------------
|
|
-- Create a couple for the train
|
|
--------------------------------------------------------------------------------
|
|
function ENT:CreateCouple(pos,ang,forward,typ)
|
|
-- Create bogey entity
|
|
local coupler = ents.Create("gmod_train_couple")
|
|
coupler:SetPos(self:LocalToWorld(pos))
|
|
coupler:SetAngles(self:GetAngles() + ang)
|
|
coupler.CoupleType = typ
|
|
coupler:Spawn()
|
|
|
|
-- Assign ownership
|
|
if IsValid(self:GetPlayer()) then coupler:SetPlayer(self:GetPlayer()) end
|
|
if CPPI and IsValid(self:CPPIGetOwner()) then coupler:CPPISetOwner(self:CPPIGetOwner()) end
|
|
|
|
-- Some shared general information about the bogey
|
|
coupler:SetNW2Bool("IsForwardCoupler", forward)
|
|
coupler:SetNW2Entity("TrainEntity", self)
|
|
coupler.SpawnPos = pos
|
|
coupler.SpawnAng = ang
|
|
local index=1
|
|
local x = self:WorldToLocal(coupler:LocalToWorld(coupler.CouplingPointOffset)).x
|
|
for i,v in ipairs(self.JointPositions) do
|
|
if v>pos.x then index=i+1 else break end
|
|
end
|
|
table.insert(self.JointPositions,index,x)
|
|
-- Constraint bogey to the train
|
|
if self.NoPhysics then
|
|
coupler:SetParent(self)
|
|
else
|
|
constraint.AdvBallsocket(
|
|
self,
|
|
coupler,
|
|
0, --bone
|
|
0, --bone
|
|
pos,
|
|
Vector(0,0,0),
|
|
1, --forcelimit
|
|
1, --torquelimit
|
|
-2, --xmin
|
|
-2, --ymin
|
|
-15, --zmin
|
|
2, --xmax
|
|
2, --ymax
|
|
15, --zmax
|
|
0.1, --xfric
|
|
0.1, --yfric
|
|
1, --zfric
|
|
0, --rotonly
|
|
1 --nocollide
|
|
)
|
|
|
|
if forward and IsValid(self.FrontBogey) then
|
|
constraint.NoCollide(self.FrontBogey,coupler,0,0)
|
|
elseif not forward and IsValid(self.RearBogey) then
|
|
constraint.NoCollide(self.RearBogey,coupler,0,0)
|
|
end
|
|
--[[
|
|
constraint.Axis(coupler,self,0,0,
|
|
Vector(0,0,0),Vector(0,0,0),
|
|
0,0,0,1,Vector(0,0,1),false)]]
|
|
end
|
|
|
|
-- Add to cleanup list
|
|
table.insert(self.TrainEntities,coupler)
|
|
return coupler
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Create an entity for the seat
|
|
--------------------------------------------------------------------------------
|
|
function ENT:CreateSeatEntity(seat_info)
|
|
-- Create seat entity
|
|
local seat = ents.Create("prop_vehicle_prisoner_pod")
|
|
seat:SetModel(seat_info.model or "models/nova/jeep_seat.mdl") --jalopy
|
|
seat:SetPos(self:LocalToWorld(seat_info.offset))
|
|
seat:SetAngles(self:GetAngles()+Angle(0,-90,0)+seat_info.angle)
|
|
seat:SetKeyValue("limitview",0)
|
|
seat:Spawn()
|
|
seat:GetPhysicsObject():SetMass(10)
|
|
seat:SetCollisionGroup(COLLISION_GROUP_WORLD)
|
|
self:DrawShadow(false)
|
|
|
|
--Assign ownership
|
|
if IsValid(self:GetPlayer()) then seat:SetCreator(self:GetPlayer()) end
|
|
if CPPI and IsValid(self:CPPIGetOwner()) then seat:CPPISetOwner(self:CPPIGetOwner()) end
|
|
|
|
-- Hide the entity visually
|
|
if seat_info.type == "passenger" then
|
|
seat:SetColor(Color(0,0,0,0))
|
|
seat:SetRenderMode(RENDERMODE_TRANSALPHA)
|
|
end
|
|
|
|
-- Set some shared information about the seat
|
|
local seats = self:GetNW2Int("seats",0)+1
|
|
self:SetNW2Entity("seat_"..seats, seat)
|
|
self:SetNW2Int("seats", seats)
|
|
seat:SetNW2String("SeatType", seat_info.type)
|
|
seat:SetNW2Entity("TrainEntity", self)
|
|
seat_info.entity = seat
|
|
|
|
-- Constrain seat to this object
|
|
-- constraint.NoCollide(self,seat,0,0)
|
|
seat:SetParent(self)
|
|
|
|
-- Add to cleanup list
|
|
table.insert(self.TrainEntities,seat)
|
|
return seat
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Create a seat position
|
|
--------------------------------------------------------------------------------
|
|
function ENT:CreateSeat(type,offset,angle,model)
|
|
-- Add a new seat
|
|
local seat_info = {
|
|
type = type,
|
|
offset = offset,
|
|
model = model,
|
|
angle = angle or Angle(0,0,0),
|
|
}
|
|
table.insert(self.Seats,seat_info)
|
|
|
|
-- If needed, create an entity for this seat
|
|
if (type == "driver") or (type == "instructor") or (type == "passenger") then
|
|
return self:CreateSeatEntity(seat_info)
|
|
end
|
|
end
|
|
|
|
-- Returns if KV/reverser wrench is present in cabin
|
|
function ENT:IsWrenchPresent()
|
|
if self.DriversWrenchPresent then return true end
|
|
if self.DriversWrenchMissing then return false end
|
|
for k,v in pairs(self.Seats) do
|
|
if IsValid(v.entity) and v.entity.GetPassenger and
|
|
((v.type == "driver") or (v.type == "instructor")) then
|
|
local player = v.entity:GetPassenger(0)
|
|
if player and player:IsValid() then return true end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function ENT:GetDriver()
|
|
if IsValid(self.DriverSeat) then
|
|
local ply = self.DriverSeat:GetPassenger(0)
|
|
if IsValid(ply) then return ply end
|
|
end
|
|
end
|
|
|
|
hook.Remove("SetupPlayerVisibility","PVSDormantFix",function(ply)
|
|
for _,ent in pairs(ents.FindByClass("env_*")) do
|
|
if ent.DormantFix then
|
|
ent:SetPreventTransmit(ply,not ply:TestPVS(ent))
|
|
end
|
|
end
|
|
end)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Turn light on or off
|
|
--------------------------------------------------------------------------------
|
|
function ENT:SetLightPower(index,power,brightness)
|
|
local lightData = self.Lights[index]
|
|
self.GlowingLights = self.GlowingLights or {}
|
|
self.LightBrightness = self.LightBrightness or {}
|
|
brightness = brightness or 1
|
|
|
|
-- Check if light already glowing
|
|
if (power and (self.GlowingLights[index])) and
|
|
(brightness == self.LightBrightness[index]) then return end
|
|
|
|
-- If light already glowing and only brightness changed
|
|
if (power and (self.GlowingLights[index])) and
|
|
(brightness ~= self.LightBrightness[index]) then
|
|
local light = self.GlowingLights[index]
|
|
if (lightData[1] == "glow") or (lightData[1] == "light") then
|
|
local brightness = brightness * (lightData.brightness or 0.5)
|
|
light:SetKeyValue("rendercolor",
|
|
Format("%i %i %i",
|
|
lightData[4].r*brightness,
|
|
lightData[4].g*brightness,
|
|
lightData[4].b*brightness
|
|
)
|
|
)
|
|
end
|
|
if (lightData[1] == "headlight") then
|
|
-- Set Brightness
|
|
local brightness = brightness * (lightData.brightness or 1.25)
|
|
light:SetKeyValue("lightcolor",
|
|
Format("%i %i %i 255",
|
|
lightData[4].r*brightness,
|
|
lightData[4].g*brightness,
|
|
lightData[4].b*brightness
|
|
)
|
|
)
|
|
end
|
|
if (lightData[1] == "dynamiclight") then
|
|
--light:SetKeyValue("brightness", brightness * (lightData.brightness or 2))
|
|
light:SetKeyValue("_light",
|
|
Format("%i %i %i",
|
|
lightData[4].r*brightness,
|
|
lightData[4].g*brightness,
|
|
lightData[4].b*brightness
|
|
)
|
|
)
|
|
end
|
|
self.LightBrightness[index] = brightness
|
|
return
|
|
end
|
|
|
|
-- Turn off light
|
|
SafeRemoveEntity(self.GlowingLights[index])
|
|
self.GlowingLights[index] = nil
|
|
self.LightBrightness[index] = brightness
|
|
|
|
-- Create light
|
|
if (lightData[1] == "headlight") and (power) then
|
|
local light = ents.Create("env_projectedtexture")
|
|
light.DormantFix = true
|
|
light:SetTransmitWithParent(true)
|
|
light:SetParent(self)
|
|
light:SetPos(self:LocalToWorld(lightData[2]))
|
|
light:SetAngles(self:LocalToWorldAngles(lightData[3]))
|
|
|
|
-- Set parameters
|
|
light:SetKeyValue("enableshadows", lightData.shadows or 1)
|
|
light:SetKeyValue("farz", lightData.farz or 2048)
|
|
light:SetKeyValue("nearz", lightData.nearz or 16)
|
|
light:SetKeyValue("lightfov", lightData.fov or 120)
|
|
|
|
-- Set Brightness
|
|
local brightness = brightness * (lightData.brightness or 1.25)
|
|
light:SetKeyValue("lightcolor",
|
|
Format("%i %i %i 255",
|
|
lightData[4].r*brightness,
|
|
lightData[4].g*brightness,
|
|
lightData[4].b*brightness
|
|
)
|
|
)
|
|
|
|
-- Turn light on
|
|
light:Spawn() --"effects/flashlight/caustics"
|
|
light:Input("SpotlightTexture",nil,nil,lightData.texture or "effects/flashlight001")
|
|
self.GlowingLights[index] = light
|
|
end
|
|
if (lightData[1] == "glow") and (power) then
|
|
local light = ents.Create("env_sprite")
|
|
light.DormantFix = true
|
|
light:SetTransmitWithParent(true)
|
|
light:SetParent(self)
|
|
light:SetLocalPos(lightData[2])
|
|
light:SetLocalAngles(lightData[3])
|
|
|
|
-- Set parameters
|
|
local brightness = brightness * (lightData.brightness or 0.5)
|
|
light:SetKeyValue("rendercolor",
|
|
Format("%i %i %i",
|
|
lightData[4].r*brightness,
|
|
lightData[4].g*brightness,
|
|
lightData[4].b*brightness
|
|
)
|
|
)
|
|
light:SetKeyValue("rendermode", lightData.type or 3) -- 9: WGlow, 3: Glow
|
|
light:SetKeyValue("renderfx", 14)
|
|
light:SetKeyValue("model", lightData.texture or "sprites/glow1.vmt")
|
|
-- light:SetKeyValue("model", "sprites/light_glow02.vmt")
|
|
-- light:SetKeyValue("model", "sprites/yellowflare.vmt")
|
|
light:SetKeyValue("scale", lightData.scale or 1.0)
|
|
light:SetKeyValue("spawnflags", 1)
|
|
|
|
-- Turn light on
|
|
light:Spawn()
|
|
self.GlowingLights[index] = light
|
|
end
|
|
if (lightData[1] == "light") and (power) then
|
|
local light = ents.Create("env_sprite")
|
|
light.DormantFix = true
|
|
light:SetTransmitWithParent(true)
|
|
light:SetParent(self)
|
|
light:SetLocalPos(lightData[2])
|
|
light:SetLocalAngles(lightData[3])
|
|
|
|
-- Set parameters
|
|
local brightness = brightness * (lightData.brightness or 0.5)
|
|
light:SetKeyValue("rendercolor",
|
|
Format("%i %i %i",
|
|
lightData[4].r*brightness,
|
|
lightData[4].g*brightness,
|
|
lightData[4].b*brightness
|
|
)
|
|
)
|
|
light:SetKeyValue("rendermode", lightData.type or 9) -- 9: WGlow, 3: Glow
|
|
light:SetKeyValue("renderfx", 14)
|
|
-- light:SetKeyValue("model", "sprites/glow1.vmt")
|
|
light:SetKeyValue("model", lightData.texture or "sprites/light_glow02.vmt")
|
|
-- light:SetKeyValue("model", "sprites/yellowflare.vmt")
|
|
light:SetKeyValue("scale", lightData.scale or 1.0)
|
|
--Size of Glow Proxy Geometry
|
|
light:SetKeyValue("spawnflags", 1)
|
|
|
|
-- Turn light on
|
|
light:Spawn()
|
|
self.GlowingLights[index] = light
|
|
end
|
|
if (lightData[1] == "dynamiclight") and (power) then
|
|
local light = ents.Create("light_dynamic")
|
|
light:SetParent(self)
|
|
|
|
-- Set position
|
|
light:SetLocalPos(lightData[2])
|
|
light:SetLocalAngles(lightData[3])
|
|
|
|
-- Set parameters
|
|
light:SetKeyValue("_light",
|
|
Format("%i %i %i",
|
|
lightData[4].r*brightness,
|
|
lightData[4].g*brightness,
|
|
lightData[4].b*brightness
|
|
)
|
|
)
|
|
light:SetKeyValue("style", 0)
|
|
light:SetKeyValue("distance", lightData.distance or 300)
|
|
light:SetKeyValue("brightness", (lightData.brightness or 2))
|
|
|
|
-- Turn light on
|
|
light:Spawn()
|
|
light:Fire("TurnOn","","0")
|
|
self.GlowingLights[index] = light
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Joystick input
|
|
--------------------------------------------------------------------------------
|
|
function ENT:HandleJoystickInput(ply)
|
|
for k,v in pairs(jcon.binds) do
|
|
if v:GetCategory() == "Metrostroi" then
|
|
local jvalue = Metrostroi.GetJoystickInput(ply,k)
|
|
if (jvalue != nil) and (self.JoystickBuffer[k] ~= jvalue) then
|
|
local inputname = Metrostroi.JoystickSystemMap[k]
|
|
self.JoystickBuffer[k] = jvalue
|
|
if inputname then
|
|
if type(jvalue) == "boolean" then
|
|
if jvalue then
|
|
jvalue = 1.0
|
|
else
|
|
jvalue = 0.0
|
|
end
|
|
end
|
|
self:TriggerInput(inputname,jvalue)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Keyboard input
|
|
--------------------------------------------------------------------------------
|
|
function ENT:IsModifier(key)
|
|
return type(self.KeyMap[key]) == "table"
|
|
end
|
|
|
|
function ENT:HasModifier(key)
|
|
return self.KeyMods[key] ~= nil
|
|
end
|
|
|
|
function ENT:GetActiveModifiers(key)
|
|
local tbl = {}
|
|
local mods = self.KeyMods[key]
|
|
for k,v in pairs(mods) do
|
|
if self.KeyBuffer[k] ~= nil then
|
|
table.insert(tbl,k)
|
|
end
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
function ENT:OnKeyEvent(key,state,ply,helper)
|
|
if state then
|
|
self:OnKeyPress(key)
|
|
else
|
|
self:OnKeyRelease(key)
|
|
end
|
|
local keyT = self.KeyMap[key]
|
|
if self:HasModifier(key) and not helper then
|
|
--If we have a modifier
|
|
local actmods = self:GetActiveModifiers(key)
|
|
if #actmods > 0 then
|
|
--Modifier is being preseed
|
|
for k,v in pairs(actmods) do
|
|
if self.KeyMap[v][key] ~= nil then
|
|
self:ButtonEvent(self.KeyMap[v][key],state,ply)
|
|
end
|
|
end
|
|
return
|
|
end
|
|
end
|
|
if self:IsModifier(key) then
|
|
if keyT.helper and (helper or keyT[1]) then
|
|
self:ButtonEvent(helper and keyT.helper or keyT[1],state,ply)
|
|
elseif not helper then
|
|
if state and keyT.def and not helper then
|
|
self:ButtonEvent(keyT.def,state,ply)
|
|
elseif not state then
|
|
if keyT.def then
|
|
self:ButtonEvent(keyT.def,state,ply)
|
|
end
|
|
for k,v in pairs(keyT) do
|
|
self:ButtonEvent(v,false,ply)
|
|
end
|
|
end
|
|
end
|
|
elseif keyT ~= nil and type(keyT) == "string" and not helper then
|
|
--If we're a regular binded key
|
|
self:ButtonEvent(keyT,state,ply)
|
|
end
|
|
end
|
|
function ENT:OnKeyPress(key)
|
|
|
|
end
|
|
|
|
function ENT:OnKeyRelease(key)
|
|
|
|
end
|
|
|
|
function ENT:ProcessKeyMap()
|
|
self.KeyMods = {}
|
|
|
|
for mod,v in pairs(self.KeyMap) do
|
|
if type(v) == "table" then
|
|
for k,_ in pairs(v) do
|
|
if not self.KeyMods[k] then
|
|
self.KeyMods[k]={}
|
|
end
|
|
self.KeyMods[k][mod]=true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function HandleKeyHook(ply,k,state)
|
|
local train = ply:GetTrain()
|
|
if IsValid(train) then
|
|
train.KeyMap[k] = state or nil
|
|
end
|
|
end
|
|
|
|
function ENT:HandleKeyboardInput(ply)
|
|
if not self.KeyMods and self.KeyMap then
|
|
self:ProcessKeyMap()
|
|
end
|
|
|
|
-- Check for newly pressed keys
|
|
for k,v in pairs(ply.keystate) do
|
|
if self.KeyBuffer[k] == nil then
|
|
self.KeyBuffer[k] = true
|
|
self:OnKeyEvent(k,true,ply)
|
|
end
|
|
end
|
|
|
|
-- Check for newly released keys
|
|
for k,v in pairs(self.KeyBuffer) do
|
|
if ply.keystate[k] == nil then
|
|
self.KeyBuffer[k] = nil
|
|
self:OnKeyEvent(k,false,ply)
|
|
end
|
|
end
|
|
end
|
|
hook.Add("PlayerButtonUp","metrostroi_button",function(ply, button)
|
|
local train,seat = ply:GetTrain()
|
|
if IsValid(train) and train.KeyBuffer then
|
|
if train.KeyBuffer[button] then
|
|
train.KeyBuffer[button] = nil
|
|
train:OnKeyEvent(button,false,ply,train.DriverSeat ~= seat)
|
|
end
|
|
end
|
|
end)
|
|
hook.Add("PlayerButtonDown","metrostroi_button",function(ply, button)
|
|
local train,seat = ply:GetTrain()
|
|
if IsValid(train) and train.KeyBuffer then
|
|
if train.KeyBuffer[button] == nil then
|
|
train.KeyBuffer[button] = true
|
|
train:OnKeyEvent(button,true,ply,train.DriverSeat ~= seat)
|
|
end
|
|
end
|
|
end)
|
|
function ENT:CreateJointSound(sndnum)
|
|
local jID = self.SpeedSign>0 and 1 or #self.JointPositions
|
|
table.insert(self.Joints,
|
|
{
|
|
type = sndnum,
|
|
state = jID,
|
|
dist = self.JointPositions[jID]
|
|
}
|
|
)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Process train logic
|
|
--------------------------------------------------------------------------------
|
|
-- Think and execute systems stuff
|
|
local joints = {
|
|
{2,12.5},
|
|
{3,25},
|
|
{3,50},
|
|
{2,75},
|
|
}
|
|
function ENT:Think()
|
|
if self.FrontBogey then
|
|
if self.SpeedSign and self.WagonList[1] == self and (not self.FrontTrain and self.Speed * self.SpeedSign > 0.25 or not self.RearTrain and self.Speed * self.SpeedSign < -0.25) then
|
|
--print(self.FrontBogey.Wheels,self.RearBogey)
|
|
--self.TargetDist
|
|
--self.rep = 0
|
|
--self.rep = nil
|
|
if not self.JointRepeats or self.JointRepeats <= 0 then
|
|
local ch
|
|
repeat
|
|
ch = math.ceil(math.random()*#joints)
|
|
until ch~=self.LastJoint
|
|
self.LastJoint = ch
|
|
self.JointDist = joints[ch][2]
|
|
self.JointRepeats = math.floor(math.random(1, joints[ch][1]))
|
|
end
|
|
if not self.CurrentJointDist then self.CurrentJointDist = 0 end
|
|
|
|
|
|
if self.JointDist then
|
|
self.CurrentJointDist = self.CurrentJointDist + self.DeltaTime * self.Speed * self.SpeedSign / 3.6
|
|
|
|
if self.JointRepeats > 0 and (self.CurrentJointDist > self.JointDist or self.CurrentJointDist < -self.JointDist) then
|
|
self.JointRepeats = self.JointRepeats - 1
|
|
local snd = math.ceil(math.random()*32)
|
|
self:CreateJointSound(snd)
|
|
self.CurrentJointDist = 0
|
|
end
|
|
end
|
|
end
|
|
|
|
--DISTANCES
|
|
--0.774
|
|
--WHEELS:81 2.05 --2.66
|
|
--TRAIN:755 19.17 24.79
|
|
--89 2.26 2.92
|
|
--171 4.34 5.61
|
|
--584 14.83 19.17
|
|
--666 16.91 21.84
|
|
if #self.Joints > 0 then
|
|
local first,last = self.JointPositions[1],self.JointPositions[#self.JointPositions]
|
|
--self.Joints = {}
|
|
for i, j in ipairs(self.Joints) do
|
|
j.dist = j.dist + self.DeltaTime * (-self:GetVelocity():Dot(self:GetAngles():Forward()))
|
|
local dist = j.dist
|
|
local ch = false
|
|
for iD,jD in ipairs(self.JointPositions) do
|
|
if dist<jD and j.state<iD or dist>jD and j.state>iD then
|
|
--RunConsoleCommand("say",Format("[%d] dist(%.1f)%sjD(%.1f) %d%+d",i,dist,dist>jD and ">" or "<",jD,j.state,j.state>iD and j.state-1 or j.state+1))
|
|
j.state = iD--j.state>iD and j.state-1 or j.state+1
|
|
if 1<iD and iD<#self.JointPositions then
|
|
ch = iD
|
|
else
|
|
if (iD==1 and dist>first) and IsValid(self.FrontTrain) then
|
|
self.FrontTrain:CreateJointSound(j.type)
|
|
elseif (iD~=1 and dist<last) and IsValid(self.RearTrain) then
|
|
self.RearTrain:CreateJointSound(j.type)
|
|
end
|
|
table.remove(self.Joints, i)
|
|
end
|
|
break
|
|
end
|
|
end
|
|
if ch then
|
|
self:PlayOnce(ch%2>0 and "a" or "b","styk",j.type,math.floor(j.state/2))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if self.FailSim and self.FailSim.TrainWireFall and self.FailSim.TrainWireFail > 0 then
|
|
self.TrainWireOutside[self.FailSim.TrainWireFail] = 1
|
|
if fail then self.FailSim:TriggerInput("ResetTW") end
|
|
end
|
|
self.PrevTime = self.PrevTime or CurTime()
|
|
self.DeltaTime = (CurTime() - self.PrevTime)
|
|
self.PrevTime = CurTime()
|
|
-- Calculate train acceleration
|
|
--[[self.PreviousVelocity = self.PreviousVelocity or self:GetVelocity()
|
|
local accelerationVector = 0.01905*(self:GetPhysicsObject():GetVelocity() - self.PreviousVelocity) / self.DeltaTime
|
|
accelerationVector:Rotate(self:GetAngles())
|
|
self:SetTrainAcceleration(accelerationVector)
|
|
self.PreviousVelocity = self:GetVelocity()]]--
|
|
|
|
-- Get angular velocity
|
|
--self:SetTrainAngularVelocity(math.pi*self:GetPhysicsObject():GetAngleVelocity()/180)
|
|
-- Apply mass of passengers
|
|
if self.NormalMass then self:GetPhysicsObject():SetMass(self.NormalMass + 60*self:GetNW2Float("PassengerCount")) end
|
|
if self.AnnouncementToLeaveWagon and self:GetNW2Float("PassengerCount") == 0 then self.AnnouncementToLeaveWagon = false end
|
|
|
|
-- Hack for VAH switch on non-supported maps so you don't have to hold space all the time
|
|
if not self.NonSupportedChecked then
|
|
self.NonSupportedChecked = true
|
|
if not Metrostroi.MapHasFullSupport("ars") and self.NonSupportTrigger then self:NonSupportTrigger() end
|
|
end
|
|
|
|
-- Calculate turn information, unused right now
|
|
--[[if self.FrontBogey and self.RearBogey then
|
|
self.BogeyDistance = self.BogeyDistance or self.FrontBogey:GetPos():Distance(self.RearBogey:GetPos())
|
|
local a = math.AngleDifference(self.FrontBogey:GetAngles().y,self.RearBogey:GetAngles().y+180)
|
|
self.TurnRadius = (self.BogeyDistance/2)/math.sin(math.rad(a/2))
|
|
|
|
-- If we're pretty much going straight, correct massive values
|
|
if math.abs(self.TurnRadius) > 1e4 then
|
|
self.TurnRadius = 0
|
|
end
|
|
end]]--
|
|
|
|
-- Process the keymap for modifiers
|
|
-- TODO: Need a neat way of calling this once after self.KeyMap is populated
|
|
if not self.KeyMods and self.KeyMap then
|
|
self:ProcessKeyMap()
|
|
end
|
|
|
|
-- Keyboard input is done via PlayerButtonDown/Up hooks that call ENT:OnKeyEvent
|
|
-- Joystick input
|
|
if IsValid(self.DriverSeat) then
|
|
local ply = self.DriverSeat:GetPassenger(0)
|
|
|
|
if IsValid(ply) then
|
|
--if self.KeyMap then self:HandleKeyboardInput(ply) end
|
|
if joystick then self:HandleJoystickInput(ply) end
|
|
self:SetNW2Bool("GoldenReverser",IsValid(ply) and IsValid(ply:GetWeapon("train_kv_wrench_gold")))
|
|
end
|
|
|
|
end
|
|
|
|
if Turbostroi and not self.DontAccelerateSimulation then
|
|
-- Run iterations on systems simulation
|
|
local iterationsCount = 1
|
|
if (not self.Schedule) or (iterationsCount ~= self.Schedule.IterationsCount) then
|
|
self.Schedule = { IterationsCount = iterationsCount }
|
|
local SystemIterations = {}
|
|
|
|
-- Find max number of iterations
|
|
local maxIterations = 0
|
|
for k,v in pairs(self.Systems) do
|
|
if v.DontAccelerateSimulation then
|
|
SystemIterations[k] = (v.SubIterations or 1)
|
|
maxIterations = math.max(maxIterations,(v.SubIterations or 1))
|
|
end
|
|
end
|
|
for iteration=1,maxIterations do
|
|
self.Schedule[iteration] = {}
|
|
-- Populate schedule
|
|
for k,v in pairs(self.Systems) do
|
|
if v.DontAccelerateSimulation and ((iteration)%(maxIterations/(v.SubIterations or 1))) == 0 then
|
|
table.insert(self.Schedule[iteration],v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- Simulate according to schedule
|
|
for i,s in ipairs(self.Schedule) do
|
|
for k,v in ipairs(s) do
|
|
--v:Think(self.DeltaTime / (v.SubIterations or 1)/2,i)
|
|
v:Think(self.DeltaTime / (v.SubIterations or 1),i)
|
|
end
|
|
end
|
|
else
|
|
-- Output all variable values
|
|
for sys_name,system in pairs(self.Systems) do
|
|
if system.OutputsList and (not system.DontAccelerateSimulation) then
|
|
for _,name in pairs(system.OutputsList) do
|
|
local value = (system[name] or 0)
|
|
--if type(value) == "boolean" then value = value and 1 or 0 end
|
|
if not self.DataCache[sys_name] then self.DataCache[sys_name] = {} end
|
|
if self.DataCache[sys_name][name] ~= value then
|
|
self:TriggerTurbostroiInput(sys_name,name,value)
|
|
self.DataCache[sys_name][name] = value
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- Run iterations on systems simulation
|
|
local iterationsCount = 1
|
|
if (not self.Schedule) or (iterationsCount ~= self.Schedule.IterationsCount) then
|
|
self.Schedule = { IterationsCount = iterationsCount }
|
|
local SystemIterations = {}
|
|
|
|
-- Find max number of iterations
|
|
local maxIterations = 0
|
|
for k,v in pairs(self.Systems) do
|
|
SystemIterations[k] = (v.SubIterations or 1)
|
|
maxIterations = math.max(maxIterations,SystemIterations[k])
|
|
end
|
|
for iteration=1,maxIterations do
|
|
self.Schedule[iteration] = {}
|
|
-- Populate schedule
|
|
for k,v in pairs(self.Systems) do
|
|
if ((iteration)%(maxIterations/SystemIterations[k])) == 0 then
|
|
table.insert(self.Schedule[iteration],v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Simulate according to schedule
|
|
for i,s in ipairs(self.Schedule) do
|
|
for k,v in ipairs(s) do
|
|
local el = v.FileName:find("electric")
|
|
if el then
|
|
--time = SysTime()
|
|
end
|
|
--if v.DontAccelerateSimulation then
|
|
v:Think(self.DeltaTime / (v.SubIterations or 1),i)
|
|
--[[ else
|
|
v:Think(self.DeltaTime / (v.SubIterations or 1)/2,i)
|
|
v:Think(self.DeltaTime / (v.SubIterations or 1)/2,i)
|
|
end--]]
|
|
end
|
|
end
|
|
--print(1/(SysTime()-time))
|
|
-- Wire outputs
|
|
--local triggerOutput = self.TriggerOutput
|
|
end
|
|
for _,name in pairs(self.WireIOSystems) do
|
|
local system = self.Systems[name]
|
|
if system and system.OutputsList then
|
|
for _,name in pairs(system.OutputsList) do
|
|
local varname = (system.Name or "")..name
|
|
if type(system[name]) == "boolean" then
|
|
self.TriggerOutput(self,varname,system[name] and 1 or 0)
|
|
else
|
|
self.TriggerOutput(self,varname,tonumber(system[name]) or 0)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--[[ for k,v in pairs(self.TrainWireTurbostroi) do
|
|
self:WriteTrainWire(k,v)
|
|
end
|
|
-- Write and read train wires
|
|
local readTrainWire = self.ReadTrainWire
|
|
local writeTrainWire = self.WriteTrainWire
|
|
for k,v in pairs(self.TrainWireOverrides) do
|
|
if v > 0 then writeTrainWire(self,k,v,true) end
|
|
end
|
|
for k,v in pairs(self.TrainWireOutside) do
|
|
if v > 0 then writeTrainWire(self,k,v,true) end
|
|
end--]]
|
|
if self.TrainWireLeader then
|
|
if #self.WagonList == 1 then
|
|
for twID in pairs(self.TrainWireWritersID) do
|
|
self.TrainWires[twID] = self:LeaderReadTrainWire(twID)
|
|
end
|
|
else
|
|
local wires = {}
|
|
for i,train in ipairs(self.WagonList) do
|
|
local inv = train.TrainCoupledIndex ~= self.TrainCoupledIndex
|
|
for twID in pairs(train.TrainWireWritersID) do
|
|
local target = twID
|
|
if inv then
|
|
for a,b in pairs(train.TrainWireCrossConnections) do
|
|
if target == a then
|
|
target = b
|
|
elseif target == b then
|
|
target = a
|
|
end
|
|
end
|
|
end
|
|
local twVal = train:LeaderReadTrainWire(target)
|
|
if train.TrainWireInverts[twID] or wires[twID] == true then
|
|
if twVal <= 0 then
|
|
wires[twID] = true
|
|
elseif not wires[twID] or wires[twID] ~= true then
|
|
wires[twID] = (wires[twID] or 0) + twVal
|
|
end
|
|
else
|
|
wires[twID] = (wires[twID] or 0) + twVal
|
|
end
|
|
end
|
|
end
|
|
local TrainCount = #self.WagonList
|
|
for i,train in ipairs(self.WagonList) do
|
|
local inv = train.TrainCoupledIndex ~= self.TrainCoupledIndex
|
|
for twID in pairs(wires) do
|
|
local target = twID
|
|
if inv then
|
|
for a,b in pairs(train.TrainWireCrossConnections) do
|
|
if target == a then
|
|
target = b
|
|
elseif target == b then
|
|
target = a
|
|
end
|
|
end
|
|
end
|
|
if wires[twID] == true then
|
|
train.TrainWires[target] = 0
|
|
else
|
|
train.TrainWires[target] = wires[twID]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if Wire_CreateInputs then
|
|
local readTrainWire = self.ReadTrainWire
|
|
for i=1,32 do
|
|
self.TriggerOutput(self,"TrainWire"..i,readTrainWire(self,i))
|
|
end
|
|
self.TriggerOutput(self,"TrainWire35",readTrainWire(self,35))
|
|
self.TriggerOutput(self,"TrainWire36",readTrainWire(self,36))
|
|
end
|
|
|
|
-- Calculate own speed and acceleration
|
|
local speed,acceleration = 0,0
|
|
if IsValid(self.FrontBogey) and IsValid(self.RearBogey) and not self.IgnoreEngine then
|
|
self.Speed = (self.FrontBogey.Speed + self.RearBogey.Speed)/2
|
|
self.SpeedSign = self.FrontBogey.SpeedSign or 1
|
|
self.Acceleration = (self.FrontBogey.Acceleration + self.RearBogey.Acceleration)/2
|
|
else
|
|
self.Acceleration = 0
|
|
self.Speed = 0
|
|
self.SpeedSign = 0
|
|
end
|
|
if self.Plombs and self.Plombs.Init then
|
|
for k,v in pairs(self.Plombs) do
|
|
if k == "Init" then continue end
|
|
if self.Plombs.Init then
|
|
--self[k]:TriggerInput("Reset",true)
|
|
self[k]:TriggerInput("Block",true)
|
|
end
|
|
if type(v) == "table" then
|
|
self:SetPackedBool(k.."Pl",v[1])
|
|
else
|
|
self:SetPackedBool(k.."Pl",v)
|
|
end
|
|
end
|
|
self.Plombs.Init = nil
|
|
end
|
|
if self.Electric and self.Electric.Overheat1 then
|
|
-- Draw overheat of the engines FIXME
|
|
local smoke_intensity =
|
|
self.Electric.Overheat1*((self.Electric.T1-200)/400) or
|
|
self.Electric.Overheat2*((self.Electric.T2-200)/400) or 0
|
|
|
|
-- Generate smoke
|
|
self.PrevSmokeTime = self.PrevSmokeTime or CurTime()
|
|
if (smoke_intensity > 0.0) and (CurTime() - self.PrevSmokeTime > 0.5+4.0*(1-smoke_intensity)) then
|
|
self.PrevSmokeTime = CurTime()
|
|
|
|
ParticleEffect("generic_smoke",
|
|
self:LocalToWorld(Vector(100*math.random(),40,-80)),
|
|
Angle(0,0,0),self)
|
|
end
|
|
end
|
|
|
|
if (not self.SpritesTimer or CurTime()-self.SpritesTimer > 1) and self.GlowingLights then
|
|
for _,ply in ipairs(player.GetAll()) do
|
|
local inPVS = self:TestPVS(ply)
|
|
for _,light in pairs(self.GlowingLights) do
|
|
light:SetPreventTransmit(ply,not inPVS)
|
|
end
|
|
end
|
|
self.SpritesTimer = CurTime()
|
|
end
|
|
--[[
|
|
-- Update speed and acceleration
|
|
self.Speed = speed
|
|
self.Acceleration = acceleration
|
|
]]
|
|
--[[
|
|
if(self.DriverSeat and IsValid(self.DriverSeat)) then
|
|
if not self.DriverSeatPos then self.DriverSeatPos = self.DriverSeat:GetPos() end
|
|
if self:GetDriver() then
|
|
self.HeadAcceleration = math.Clamp((self.HeadAcceleration or 0)*0.95 + ((self.OldSpeed or 0) - self.Speed)*1.1, -10, 10)
|
|
self.DriverSeat:SetPos(self.DriverSeatPos + Vector(math.Round(self.HeadAcceleration,2),0,0))
|
|
elseif self.DriverSeat:GetPos() ~= self.DriverSeatPos then
|
|
self.DriverSeat:SetPos(self.DriverSeatPos)
|
|
self.HeadAcceleration = 0
|
|
end
|
|
end
|
|
self.OldSpeed = self.Speed]]
|
|
-- Go to next think
|
|
self:SetNW2Float("Accel",math.Round((self.OldSpeed or 0) - (self.Speed or 0)*(self.SpeedSign or 0),2))
|
|
self:SetNW2Float("TrainSpeed",self.Speed)
|
|
self.OldSpeed = (self.Speed or 0)*(self.SpeedSign or 0)
|
|
|
|
for k,v in pairs(self.CustomThinks) do if k ~= "BaseClass" then v(self) end end
|
|
self:NextThink(CurTime()+0.05)
|
|
return true
|
|
end
|
|
|
|
function ENT:TriggerTurbostroiInput(sys,name,val)
|
|
if name == "Value" then
|
|
-- Autosend values to client
|
|
if self.SyncTable and table.HasValue(self.SyncTable,sys) then
|
|
self:SetPackedBool(sys,val > 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Default spawn function
|
|
--------------------------------------------------------------------------------
|
|
function ENT:SpawnFunction(ply, tr,className,rotate,func)
|
|
--MaxTrains limit
|
|
if self.ClassName ~= "gmod_subway_base" and not self.NoTrain then
|
|
local Limit1 = math.min(2,C_MaxWagons:GetInt())*C_MaxTrainsOnPly:GetInt()-1
|
|
local Limit2 = math.max(0,C_MaxWagons:GetInt()-2)*C_MaxTrainsOnPly:GetInt()-1
|
|
|
|
if Metrostroi.TrainCount() > C_MaxTrains:GetInt()*C_MaxWagons:GetInt()-1 then
|
|
ply:LimitHit("train_limit")
|
|
--Metrostroi.LimitMessage(ply)
|
|
return
|
|
end
|
|
if Metrostroi.TrainCountOnPlayer(ply) > C_MaxWagons:GetInt()*C_MaxTrainsOnPly:GetInt()-1 then
|
|
ply:LimitHit("train_limit")
|
|
--Metrostroi.LimitMessage(ply)
|
|
return
|
|
end
|
|
if self.SubwayTrain and self.SubwayTrain.WagType == 1 then
|
|
if Metrostroi.TrainCountOnPlayer(ply, 1) > Limit1 then
|
|
ply:LimitHit("train_limit")
|
|
--Metrostroi.LimitMessage(ply)
|
|
return
|
|
end
|
|
elseif self.SubwayTrain and self.SubwayTrain.WagType == 2 then
|
|
if Metrostroi.TrainCountOnPlayer(ply, 2) > Limit2 then
|
|
ply:LimitHit("train_limit")
|
|
--Metrostroi.LimitMessage(ply)
|
|
return
|
|
end
|
|
--elseif self.ClassName:find("tatra") then
|
|
end
|
|
end
|
|
local verticaloffset = 5 -- Offset for the train model
|
|
local distancecap = 2000 -- When to ignore hitpos and spawn at set distanace
|
|
local pos, ang = nil
|
|
local inhibitrerail = false
|
|
|
|
if func then
|
|
pos,ang = func(ply)
|
|
--TODO: Make this work better for raw base ent
|
|
elseif tr.Hit and self.NoTrain then
|
|
-- Regular spawn
|
|
if tr.HitPos:Distance(tr.StartPos) > distancecap then
|
|
-- Spawnpos is far away, put it at distancecap instead
|
|
pos = tr.StartPos + tr.Normal * distancecap
|
|
else
|
|
-- Spawn is near
|
|
pos = tr.HitPos + tr.HitNormal * verticaloffset
|
|
end
|
|
ang = Angle(0,tr.Normal:Angle().y,0)
|
|
elseif tr.Hit and not self.NoTrain then
|
|
-- Setup trace to find out of this is a track
|
|
local tracesetup = {}
|
|
tracesetup.start=tr.HitPos
|
|
tracesetup.endpos=tr.HitPos+tr.HitNormal*80
|
|
tracesetup.filter=ply
|
|
|
|
local tracedata = util.TraceLine(tracesetup)
|
|
|
|
if tracedata.Hit then
|
|
-- Trackspawn
|
|
pos = (tr.HitPos + tracedata.HitPos)/2 + Vector(0,0,verticaloffset)
|
|
ang = tracedata.HitNormal
|
|
ang:Rotate(Angle(0,90,0))
|
|
ang = ang:Angle()
|
|
-- Bit ugly because Rotate() messes with the orthogonal vector | Orthogonal? I wrote "origional?!" :V
|
|
else
|
|
-- Regular spawn
|
|
if tr.HitPos:Distance(tr.StartPos) > distancecap then
|
|
-- Spawnpos is far away, put it at distancecap instead
|
|
pos = tr.StartPos + tr.Normal * distancecap
|
|
inhibitrerail = true
|
|
else
|
|
-- Spawn is near
|
|
pos = tr.HitPos + tr.HitNormal * verticaloffset
|
|
end
|
|
ang = Angle(0,tr.Normal:Angle().y,0)
|
|
end
|
|
else
|
|
-- Trace didn't hit anything, spawn at distancecap
|
|
pos = tr.StartPos + tr.Normal * distancecap
|
|
ang = Angle(0,tr.Normal:Angle().y,0)
|
|
end
|
|
local ent = ents.Create(className or self.ClassName)
|
|
ent:SetPos(pos)
|
|
ent:SetAngles(ang)
|
|
if rotate then ent:SetAngles(ent:LocalToWorldAngles(Angle(0,180,0))) end
|
|
ent.Owner = ply
|
|
ent:Spawn()
|
|
ent:Activate()
|
|
if not inhibitrerail then inhibitrerail = not Metrostroi.RerailTrain(ent) end
|
|
if rotate and inhibitrerail then ent:Remove() return false end
|
|
|
|
-- Debug mode
|
|
--Metrostroi.DebugTrain(ent,ply)
|
|
return ent
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Process Cabin button and keyboard input
|
|
--------------------------------------------------------------------------------
|
|
function ENT:OnButtonPress(button,ply)
|
|
end
|
|
|
|
function ENT:OnButtonRelease(button)
|
|
|
|
end
|
|
|
|
-- Clears the serverside keybuffer and fires events
|
|
function ENT:ClearKeyBuffer(helper)
|
|
for k,v in pairs(self.KeyBuffer) do
|
|
local button = self.KeyMap[k]
|
|
if button ~= nil then
|
|
if helper then
|
|
if type(button) == "table" and button.helper then
|
|
self:ButtonEvent(button.helper,false)
|
|
end
|
|
elseif type(button) == "string" then
|
|
self:ButtonEvent(button,false)
|
|
else
|
|
--Check modifiers as well
|
|
for k2,v2 in pairs(button) do
|
|
self:ButtonEvent(v2,false)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self.KeyBuffer = {}
|
|
end
|
|
|
|
local function ShouldWriteToBuffer(buffer,state)
|
|
if state == nil then return false end
|
|
if state == false and buffer == nil then return false end
|
|
return true
|
|
end
|
|
|
|
local function ShouldFireEvents(buffer,state)
|
|
if state == nil then return true end
|
|
if buffer == nil and state == false then return false end
|
|
return (state ~= buffer)
|
|
end
|
|
|
|
-- Checks a button with the buffer and calls
|
|
-- OnButtonPress/Release as well as TriggerInput
|
|
|
|
function ENT:ButtonEvent(button,state,ply)
|
|
if ShouldFireEvents(self.ButtonBuffer[button],state) then
|
|
if state == false and not self:OnButtonRelease(button,ply) then
|
|
self:TriggerInput(button,0.0)
|
|
elseif state ~= false and not self:OnButtonPress(button,ply) then
|
|
self:TriggerInput(button,1.0)
|
|
if self.Plombs and button:sub(-2,-1) == "Pl" and self.Plombs[button:sub(1,-3)] then
|
|
local plomb = self.Plombs[button:sub(1,-3)]
|
|
self:BrokePlomb(button:sub(1,-3),ply)
|
|
if type(plomb) == "table" then
|
|
for i=2,#plomb do
|
|
self:BrokePlomb(plomb[i],ply,i>1)
|
|
end
|
|
end
|
|
self:PlayOnce("plomb","cabin",0.7)
|
|
end
|
|
end
|
|
end
|
|
|
|
if ShouldWriteToBuffer(self.ButtonBuffer[button],state) then
|
|
self.ButtonBuffer[button]=state
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Handle cabin buttons
|
|
--------------------------------------------------------------------------------
|
|
-- Receiver for CS buttons, Checks if people are the legit driver and calls buttonevent on the train
|
|
net.Receive("metrostroi-mouse-move", function(len, ply)
|
|
local train = net.ReadEntity()
|
|
if not IsValid(train) then return end
|
|
if train.CursorMove then
|
|
local sys = net.ReadString()
|
|
local dX = net.ReadFloat()
|
|
local dY = net.ReadFloat()
|
|
train:CursorMove(sys,dX,dY)
|
|
end
|
|
end)
|
|
|
|
net.Receive("metrostroi-cabin-button", function(len, ply)
|
|
local train = net.ReadEntity()
|
|
local button = net.ReadString()
|
|
local eventtype = net.ReadBit()
|
|
local seat = ply:GetVehicle()
|
|
local outside = net.ReadBool()
|
|
if outside then
|
|
if not IsValid(train) then return end
|
|
if outside and (train.CPPICanPickup and not train:CPPICanPickup(ply)) then return end
|
|
if not outside and ply != train.DriverSeat.lastDriver then return end
|
|
if not outside and train.DriverSeat.lastDriverTime and (CurTime() - train.DriverSeat.lastDriverTime) > 1 then return end
|
|
else
|
|
if not IsValid(train) then return end
|
|
if (seat != train.DriverSeat) and (seat != train.InstructorsSeat) and (train.CPPICanPhysgun and not train:CPPICanPhysgun(ply)) and not button:find("Door") then return end
|
|
end
|
|
train:ButtonEvent(button,(eventtype > 0),ply)
|
|
end)
|
|
|
|
-- Receiver for panel touchs, Checks if people are the legit driver and calls buttonevent on the train
|
|
net.Receive("metrostroi-panel-touch", function(len, ply)
|
|
local panel = net.ReadString()
|
|
local x = net.ReadUInt(11)
|
|
local y = net.ReadUInt(11)
|
|
local outside = net.ReadBool()
|
|
local state = net.ReadBool()
|
|
local seat = ply:GetVehicle()
|
|
local train
|
|
|
|
if seat and IsValid(seat) and not outside then
|
|
-- Player currently driving
|
|
train = seat:GetNW2Entity("TrainEntity")
|
|
if (not train) or (not train:IsValid()) then return end
|
|
if (seat != train.DriverSeat) and (seat != train.InstructorsSeat) and (not train.CPPICanPhysgun or not train:CPPICanPhysgun(ply)) then return end
|
|
else
|
|
-- Player not driving, check recent train
|
|
train = IsValid(ply.lastVehicleDriven) and ply.lastVehicleDriven:GetNW2Entity("TrainEntity") or NULL
|
|
if outside then
|
|
local trace = util.TraceLine({
|
|
start = ply:EyePos(),
|
|
endpos = ply:EyePos() + ply:EyeAngles():Forward() * 100,
|
|
filter = function( ent ) if ent:GetClass():find("subway") then return true end end
|
|
})
|
|
train = trace.Entity
|
|
end
|
|
if !IsValid(train) then return end
|
|
if outside and train.CPPICanPickup and not train:CPPICanPickup(ply) then return end
|
|
if not outside and ply != train.DriverSeat.lastDriver then return end
|
|
if not outside and train.DriverSeat.lastDriverTime and (CurTime() - train.DriverSeat.lastDriverTime) > 1 then return end
|
|
end
|
|
if panel == "" and train.PanelTouch then train:PanelTouch(state,x,y) return end
|
|
if panel ~= "" and not train[panel] then print("Metrostroi:System not found,"..panel) return end
|
|
if panel ~= "" and not train[panel].Touch then print("Metrostroi:Touch function not found in system "..panel) return end
|
|
if panel ~= "" then train[panel]:Touch(state,x,y) else train:Touch(state,x,y) end
|
|
end)
|
|
|
|
-- Denies entry if player recently sat in the same train seat
|
|
-- This prevents getting stuck in seats when trying to exit
|
|
local function CanPlayerEnter(ply,vec,role)
|
|
local train = vec:GetNW2Entity("TrainEntity")
|
|
|
|
if IsValid(train) and IsValid(ply.lastVehicleDriven) and ply.lastVehicleDriven.lastDriverTime != nil then
|
|
if CurTime() - ply.lastVehicleDriven.lastDriverTime < 1 then return false end
|
|
end
|
|
end
|
|
|
|
-- Exiting player hook, stores some vars and moves player if vehicle was train seat
|
|
local function HandleExitingPlayer(ply, vehicle)
|
|
vehicle.lastDriver = ply
|
|
vehicle.lastDriverTime = CurTime()
|
|
ply.lastVehicleDriven = vehicle
|
|
|
|
local train = vehicle:GetNW2Entity("TrainEntity")
|
|
if IsValid(train) then
|
|
ply.lastTrain = train
|
|
ply.lastTrainSeat = vehicle
|
|
-- Move exiting player
|
|
local seattype = vehicle:GetNW2String("SeatType")
|
|
local offset
|
|
|
|
if (seattype == "driver") then
|
|
offset = Vector(0,10,-17)
|
|
elseif (seattype == "instructor") then
|
|
offset = Vector(5,-10,-17)
|
|
elseif (seattype == "passenger") then
|
|
offset = Vector(10,0,-17)
|
|
end
|
|
|
|
offset:Rotate(train:GetAngles())
|
|
ply:SetPos(vehicle:GetPos()+offset)
|
|
|
|
ply:SetEyeAngles(vehicle:GetForward():Angle())
|
|
|
|
-- Server
|
|
train:ClearKeyBuffer(seattype)
|
|
-- Client
|
|
net.Start("metrostroi-cabin-reset")
|
|
net.WriteEntity(train)
|
|
net.Send(ply)
|
|
end
|
|
end
|
|
|
|
|
|
function ENT:UpdateTransmitState()
|
|
return TRANSMIT_PVS
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Register joystick buttons
|
|
-- Won't get called if joystick isn't installed
|
|
-- I've put it here for now, trains will likely share these inputs anyway
|
|
local function JoystickRegister()
|
|
Metrostroi.RegisterJoystickInput("met_controller",true,"Controller",-3,3)
|
|
Metrostroi.RegisterJoystickInput("met_reverser",true,"Reverser",-1,1)
|
|
Metrostroi.RegisterJoystickInput("met_pneubrake",true,"Pneumatic Brake",1,5)
|
|
Metrostroi.RegisterJoystickInput("met_headlight",false,"Headlight Toggle")
|
|
|
|
-- Metrostroi.RegisterJoystickInput("met_reverserup",false,"Reverser Up")
|
|
-- Metrostroi.RegisterJoystickInput("met_reverserdown",false,"Reverser Down")
|
|
-- Will make this somewhat better later
|
|
-- Uncommenting these somehow makes the joystick addon crap itself
|
|
|
|
Metrostroi.JoystickSystemMap["met_controller"] = "KVControllerSet"
|
|
Metrostroi.JoystickSystemMap["met_reverser"] = "KVReverserSet"
|
|
Metrostroi.JoystickSystemMap["met_pneubrake"] = "PneumaticBrakeSet"
|
|
Metrostroi.JoystickSystemMap["met_headlight"] = "HeadLightsToggle"
|
|
-- Metrostroi.JoystickSystemMap["met_reverserup"] = "KVReverserUp"
|
|
-- Metrostroi.JoystickSystemMap["met_reverserdown"] = "KVReverserDown"
|
|
end
|
|
|
|
hook.Add("JoystickInitialize","metroistroi_cabin",JoystickRegister)
|
|
|
|
hook.Add("PlayerLeaveVehicle", "gmod_subway_81-717-cabin-exit", HandleExitingPlayer )
|
|
hook.Add("CanPlayerEnterVehicle","gmod_subway_81-717-cabin-entry", CanPlayerEnter )
|
|
|
|
function ENT:BrokePlomb(but,ply,nosnd)
|
|
if ply then
|
|
local nomsg,noplomb = hook.Run("MetrostroiPlombBroken",self,but,ply)
|
|
if noplomb then return end
|
|
if not nosnd and not nomsg then RunConsoleCommand("say",ply:GetName().." broke seal on "..but.."!") end
|
|
end
|
|
self[but]:TriggerInput("Block",false)
|
|
self.Plombs[but] = false
|
|
self:SetPackedBool(but.."Pl",false)
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Common functions
|
|
--------------------------------------------------------------------------------
|
|
local types = {Texture = "train",PassTexture = "pass",CabTexture = "cab"}
|
|
function ENT:FindFineSkin()
|
|
if not self.SkinsType then return end
|
|
|
|
for id,typ in pairs(types) do
|
|
local fineSkins = {all={},def={}}
|
|
for k,v in pairs(Metrostroi.Skins[typ]) do
|
|
if v.textures and v.typ == self.SkinsType then
|
|
table.insert(fineSkins.all,k)
|
|
if v.def then table.insert(fineSkins.def,k) end
|
|
end
|
|
end
|
|
if #fineSkins.def > 0 then
|
|
self:SetNW2String(id,table.Random(fineSkins.def))
|
|
self:UpdateTextures()
|
|
elseif #fineSkins.all > 0 then
|
|
self:SetNW2String(id,table.Random(fineSkins.all))
|
|
self:UpdateTextures()
|
|
end
|
|
end
|
|
end
|
|
|
|
function ENT:UpdateTextures()
|
|
local texture = Metrostroi.Skins["train"][self:GetNW2String("Texture")]
|
|
local passtexture = Metrostroi.Skins["pass"][self:GetNW2String("PassTexture")]
|
|
local cabintexture = Metrostroi.Skins["cab"][self:GetNW2String("CabTexture")]
|
|
if texture and texture.func then
|
|
self:SetNW2String("Texture",texture.func(self))
|
|
end
|
|
if passtexture and passtexture.func then
|
|
self:SetNW2String("PassTexture",passtexture.func(self))
|
|
end
|
|
if cabintexture and cabintexture.func then
|
|
self:SetNW2String("CabTexture",cabintexture.func(self))
|
|
end
|
|
|
|
self.Texture = self:GetNW2String("Texture")
|
|
self.PassTexture = self:GetNW2String("PassTexture")
|
|
self.CabTexture = self:GetNW2String("CabTexture")
|
|
local texture = Metrostroi.Skins["train"][self.Texture]
|
|
local passtexture = Metrostroi.Skins["pass"][self.PassTexture]
|
|
local cabintexture = Metrostroi.Skins["cab"][self.CabTexture]
|
|
for k in pairs(self:GetMaterials()) do self:SetSubMaterial(k-1,"") end
|
|
for k,v in pairs(self:GetMaterials()) do
|
|
local tex = v:gsub("^.+/","")
|
|
if self.GetAdditionalTextures then
|
|
local tex = self:GetAdditionalTextures(tex)
|
|
if tex then
|
|
self:SetSubMaterial(k-1,tex)
|
|
continue
|
|
end
|
|
end
|
|
if cabintexture and cabintexture.textures and cabintexture.textures[tex] then
|
|
self:SetSubMaterial(k-1,cabintexture.textures[tex])
|
|
end
|
|
if passtexture and passtexture.textures and passtexture.textures[tex] then
|
|
self:SetSubMaterial(k-1,passtexture.textures[tex])
|
|
end
|
|
if texture and texture.textures and texture.textures[tex] then
|
|
self:SetSubMaterial(k-1,texture.textures[tex])
|
|
end
|
|
end
|
|
|
|
if texture and texture.postfunc then texture.postfunc(self) end
|
|
if passtexture and passtexture.postfunc then passtexture.postfunc(self) end
|
|
if cabintexture and cabintexture.postfunc then cabintexture.postfunc(self) end
|
|
|
|
local level = math.random() > 0.95 and 0.7 or math.random() > 0.8 and 0.55 or math.random() > 0.35 and 0.25 or 0
|
|
self:SetNW2Vector("DirtLevel",math.Clamp(level+math.random()*0.2-0.1,0,1))
|
|
end
|
|
function ENT:GenerateWagonNumber(func)
|
|
if self.NumberRanges then
|
|
self.WagonNumber,self.WagonNumberConf = Metrostroi.GenerateNumber(self,self.NumberRanges,func)
|
|
if self.WagonNumber then
|
|
self:SetNW2Int("WagonNumber",self.WagonNumber)
|
|
end
|
|
self:UpdateWagonNumber()
|
|
end
|
|
end
|
|
function ENT:UpdateWagonNumber() end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Common functions for RKSU(81-71) trains
|
|
--------------------------------------------------------------------------------
|
|
function ENT:GenerateJerks()
|
|
if not IsValid(self.FrontBogey) or not IsValid(self.RearBogey) then return end
|
|
local jerk = math.abs((self.Acceleration - (self.PrevAcceleration or 0)) / self.DeltaTime)
|
|
|
|
local roll = self:GetLocalAngles().roll
|
|
--local rjerk = ((roll - (self.PrevRoll or 0)) / self.DeltaTime)
|
|
self.PrevAcceleration = self.Acceleration
|
|
self.PrevRoll = roll
|
|
|
|
if jerk > (2.0 + self.Speed/15.0) then
|
|
self.PrevTriggerTime1 = self.PrevTriggerTime1 or CurTime()
|
|
self.PrevTriggerTime2 = self.PrevTriggerTime2 or CurTime()
|
|
|
|
if ((math.random() > 0.00) or (jerk > 10)) and (CurTime() - self.PrevTriggerTime1 > 0.01) then
|
|
self.PrevTriggerTime1 = CurTime()
|
|
self.FrontBogey:EmitSound(table.Random(self.SoundNames["junk_small"]), 69, math.random(96,110))
|
|
self.FrontBogey:EmitSound(table.Random(self.SoundNames["junk_medium"]), 72, math.random(96,110))
|
|
self.RearBogey:EmitSound(table.Random(self.SoundNames["junk_small"]), 69, math.random(96,110))
|
|
self.RearBogey:EmitSound(table.Random(self.SoundNames["junk_medium"]), 72, math.random(96,110))
|
|
end
|
|
end
|
|
|
|
local trigger = (0.25 + math.Clamp((2-self.Speed)/8,0,0.25) + math.max(0,(self.Speed-2)/35.0))
|
|
local rjerkstate = math.Round(roll/trigger)
|
|
if self.RollJerk ~= rjerkstate then
|
|
self.PrevTriggerTime1 = self.PrevTriggerTime1 or CurTime()
|
|
self.RollJerk = rjerkstate
|
|
if --[[ math.abs(self.Speed) > 0.5 and --]] (CurTime() - self.PrevTriggerTime1 > 0.01) then
|
|
self.PrevTriggerTime1 = CurTime()
|
|
self.FrontBogey:EmitSound(table.Random(self.SoundNames["junk_small"]), 69, math.random(96,110))
|
|
self.FrontBogey:EmitSound(table.Random(self.SoundNames["junk_medium"]), 72, math.random(96,110))
|
|
self.RearBogey:EmitSound(table.Random(self.SoundNames["junk_small"]), 69, math.random(96,110))
|
|
self.RearBogey:EmitSound(table.Random(self.SoundNames["junk_medium"]), 72, math.random(96,110))
|
|
end
|
|
end--]]
|
|
local accel = self.Acceleration--*self.SpeedSign
|
|
if self.Speed < 0.1 and accel < 0.1 then
|
|
self.PrepareStart = true
|
|
elseif self.Speed > 2 then
|
|
self.PrepareStart = false
|
|
elseif self.Speed >= 0.5-math.max(0.25,(1.5-accel)*0.25) and accel > 0.5 and self.PrepareStart then
|
|
if IsValid(self.FrontBogey) then self.FrontBogey:EmitSound(table.Random(self.SoundNames["junk_enginestart_speed"]), 68, 1.2*math.random(96,110)) end
|
|
if IsValid(self.RearBogey) then self.RearBogey:EmitSound(table.Random(self.SoundNames["junk_enginestart_speed"]), 68, 1.2*math.random(96,110)) end
|
|
self.PrepareStart = false
|
|
end
|
|
end
|
|
|
|
|
|
|
|
if game.SinglePlayer() then
|
|
util.AddNetworkString("PlayerButtonDown_metrostroi")
|
|
util.AddNetworkString("PlayerButtonUp_metrostroi")
|
|
hook.Add("PlayerButtonDown","metrostori_button",function(ply,button)
|
|
if not IsFirstTimePredicted() then return end
|
|
net.Start("PlayerButtonDown_metrostroi")
|
|
net.WriteUInt(button,16)
|
|
net.Send(ply)
|
|
end)
|
|
hook.Add("PlayerButtonUp","metrostori_button",function(ply,button)
|
|
if not IsFirstTimePredicted() then return end
|
|
net.Start("PlayerButtonUp_metrostroi")
|
|
net.WriteUInt(button,16)
|
|
net.Send(ply)
|
|
end)
|
|
end
|
|
|
|
|
|
--[[
|
|
if self:GetWagonNumber() == 0000 or self:EntIndex()==1531 then --DEBUG
|
|
local accel = 0
|
|
for i=1,#self.WagonList do
|
|
accel=accel+self.WagonList[i].Acceleration
|
|
end
|
|
local drivers = {self.DriverSeat,self.InstructorsSeat,self.ExtraSeat1,self.ExtraSeat2}
|
|
if math.abs(accel) > 0.1 then
|
|
for k,v in pairs(drivers) do
|
|
if IsValid(v) and IsValid(v:GetDriver()) then
|
|
v:GetDriver():ChatPrint(Format("v=%.2f I=%.2f RK=%02d a=%.2f",self.Speed,(self.Electric.I13+self.Electric.I24)/2,self.RheostatController.SelectedPosition or 0,accel/#self.WagonList))--(accel/#self.WagonList)))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
self.TestA = self.TestA or nil
|
|
self.TestV = self.TestV or nil
|
|
local accel = self.Acceleration
|
|
if (self.Speed > 75 or self.Speed > 20 and self.Speed < 60) and accel < -0.5 and not self.TestA then
|
|
self.TestA = CurTime()
|
|
self.TestV = self.Speed/3600*1000
|
|
self.TestTyp = self.Speed > 55 and 2 or 1
|
|
self.TestS = 0
|
|
end
|
|
if accel > -0.5 and self.TestA then
|
|
self.TestA = nil
|
|
self.TestV = nil
|
|
self.TestS = nil
|
|
end
|
|
|
|
if self:GetWagonNumber() == 0000 or self:EntIndex()==0065 then --DEBUG
|
|
local accel = 0
|
|
for i=1,#self.WagonList do
|
|
accel=accel+self.WagonList[i].Acceleration
|
|
end
|
|
local drivers = {self.DriverSeat,self.InstructorsSeat,self.ExtraSeat1,self.ExtraSeat2}
|
|
if math.abs(accel) > 0.1 then
|
|
for k,v in pairs(drivers) do
|
|
if IsValid(v) and IsValid(v:GetDriver()) then
|
|
v:GetDriver():ChatPrint(Format("v=%.2f I=%.2f RK=%02d a=%.2f",self.Speed,(self.Electric.I13+self.Electric.I24)/2,self.RheostatController.SelectedPosition or 0,accel/#self.WagonList))--(accel/#self.WagonList)))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if self.TestS then self.TestS=self.TestS+self.Speed*self.SpeedSign/3600*1000*self.DeltaTime end
|
|
if (self.Speed<2 and self.TestTyp ==2 or self.Speed<2 and self.TestTyp ==1) and self.TestA then
|
|
local curSpeed = self.Speed/3600*1000
|
|
local a = (curSpeed-self.TestV)/(CurTime()-self.TestA)
|
|
RunConsoleCommand("say",Format("[%05d]V0= %.1f V1=%.1f t=%.2f a=%.2f s=%.1f",self:GetWagonNumber(),self.TestV*3600/1000,curSpeed*3600/1000,CurTime()-self.TestA,a,self.TestS))
|
|
|
|
|
|
self.TestA = nil
|
|
self.TestV = nil
|
|
self.TestS = nil
|
|
end--]]
|
|
--[[
|
|
if (self.Speed < 20 or self.Speed < 70) and accel > 0.1 and not self.TestA then
|
|
self.TestA = CurTime()
|
|
self.TestV = self.Speed/3600*1000
|
|
self.TestTyp = self.Speed > 60 and 2 or self.Speed > 30 and 1 or 0
|
|
print("!!!",self.TestTyp)
|
|
self.TestS = 0
|
|
end
|
|
if self.TestA and self.KV.ControllerPosition<=0 and (self.Speed<0.1 or self.Speed<1 and self.TestA>0) then
|
|
self.TestA = nil
|
|
self.TestV = nil
|
|
self.TestS = nil
|
|
end
|
|
if self.TestS then self.TestS=self.TestS+self.Speed*self.SpeedSign/3600*1000*dT end
|
|
if (self.Speed>=30 and self.TestTyp ==0 or self.Speed>=60 and self.TestTyp ==1 or self.Speed>=80 and self.TestTyp ==2) and self.TestA then
|
|
local curSpeed = self.Speed/3600*1000
|
|
local a = (curSpeed-self.TestV)/(CurTime()-self.TestA)
|
|
RunConsoleCommand("say",Format("[%05d]V0= %.1f V1=%.1f t=%.2f a=%.2f",self:GetWagonNumber(),self.TestV*3600/1000,curSpeed*3600/1000,CurTime()-self.TestA,a))
|
|
|
|
|
|
self.TestA = nil
|
|
self.TestV = nil
|
|
self.TestS = nil
|
|
end
|
|
if self:GetWagonNumber() == 0000 then --DEBUG
|
|
local accel = 0
|
|
for i=1,#self.WagonList do
|
|
accel=accel+self.WagonList[i].Acceleration
|
|
end
|
|
|
|
if math.abs(accel) > 0.1 then
|
|
Player(6):ChatPrint(Format("v=%.2f I=%.2f",self.Speed,(accel/#self.WagonList)))
|
|
Player(7):ChatPrint(Format("v=%.2f I=%.2f",self.Speed,(accel/#self.WagonList)))
|
|
Player(9):ChatPrint(Format("v=%.2f I=%.2f",self.Speed,(accel/#self.WagonList)))
|
|
end
|
|
end--]]
|
|
|
|
|
|
Metrostroi.OptimisationPatch() |