1. This forum is obsolete and read-only. Feel free to contact us at support.keenswh.com

SE Brauche Hilfe beim Scripten

Discussion in 'German Community Discussion' started by HyperStorm, Apr 1, 2019.

Thread Status:
This last post in this thread was made more than 31 days old.
  1. HyperStorm

    HyperStorm Apprentice Engineer

    Messages:
    122
    Hey. Seit 2 Tagen sitz ich nun an nem Problem und bin kurz davor
    meinen Kopf durch den Bildschirm zu rammen...

    Ich möchte per Script (möglichst eben nur 1 Programm Block, ohne Timer)
    realisieren, dass ein Licht eingeschaltet wird, NACHDEM eine Tür geöffnet wurde.

    Nun bin was programmieren angeht in etwa so schlau wie 2m Feldweg.
    Deshalb bin ich froh, dass ich diesen Visual Script Builder (https://dco.pe/) gefunden hab.
    Der spuckt mir hier folgendes aus:

    Ganz unten bei //logic wird dann auch ansatzweiße das gemacht, was ich will.
    Nur eben gleichzeitig...

    Ich komm einfach nicht drauf klar, dass es keine Funktion gibt, womit das Script wartet,
    bis eine andre Funktion durchgelaufen ist, oder wenigstens ein rudimentäres "sleep" oder so.

    Im Endeffekt gehts mir ja nicht drum, ein Licht einzuschalten,
    ich hatte gehofft, damit auch weiter arbeiten, bzw. bauen zu können.
    Aber wenns halt schon scheitert, bevor es angefangen hat, frustet das extrem.
    Im Netz gibts zwar Tonnenweise Material zu dem Thema, durchblicken tu ich da aber
    wenns hoch kommt bei 5%... Und ich wollt mich jetz auch nich wegen nem Spiel
    hinsetzten und c++ lernen -.-

    Also, kann mir jemand verständlich verraten, ob das was ich vor hab, ohne einen Timer Block
    überhaupt machbar ist, und wie das dann funktioniern soll?

    lg
    Hyper
     
  2. TodesRitter

    TodesRitter Moderator

    Messages:
    2,127
    Also auf den ersten Blick fällt mir auf, dass eine Unterscheidung zwischen offener Tür und geschlossener Tür fehlt. Das ist sehr wahrscheinlich der Grund für den Fehler das es "Gleichzeitig" passiert.

    PSEUDOCODE

    Code:
      if(((IMyDoor)v0).Open == true) {
    v1.ApplyAction("OnOff_On");
    }
    if(((IMyDoor)v0).Open == false) {
    v1.ApplyAction("OFF");
    }
     
    
    Also wenn die Tür offen ist schalte Licht ein, wenn nicht Licht aus
     
  3. HyperStorm

    HyperStorm Apprentice Engineer

    Messages:
    122
    Guter Ansatz.
    Aber das Licht soll in dem Beispiel ja die ca. 1 sek. warten, die die Tür (Sliding Door) zum aufgehn braucht.
    Also sowas wie...
    - Öffne Tür
    - Warte bis Tür offen (oder alternativ: warte 1.000 ms)
    - Schalte Licht ein.

    Aber das mit warten bis, oder generell Loops sind hier wohl ein Problem, wie ich schon festgestellt hab.

    Die Idee kam mir eigl. als ich versucht hab mit etlichen Timer Blöcken eine klassische Luftschleuse
    zu basteln. Ja, ich weiß, dazu gibts bereits tonnenweise Scripts, aber man will ja auch mal selbst was auf die Reihe bekommen ^^

    Im Grunde sollte das ganze so aussehn:
    Code:
    If (InnereTür) = geschlossen & (ÄußereTür) = offen Then
    	 Schalte (ÄußereTür) ein
    	 Schließe (ÄußereTür)
    		  <warte 1000 ms>
    	 Schalte (ÄußereTür) ab
    	 Starte Belüftung
    		  <warte 2000 ms>
    	 Wechsle Lichtfarbe auf (Grün)
    	 Schalte (InnereTür) ein
    	 Öffne (InnereTür)
    		  <warte 1000 ms>
    	 Schalte (Innere Tür) ab
    ElseIf (InnereTür) = offen & (ÄußereTür) = geschlossen Then
    	 Schalte (InnereTür) ein
    	 Schließe (InnereTür)
    		  <warte 1000 ms>
    	 Schalte (InnereTür) ab
    	 Wechsle Lichtfarbe auf (Rot)
    	 Starte Entlüftung
    		  <warte 2000 ms>
    	 Schalte (ÄußereTür) ein
    	 Öffne (ÄußereTür)
    		  <warte 1000 ms>
    	 Schalte (ÄußereTür) ab
    End
    Bis auf das <warten> ist das ja auch alles kein Problem.
    Für sowas banales muss es doch ne Lösung
     
  4. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    Es kann kein "sleep" geben da die Programme komplett durchlaufen zwischen zwei Game-Ticks.

    Sprich:

    Physik-Tick N
    Alle IngameAPI Programme die in Tick N gestarted wurden laufen durch bis sie terminieren.
    Physik-Tick N+1
    Alle IngameAPI Programme die in Tick N+1 gestarted wurden laufen durch bis sie terminieren.
    Physik-Tick N+2
    Alle IngameAPI Programme die in Tick N+2 gestarted wurden laufen durch bis sie terminieren.
    ...


    Man muss stattdessen gewisse Konditionen aufstellen beim start jedes Aufrufs des Programms. Man kann jedoch globale Variablen verwenden um Informationen zwischen den Aufrufen zu speichern.

    Beispielsweise kann man das Programm betrachen:
    Code:
    int i = 0;
    public void Main(string arg)
    {
    	Echo($"i: {++i}");
    }
    Die Variable i wird einmal initialisiert und dann bei jedem Start des Programms, welches die Main-Funktion ausführt, um eins erhöht (++i). Der Wert von i wird also gespeichert und bleibt zwischen mehreren Aufrufen hindurch erhalten.

    Das Programm MUSS jedoch terminieren. Sprich das hier geht nicht, bzw es führt zu einer "Script execution terminated, script is too complex" Fehlermeldung:
    Code:
    int i = 0;
    public void Main(string arg)
    {
    	while(true)
    		Echo($"i: {++i}");
    }
    Der Grund ist die endlose Schleife while(true) ohne break.

    Somit ist auch keine solche Funkion möglich (Pseudocode):
    Code:
    while(true)
    {
      if(door.IsOpen)
    	//do something
    
      sleep(100);
    }

    So genug Grundlagengeschwafel.

    Ich habe dir mal eine dynamische variante geschrieben.
    Es werden alle Interior Lights gesucht und im Umkreis von genau 1 Block in jede Richtung wird gecheckt ob sich dort eine Tür befindet welche offen ist.

    Hier die Funktion unterschiedlichen Tür-InteriorLight Layouts


    Und hier der Code
    Code:
    // interiorlights for open doors //
    
    List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
    
    Program()
    {
    	Runtime.UpdateFrequency = UpdateFrequency.Update10;
    }
    
    public void Main(string arg)
    {
    	// Get all functional interior lights
    	GridTerminalSystem.GetBlocksOfType<IMyInteriorLight>(blocks, x => x.IsFunctional);
    
    	// Terminate if no interior lights have been found
    	if(!blocks.Any())
    	{
    		Echo("No interior lights found\nTerminating");
    	}
    	Echo($"{blocks.Count} interior light{(blocks.Count > 1 ? "s" : "")} found");
    
    	IMyDoor door;
    	IMyInteriorLight light;
    	bool lightTarget;
    	Vector3I offset;
    
    	foreach(IMyTerminalBlock block in blocks)
    	{
    		light = block as IMyInteriorLight;
    		lightTarget = false;
    		// Go through all adjacent blocks
    		for(int x = -1; x <= 1 && !lightTarget; x++) {
    			for(int y = -1; y <= 1 && !lightTarget; y++) {
    				for(int z = -1; z <= 1 && !lightTarget; z++) {
    					if(x == 0 && y == 0 && z == 0)
    						continue;
    					offset = new Vector3I(x,z,y);
    						door = light.CubeGrid.GetCubeBlock(light.Position + offset)?.FatBlock as IMyDoor;
    						if(door != null && door.Status == DoorStatus.Open)
    						lightTarget = true;
    				}
    			}
    		}
    		light.Enabled = lightTarget;
    	}
    }
    
    Das Programm läuft automatisch alle 10 ticks (ca 1/6tel Sekunden) und hat somit eine leichte Verzögerung. Das ist jedoch nicht schlimm und belastet die CPU nicht so sehr.

    Alternativ kannst du auch diese Zeile ändern
    Code:
    Program()
    {
    	Runtime.UpdateFrequency = UpdateFrequency.Update10;
    }
    und das Update10 durch Update100 ersetzen. Dann läuft es alle ca 1.67 Sekunden. Das sollte jedoch nur dann nötig sein, wenn du echt viele Interior Lights hast, sprich mehrere tausend.

    Ich hoffe, dass das hilft :)
     
    Last edited: Apr 2, 2019
  5. HyperStorm

    HyperStorm Apprentice Engineer

    Messages:
    122
    Wow, vielen Dank.
    Ok ich bin noch aufer Arbeit, deswegen geh ich erstmal nur auf dein erstes Beispiel mit der globalen Variable ein.

    Das würde also bedeuten, dass ich mit einem Switch (wenns das hier gibt, ansonsten if/elseif) nach jeden start des scripts den status zuerst abfrage, dann die aktion ausführe, den status neu definiere und das script beende. Liege ich da richtig?

    Und alle variablen, die unter "public void main (...)" gesetzt werden, werden gespeichert und können nach den neustart des scripts abgefragt werden?

    Sollte das so stimmen, bin ich mir sicher, dass ich mir den Rest zusammen basteln kann :)

    Ich schau mir zu hause noch den rest deines codes an, aaber bis her schon mal super ✓

    Edit: Für was steht eigl das $ Zeichen im Echo?
     
  6. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    Ja richtig.

    Du könntest zum Beispiel eine Funktion schreiben die eine Tür alle 5 Sekunden öffnet wenn sie zu ist oder schließt wenn sie offen ist. Da würdest du dann den letzten Zustand, eine Referenzzeit, die Tür als auch die Zeiten vorerst ermitteln und dann:

    Code:
    Wenn(Tür.Zustand == Offen && LetzterZustandTür == Zu) {
      Zeitstempel = Zeit.Jetzt;
      LetzterZustandTür = Tür.Zustand;
    }
    Wenn(Tür.Zustand == Zu && LetzerZustandTür == Offen {
      Zeitstempel = Zeit.Jetzt;
      LetzterZustandTür = Tür.Zustand;
    }
    Well(Tür.Zustand == LetzterZustandTür && Zeitstempel + Verzögerung >= Zeit.Jetzt) {
      Tür.WechselZustand;
    }
    (Ich hoffe, dass das richtig ist :p)

    Nein, nur globale variablen.

    Wenn du das folgende Program hast:
    Code:
    int globaleVariable = 0;
    
    public void Main(string arg) {
      int lokaleVariable = 0;
      Echo($"globaleVariable = {++globaleVariable}\nlokaleVariable = {++lokaleVariable}");
    }
    Hier wird beim ersten Initialisieren die globaleVariable auf 0 gesetzt und behält ihren Wert über die Aufrufe hinweg. Die lokaleVariable hingegen wird erst beim start der Main-Funktion erstellt und initiiert. Sobald der lokale Kontext jedoch beendet wird, sprich die Main-Funktion terminiert, wird die Variable frei gemacht.

    Fürst du das Programm also das erste mal aus, so siehst du:
    Beim zweiten Aufruf hast du:
    Und beim Dritten dann:
    etc.

    Die globaleVariable behält ihren Wert bei. Die lokaleVariable wird immer neu erstellt.

    Das nennt sich String Interpolation.
    Hier können variablen direkt im Text genutzt werden ohne den string "unnötig zusammenzuschustern".

    man könnte quasi versuchen das hier zu machen:
    Code:
    int i = 2;
    Echo("i = " + i + "; i² = " + (i*i));
    oder man nutzt die String Interpolation:
    Code:
    int i = 2;
    Echo($"i = {i}; i² = {i*i}");
    Der Code wird dadurch also lesbarer. Ein anderes Beispiel ist hier in der offiziellen Dokumentation ebenfalls gegeben und macht das Ganze noch weit deutlicher:
    https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated
     
  7. HyperStorm

    HyperStorm Apprentice Engineer

    Messages:
    122
    Super, ich glaub hier hab ich in ein paar Zeilen mehr über den Code gelernt, als in 2 tagen Google aufn Kopf stelln :woot:
    Nochmals vielen Dank, dass Du Dir die Zeit genommen hast. Hab inzwischen einen Prototypen, der ziemlich genau das tut, was er soll.
    Mit dem kann ich jetz weiter machen :)

    Ich muss allerdings sagen, dass mir die Syntax von C++ immer noch in den Augen wehtut :woot:
    Hatte vor ein paar Jahren mal was mit AutoIt zu tun, ner Scriptsprache, die total einfach zu verstehn ist. Damit hätt selbst mein Hamster damals n Script schreiben könn ^^
     
  8. plaYer2k

    plaYer2k Master Engineer

    Messages:
    3,160
    Das hier ist C# und nicht C++.
    AutoIt ist fein aber leider nicht Umfangreich genug, meiner Meinung nach.

    Würdest du C++ schreiben müssen, dann wäre es um einiges schwerer. C# ist da noch sehr angenehm :)
     
  9. HyperStorm

    HyperStorm Apprentice Engineer

    Messages:
    122
    Oh, ok, da wärn wir dann wieder bei den 2m Feldweg und so :woot:
     
Thread Status:
This last post in this thread was made more than 31 days old.