mirror of
https://github.com/metrostroi-repo/MetrostroiAddon.git
synced 2026-05-02 00:42:29 +00:00
1030 lines
40 KiB
Lua
1030 lines
40 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- Assign train IDs
|
|
--------------------------------------------------------------------------------
|
|
if not Metrostroi.WagonID then
|
|
Metrostroi.WagonID = 1
|
|
end
|
|
function Metrostroi.NextWagonID()
|
|
local id = Metrostroi.WagonID
|
|
Metrostroi.WagonID = Metrostroi.WagonID + 1
|
|
if Metrostroi.WagonID > 99 then Metrostroi.WagonID = 1 end
|
|
return id
|
|
end
|
|
|
|
Metrostroi.UsedNumbers = Metrostroi.UsedNumbers or {}
|
|
hook.Add("EntityRemoved","WagonNumberRemove",Metrostroi.RemoveNumber)
|
|
function Metrostroi.RemoveNumber(ent)
|
|
if IsValid(ent) and ent.WagonNumber then
|
|
local typ = ent.SubwayTrain and ent.SubwayTrain.Type or ent:GetClass()
|
|
local tbl = Metrostroi.UsedNumbers[typ]
|
|
if not tbl then return end
|
|
print(Format("Removed number %05d to %s(%04d %s)",ent.WagonNumber,ent:GetClass(),ent:EntIndex(),typ))
|
|
tbl[ent.WagonNumber] = nil
|
|
end
|
|
end
|
|
function Metrostroi.GenerateNumber(train,tbl,func,retry)
|
|
Metrostroi.RemoveNumber(train)
|
|
if not tbl or not IsValid(train) then return 0 end
|
|
local typ = train.SubwayTrain and train.SubwayTrain.Type or train:GetClass()
|
|
if not Metrostroi.UsedNumbers[typ] then Metrostroi.UsedNumbers[typ] = {} end
|
|
for i=1,1000 do
|
|
local range = train.NumberRangesID and tbl[train.NumberRangesID] or tbl[math.random(1,#tbl)]
|
|
local number
|
|
if range[1]==true then
|
|
local tblN = range[2]
|
|
number = tblN[math.random(1,#tblN)]
|
|
else
|
|
number = math.random(range[1],range[2])
|
|
end
|
|
if func then number = func(train,number) or number end
|
|
if number and number~=true and not Metrostroi.UsedNumbers[typ][number] then
|
|
Metrostroi.UsedNumbers[typ][number] = true
|
|
print(Format("Assigned number %05d to %s(%04d %s)",number,train:GetClass(),train:EntIndex(),typ))
|
|
return number,range[3]
|
|
end
|
|
end
|
|
if retry then
|
|
ErrorNoHalt(Format("Metrostroi: Error generating number for train %s! Second try was failed.\n",train:GetClass()))
|
|
return 0
|
|
else
|
|
ErrorNoHalt(Format("Metrostroi: Error generating number for train %s! Clearing wagon numbers table...\n",train:GetClass()))
|
|
Metrostroi.UsedNumbers = {}
|
|
return Metrostroi.GenerateNumber(train,tbl,func,true)
|
|
end
|
|
end
|
|
|
|
if not Metrostroi.EquipmentID then
|
|
Metrostroi.EquipmentID = 1
|
|
end
|
|
function Metrostroi.NextEquipmentID()
|
|
local id = Metrostroi.EquipmentID
|
|
Metrostroi.EquipmentID = Metrostroi.EquipmentID + 1
|
|
return id
|
|
end
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Custom drop to floor that only checks origin and not bounding box
|
|
--------------------------------------------------------------------------------
|
|
function Metrostroi.DropToFloor(ent)
|
|
local result = util.TraceLine({
|
|
start = ent:GetPos(),
|
|
endpos = ent:GetPos() - Vector(0,0,256),
|
|
mask = -1,
|
|
filter = { ent },
|
|
})
|
|
if result.Hit then ent:SetPos(result.HitPos) end
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Get random number that is same over a period of 1 minute
|
|
--------------------------------------------------------------------------------
|
|
local prediods = {}
|
|
local periodNumbers = {}
|
|
local randomPeriodStart = 0
|
|
local randomPeriodNumber = math.random()
|
|
function Metrostroi.PeriodRandomNumber(typ)
|
|
typ = typ or 0
|
|
if not prediods[typ] or (CurTime() - prediods[typ]) > 60 then
|
|
periodNumbers[typ] = math.random()
|
|
end
|
|
|
|
-- Refresh the period
|
|
prediods[typ] = CurTime()
|
|
|
|
-- Return number
|
|
return periodNumbers[typ]
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Joystick controls
|
|
-- Author: HunterNL
|
|
--------------------------------------------------------------------------------
|
|
if not Metrostroi.JoystickValueRemap then
|
|
Metrostroi.JoystickValueRemap = {}
|
|
Metrostroi.JoystickSystemMap = {}
|
|
end
|
|
|
|
function Metrostroi.RegisterJoystickInput (uid,analog,desc,min,max)
|
|
if not joystick then
|
|
Error("Joystick Input registered without joystick addon installed, get it at https://github.com/MattJeanes/Joystick-Module")
|
|
end
|
|
--If this is only called in a JoystickRegister hook it should never even happen
|
|
|
|
if #uid > 20 then
|
|
print("Metrostroi Joystick UID too long, trimming")
|
|
local uid = string.Left(uid,20)
|
|
end
|
|
|
|
|
|
local atype
|
|
if analog then
|
|
atype = "analog"
|
|
else
|
|
atype = "digital"
|
|
end
|
|
|
|
local temp = {
|
|
uid = uid,
|
|
type = atype,
|
|
description = desc,
|
|
category = "Metrostroi" --Just Metrostroi for now, seperate catagories for different trains later?
|
|
--Catergory is also checked in subway base, don't just change
|
|
}
|
|
|
|
|
|
--Joystick addon's build-in remapping doesn't work so well, so we're doing this instead
|
|
if min ~= nil and max ~= nil and analog then
|
|
Metrostroi.JoystickValueRemap[uid]={min,max}
|
|
end
|
|
|
|
jcon.register(temp)
|
|
end
|
|
|
|
-- Wrapper around joystick get to implement our own remapping
|
|
function Metrostroi.GetJoystickInput(ply,uid)
|
|
local remapinfo = Metrostroi.JoystickValueRemap[uid]
|
|
local jvalue = joystick.Get(ply,uid)
|
|
if remapinfo == nil then
|
|
return jvalue
|
|
elseif jvalue ~= nil then
|
|
return math.Remap(joystick.Get(ply,uid),0,255,remapinfo[1],remapinfo[2])
|
|
else
|
|
return jvalue
|
|
end
|
|
end
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Player meta table magic
|
|
-- Author: HunterNL
|
|
--------------------------------------------------------------------------------
|
|
local Player = FindMetaTable("Player")
|
|
|
|
function Player:CanDriveTrains()
|
|
return IsValid(self:GetWeapon("train_kv_wrench")) or self:IsAdmin()
|
|
end
|
|
|
|
function Player:GetTrain()
|
|
local seat = self:GetVehicle()
|
|
if IsValid(seat) then
|
|
return seat:GetNW2Entity("TrainEntity"),seat
|
|
end
|
|
end
|
|
|
|
hook.Add("PlayerEnteredVehicle","MetrostroiPlayerTrain",function(ply,veh)
|
|
ply.InMetrostroiTrain = IsValid(veh:GetNW2Entity("TrainEntity")) and veh:GetNW2Entity("TrainEntity")
|
|
end)
|
|
hook.Add("PlayerLeaveVehicle","MetrostroiPlayerTrain",function(ply,veh)
|
|
ply.InMetrostroiTrain = false
|
|
end)
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Train count
|
|
--------------------------------------------------------------------------------
|
|
function Metrostroi.TrainCount(...)
|
|
local classnames = {...}
|
|
if #classnames == 1 then
|
|
return #ents.FindByClass(classnames[1])
|
|
end
|
|
|
|
local N = 0
|
|
for k,v in pairs(#classnames > 0 and classnames or Metrostroi.TrainClasses) do
|
|
if not baseclass.Get(v).SubwayTrain then continue end
|
|
N = N + #ents.FindByClass(v)
|
|
end
|
|
return N
|
|
end
|
|
|
|
function Metrostroi.TrainCountOnPlayer(ply ,...)
|
|
local classnames = {...}
|
|
local typ
|
|
if type(classnames[1]) == "number" then
|
|
typ = classnames[1]
|
|
classnames = {}
|
|
end
|
|
if CPPI then
|
|
local N = 0
|
|
for k,v in pairs(#classnames > 0 and classnames or Metrostroi.TrainClasses) do
|
|
if not baseclass.Get(v).SubwayTrain then continue end
|
|
local ents = ents.FindByClass(v)
|
|
for k2,v2 in pairs(ents) do
|
|
if ply == v2:CPPIGetOwner() and (not typ or v2.SubwayTrain.WagType == typ) then
|
|
N = N + 1
|
|
end
|
|
end
|
|
end
|
|
return N
|
|
end
|
|
return 0
|
|
end
|
|
|
|
concommand.Add("metrostroi_train_count", function(ply, _, args)
|
|
print("Trains on server: "..Metrostroi.TrainCount())
|
|
if CPPI then
|
|
local N = {}
|
|
for k,v in pairs(Metrostroi.TrainClasses) do
|
|
if v == "gmod_subway_base" then continue end
|
|
local ents = ents.FindByClass(v)
|
|
for k2,v2 in pairs(ents) do
|
|
N[v2:CPPIGetOwner() or "(disconnected)"] = (N[v2:CPPIGetOwner() or "(disconnected)"] or 0) + 1
|
|
end
|
|
end
|
|
for k,v in pairs(N) do
|
|
print(k,"Trains count: "..v)
|
|
end
|
|
end
|
|
end)
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Simple hack to get a driving schedule
|
|
--------------------------------------------------------------------------------
|
|
concommand.Add("metrostroi_schedule", function(ply, _, args)
|
|
if not IsValid(ply) then return end
|
|
local train = ply:GetTrain()
|
|
local pos = Metrostroi.TrainPositions[train]
|
|
--if pos and pos[1] then
|
|
local line = tonumber(args[1])
|
|
local path = tonumber(args[2]) or 1
|
|
local starts = tonumber(args[3])
|
|
local ends = tonumber(args[4])
|
|
train.Schedule = Metrostroi.GenerateSchedule("Line"..line.."_Platform"..path,starts,ends)
|
|
if train.Schedule then
|
|
train:SetNW2Int("_schedule_id",train.Schedule.ScheduleID)
|
|
train:SetNW2Int("_schedule_duration",train.Schedule.Duration)
|
|
train:SetNW2Int("_schedule_interval",train.Schedule.Interval)
|
|
train:SetNW2Int("_schedule_N",#train.Schedule)
|
|
train:SetNW2Int("_schedule_path",path)
|
|
for k,v in ipairs(train.Schedule) do
|
|
train:SetNW2Int("_schedule_"..k.."_1",v[1])
|
|
train:SetNW2Int("_schedule_"..k.."_2",v[2])
|
|
train:SetNW2Int("_schedule_"..k.."_3",v[3])
|
|
train:SetNW2Int("_schedule_"..k.."_4",v[4])
|
|
train:SetNW2String("_schedule_"..k.."_5",Metrostroi.StationNames[v[1]] or v[1])
|
|
end
|
|
end
|
|
--end
|
|
end)
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Failures related stuff
|
|
--------------------------------------------------------------------------------
|
|
concommand.Add("metrostroi_failures", function(ply, _, args)
|
|
local i = 0
|
|
for _,class in pairs(Metrostroi.TrainClasses) do
|
|
local trains = ents.FindByClass(class)
|
|
for _,train in pairs(trains) do
|
|
timer.Simple(0.1+i*0.2,function()
|
|
print("Failures for train "..train:EntIndex())
|
|
train:TriggerInput("FailSimStatus",1)
|
|
end)
|
|
i = i + 1
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("metrostroi_fail", function(ply, _, args)
|
|
local trainList = {}
|
|
if not IsValid(ply) then
|
|
for _,class in pairs(Metrostroi.TrainClasses) do
|
|
local trains = ents.FindByClass(class)
|
|
for _,train in pairs(trains) do
|
|
table.insert(trainList,train)
|
|
end
|
|
end
|
|
else
|
|
local train = ply:GetTrain()
|
|
if IsValid(train) then
|
|
train:UpdateWagonList()
|
|
for k,v in pairs(train.WagonList) do
|
|
trainList[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
local train = table.Random(trainList)
|
|
if train then
|
|
if IsValid(ply) then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Generating random failure in your train!")
|
|
print("Player "..tostring(ply).." generated random failure in train "..train:EntIndex())
|
|
else
|
|
print("Generating random failure in train "..train:EntIndex())
|
|
end
|
|
train:TriggerInput("FailSimFail",1)
|
|
else
|
|
if IsValid(ply) then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"You must be inside a train to generate a failure!")
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("metrostroi_fail_reset", function(ply, _, args)
|
|
local trainList = {}
|
|
if not IsValid(ply) then
|
|
for _,class in pairs(Metrostroi.TrainClasses) do
|
|
local trains = ents.FindByClass(class)
|
|
for _,train in pairs(trains) do
|
|
table.insert(trainList,train)
|
|
end
|
|
end
|
|
else
|
|
local train = ply:GetTrain()
|
|
if IsValid(train) then
|
|
train:UpdateWagonList()
|
|
for k,v in pairs(train.WagonList) do
|
|
trainList[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
if #trainList > 0 then
|
|
if IsValid(ply) then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Reset all failures in your train!")
|
|
print("Player "..tostring(ply).." reset all failures in train "..trainList[1]:EntIndex())
|
|
else
|
|
print("Reset all failures in train "..trainList[1]:EntIndex())
|
|
end
|
|
for _,v in pairs(trainList) do v:TriggerInput("FailSimReset") end
|
|
else
|
|
if IsValid(ply) then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"You must be inside a train to reset all failures!")
|
|
end
|
|
end
|
|
end)
|
|
|
|
concommand.Add("metrostroi_wire", function(ply, _, args)
|
|
local trainList = {}
|
|
if not IsValid(ply) then
|
|
for _,class in pairs(Metrostroi.TrainClasses) do
|
|
local trains = ents.FindByClass(class)
|
|
for _,train in pairs(trains) do
|
|
table.insert(trainList,train)
|
|
end
|
|
end
|
|
else
|
|
local train = ply:GetTrain()
|
|
if IsValid(train) then
|
|
--train:UpdateWagonList()
|
|
for k,v in pairs(train.WagonList) do
|
|
trainList[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
local train = table.Random(trainList)
|
|
if train then
|
|
if IsValid(ply) then
|
|
args[1] = tonumber(args[1])
|
|
if not args[1] then ply:PrintMessage(HUD_PRINTCONSOLE,"Argument must be a number") return end
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"sets outside power in train wire"..args[1].."!")
|
|
print("Player "..tostring(ply).." sets outside power in train "..args[1].." wire failure in train "..train:EntIndex())
|
|
else
|
|
print("sets outside power in train wire "..train:EntIndex())
|
|
end
|
|
--if train.WriteTrainWire then train:WriteTrainWire(args[1],1) end
|
|
train.TrainWireOutside[args[1]] = 1
|
|
--if train.WriteTrainWire then train:WriteTrainWire(args[1],1) end
|
|
else
|
|
if IsValid(ply) then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"You must be inside a train!")
|
|
end
|
|
end
|
|
end)
|
|
concommand.Add("metrostroi_wire_reset", function(ply, _, args)
|
|
local trainList = {}
|
|
if not IsValid(ply) then
|
|
for _,class in pairs(Metrostroi.TrainClasses) do
|
|
local trains = ents.FindByClass(class)
|
|
for _,train in pairs(trains) do
|
|
table.insert(trainList,train)
|
|
end
|
|
end
|
|
else
|
|
local train = ply:GetTrain()
|
|
if IsValid(train) then
|
|
--train:UpdateWagonList()
|
|
for k,v in pairs(train.WagonList) do
|
|
trainList[k] = v
|
|
end
|
|
end
|
|
end
|
|
|
|
if #trainList > 0 then
|
|
if IsValid(ply) then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"reset wire outside power in train!")
|
|
print("Player "..tostring(ply).." reset outside power in train ")
|
|
else
|
|
print("Reset outside power in trains ")
|
|
end
|
|
for _,v in pairs(trainList) do v.TrainWireOutside = {} end
|
|
else
|
|
if IsValid(ply) then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"You must be inside a train!")
|
|
end
|
|
end
|
|
end)
|
|
concommand.Add("metrostroi_can", function(ply, _, args)
|
|
if not IsValid(ply) or not IsValid(ply:GetVehicle()) then return end
|
|
local train = ply:GetVehicle():GetNW2Entity("TrainEntity")
|
|
if not IsValid(train) then return end
|
|
if not args[4] then return end
|
|
local system = args[1]
|
|
local id = tonumber(args[2])
|
|
local name = args[3]
|
|
local value = args[4]
|
|
if args[4] == "curtime" then value = CurTime() end
|
|
if args[4] == "true" then value = true end
|
|
if args[4] == "false" then value = false end
|
|
if tonumber(args[4]) then value = tonumber(args[4]) end
|
|
local srcid = tonumber(args[5])
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Hacking CAN!")
|
|
print(Format("Player %s hack CAN(%s->%s:%s %s=%s)",ply,srcid,system,id,name,value))
|
|
train:CANWrite("Hacker",srcid or train:GetWagonNumber(),system,id,name,value)
|
|
end)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Electric consumption stats
|
|
--------------------------------------------------------------------------------
|
|
-- Load total kWh
|
|
timer.Create("Metrostroi_TotalkWhTimer",5.00,0,function()
|
|
file.Write("metrostroi_data/total_kwh.txt",Metrostroi.TotalkWh or 0)
|
|
end)
|
|
Metrostroi.TotalkWh = Metrostroi.TotalkWh or tonumber(file.Read("metrostroi_data/total_kwh.txt") or "") or 0
|
|
Metrostroi.TotalRateWatts = Metrostroi.TotalRateWatts or 0
|
|
Metrostroi.Voltage = 750
|
|
Metrostroi.Voltages = Metrostroi.Voltages or {}
|
|
Metrostroi.Currents = Metrostroi.Currents or {}
|
|
Metrostroi.Current = 0
|
|
Metrostroi.PeopleOnRails = 0
|
|
Metrostroi.VoltageRestoreTimer = 0
|
|
|
|
local prevTime
|
|
hook.Add("Think", "Metrostroi_ElectricConsumptionThink", function()
|
|
-- Change in time
|
|
prevTime = prevTime or CurTime()
|
|
local deltaTime = (CurTime() - prevTime)
|
|
prevTime = CurTime()
|
|
|
|
-- Calculate total rate
|
|
Metrostroi.TotalRateWatts = 0
|
|
Metrostroi.Current = 0
|
|
for k,v in pairs(Metrostroi.Currents) do Metrostroi.Currents[k] = 0 end
|
|
local bogeys = ents.FindByClass("gmod_train_bogey")
|
|
for _,bogey in pairs(bogeys) do
|
|
if bogey.Feeder then
|
|
Metrostroi.Currents[bogey.Feeder] = Metrostroi.Currents[bogey.Feeder] + bogey.DropByPeople
|
|
else
|
|
Metrostroi.Current = Metrostroi.Current + bogey.DropByPeople
|
|
end
|
|
end
|
|
for _,class in pairs(Metrostroi.TrainClasses) do
|
|
local trains = ents.FindByClass(class)
|
|
for _,train in pairs(trains) do
|
|
if train.Electric then
|
|
Metrostroi.TotalRateWatts = Metrostroi.TotalRateWatts + math.max(0,(train.Electric.EnergyChange or 0))
|
|
local current = math.max(0,(train.Electric.Itotal or 0)) - math.max(0,(train.Electric.Iexit or 0))
|
|
local feeder = false
|
|
local fB = train.FrontBogey
|
|
local rB = train.RearBogey
|
|
if IsValid(fB) then
|
|
if fB.Feeder then
|
|
Metrostroi.Currents[fB.Feeder] = Metrostroi.Currents[fB.Feeder] + fB.DropByPeople+current*0.4
|
|
feeder = true
|
|
else
|
|
Metrostroi.Current = Metrostroi.Current + fB.DropByPeople
|
|
end
|
|
end
|
|
if IsValid(rB) then
|
|
if rB.Feeder then
|
|
Metrostroi.Currents[rB.Feeder] = Metrostroi.Currents[rB.Feeder] + rB.DropByPeople+current*0.4
|
|
feeder = true
|
|
else
|
|
Metrostroi.Current = Metrostroi.Current + rB.DropByPeople
|
|
end
|
|
end
|
|
if not feeder then Metrostroi.Current = Metrostroi.Current + current*0.4 end
|
|
end
|
|
end
|
|
end
|
|
-- Ignore invalid values
|
|
if Metrostroi.TotalRateWatts > 1e8 then Metrostroi.TotalRateWatts = 0 end
|
|
if Metrostroi.TotalRateWatts > 0 then
|
|
-- Calculate total kWh
|
|
Metrostroi.TotalkWh = Metrostroi.TotalkWh + (Metrostroi.TotalRateWatts/(3.6e6))*deltaTime
|
|
end
|
|
-- Calculate total resistance of people on rails and current flowing through
|
|
--local Rperson = 0.613
|
|
--local Iperson = Metrostroi.Voltage / (Rperson/(Metrostroi.PeopleOnRails + 1e-9))
|
|
--Metrostroi.Current = Metrostroi.Current + Iperson
|
|
|
|
-- Check if exceeded global maximum current
|
|
if Metrostroi.Current > GetConVarNumber("metrostroi_current_limit") then
|
|
Metrostroi.VoltageRestoreTimer = CurTime() + 7.0
|
|
print(Format("[!] Power feed protection tripped: current peaked at %.1f A",Metrostroi.Current))
|
|
end
|
|
|
|
local voltage = math.max(0,GetConVarNumber("metrostroi_voltage"))
|
|
|
|
-- Calculate new voltage
|
|
local Rfeed = 0.03 --25
|
|
Metrostroi.Voltage = voltage - Metrostroi.Current*Rfeed
|
|
if CurTime() < Metrostroi.VoltageRestoreTimer then Metrostroi.Voltage = 0 end
|
|
for i in pairs(Metrostroi.Voltages) do
|
|
Metrostroi.Voltages[i] = math.max(0,voltage - Metrostroi.Currents[i]*Rfeed)
|
|
end
|
|
--print(Format("%5.1f v %.0f A",Metrostroi.Voltage,Metrostroi.Current))
|
|
end)
|
|
|
|
concommand.Add("metrostroi_electric", function(ply, _, args) -- (%.2f$) Metrostroi.GetEnergyCost(Metrostroi.TotalkWh),
|
|
local m = Format("[%25s] %010.3f kWh, %.3f kW (%5.1f v, %4.0f A)","<total>",
|
|
Metrostroi.TotalkWh,Metrostroi.TotalRateWatts*1e-3,
|
|
Metrostroi.Voltage,Metrostroi.Current)
|
|
if IsValid(ply)
|
|
then ply:PrintMessage(HUD_PRINTCONSOLE,m)
|
|
else print(m)
|
|
end
|
|
|
|
if CPPI then
|
|
local U = {}
|
|
local D = {}
|
|
for _,class in pairs(Metrostroi.TrainClasses) do
|
|
local trains = ents.FindByClass(class)
|
|
for _,train in pairs(trains) do
|
|
local owner = "(disconnected)"
|
|
if train:CPPIGetOwner() then
|
|
owner = train:CPPIGetOwner():GetName()
|
|
end
|
|
if train.Electric then
|
|
U[owner] = (U[owner] or 0) + train.Electric.ElectricEnergyUsed
|
|
D[owner] = (D[owner] or 0) + train.Electric.ElectricEnergyDissipated
|
|
end
|
|
end
|
|
end
|
|
for player,_ in pairs(U) do --, n=%.0f%%
|
|
--local m = Format("[%20s] %08.1f KWh (lost %08.1f KWh)",player,U[player]/(3.6e6),D[player]/(3.6e6)) --,100*D[player]/U[player]) --,D[player])
|
|
local m = Format("[%25s] %010.3f kWh (%.2f$)",player,U[player]/(3.6e6),Metrostroi.GetEnergyCost(U[player]/(3.6e6)))
|
|
if IsValid(ply)
|
|
then ply:PrintMessage(HUD_PRINTCONSOLE,m)
|
|
else print(m)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
timer.Create("Metrostroi_ElectricConsumptionTimer",0.5,0,function()
|
|
if CPPI then
|
|
local U = {}
|
|
local D = {}
|
|
for _,class in pairs(Metrostroi.TrainClasses) do
|
|
local trains = ents.FindByClass(class)
|
|
for _,train in pairs(trains) do
|
|
local owner = train:CPPIGetOwner()
|
|
if owner and (train.Electric) then
|
|
U[owner] = (U[owner] or 0) + train.Electric.ElectricEnergyUsed
|
|
D[owner] = (D[owner] or 0) + train.Electric.ElectricEnergyDissipated
|
|
end
|
|
end
|
|
end
|
|
for player,_ in pairs(U) do
|
|
if IsValid(player) then
|
|
player:SetDeaths(10*U[player]/(3.6e6))
|
|
player.MUsedEnergy = (player.MUsedEnergy or 0) + 10*U[player]/(3.6e6)
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
local function murder(v)
|
|
local positions = Metrostroi.GetPositionOnTrack(v:GetPos())
|
|
for k2,v2 in pairs(positions) do
|
|
local y,z = v2.y,v2.z
|
|
y = math.abs(y)
|
|
|
|
local y1 = 0.91-0.10
|
|
local y2 = 1.78 ---0.50
|
|
if (y > y1) and (y < y2) and (z < -1.70) and (z > -1.72) and (Metrostroi.Voltage > 40) then
|
|
local pos = v:GetPos()
|
|
|
|
util.BlastDamage(v,v,pos,64,3.0*Metrostroi.Voltage)
|
|
|
|
local effectdata = EffectData()
|
|
effectdata:SetOrigin(pos + Vector(0,0,-16+math.random()*(40+0)))
|
|
util.Effect("cball_explode",effectdata,true,true)
|
|
|
|
sound.Play("ambient/energy/zap"..math.random(1,3)..".wav",pos,75,math.random(100,150),1.0)
|
|
Metrostroi.PeopleOnRails = Metrostroi.PeopleOnRails + 1
|
|
|
|
--if math.random() > 0.85 then
|
|
--Metrostroi.VoltageRestoreTimer = CurTime() + 7.0
|
|
--print("[!] Power feed protection tripped: "..(tostring(v) or "").." died on rails")
|
|
--end
|
|
end
|
|
end
|
|
end
|
|
--[[
|
|
timer.Create("Metrostroi_PlayerKillTimer",0.1,0,function()
|
|
if true then return end
|
|
Metrostroi.PeopleOnRails = 0
|
|
for k,v in pairs(player.GetAll()) do
|
|
murder(v)
|
|
end
|
|
for k,v in pairs(ents.FindByClass("npc_*")) do
|
|
murder(v)
|
|
end
|
|
end)
|
|
]]
|
|
timer.Remove("Metrostroi_PlayerKillTimer")
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Does current map have any sort of metrostroi support
|
|
--------------------------------------------------------------------------------
|
|
function Metrostroi.MapHasFullSupport(typ)
|
|
if not typ then
|
|
return (#Metrostroi.Paths > 0)
|
|
elseif typ=="ars" then
|
|
return next(Metrostroi.SignalEntitiesByName)
|
|
elseif typ=="auto" then
|
|
return Metrostroi.HaveAuto
|
|
elseif typ=="sbpp" then
|
|
return Metrostroi.HaveSBPP
|
|
elseif typ=="pa" then
|
|
return next(Metrostroi.PAMConfTest)
|
|
end
|
|
end
|
|
|
|
concommand.Add("metrostroi_insert_signs", function(ply,_,args)
|
|
if IsValid(ply) then error("Metrostroi: This command can be run only from server console!") end
|
|
local MAP_NAME = game.GetMap() --"gm_mus_loopline_a3"
|
|
local MAP_VERSION = args and args[1] or ""
|
|
|
|
local commands = {Format("session_begin %s %s",MAP_NAME,MAP_VERSION)}
|
|
|
|
local function createSign(pos,ang,model)
|
|
table.insert(commands,Format("entity_create prop_static %s",pos))
|
|
table.insert(commands,Format("entity_set_keyvalue prop_static %s \"angles\" \"%s\"",pos,ang))--table.insert(commands,Format("entity_rotate_incremental prop_static %s %s",pos,ang))
|
|
table.insert(commands,Format("entity_set_keyvalue prop_static %s \"model\" \"%s\"",pos,model))
|
|
end
|
|
|
|
for k,v in pairs(ents.FindByClass("gmod_track_signs")) do
|
|
local data = v.SignModels[v.SignType-1]
|
|
local left = v.Left
|
|
local offset = Vector(0,v.YOffset,v.ZOffset)
|
|
local model = data.model
|
|
if left and not data.noleft then
|
|
if model:find("_r.mdl") then
|
|
model = model:Replace("_r.mdl","_l.mdl")
|
|
else
|
|
model = model:Replace("_l.mdl","_r.mdl")
|
|
end
|
|
end
|
|
local RAND = math.random(-10,10)
|
|
local pos = data.pos + offset
|
|
local ang = data.angles
|
|
if not data.noauto then pos = pos+Vector(0,0,RAND/5); ang = ang+Angle(0,0,RAND) end
|
|
if left then pos = pos*Vector(1,-1,1) end
|
|
if left and data.rotate then ang = ang-Angle(0,180,0) end
|
|
|
|
createSign(v:LocalToWorld(pos),v:LocalToWorldAngles(ang),model)
|
|
end
|
|
|
|
table.insert(commands,"session_end")
|
|
|
|
for k,v in pairs(commands) do
|
|
local result = hammer.SendCommand(v)
|
|
if result ~= "ok" then
|
|
hammer.SendCommand("session_end")
|
|
error(Format("Error \"%s\" on command %s(%d)",result,v,k))
|
|
end
|
|
end
|
|
end)
|
|
SafeRemoveEntity(Metrostroi.RTCamera)
|
|
function Metrostroi.GetCam()
|
|
if not IsValid(Metrostroi.RTCamera) then
|
|
Metrostroi.RTCamera = ents.Create( "point_camera" )
|
|
Metrostroi.RTCamera:SetKeyValue( "GlobalOverride", 1 )
|
|
Metrostroi.RTCamera:SetKeyValue( "fogEnable", 1 )
|
|
Metrostroi.RTCamera:SetKeyValue( "fogStart", 1 )
|
|
Metrostroi.RTCamera:SetKeyValue( "fogEnd", 4096 )
|
|
Metrostroi.RTCamera:SetKeyValue( "fogColor", "0 0 0 127" )
|
|
Metrostroi.RTCamera:SetPos(Vector(0,0,-2^16))
|
|
Metrostroi.RTCamera:SetNoDraw(true)
|
|
Metrostroi.RTCamera:Activate()
|
|
Metrostroi.RTCamera:Spawn()
|
|
Metrostroi.RTCamera:Fire( "SetOff", "", 0.0 )
|
|
end
|
|
return Metrostroi.RTCamera
|
|
end
|
|
util.AddNetworkString("metrostroi_cam_update")
|
|
local function updateCam()
|
|
timer.Simple(0,function()
|
|
local cam = Metrostroi.GetCam()
|
|
net.Start("metrostroi_cam_update")
|
|
net.WriteUInt(cam:EntIndex(),16)
|
|
net.Broadcast()
|
|
end)
|
|
end
|
|
net.Receive("metrostroi_cam_update",updateCam)
|
|
updateCam()
|
|
|
|
|
|
function Metrostroi.FindNextStation(src,stationsPath,stations)
|
|
-- Determine direction of travel
|
|
--assert(src.path == dest.path)
|
|
--local direction = src.x < dest.x
|
|
print("start")
|
|
|
|
local markersForNode = {}
|
|
local sensorsForNode = {}
|
|
local entities = ents.FindByClass("gmod_track_pa_marker")
|
|
for k,v in pairs(entities) do
|
|
if v.TrackPosition then
|
|
local node = v.TrackPosition.node1
|
|
if not markersForNode[node] then markersForNode[node] = {} end
|
|
table.insert(markersForNode[node],v)
|
|
end
|
|
end
|
|
|
|
local entities = ents.FindByClass("gmod_track_autodrive_plate")
|
|
for k,v in pairs(entities) do
|
|
--print(v.PlateType)
|
|
if v.PlateType ~= METROSTROI_LSENSOR then continue end
|
|
local results = Metrostroi.GetPositionOnTrack(v:GetPos(),v:GetAngles())
|
|
--[[ for k,v in pairs(results) do
|
|
print(k,v.x,v.node2.x)
|
|
end--]]
|
|
local pos = results[1]
|
|
if pos then -- FIXME make it select proper path
|
|
sensorsForNode[pos.node1] = sensorsForNode[pos.node1] or {}
|
|
table.insert(sensorsForNode[pos.node1],v)
|
|
|
|
-- A signal belongs only to a single track
|
|
sensorsForNode[v] = pos
|
|
end
|
|
end
|
|
-- Accumulate travel time
|
|
local iter = 0
|
|
local function scan(node,stations,path,trace,dist,branches)
|
|
while node do
|
|
local nextnode = path and node.next or not path and node.prev
|
|
assert(iter < 1000000, "OH SHI~")
|
|
iter = iter + 1
|
|
if nextnode then
|
|
local heightDist = (nextnode.pos.z-node.pos.z)*0.01905
|
|
local slope = heightDist/node.length*1000
|
|
if math.abs(slope)>2 then
|
|
if not trace.slopeCurrent then
|
|
trace.slopeCurrent = node.id
|
|
trace.slopeLength = node.length
|
|
trace.slope = {}
|
|
else
|
|
trace.slopeLength = trace.slopeLength+node.length
|
|
end
|
|
--print(dist,slope)
|
|
table.insert(trace.slope,{dist,slope})
|
|
elseif trace.slopeCurrent then
|
|
local slm,slmx,slc = 0,0,0
|
|
--print(#trace.slope,trace.slopeLength)
|
|
local restbl = {}
|
|
for _,v in pairs(trace.slope) do
|
|
local sl = math.floor((v[2]+2.5)/5)*5
|
|
if slm~=sl then
|
|
local slmax = sl>0 and math.max(sl,slm) or math.min(sl,slm)
|
|
if (slc>2 or math.abs(slm-sl)>20) and (not restbl[#restbl] or restbl[#restbl][1] ~= slmax) then
|
|
table.insert(restbl,{slmax,slmx})
|
|
slmx = v[1]
|
|
end
|
|
if slmx==0 then slmx = v[1] end
|
|
slm = sl
|
|
slc = 0
|
|
else
|
|
slc = slc + 1
|
|
end
|
|
--print(k,v,)
|
|
--slc = slc+v[1]
|
|
end
|
|
if #restbl==0 and slm~=0 and math.abs(dist-slmx)>30 then table.insert(restbl,{slm,slmx,slc}) end
|
|
if #restbl~=0 then
|
|
table.insert(restbl,{0,dist})
|
|
for k,v in pairs(restbl) do
|
|
--print(k,v[1],v[2],v[3])
|
|
table.insert(trace.slopes,v)
|
|
end
|
|
end
|
|
--print("E---",node.pos)
|
|
trace.slopeCurrent = nil
|
|
end
|
|
end
|
|
|
|
local targetStation = tonumber(stations[1])
|
|
--local stationT = Metrostroi.Stations[tonumber(id)]
|
|
if markersForNode[node] then
|
|
for i,marker in ipairs(markersForNode[node]) do
|
|
if marker.PAType == 1 then
|
|
print("MAKR",marker.TrackX,marker.TrackPosition.x,marker.PAStationID,targetStation,marker.PAStationPath,stationsPath)
|
|
if marker.PAStationID ~= targetStation or tonumber(marker.PAStationPath) ~= stationsPath then return end
|
|
table.remove(stations,1)
|
|
--if marker.PAStationCorrection then print(targetStation) end
|
|
local distance = dist+(marker.TrackPosition.x-node.x)-(marker.PAStationCorrection or 0)
|
|
local lastSens,nearSens
|
|
for s=0,3 do
|
|
if not trace.sensors[#trace.sensors-s] or distance-trace.sensors[#trace.sensors-s] > 150 then break end
|
|
lastSens = #trace.sensors-s
|
|
end
|
|
table.insert(trace.stations,{
|
|
id=targetStation,
|
|
path=stationsPath,
|
|
pos=distance,
|
|
isHorlift = marker.PAStationHorlift,
|
|
hasSwitches = marker.PAStationHasSwtiches,
|
|
rightDoors = marker.PAStationRightDoors,
|
|
name = marker.PAStationName,
|
|
isLast = marker.PALastStation,
|
|
isInWrong = marker.PAWrongPath,
|
|
name_last = marker.PALastStationName,
|
|
dist_last_start = marker.PADeadlockStart and distance+marker.PADeadlockStart,
|
|
dist_last_end = marker.PADeadlockEnd and distance+marker.PADeadlockEnd,
|
|
linkedSensor = lastSens,
|
|
})
|
|
--print("MAKR GOOD",marker.TrackX,marker.TrackPosition.x,targetStation)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if Metrostroi.SignalEntitiesForNode[node] then
|
|
for k,signal in pairs(Metrostroi.SignalEntitiesForNode[node]) do
|
|
if signal.TrackDir ~= path then continue end
|
|
table.insert(trace.signals,{signal.Name,dist+(signal.TrackX-node.x)--[[ signal.TrackPosition.x--]] })
|
|
end
|
|
end
|
|
if sensorsForNode[node] then
|
|
local sensor = sensorsForNode[node][1]
|
|
local x = sensorsForNode[sensor].x
|
|
--print("SENS",node,dist,dist+(x-node.x))
|
|
table.insert(trace.sensors,dist+(x-node.x)--[[ sensor.TrackPosition.x--]] )
|
|
end
|
|
dist = dist+node.length
|
|
if node.branches then
|
|
for k,v in pairs(node.branches) do
|
|
if branches[v[2]] or v[2].path == src.path then continue end
|
|
branches[v[2]] = true
|
|
local result = scan(v[2],table.Copy(stations),true,table.Copy(trace),dist,branches) or scan(v[2],table.Copy(stations),false,table.Copy(trace),dist,branches)
|
|
if result and #result[3] == 0 then return result end
|
|
end
|
|
end
|
|
--[=[if node.branches and not branches[node.branches[1]] and node.branches[1][2].path ~= src.path then
|
|
branches[node.branches[1]] = true
|
|
local result = scan(node,table.Copy(stations),true,table.Copy(trace),dist,branches) or scan(node,table.Copy(stations),false,table.Copy(trace),dist,branches)
|
|
if result and #stations == 0 then return result end
|
|
end
|
|
if node.branches and not node.branches[2] and branches[node.branches[2]] and node.branches[2][2].path ~= src.path then
|
|
branches[node.branches[2]] = true
|
|
local result = scan(node,table.Copy(stations),true,table.Copy(trace),dist,branches) or scan(node,table.Copy(stations),false,table.Copy(trace),dist,branches)
|
|
if result and #stations == 0 then return result end
|
|
end]=]
|
|
node = nextnode
|
|
if not node then break end
|
|
end
|
|
if #stations == 0 then print(debug.traceback()) end
|
|
return #stations == 0 and {trace,dist,stations}
|
|
end
|
|
|
|
return scan(src,stations,true,{signals={},stations={},sensors={},slopes={}},0,{}) or scan(src,stations,false,{signals={},stations={},sensors={},slopes={}},0,{})
|
|
end
|
|
concommand.Add("metrostroi_pam_genconfig", function(ply, _, args)
|
|
if not IsValid(ply) or not ply:IsAdmin() then return end
|
|
|
|
if args[1] == "clear" then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Cleared!")
|
|
Metrostroi.PAMConfTest = {}
|
|
return
|
|
end
|
|
|
|
local line = tonumber(table.remove(args,1) or false)
|
|
local path = tonumber(table.remove(args,1) or false)
|
|
if (not line or not path) or #args == 0 then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Bad metrostroi_pam_genconfig use.\nmetrostroi_pam_genconfig line path station1 ... stationN-1 stationN")
|
|
return
|
|
end
|
|
|
|
local badFind = false
|
|
for k,id in pairs(args) do
|
|
id = tonumber(id)
|
|
if not id--[[ or not Metrostroi.Stations[id]--]] then
|
|
badFind = id
|
|
break
|
|
end
|
|
end
|
|
|
|
if badFind then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,Format("Check station id %s!",badFind))
|
|
return
|
|
end
|
|
|
|
-- Print interesting information
|
|
local results = Metrostroi.GetPositionOnTrack(ply:GetPos(),ply:GetAimVector():Angle())
|
|
for k,v in pairs(results) do
|
|
--ply:PrintMessage(HUD_PRINTCONSOLE,Format("\t[%d] Path #%d, ID #%d: (%.2f x %.2f x %.2f) m Facing %s",k,v.path.id,v.node1.id,v.x,v.y,v.z,v.forward and "forward" or "v.node1"))
|
|
local result = Metrostroi.FindNextStation(v.node1,path,args)
|
|
if not result then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,Format("Config not generated! Can't find all stations"))
|
|
return
|
|
end
|
|
PrintTable(result)
|
|
if not Metrostroi.PAMConfTest then
|
|
Metrostroi.PAMConfTest = {}
|
|
end
|
|
if not Metrostroi.PAMConfTest[line] then
|
|
Metrostroi.PAMConfTest[line] = {}
|
|
end
|
|
--if not Metrostroi.PAMConfTest[line][path] then
|
|
Metrostroi.PAMConfTest[line][path] = result
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Generated!")
|
|
--end
|
|
Metrostroi.PARebuildStations()
|
|
--print(distance)
|
|
end
|
|
end)
|
|
function Metrostroi.PARebuildStations()
|
|
Metrostroi.PAMStations = {}
|
|
Metrostroi.LineCount = 1
|
|
for lineID,line in pairs(Metrostroi.PAMConfTest) do
|
|
if type(lineID) ~= "number" then continue end
|
|
Metrostroi.LineCount = math.max(Metrostroi.LineCount)
|
|
for _,path in ipairs(line) do
|
|
for _,station in ipairs(path[1].stations) do
|
|
if not station.id then continue end
|
|
if not Metrostroi.PAMStations[lineID] then Metrostroi.PAMStations[lineID] = {} end
|
|
if not Metrostroi.PAMStations[lineID][station.id] then
|
|
Metrostroi.PAMStations[lineID][station.id] =table.insert(Metrostroi.PAMStations[lineID],station)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
concommand.Add("metrostroi_pam_add_station", function(ply, _, args)
|
|
if not IsValid(ply) or not ply:IsAdmin() then return end
|
|
|
|
local line = tonumber(table.remove(args,1) or false)
|
|
local path = tonumber(table.remove(args,1) or false)
|
|
local station = tonumber(table.remove(args,1) or false)
|
|
local dist = tonumber(table.remove(args,1) or false)
|
|
if (not line or not path or not station) then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Bad metrostroi_pam_add_station use.\nmetrostroi_pam_add_station line path station dist")
|
|
return
|
|
end
|
|
if not Metrostroi.PAMConfTest[line][path] then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Generate config first!")
|
|
return
|
|
end
|
|
local PA
|
|
if not dist then
|
|
local train = ply:GetTrain()
|
|
if IsValid(train) and train.PAM and train.PAM.Distance then
|
|
dist = train.PAM.Distance
|
|
PA = train.PAM
|
|
end
|
|
end
|
|
if not dist then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Bad metrostroi_pam_add_station use.\nCan't get dist, because it's not entered and not found train with PAM")
|
|
return
|
|
end
|
|
local tbl = Metrostroi.PAMConfTest[line][path][1].stations
|
|
local badFind = false
|
|
for k,id in ipairs(tbl) do
|
|
if id.id == station then
|
|
if math.abs(id.pos-dist)>20 then
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,Format("Too big delta, pos %.1f station %d station pos %.1f delta %.1f",dist,station,id.pos,id.pos-dist))
|
|
return
|
|
end
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,Format("Set pos %.1f to station %d, prev dist %.1f",dist,station,id.pos))
|
|
id.pos = dist
|
|
if PA then
|
|
PA.StationTable = PA:FindStation(PA.Line,PA.StationTable.path,PA.StationTable.id)
|
|
end
|
|
table.sort(tbl, function(a,b) return a.pos < b.pos end)
|
|
return
|
|
end
|
|
end
|
|
--[[ table.insert(Metrostroi.PAMConfTest[line][path][1].stations,{station,dist})
|
|
table.sort(tbl, function(a,b) print(a,b,a[2],b[2]) return a[2] < b[2] end)
|
|
Metrostroi.PARebuildStations()
|
|
ply:PrintMessage(HUD_PRINTCONSOLE,"Setted!")--]]
|
|
end)
|