Pop-Up-Targets mit JIP - Bitte um Hilfe

  • Multiplayer

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Pop-Up-Targets mit JIP - Bitte um Hilfe

    Hallo Leute,

    wir bauen derzeit eine Trainingsmap für unseren Clan und benötigen etwas Hilfe. Die Map enthält neben einem CQC-Parcours auch eine Shooting Range und ein Scharfschützen-Training mit den gelb-schwarzen Pop-Up-Zielen. Ein Teil der Targets läuft über das Script von Feuerex (youtube.com/watch?v=ehIzXg2Ttqw ), ein anderer Teil über Trigger. Alle Varianten funktionieren soweit, nur: Wenn jemand den Server joint bzw. rejoint, wird alles global in die Ausgangsposition zurückgesetzt. Das sollte so natürlich nicht sein. Es gibt also offensichtlich ein Lokalitätsproblem bei JIP auf unserem Dedicated Server, wir bekommen es aber nicht hin. Ich habe mich durch die Foren dieser Welt gekämpft und bin bis hierhin gekommen.


    Folgendes wurde gescripted (hier nur die Variante mit den Triggern):

    Die Init.sqf enthält den Befehl

    Quellcode

    1. nopop = true;
    ...der global verhindert, dass die Targets nach einem Treffer automatisch wieder nach kurzer Zeit hochpoppen. Sie bleiben unten.

    Den Rest breche ich mal ganz auf ein einzelnes Ziel herunter, um das Prinzip zu veranschaulichen, nachdem die Range gebaut wurde.

    Ein Target erhält in der Init:

    Quellcode

    1. v1 animate ["terc",1];
    "v1" ist der Var.Name des targets, "animate ["terc", 1]" der Befehl für die Ausgangsstellung (1 = liegend).



    Ein (wiederverwendbarer) Trigger hat eine publicVariable als Bedingung ("hundert;"), die per AddAction auf "true" gesetzt werden kann (siehe unten). Die addAction befindet sich in einer Init eines Objekts (Laptop) und kann von jedem verwendet werden:

    Quellcode

    1. this AddAction ["100m plus",{hundert = true; publicVariable "hundert";}, [], 4, false, true, "", "", 3];

    Bei Aktivierung wird dem Trigger folgender Befehl gegeben:

    Quellcode

    1. v1 animate ["terc",0]; if (isServer) then {hundert = false; publicVariable 'hundert';};
    Das Target richtet sich auf (0 = stehend), die Variable wird auf "false" geschaltet und kann erneut aktiviert werden, nachdem das Ziel umgeschossen wurde (und liegen bleibt). Usw.

    Das alles funktioniert, solange es kein JIP gibt. Beim Serverstart sind alle Targets unten, man kann sie per addAction hochfahren, beschießen: Sie kippen um, bleiben unten, bis die addAction erneut verwendet wird. Nun ist es aber so, dass mit jedem Beitritt eines Clients entweder die Variable auf "false" geschaltet wird oder die Targets werden neu initiiert. Oder ist beides dasselbe? Ich bin mir da unsicher. Wir haben es mit einer initServer.sqf probiert, die aber für die Clients "nopop = true" nicht mehr gelten lässt und auch das Problem nicht löst.

    Macht es überhaupt Sinn, den initialen Liegebefehl in die Target-Init zu setzen? Oder müssen wir da stattdessen mit anderen Bedingungen arbeiten (z. B. "if (isDedicated)...)? Sollten wir mit remoteExec arbeiten und alles in eine externe .sqf auslagern?

    Wir sind für jede Hilfe dankbar, ein Stoß in die richtige Richtung genügt vielleicht schon... das Problem scheint grundsätzlicher zu sein und nicht nur für diese Targets zu gelten.

    Grüße

    Webster
  • >Nun ist es aber so, dass mit jedem Beitritt eines Clients entweder die Variable auf "false" geschaltet wird

    Mit "die Variable" meinst du hundert? hundert ist doch immer false. Nur wenn jemand die User Action ausführt, dann ist die Variable für ein paar Millisekunden true, solange bis auf dem Server der Trigger auslöst und die Variable wieder für alle zurück auf falsesetzt.

    >oder die Targets werden neu initiiert.

    Meinst du mit "initiiert", dass sie umgeworfen werden? Denn genau das würde ich nach deiner Beschreibung der Missionsskripte erwarten. Schließlich sagt die Init-Box ja v1 animate ["terc",1]; und das wirft die Ziele um.
    Dazu muss man wissen, dass die Init-Box auf jedem Klienten ausgeführt wird ("global"). Verbindet jemand JIP, dann wird die Init-Box auf seinem/ihrem Rechner ausgeführt. Der animate-Befehl hat globale Auswirkungen und funktioniert für remote Objekte. Jedes mal wenn jemand verbindet wirft dieser die Ziele um.


    isDedicated ist quasi immer Blödsinn, aber die Init-Box sollte wohl besser if (isServer) then {v1 animate ["terc",1]}; lauten, sodass nur der Server am Missionsbeginn die Ziele umwirft.

    Sollte animate am Missionsstart aufgrund einer Race Condition nicht funktionieren, dann müsste man das so scheiben:

    Quellcode

    1. if (isServer) then {
    2. v1 spawn {
    3. _this animate ["terc",1];
    4. };
    5. };
    Btw, warum v1? Wenn man sich sowieso schon die Mühe macht und jedes Ziel im Editor anklickt, dann kann man wenigstens gleich die this wildcard benutzen.
  • Spitze! Frohes Neues und danke für die klare und aufschlussreiche Antwort! Deine Hinweise leuchten mir ein. Wir testen das.

    Die Ziele brauchen übrigens alle einen Var.Name, weil sie in Gruppen aufgerufen werden. Aber "this" würde trotzdem völlig reichen, ja. Falls es denn eine elegantere Lösung gibt, sag ich nicht nein.

    Zwei Rückfragen:

    Erstens: Warum ist "isDedicated" immer Blödsinn?

    Zweitens: Das Script von Feuerex arbeitet mit einer reset.sqf, die diesen Inhalt hat:

    Quellcode

    1. /*-------
    2. Makes targets pop up at the user's command. Targets go down after being hit,
    3. and return back with user action. Because swivel targets have a different
    4. script assigned to them that works differently from all other targets,
    5. they are handled separately in the script. If you don't plan
    6. to use swivel targets at all, feel free to delete the corresponding part
    7. of the code.
    8. -------*/
    9. params [["_dist",15,[1]],["_center",player,[objNull]]]; //in params
    10. _targets = nearestObjects [position _center, ["TargetBase"], _dist]; //take all nearby practice targets
    11. if (count _targets < 1) exitWith {
    12. systemChat "No compatible targets were found."; //exit if no targets have been found
    13. };
    14. {_x animate ["Terc",0];} forEach _targets; //get all targets to upright pos
    15. {_x addEventHandler ["HIT", { //add EH
    16. (_this select 0) animate ["Terc",1]; //if hit, get to the ground
    17. (_this select 0) RemoveEventHandler ["HIT",0]; //remove EH
    18. }
    19. ]} forEach _targets;
    20. //systemChat "Ready.";
    21. //swivel targets start here
    22. _SwivelTargets = nearestObjects [position _center, ["Target_Swivel_01_base_F"], _dist]; //swivel targets work differently
    23. if (count _SwivelTargets < 1) exitWith {
    24. systemChat "No swivel targets were found.";
    25. };
    26. {_x animate ["Terc",0]; _x setVariable ["BIS_poppingEnabled", false];} forEach _SwivelTargets; //nopop has no effect, it's poppingEnabled now
    27. {_x addEventHandler ["HitPart", {
    28. ((_this select 0) select 0) animate ["Terc",1];
    29. ((_this select 0) select 0) RemoveEventHandler ["HitPart",0];
    30. }
    31. ]} forEach _SwivelTargets;
    32. //systemChat "Swivel ready.";
    Alles anzeigen

    Wie man vielleicht erkennen kann, fragt das Script in einem gegebenen Radius um ein Game Logic-Modul alle Ziele ab, die aufgerichtet werden können - und tut dies. Diese Targets sind initial nicht liegend, sondern stehend (Init-Box ist leer). Es gibt zwei Abschnitte im Killhouse, daher zwei Module mit verschienenen Radien (iCenter & iCenter 2, siehe unten). Die Swivel targets funktionieren übrigens auf dem DedServer nicht, aber das ist ein bekanntes Problem und daher jetzt mal egal.

    Die dazugehörige init.sqf (ist die gleiche init.sqf wie im ursprünglichen Beitrag) hat folgenden Inhalt:

    Quellcode

    1. nopop = true;
    2. _0 = [15,iCenter] execVM "reset.sqf";
    3. _0 = [13,iCenter2] execVM "reset.sqf";

    Ein Laptop im Killhouse erhält folgendes in die Init-Box:

    Quellcode

    1. this addAction ["Reset targets", {0 = [15, iCenter] execVM "reset.sqf"}];
    2. this addAction ["Reset targets", {0 = [13, iCenter2] execVM "reset.sqf"}];
    iCenter ist der Name des Moduls, 15 der Radius. Es wird also das Script aus der reset.sqf abgerufen, sodass alle Targets im gegebenen Radius um das jeweilige Modul auf Kommando (wieder) hochpoppen. Auch hier haben wir das Problem, dass mit jedem JIP alle Targets targets zurück, also aufrecht gesetzt werden, die in einem laufenden Training niedergeschossen wurden.

    Der von dir vorgechlagene Befehl ist also auch in diesem Script entsprechend unterzubringen, richtig? In der init.sqf? Denn die wird doch bei jedem JIP abgerufen, wenn ich das hier richtig verstehe:

    community.bistudio.com/wiki/Initialization_Order
  • Re 1:
    Weil es keine Anwendungen für den Befehl gibt.

    Möchte ich ein Skript auf dem Server ausführen, dann benutze ich isServer als Bedingung.
    Möchte ich ein Skript auf einem Klienten mit Spieler ausführen, dann benutze ich hasInterface als Bedingung.
    Möchte ich ein Skript auf der lokalen Maschine eines Objekts ausführen, dann benutze ich local _object als Bedingung.
    Möchte ich ein Skript auf jeder Maschine ausführen, dann brauche ich keine Bedingung.

    Der Befehl gibt im lokal gehosteten Mehrspieler- und im Einzelspielermodus immer false zurück. Also funktioniert ein solches Skript dort nicht. Kann ja sein, dass die Mission später nur auf einem dedizierten Server läuft, aber warum benutze ich einen Skriptbefehl, der mir bewusst das Debugging erschwert, wenn er keinen auch nur denkbaren Vorteil hat?


    Re 2:
    Okay, also einerseits wird reset.sqf am Missionsbeginn von jedem Klienten ausgeführt (init.sqf ist global). Andererseits wird durch die User Action reset.sqf während der Mission immer nur von einem Klienten, der die Aktion ausführt, aufgerufen (Action statement ist lokal). Das ganze ist also schon mal sehr merkwürdig.

    Dann wird innerhalb des Hit/HitPart-Events ein Hit/HitPart-Event mit der festgeschriebenen ID: 0 über removeEventHandler entfernt. Aber dabei weiß man doch gar nicht, ob ID: 0 die ID des jeweiligen Eventhandlers ist. Benutzt man jetzt z.B. zweimal die User Action, dann sind da schon zwei Eventhandler mit ID: 0 und ID: 1 auf dem Ziel. Nur eins wird entfernt. Jedes neu hinzugefügte hat dann außerdem ID: 2 usw, denn bei 0 beginnt das Verteilen von addEventHandler erst wieder, wenn alle entfernt wurden.

    Was ist außerdem, wenn ein anderes Skript einen solchen Eventhandler hinzufügt und das bevor reset.sqf läuft und damit bereits ID: 0 für sich beansprucht? Z.B. irgendeine Mod, die - was weiß ich - Funkensprüheffekte zu einigen Metallobjekten hinzufügt? Dann wird der Effekt-Eventhandler entfernt, statt der Umfaller-Eventhandler.

    Ich verstehe auch den Sinn des Entfernens des Eventhandlers sowieso nicht. Die Ziele sollen doch trotzdem umfallen, wenn sie erneut getroffen werden und das unabhängig davon, ob sie zurzeit liegen oder schon mal wieder aufgestellt wurden. Wenn man damit verhindern will, dass die Eventhandler mehrfach gleichzeitig laufen, dann sollte man stattdessen den Eventhandler nicht mehrfach hinzufügen, sondern nur einmal am Missionstart.

    Das ganze führt zu sehr eigenartigen Ergebnissen. Wenn ich alles richtig interpretiere, dann könnte man die Ziele im aufgestellten Zustand einfrieren. Dazu müssten sie einmal umgeschossen werden, sodass der Eventhandler auf dem Server auslöst und entfernt wird. Dann müsste sich einer Spieler opfern und sie über die User Action aufstellen. Der Spieler verlässt dann den Server: Die Ziele sind nun aufgestellt, aber es befindet sich keine Maschine mehr auf dem Server, die das Umfaller-Event am laufen hat. Das ganze würde dadurch gelöst werden, dass jemand anderes die User Action bedient, oder jemand JIP verbindet.

    Das Skript, dass du da ausgegraben hast, ist ziemlicher Müll, um ehrlich zu sein.

    Zur schnellen Lösung: Hier stellen sich die Ziele aus dem selbem Grund auf, weshalb sie beim ersten Skript umfallen. reset.sqf wird von einem JIP Klienten via init.sqf ausgeführt und Zeile 15 Stellt alle Ziele im Radius auf. Band Aid-Lösung könnte auch hier isServer sein, allerdings muss, da bereits beschrieben die User Action so geändert werden, dass die reset.sqf z.B. über remoteExec auf dem Server ausgeführt wird.

    Frohes Neues.
  • Zu 1. ok, danke für die Erklärung!

    Zu 2.: Ich verstehe das Script und deine Erklärung nicht vollständig, weil ich mich mit EventHandlern noch nicht auskenne (ich mache das alles noch nicht sooo lange). Was ich aber verstehe, ist dein Argument, dass hier in sehr komplizierter Art und Weise und womöglich Bug-anfälliger dieselbe Leistung erbracht werden soll, die mit den Triggern (erstes Script) ebenso funktioniert - nur umgekehrt. Der Entwickler war 2017 offenbar der Auffassung, dass "nopop" eine unzuverlässige Sache ist, weshalb er den EventHandler eingebaut hat ("//if hit, get to the ground" usw.). Allerdings zeigt sich "nopop" nach meiner bisherigen Erfahrung als sehr zuverlässig.

    Whatever... mir kam es bisher übrigens auch komisch vor, dass das reset-Script zu Beginn der Mission über die init.sqf ausgeführt werden soll, denn man will ja gerade NICHT, dassjemand beim JIP automatisch das Script ausführt und alles auf Reset ( = aufrecht) schaltet. Insofern ist es mit Blick darauf sowieso kein Wunder, dass das alles nicht JIP-proof ist.

    Vielleicht kloppen wir das ganze Script besser in die Tonne und verwenden die gleiche Vorgehensweise wie oben. Ich probiere rum und antworte dann hier kurz, ob das Problem damit gelöst wurde. Dann kann man die Kiste zumachen.

    thx again!
  • Mit nopop hat der Eventhandler erstmal nichts zu tun. Das ist nur eine Variable, die letztendlich von irgendwelchen Skripten von BI abgefragt wird.

    Zum Verständnis von Eventhandlern muss man sich klar machen, dass Code in Arma nicht unbedingt von oben nach unten ausgeführt wird. Alles was in geschweiften Klammern steht ist grundsätzlich nichts anderes als eine spezielle Zeichenkette (nur mit {} statt ""}. Diese Zeichenkette steht dann für eine andere Skriptinstanz bzw. Befehlsliste, welche später und mehrmals aufgerufen werden kann.

    Der Befehl addEventHandler hinterlegt im Speicher bezogen auf ein Objekt diese Befehlsliste. Je nach Art des Eventhandlers wird dann die Befehlsliste ausgeführt. Beim Killed Eventhandler wird das Skript ausgeführt, wenn das Objekt stirbt oder zerstört wird (was logischerweise nur einmal passieren kann). Beim Fired Eventhandler wird das Skript ausgeführt, wenn das Objekt eine Waffe abfeuert. Beim Hit Eventhandler wird das Skript ausgeführt, wenn das Objekt getroffen wird usw.

    Es können mehrere Eventhandler des gleichen Typs zu einem Objekt hinzugefügt werden. Tritt das Ereignis (Killed/Fired/Hit usw.) dann ein, dann werden alle hinterlegten Skripte nacheinander ausgeführt.