[Gelöst] Regeln durch Auslöser dazuschalten

Hey,

gibt es einen Befehl der z.b so lautet "Wenn gebäude x gebaut/erforscht dann schalte Regel y dazu" ?
Für mein Szenario würde ich nämlich gerne die Regeln Predator und Scorpion Angriff dazuschalten (aus dem Hazard Ships Paket), da man aber noch völlig ohne Zukunftsdinge startet, sollen die erst anfangen sobald ein bestimmtes zukunftsgebäude erforscht/gebaut ist.

Weiterführend wärs cool, wenn man die Regel dann mit bestimmten Bedingungen wieder entfernen könnte. Also z.b wenn x Predator und Scorpions zerstört wurden.

Ich denke mal, man kann einen einfachen "if" Befehl in einer while schleife dafür nehmen, oder? Oder man setzt einfach einen Verweis auf eine Funktion in die "Completion" Funktion eines bestimmten Gebäudes rein, sodass der Bau des Gebäudes das Ereignis auslöst. Wobei die Schleife dann dennoch für das Abschalten der Regel sinnvoll wäre… Oder ich such mir da ebenfalls ein Gebäude aus, welches dafür sorgt, dass die Regel abgeschaltet wird… ja ich denke letztere Variante ist am einfachsten umzusetzen.

Was mir aber fehlt, ist der Befehl, wie man eine Regel dazu schaltet :slight_smile:
Aber ich denke mal sowas ist machbar, oder?

Was evtl schwieriger wird: kann man irgendwie Tiere durch Bedingungen spawnen?

Edit:
ich sehe gerade aus irgendeinem Pack habe ich bereits eine Regel, die mch Spielregeln/ziele/umweltobjekte im Spiel aussuchen lässt :slight_smile: Ich schau mir mal an, wie dies funktioniert :slight_smile:

> gibt es einen Befehl der z.b so lautet "Wenn gebäude x gebaut/erforscht dann schalte Regel y dazu" ?


Nein, vorgefertigt gibt es das nicht.

> Ich denke mal, man kann einen einfachen "if" Befehl in einer while schleife dafür nehmen, oder?


Nein, wohl nicht. Die kannst mit einer while-Schleife nicht fortlaufend das Spielgeschehen beobachten und Veränderungen registrieren. Dies ist in anderen Sprachen manchmal möglich, wenn ein Befehl existiert, der die Codeausführung warten lässt (häufig "sleep"). Dann würde eine Schleife gestoppt und die restliche Ausführung geht weiter. Das geht in C4Script nicht. Hier stoppt quasi das gesamte Spiel, bis deine Schleife fertig ist. Wenn deine Schleife erst endet, wenn das Gebäude erforscht ist, dann würdest du damit das Spiel komplett lahmlegen (Endlosschleife).

Am einfachsten kannst du das Spielgeschehen überwachen, wenn du dafür ein Hilfsobjekt im Szenario hast, welches mittels einer Timers regelmäßig Abfragen startet. Wenn du nicht gerade ein bestimmtes Szenario bearbeitest und darin über das Szenarioscript Objekte erstellen kannst, bietet sich eine Regel für so etwas an. Alle ausgewählten Regeln werden von der Engine als Objekte an der Position 0,0 erzeugt (gilt übrigens auch für Umweltobjekte). Damit hättest du dein Objekt, welches dann noch etwas tun muss.

Einen Timer kannst du über DefCore setzen:
TimerCall  Zeichenfolge (max. 30)  Regelmäßig aufgerufene Funktion des Objektscripts. Siehe Objektscripte.
Timer  Integer  Zeitabstand der TimerCalls in Frames. Ohne Angabe gilt der Vorgabewert 35.


Das ist einfacher als über Effekte, wenn es nur darum geht, dass eine Funktion im Objektscript immer wieder aufgerufen wird. Du würdest also z.B. "Timer=1" und in die nächste Zeile "TimerCall=MeineFunktion" schreiben. Dann wird in jedem Frame die Funktion MeineFunktion() im Script des Objekts aufgerufen.

In MeineFunktion() (oder der entsprechend von dir gewählte Name) prüfst du dann, ob etwas bestimmtes erforscht wurde. Forschen bedeutet, dass am Ende der forschende Spieler den Bauplan für das Objekt erhält. Welche Baupläne ein Spieler hat, kannst du mit GetPlrKnowledge abfragen. Das kannst du nur pro Spieler machen. Falls du keine Gemeinschaftsbaupläne angeschaltet hast, müsstest du in einer Schleife die Baupläne aller Spieler abfragen und schauen, ob einer das Objekt schon erforscht hat. Ansonsten reicht es, wenn du mit GetPlayerByIndex den ersten Spieler ("GetPlayerByIndex(0)") abfragst.
Sobald du dann den Bauplan findest (also if (GetPlrKnowledge(...)) ) kann deine Timer-Funktion die weitere Regel erstellen (CreateObject(...)). Du solltest aber aufpassen, dass die weitere Regel nicht mehrfach erstellt wird, weil natürlich der Timer weiterläuft und der Bauplan weiterhin exitiert. Also eine zweite Vorbedingung: if ( ! FindObject(...)).

Das Entfernen der Regel ist schwieriger. Wenn du nicht das Script der Gegner modifizieren kannst / willst (könnte auch per #appendto laufen), dann müsste deine Regel ständig die Anzahl der Gegner feststellen (ObjectCount), speichern und im nächsten Timer-Aufruf vergleichen, ob die Zahl kleiner geworden ist. Das ist nicht ganz sauber, weil im gleichen Moment ein Gegner sterben und einer neu erscheinen könnte, außerdem du nicht genau weißt, ob die Gegner getötet wurden oder sonst wie verschwunden sind. Bei der Anzahl x müsstest du dann die zuvor erstellte Regel entfernen.

Wenn du das Script der Gegner änderst, dann müssen diese in ihrer Death()-Funktion deiner Regel Bescheid geben, dass sie getötet wurden. Am einfachsten läuft dies durch "GameCallEx". Das funktioniert wie GameCall mit dem Unterschied, dass auch Regelobjekte den Aufruf erhalten. Dokumentiert ist das nicht, weil es wenn ich mich recht entsinne in System.c4g drin ist. Dann würden deine Gegner also den Aufruf machen und deine Regel in diesem die Zahl getöteter Gegner speichern und bei x wiederum die Regel entfernen.

vielen dank für diese ausführliche Antwort =)

Ich bin ein freund von möglichst einfachen Lösungen :D  Deswegen werde ich für das aktivieren der Regel wohl wirlich einfach nur die Completion Funktion von bestimmten Gebäuden/Gegenständen/Fahrzeugen benutzen, die für das Vorankommen wichtig sind und somit gebaut werden müssen, wodurch dann der Angriff startet. Habe es auch schon getestet und es funktioniert mit CreateObject(idRule) (also die ID der Regel)  und RemoveObject(FindObject(idRule)) einwandfrei :slight_smile:

Anzahl der Gegner zu nutzen klappt, wie du schon schreibst, nicht unbedingt, da ja immer wieder neue Gegner gespawnt werden, solange die Regel aktiv ist. (da fällt mir ein auf diese weise könnte man doch eig auch Tiere spawnen… ich schau mir diese angriffsregel auch nochmal an, ob ich da einfach die Predatoren durch Feuermonster oderso ersetzen kann ^^).
Jedenfalls ist dein Vorschlag mit der Todesfunktion schon sehr gut und wäre auch vom Spielgeschehen her schöner, als wenn es beim Bau eines Gebäudes aufhört. Zwar zerstören sich diese Gegner häufig selbst, weil sie auf dem Boden aufschlagen und explodieren :smiley: , aber das könnte man ja bei der benötigten Anzahl berücksichtigen. Dadurch würde der angriff dann z.b nach 20 minuten von alleine beendet (weil x Stück dann auf dem boden zerschellt sind), oder evlt auch früher, weil man einige abgeschossen hat.
Also versuchen wir das mal weiter auszuführen:
Mit dem Befehl "GameCallEx", welchen ich in die Death-Funktion der Gegner per appendto schreibe, würde dann also der Regel Bescheid gegeben werden können. Aber welcher Regel genau? Der Regel für den Angriff, oder einer eigenen Regel, die für das zählen zuständig ist und dann bei x die Angriffsregel entfernt?  Oder was genau würde das zählen und auslösen des Angriffs dann übernehmen?

Und würde dieser Zähler dann iwann auf Null gesetzt oder wäre das zu kompliziert?
Also als Beispiel: ich baue eine Hütte und ein Angriff beginnt. Nachdem 10 Stück tot sind, hört der Angriff auf. Nun baue ich noch eine Hütte (oder ein anderes gebäude welches einen Angriff auslöst). Wird dieser Angriff dann automatisch sofort wieder beendet, weil ja zuvor bereits 10 stück gestorben sind?..
Hmmm… also ich denke es wäre tatsächlich gut, wenn dieser Zähler nicht auf Null gesetzt wird. Denn ich will ja nicht unbedingt, dass bei jedem Hüttenbau dann wieder Gegner auftauchen. Demnach müsste ich das Gebäude und die Anzahl irgendwie koppeln. Soll heißen beim Hüttenbau kommen immer Gegner, aber die Regel wird bei insg. 10 getöteten Feinden sofort wieder entfernt, wodurch dann keine Gegner mehr kommen, auch wenn ich weiterhin Hütten baue.  Dasselbe Prinzip dann zum Beispiel für das Schloss. Nur das hier dann insg. 30 Feinde (also 20 weitere feinde) tot sein müssen, bevor die Regel deaktiviert wird.  Kann man das auf diese Weise irgendwie koppeln?

Falls das nicht zu koppeln geht, wäre es auch nicht tragisch, wenn dann bei jedem Hüttenbau immer wieder Feinde kommen. Man müsste dann aber die Zähler immer wieder auf Null setzen können, sodass dann jedesmal z.b 10 Feinde getötet werden müssen, bis der Angriff aufhört. Also falls man die Anzahl zu tötender Feinde nicht mit dem Auslöser koppeln kann, kann man das auch bei z.b 10 belassen, egal ob der Auslöser nun eine Hütte, oder ein Schloss ist.

> würde dann also der Regel Bescheid gegeben werden können. Aber welcher Regel genau?


Die Funktion wird bei allen Regelobjekten im Spiel aufgerufen. Aber nur deine selbst geschriebene würde diesen Aufruf verarbeiten, da die anderen die entsprechende Funktion nicht besitzen. Dazu eine eigene Zählregel basteln, die dann die Angriffsregel entfernt und den Zähler auf Null setzt. Beim nächsten Erscheinen der Angriffsregel geht dann alles wieder von vorne los.

hattest du meinen editierten Text bezüglich des Koppelns von Auslöser zu Anzahl der zu tötenen Gegner noch gelesen?

Okay… also ich muss eine eigene Regel schreiben, welche einen Zähler enthält und die Angriffsregel bei x entfernen kann und dann der Zähler auf Null gesetzt wird. 
Wie baue ich so einen Zähler? Ich tippe jetzt mal auf das Zauberwort "Array". Nur ist "Array" für mich noch ein absolutes fremdwort und ich hab noch nicht kapiert, was das genau ist, geschweigedenn wie es funtkioniert :smiley:

Und angenommen ich nenne meine Zählfunktion dann "func Zähler()". Dann muss ich in das Todesscript der gegner reinschreiben:
GameCallEx(Zähler());
?
(ach wie macht man ein appendto für eine Funtkion die bereits im Objekt vorhanden ist, ohne diese zu ersetzen, sondern nur zu erweitern? )

Edit:
ach und das ersetzten der Predator durch Monster oder andere Tiere funktioniert =) Erstelle mir jetzt verschiedene Regeln, die Monster/Feuermonster usw vom Himmel regnen lassen =)

>(ach wie macht man ein appendto für eine Funtkion die bereits im Objekt vorhanden ist, ohne diese zu ersetzen, sondern nur zu erweitern? )


Du kannst bestehende Funktionen grundsätzlich nur ersetzen, aber du hast die Möglichkeit, die ersetzte Funktion mit `inherited()` aufzurufen.

> Wie baue ich so einen Zähler?


Eine Variable, die eine Zahl enthält und hochgezählt wird. Kein Array!

> GameCallEx(Zähler());


GameCallEx("Zaehler");
(benutze niemals Umlaute in Funktionsnamen)

> (ach wie macht man ein appendto für eine Funtkion die bereits im Objekt vorhanden ist, ohne diese zu ersetzen, sondern nur zu erweitern? )


Dafür ist inherited() da. Damit rufst du die sonst überladene Funktion auf. In dein appendto würde also kommen:
protected func Death() {
  GameCallEx("Zaehler")
   return _inherited();
}

>Eine Variable, die eine Zahl enthält und hochgezählt wird. Kein Array!


okay.. müsste es ungefähr so klappen?

func Zaehler()
{
var anzahl = 1;
anzahl++;
Entferner();
}

func Entferner()
{
if anzahl >= 10
RemoveObject(FindObejct(idRule));
}


Und, sofern diese 2 Funktoinen richtig sind, muss ich diese ins Script einer neu erstellte Regel einfügen? Muss ich sonst noch was beachten, z.b im DefCore oderso?

Ungefähr so.

Die Variable muss eine (objekt-)lokale sein, keine funktionslokale. "var" bezeichnet immer eine funktionslokale Variable. D.h. "anzahl" wird erstellt, sobald die Funktion Zaehler() aufgerufen wird und sobald die Funktion fertig abgearbeitet ist, wird "anzahl" wieder vergessen. Eine lokale Variable bleibt dauerhaft in dem Objekt gespeichert, bis das Objekt selbst gelöscht wird. Eine solche Variable wird außerhalb jeder Funktion im Objektscript über das Keyword "local" definiert.
Und weil ich eh schon dabei bin: Darüber hinaus gibt es auch noch globale Variablen. Diese können von jedem Script aus benutzt werden und werden bis zum Ende des Szenarios gespeichert. Das Keyword dafür ist "static", die Variable wird einfach irgendwo (außerhalb von Funktionen) definiert.

Entsprechend dein Script:

local anzahl; // anzahl wird als lokale Variable definiert

func Zaehler()
{
  anzahl++; // anzahl wird um 1 erhöht
  if (anzahl > 10) // Bei if die Bedingung immer in runde Klammern verpacken
  { // Durch diesen Block geschweifter Klammern nach einer If-Abfrage kannst du mehrere Befehle unter dem if zusammenfassen, die nur im Falle von anzahl > 10 ausgeführt werden
    if ( FindObject(idRule) ) // Zur Sicherheit überprüfst du vor dem Entfernen, ob das Zielobjekt vorhanden ist*
      RemoveObject( FindObject(idRule) );
    anzahl = 0; // anzahl zurücksetzen
  }
}


Ich habe deine beiden Funktionen in eine zusammengefasst. idRule ist durch die entsprechende Regel-ID zu ersetzen.

*: Warum ist es wichtig, dass du vor dem Aufruf von RemoveObject noch eine If-Abfrage einfügst, die prüft, ob das Zielobjekt (die andere Regel) vorhanden ist? FindObject(idRule) würde 0 (vielleicht auch false, weiß ich gerade nicht) zurückliefern, wenn das Objekt idRule nicht gefunden wird. Dadurch wäre der Aufruf dann "RemoveObject(0);". Das würde dazu führen, dass das aufrufende Objekt selber gelöscht wird und das wäre deine eigene Regel. Die würde sich also selber löschen, sobald aus irgendeinem Grunde die Angriffsregel schon entfernt ist.

> Muss ich sonst noch was beachten, z.b im DefCore oderso?


Die Kategory sollte C4D_Rule sein.

Das mit den Variablen local und static habe ich zufällig gerade hier https://clonkspot.org/forum/topic_show.pl?tid=308 rausgefunden, aber danke für deine ausführliche erklärung :)

Werde dein Script gleich mal ausprobieren und dann hier editieren, ob es funtkioniert :)

>Dadurch wäre der Aufruf dann "RemoveObject(0);". Das würde dazu führen, dass das aufrufende Objekt selber gelöscht wird und das wäre deine eigene Regel.


ah okay, das wusste ich nicht, danke =)

EDIT:

Im Defcore muss es außerdem ein static Objekt sein, wie ich gemerkt habe ;)
Ich habe nun mal folgendes ins Regelskript geschrieben:

/– Angriffsentferner –/

#strict
local anzahl; // anzahl wird als lokale Variable definiert

func Zaehler()
{
  var wert;
  wert = 5*ObjectCount(_AN_);
  anzahl++; // anzahl wird um 1 erhöht
  if (anzahl > wert) // Bei if die Bedingung immer in runde Klammern verpacken
  { // Durch diesen Block geschweifter Klammern nach einer If-Abfrage kannst du mehrere Befehle unter dem if zusammenfassen, die nur im Falle von anzahl > wert ausgeführt werden
   if ( FindObject(PRA_) ) // Zur Sicherheit überprüfst du vor dem Entfernen, ob das Zielobjekt vorhanden ist*
      RemoveObject( FindObject(PRA_) );
    anzahl = 0; // anzahl zurücksetzen
    Log("Regel entfernt");
  }
}

// Kann mittels des Spielzielauswählers ausgewählt werden
public func IsChooseable() { return(1); }


Die Forenformatierung nervt irgendwie… bei der Definition von "wert" steht eigentlich:  wert = 5 multipliziert mit ObjectCount([unterstrich]AN[unterstrich]) (habs mit `nun bearbeitet)
Ich teste gerade noch ein wenig, wie gut das genau klappt, und editiere hier dann nochmal mit "edit2".

EDIT2:
okay, also das script funktioniert einwandfrei, wenn ich auf meine eigene "wert"Konstruktion verzichte und stattdessen eine feste Zahl eintrage, z.b "anzahl > 5".
Wenn ich das mit "wert" mache, dann wird es viel zu früh ausgelöst, ich glaube bei bei jedem einzelnen oder jedem zweiten Tod. Weißt du woran das liegen könnte?
Könnte es wieder an "var" bzw. "local" liegen? Eigentlich sollte "var doch funktionieren , da wir ja nur eine Funktion haben.
(fertig mit editieren)

Mal von den Problemen, die das Script anscheinend macht (nicht näher angeguckt) gibt es doch RemoveAll(ID)?
Das entfernt doch alle Objekte mit der spezischen ID. Das machts doch einfacher… :slight_smile:

ja danke stimmt, hatte schon überlegt, ob ich dann einfach 5 mal RemoveObject hinschreibe, aber mit RemoveAll ist das natürlich besser :wink:

Ich vermute, dass ich einen Fehler beim definieren von "wert" habe. Vermutlich kann ich da nicht   wert = 5 * ObjectCount(AAN_); schreiben… warum auch immer…

> ob ich dann einfach 5 mal RemoveObject hinschreibe, aber mit RemoveAll ist das natürlich besser ;)


Ansonsten könntest du das auch zB so lösen:
var obj;
while (obj = FindObject2(Find_ID(AAN-))) obj->RemoveObject();

oder so
for (var obj in FindObjects(Find_ID(AAN-))) obj->RemoveObject();

> wert = 5 \* ObjectCount(AAN-); schreiben.. warum auch immer...


Müsste gehen. Ich schätze eher, dass du dich sonst irgendwie vertippt hast. Was sagt denn die Fehlermeldung?

Edit: Luchs, wie escape ich die Unterstriche...? Dein markdown springt immer an und \ funktioniert nicht...

ich denk removeAll ist schon ausreichend :slight_smile:
Ich hab die Regel ID schon von [unterstrich]AN[unterstrich] umbenannt in AAN[unterstrich], weil ich dachte, dass es daran liegt, aber tuts nicht :smiley:

Es kommt keine Fehlermeldung, es ist aber so, dass die Regel schon beim ersten getöteten Monster beendet wird.  Die Regel ist einmal aktiv, also sollte es eigentlich erst ab 5 Monstern beendet werden.   Ich kanns ja mal eben testen, wie es ist, wenn die Regel 2 mal aktiv ist… ^^

edit:
wenn dir Regel 2 mal aktiv ist, kommt der Log "Regel entfernt" erst nach 6 getöteten Monstern, allerdings kommt die Meldung 2 mal (warum?) und auch schon vorher fielen keine neuen Monster mehr vom himmel, ich hatte 4 monster aufeinmal getötet, dann gewartet, kamen keine monster mehr, dann die restlichen 2 erledigt und es stand 2 mal, dass die REgel entfernt wurde. (auch wenn die Regel nur einmal aktiv ist, kommt die Log Meldung zweimal)
edit2: habs nochmal probiert und nach 5 getöteten monstern, kamen trzd noch neue, also wird die Regel wohl wirklich erst nach 6 monstern entfernt, fragt sich nur, warum gerade 6…

so jetzt noch mal eben eine Korrektur:

Wenn ich mit dem hier stehenden Skript die Regel 1 mal aktiviert habe, passiert folgendes:
Nach einem getötetem Monster wieder einmal "Regel entfernt" ausgegeben.

Wenn ich die Regel 2 mal aktiviert habe, wird nach 6 getöteten Monstern zweimal "REgel entfernt" ausgegeben.

Wenn ich die Regel 3 mal aktiviert habe, wird nach 11 getöteten Monstern dreimal "Regel entfernt" ausgegeben :wink:

Finde das Muster :smiley:
Ich frag mich nur, wieso sich dieses Muster ergibt… und wie es richtig sein müsste…
ich möchte, dass bei einer aktivierten Regel 5 Monster, bei 2mal 10 monster usw. getötet werden müssen.    gut 1  6  11 usw. wäre zur not auch okay.
Und ich möchte natürlich, dass die Regel nur einmal entfernt wird, für den Fall, dass ich doch langsam die Monsterzahl verringern will, anstatt komplett alle aufeinmal wegzunehmen.

Am besten für Code die Codeblöcke verwenden mit ```. Wenn ich dazu komme, werden die bald auch Syntax-Highlighting unterstützen…!

Die gehen aber nicht inline…

Inline mit einfachen `

Dingdong Test();

>Finde das Muster


Das klingt danach als ob du bei dem Vergleich mit der Anzahl lieber ein `>=` anstatt eines `>` haben willst