diff --git a/Install_00/kskit.lua b/Install_00/kskit.lua index cf8bb75..38161b4 100644 --- a/Install_00/kskit.lua +++ b/Install_00/kskit.lua @@ -1,9 +1,128 @@ require("Prototype") require("On") --- Versionsnummer. Diese dient vor allem zu Diagnosezwecken. --- Ich weiss jetzt noch nicht, wie viele verschiedene Versionen von dem Script spaeter herumfliegen werden. -KsKitVer=0 +-- Tabelle in beliebigen Slot speichern +-- Im Slot steht dann eine Lua-Tabelle als String +-- Formatierungszeichen werden soweit URL-encoded, +-- dass EEP mit Formatierungszeichen keine Probleme mehr hat +-- Speziell hochkommas haben mir immer meine Daten abgeschnitten... +function speicherTabelle(Slotnummer, Tabelle) + local s=Prototype.__tostring(Tabelle):gsub("([%c%%\"])", function(c) + return string.format("%%%02X", string.byte(c)) + end) + EEPSaveData(Slotnummer, s) +end + +-- Tabelle aus beliebigen Slot laden +-- Sind die Daten nicht lesbar, wird eine leere Tabelle zurueckgegeben +function ladeTabelle(Slotnummer) + local ok, raw = EEPLoadData(Slotnummer) + if not ok then + print("WARNUNG: Daten im Slot ",Slotnummer," unleserlich") + return {} + end + return load("return "..raw:gsub("%%(%x%x)", function(x) + return string.char(tonumber(x, 16)) + end))() +end + +-- KsKit merkt sich, welche Zuege vor einem Signal stehen +-- Die Tabelle wird einmal beim Lua-Start gelesen +-- und nach Aenderungen wieder zurueck geschrieben +-- Slot 960 ist damit durch KsKit blockiert! +Zugmeldung_Slotnummer = 960 +Zugmeldung = ladeTabelle(Zugmeldung_Slotnummer) + +-- Hier werden Zuglenker je nach Fahrstrassen-Startsignal sortiert gesammelt +Zuglenkfunktionen = {} + +-- Neuen Zuglenker installieren: +-- Zuglenker schalten unter bestimmten Bedingungen automatisch Fahrstrassen +function Zuglenkung(Tab) + local FS = Tab[1] + Tab[1]=nil + if Zuglenkfunktionen[FS] == nil then Zuglenkfunktionen[FS]={} end + table.insert(Zuglenkfunktionen[FS], function(Zugname, Ankunft) + local ziel = 2 + if Tab.Ziele then + ziel = Tab.Ziele(math.random(#Tab.Ziele)) + end + return ziel + end) +end + +-- EEPMain-Komponente der Zuglenkung +Main(function() + local FS = {} + + -- Teste, ob an einem Signal Fahrstrassen und Zuglenkung existieren + -- Subfunktion, wird beim Sammeln der Fahrstrassen-Signale mehrfach aufgerufen + local VersucheAnSignal = function(Signal, Zugname, Ankunft) + if Signal == nil then return end + if Signale[Signal] == nil then return end + if Signale[Signal].FS == nil then return end + local FSignal = Signale[Signal].FS + if EEPGetSignal(FSignal) == 1 and Zuglenkfunktionen[FSignal] then + table.insert(FS, {FSignal, Zugname, Ankunft}) + end + end + + -- Sammle alle Fahrstrassen-Signale, die geschaltet werden koennen + -- Einmal die, wo direkt ein Zug in Anfahrt ist + -- Und einmal die, wo der Zug gerade das "Halt erwarten" am vorherigen Signal sieht + -- Zugname und Ankunftszeit merken + for k,v in pairs(Signale) do + if Zugmeldung[k] and v.FS then + local Begriff = v:Lese() + if Begriff[1] == HALT then + VersucheAnSignal(k, Zugmeldung[k][1], Zugmeldung[k][3]) + elseif Begriff[1] == FAHRT and Begriff.H_erwarten and v.FF then + local Ziel = EEPGetSignal(v.FS) - 1 + if v.FF[Ziel] then + VersucheAnSignal(v.FF[Ziel], Zugmeldung[k][1], Zugmeldung[k][3]) + end + end + end + end + + -- Zuglenker abarbeiten, moegliche Fahrstrassenstellungen sammeln + local FSStellungen = {} + for i=1,#FS do + local FSignal = FS[i][1] + local Zugname = FS[i][2] + local Ankunft = FS[i][3] + for j=1, #Zuglenkfunktionen[FSignal] do + local ziel = Zuglenkfunktionen[FSignal][j](Zugname,Ankunft) + if type(ziel) == "number" and ziel > 1 then + table.insert(FSStellungen,{FSignal,ziel}) + end + end + end + + -- Eine einzelne Fahrstrassenstellung mittels EEPSetSignal abarbeiten + -- Diese wird zufaellig ausgewaehlt, damit haben wir sowohl bei Verzweigungen und Zusammenfuehrungen + -- automatisch Abwechslung, soweit es die Zuglenker erlauben + -- Das wir nur eine Fahrstrasse schalten ist nicht schlimm, wir werden ja 5 mal pro Sekunde ausgefuehrt + -- Beim naechsten mal kann man dann aber via EEPGetSignal() sehen, was wir an Fahrstrassen nicht mehr zu schalten brauchen + if #FSStellungen >= 1 then + local FS, Ziel = table.unpack(FSStellungen[math.random(#FSStellungen)]) + -- Wichtig: Callbacks aktivieren, damit Mehrabschnittssignale ueber ihren Nachfolger informiert werden + EEPSetSignal(FS, Ziel, 1) + end +end) + +-- Basis-Prototyp fuer alle Signale +Signal=Prototype{} + +-- Konstanten fuer Signalbegriffe +-- KsKit benutzt ein eigenes Format, um Signalbegriffe abzubilden +-- Dabei handelt es sich um eine Tabelle mit folgenden Schluesseln: +-- - 1: Grundsaetzlicher Typ von Begriffen, Fahrt ist 1, Halt ist 2 +-- - V_max: nil oder number, Maximalgeschwindigkeit falls angezeigt +-- - H_erwarten: nil oder true, falls Begriff "Halt Erwarten" signalisiert +-- - V_erwarten: nil oder number, falls Begriff eine Geschwindigkeit vorsignalisiert +-- V_max und V_erwarten entsprechen absichtlich der Aufteilung der Zusatzanzeiger am Ks-Signal. +-- Gegen Ende der Datei sind extensiv Signalbegriffe als eigene Konstanten definiert -- Grundsätzliche Typen von Signalbildern FAHRT=1 @@ -12,11 +131,63 @@ RANGIERFAHRT=3 ERSATZFAHRT=4 AUS=5 --- Grundsignal, entspricht allen Signalen vor EEP 6 sowie dem Unsichtbaren Signal aus dem Grundbestand -Signal=Prototype{} -Signal.Begriffe={{FAHRT},{HALT}} -Signal.Beschreibung="Signal" +-- Achtung: Keine Methode, Zugriff via einfachen Punkt, nicht Doppelpunkt +-- Wandelt einen Begriff in eine Textuelle Beschreibung um +-- Es lohnt sich, diesen Text im Signal-Tooltip anzuzeigen +-- Hl-Signale sind zwar huebsch, aber nicht immer einfach zu lesen +function Signal.BegriffZuText(Begriff) + if Begriff[1]==HALT then return "Halt" end + if Begriff[1]==RANGIERFAHRT then return "Rangierfahrt" end + if Begriff[1]==ERSATZFAHRT then return "Fahrt auf Ersatzsignal" end + if Begriff[1]==AUS then return "Signal ausgeschaltet" end + local txt = "Fahrt" + if Begriff.V_max then + txt = txt.." mit "..tostring(Begriff.V_max).." km/h" + end + if Begriff.H_erwarten ~= nil then + txt = txt..", Halt erwarten" + if Begriff.kurz ~= nil then + txt = txt.." im verkürzten Abstand" + end + end + if Begriff.V_erwarten then + txt = txt..", "..tostring(Begriff.V_erwarten).." km/h erwarten" + end + return txt +end +-- Alle Signale vor EEP5 konnten nur diese beiden Begriffe +Signal.Begriffe={{FAHRT},{HALT}} + +-- Text fuer einen potentiellen Tooltip generieren +function Signal:TooltipText() + local Begriff = self:Lese() + local col=1 + local bgcol = "255,0,0" + if Begriff[1]~=HALT then col=2 end + if Begriff[1]==FAHRT and Begriff.V_max == nil and not Begriff.H_erwarten then col=3 end + + local fgcol={"255,255,255","0,0,0","0,0,0"} + local bgcol={"255,0,0","255,255,0","0,255,0"} + + r=""..self.BegriffZuText(Begriff).."" + if Zugmeldung[self.ID] then + r=r.."\n"..Zugmeldung[self.ID][1] + end + return r +end + +-- Wird derzeit nicht genutzt, das war so gedacht, das im Tooltip dann "Rangiersignal" oder "Ausfahrsignal" steht +-- Hinterher war mir der Platz im Tooltip aber dann zu schade +-- Wenn hier vom User sowas wie "Einfahrsignal aus Richtung Nossen" eingetragen wird, ware das sicher nuetzlich +Signal.Beschreibung="Signal" +function Signal:set_Beschreibung(v) + self.Beschreibung=v +end + +-- Neue Signale werden mit Signal{...} vom Prototyp abgeleitet +-- Die Optionen werden durch set_$schluessel Setter geleitet. +-- Gibt es einen Setter nicht, gibt es einen harten Fehler - hier vertippt sich keiner mehr, auch ich nicht. function Signal:init(tab) for k,v in pairs(tab) do local setter = "set_"..tostring(k) @@ -28,63 +199,119 @@ function Signal:init(tab) end end --- ID to use with EEPGetSignal/EEPSetSignal -function Signal:set_ID(id) +-- Setter fuer Signal-ID. Die selbe ID, wie auch fur EEPGetSignal und EEPSetSignal genutzt wird. +-- Die steht ganz am Anfang in der Tabelle vom Init-Aufruf und hat keinen String als Schluessel. +-- Ab hier ist auch die exakte ID unseres Signales bekannt, deswegen werden Globale Funktionen mit der ID im Namen ebenfalls hier definiert. +-- Koennte man aber auch spaeter machen. +function Signal:set_1(id) + -- Globale Tabelle wo alle Signale drinnestehen + -- So merkt man besser wenn man eine ID zweimal vergibt + -- Die Signale-Tabelle wird auch benoetigt, damit Mehrabschnittssignale ueber die ID ihres Folgesignales den Begriff dessen lesen koennen. if type(Signale) ~= "table" then Signale={} end if Signale[id]~=nil then error("Signal ID "..id.." bereits verwendet") end if type(id) ~= "number" then error("Signal ID muss eine Zahl sein") end self.ID = id Signale[id]=self -end -Signal.set_1=Signal.set_ID - --- Anfahrtsgeschwindigkeit ans Haltzeigende Signal -function Signal:set_V_halt(v) - if type(v) ~= "number" then error("V_halt muss eine Geschwindigkeit sein") end - self.V_halt = v -end - --- Unsichtbares Signal: Zum Schalten via GBS --- ggf. auch vor dem Signal zum Ersatz der Haltefunktion -function Signal:set_S(S) - self.S = S - OnSignal(S, function() - self:Update() + -- Anfahrtkontakt: Wir merken uns, das ein Zug an dieses Signal heranfaehrt. + -- Die Zuglenkung wertet diese Informationen aus + _G["Anfahrt_"..tostring(self.ID)]=function(Zugname) + local ok, Speed = EEPGetTrainSpeed(Zugname) + -- Derzeitige Fahrtrichtung ermitteln + local Richtung = 1 + if Speed < 0 then Richtung = -1 end + -- Ankündigung an den User + --print(Zugname," an: ",self.Beschreibung," ",self.ID, ", V=",math.ceil(Speed)) + -- Zuganmeldung speichern + Zugmeldung[self.ID]={Zugname, Richtung, EEPTime} + speicherTabelle(Zugmeldung_Slotnummer, Zugmeldung) + end + -- Wird ein Signal auf Halt gestellt, gehen wir davon aus, das der Zug, der bisher hier Stand, jetzt weg ist. + -- Damit kann durch einen Haltkontakt die Zugmeldung aufgeloest werden. + -- Liegengebliebende Zugmeldungen koennen damit auch durch Toggeln des Signales geloescht werden + OnSignal(self.ID, function(Stellung) + if self.Begriffe[Stellung][1] == HALT then + Zugmeldung[self.ID]=nil + speicherTabelle(Zugmeldung_Slotnummer, Zugmeldung) + end end) end --- Unsichtbares Signal: Zugbeeinflussung, hinter dem Signal -function Signal:set_V_regler(id) - self.V_regler=id -end - +-- Der Konstruktor des jeweiligen Signaltypes fuettert die Stellungen des Signals hier rein +-- Das ist eine Liste von Begriffen im oben dokumentierten Tabellenformat function Signal:set_Begriffe(Begriffe) + -- TODO: validieren das hier kein Mist reinkommt self.Begriffe=Begriffe end -function Signal:set_Wege(Wege) - if type(Fahrwege)~="table" then Fahrwege={} end - self.Wege=Wege - -- Eigenen Callback an alle potentiellen Wegelemente koppeln - local updateFunk = function() - self:Update() - end - for i=1,#Wege do - OnSignal(Wege[i][1], updateFunk) - if Wege[i].turns then - for j=1,#Wege[i].turns,2 do - OnSwitch(Wege[i].turns[j], updateFunk) - end +-- Fahrstrassenstartsignal mit Signal verbinden +function Signal:set_FS(FS) + self.FS=FS +end + +-- Folgesignale setzen +-- Der Wert ist eine Liste von Signal-IDs: +-- - Element 1 entspricht Folgesignal der ersten Fahrstrasse (Stellung 2) +-- - Element 2 entspricht dem Folgesignal der zweiten Fahrstrasse (Stellung 3) +-- - etc +-- Wird zum Vorsignalisieren genutzt +function Signal:set_FF(FF) + self.FF=FF + -- Callback fuer alle potentiellen Folgesignale eintragen + -- Wir mussen das ja hinterher ggf. vorsignalieren + for i=1,#FF do + if type(FF[i])=="number" and FF[i] > 0 then + OnSignal(FF[i], function() + self:UpdateVorsignalfunktion() + end) end end end --- Stellung als struct +-- Diese Funktion ist fuer Mehrabschnittssignale relevant +-- Ueber die FS und FF attribute koennen wir ableiten, welches Signal auf dieses folgt +-- Das lesen wir dann (geht nur, wenn es auch in KsKit eingetragen ist) +-- und basteln uns dann unsere eigenen Begriff zurecht +function Signal:UpdateVorsignalfunktion() + if self.FS == nil then return end -- Wir haben kein Fahrstrassensignal + if self.FF == nil then return end -- Wir haben keine Folgesignale + local FS_Ziel = EEPGetSignal(self.FS) - 1 + if FS_Ziel < 1 then return end -- Fahrstrasse nicht geschaltet + local Folgesignal = self.FF[FS_Ziel] + local Begriff=self:Lese() + if Begriff[1] ~= FAHRT then return end + local Folgebegriff=Signale[Folgesignal]:Lese() + local NeuerBegriff = {FAHRT} + NeuerBegriff.V_max = Begriff.V_max + NeuerBegriff.V_erwarten = Folgebegriff.V_max + if Folgebegriff[1] == FAHRT then + NeuerBegriff.H_erwarten = nil + else + NeuerBegriff.H_erwarten = true + end + self:Zeige(NeuerBegriff) +end + +-- Signalbegriff lesen +-- Sehr trivial +function Signal:Lese() + return self.Begriffe[EEPGetSignal(self.ID)] +end + +-- Signalbegriff setzen +-- Faktisch sortiert diese Funktion alle Begriffe je nachdem, wie gut sie der gesuchten Stellung entsprechen +-- Der erste (besten-passende) Begriff wird dann gesetzt +-- Die Funktion ist zwar saumaessig kompliziert, kann aber bei Geschwindigkeitsabstufungen auf gerigere Geschwindigkeiten zurueckfallen. +-- Speziell bei Hl-Signalen, die sich einen VS-Begriff fuer V40 und V60 teilen, ist das nuetzlich. +-- Bei Signalen mit einer sehr schlechten Auswahl an Begriffen kommt hier vielleicht Murks raus +-- Das sollte man vielleicht genauer testen function Signal:Zeige(Stellung) + -- Achso, wir sortieren nicht die richtige Begriffstabelle, sondern eine Ersatztabelle mit den Indexen. + -- Ist performanter. Und wir zerschiessen uns nicht die Begriffstabelle, weil wir die ja per Referenz haben. local order={} for i=1,#self.Begriffe do table.insert(order, i) end + -- Sortieraufruf mit Sortierlabda table.sort(order, function(a,b) local StlgA = self.Begriffe[a] local StlgB = self.Begriffe[b] @@ -93,6 +320,7 @@ function Signal:Zeige(Stellung) -- Folgendes Muster: Wenn Stellung A und B sich in einem Merkmal unterscheiden -- UND Stellung A in diesem Merkmal mit der Zielstellung uebereinstimmt, -- DANN ist Stellung A besset geeignet, sonst nicht + -- Wir muessen das auch andersherum mit Stellung B machen if StlgA[props[i]] ~= StlgB[props[i]] then if StlgA[props[i]] == Stellung[props[i]] then return true end if StlgB[props[i]] == Stellung[props[i]] then return false end @@ -101,7 +329,8 @@ function Signal:Zeige(Stellung) -- - zeigt eine hohere Geschwindigkeit als gewollt -- - zeigt eine noch kleinere Geschwindigkeit -- Dann ist der Vergleichsbegriff schlechter geeignet - if i == 2 or i == 4 then + -- Und das dann jeweils in beide Richtungen + if i == 2 or i == 4 then -- 2 ist V_max, 4 ist V_erwarten if StlgA[props[i]] and Stellung[props[i]] and StlgA[props[i]] < Stellung[props[i]] then if StlgB[props[i]] == nil then return true end if StlgB[props[i]] > Stellung[props[i]] then return true end @@ -117,52 +346,13 @@ function Signal:Zeige(Stellung) end return false end) + -- Begriff aktiv schalten... der Wert in unserer Indextabelle ist der Index in die Begriffstabelle + -- und damit auch genau der Wert, den EEP fuer die numerische Stellung braucht EEPSetSignal(self.ID, order[1], 1) return true end --- Fahrtstellung aktualisieren -function Signal:Update() - local Fahrt = true - if self.S and EEPGetSignal(self.S) == 2 then - Fahrt = false - end - if not Fahrt then - if self:Zeige{HALT} then return end - self:Zeige{FAHRT, H_erwarten} - return - end - - local Stlg = {FAHRT} - if self.Wege then - for i=1,#self.Wege do - local Weg = self.Wege[i] - local match = true - local V_max = nil - if Weg.turns then - for j=1,#Weg.turns,2 do - if Weg.turns[j+1] > 1 then V_max = 40 end - if EEPGetSwitch(Weg.turns[j]) ~= Weg.turns[j+1] then - match = false - end - end - end - if match then Stlg.V_max = V_max end - end - end - - if self:Zeige(Stlg) then return end - if self:Zeige{ERSATZFAHRT} then return end -end - -function Signal:set_Beschreibung(v) - self.Beschreibung=v -end - -function Signal:TooltipText() - r=self.Beschreibung.." "..tonumber(self.ID).."\n" - return r -end +-- So, der Signal-Prototyp ist definiert! Jetzt definieren wir uns die ganzen Signalsysteme und jeweils ein paar Modelle dazu -- Signalbilder OSJD/EZMG/Hl-Signale des Ostblocks -- V_erwarten=60 wird durch V_erwarten=40 signalisiert @@ -202,8 +392,14 @@ Hl_Signal_Vorsignal_V40=Signal{Begriffe={Hl10,Hl7,Hl1},Beschreibung="Vorsignal"} Hl_Signal_Vorsignalwiederholer_V40=Signal{Begriffe={Hl10,Hl7,Hl1},Beschreibung="Vorsignalwdh"} -- V11NHK10025 HL-Signale der DR *V60* - Erweiterungsset -Hl_Signal_Ausfahrt_V60=Signal{Begriffe={Hl13,Hl1,Hl3b,Hl3a,Zs1,Sh1,Hl13}} -Hl_Signal_Einfahrt_V60=Signal{Begriffe={Hl13,Hl1,Hl4,Hl7,Hl10,Hl3b,Hl6b,Hl9b,Hl12b,Hl3a,Hl6a,Hl9a,Hl12a,Zs1,Hl13}} -Hl_Signal_Vorsignalwiederholer_V60=Signal{Begriffe={Hl10,Hl7,Hl1}} +Hl_Signal_Ausfahrt_V60=Signal{Begriffe={Hl13,Hl1,Hl3b,Hl3a,Zs1,Sh1,Hl13}, Beschreibung="Ausfahrsignal"} +Hl_Signal_Einfahrt_V60=Signal{Begriffe={Hl13,Hl1,Hl4,Hl7,Hl10,Hl3b,Hl6b,Hl9b,Hl12b,Hl3a,Hl6a,Hl9a,Hl12a,Zs1,Hl13}, Beschreibung="Einfahrsignal"} +-- V40 und V60 werden bei Hl gleich vorsignalisiert, keine Ahnung warum das ueberhaupt verschiedene Modelle hat +Hl_Signal_Vorsignal_V60=Hl_Signal_Vorsignal_V40 +Hl_Signal_Vorsignalwiederholer_V60=Hl_Signal_Vorsignalwiederholer_V40 +-- Bahnhofsset Hl_Zwerg_Rangiersignal=Signal{Begriffe={Ra11,Sh1},Beschreibung="Rangierhaltsignal"} + +-- Irgendein anderes set +Hl_Signal_AZ=Signal{Begriffe={Hl13,Hl1,Hl4,Hl7,Hl10,Hl3a,Hl9a,Hl6a,Hl12a,Sh1,Zs1,Hl13,{}}, Beschreibung="Ausfahrsignal"}