|
-
[Skript] Pfeile/Bolzen wieder aufsammeln
EDIT: Dieses Skript ist im Skriptpaket G2 Free Aim enthalten und ist dort wesentlich aktueller, performanter und bietet dort mehr Features.
Diese Version hier ist nicht stabil und kann zu Crashes führen! G2 Free Aim benutzen!
Hallo,
Hier ist ein Skript, das als Nebenprodukt vom freien Zielen entstanden ist. Da es auch ohne benutzt werden kann, wollte ich es hier teilen.
Mit dem Skript ist es möglich Projektile (Pfeile und Bolzen) nach dem Verschiessen wieder aufzusammeln und wiederzuverwenden. Das gilt sowohl für Geschosse die von einer Oberfläche abprallen, darin (sichtbar) stecken bleiben oder Lebewesen treffen. Projektile, die einen Npc treffen werden in dessen Inventar gelegt, sodass man sie beim Plündern wieder bekommt. (Dass die Pfeile auch in Npcs stecken bleiben könnten, ist etwas "kniffliger", weil diese sich ja noch bewegen.)
Hier ein kleines Video, das die drei oben genannten Fälle zeigt. Unten das knappe Skript und darunter ein paar Details und genauere Erklärungen.
[Video]
Skript:
Code:
/*
* Collectible projectiles (mud-freak 2016)
* Forum-thread: http://forum.worldofplayers.de/forum/threads/1475399
* Requirements:
* - Ikarus (>= 1.2)
* - LeGo (>= 2.3.2 HookEngine)
*/
const int oCAIArrowBase__DoAI = 6948416; //0x6A0640 //len 7
const int onArrowHitNpcPtr = 6949832; //0x6A0BC8 //len 5
const int onArrowHitVobPtr = 6949929; //0x6A0C29 //len 5
const int onArrowHitStatPtr = 6949460; //0x6A0A54 //len 5
const int ARROWAI_REDIRECT = 0; // Redirect call-by-reference argument
/* Initialize collectible projectiles. Call from init_global() */
func void initCollectableProjectiles() {
const int hookprojectile = 0; if (hookprojectile) { return; };
HookEngineF(oCAIArrowBase__DoAI, 7, projectileCollectable); // AI loop for projectiles
HookEngineF(onArrowHitNpcPtr, 5, onArrowHitNpc); // Projectile hits npc
HookEngineF(onArrowHitVobPtr, 5, onArrowGetStuck); // Projectile gets stuck in vob (material)
HookEngineF(onArrowHitStatPtr, 5, onArrowGetStuck); // Projectile gets stuck in static world (material)
hookprojectile = 1;
};
/* Arrow gets stuck in npc: put projectile instance into inventory and let ai die */
func void onArrowHitNpc() {
var oCItem projectile; projectile = _^(MEM_ReadInt(ESI+88));
var C_NPC victim; victim = _^(EDI);
CreateInvItems(victim, projectile.instanz, 1); // Put respective munition instance into the inventory
MEM_WriteInt(ESI+56, -1073741824); // oCAIArrow.lifeTime (mark this AI for projectileCollectable)
};
/* Arrow gets stuck in static or dynamic world (non-npc): keep ai alive */
func void onArrowGetStuck() {
var oCItem projectile; projectile = _^(MEM_ReadInt(ESI+88));
projectile.flags = projectile.flags &~ ITEM_NFOCUS; // Focusable (collectable)
projectile._zCVob_callback_ai = 0; // Release vob from AI
// Have projectile not go to deep in. Might not make sense but trust me. (RightVec will be multiplied later)
projectile._zCVob_trafoObjToWorld[0] = mulf(projectile._zCVob_trafoObjToWorld[0], -1096111445);
projectile._zCVob_trafoObjToWorld[4] = mulf(projectile._zCVob_trafoObjToWorld[4], -1096111445);
projectile._zCVob_trafoObjToWorld[8] = mulf(projectile._zCVob_trafoObjToWorld[8], -1096111445);
};
/* Once a projectile stopped moving keep it alive */
func void projectileCollectable() {
var int arrowAI; arrowAI = ECX; // AI of the projectile
var int projectilePtr; projectilePtr = MEM_ReadInt(ESP+4); // oCItem*
var int removePtr; removePtr = MEM_ReadInt(ESP+8); // Boolean pointer (call-by-reference argument)
if (!projectilePtr) { return; }; // In case it does not exist
var oCItem projectile; projectile = _^(projectilePtr);
if (!projectile._zCVob_rigidBody) { return; }; // zCRigidBody. Might not exist the first time
// If the projectile stopped moving (and did not hit npc)
if (MEM_ReadInt(arrowAI+56) != -1073741824) && !(projectile._zCVob_bitfield[0] & zCVob_bitfield0_physicsEnabled) {
projectile.flags = projectile.flags &~ ITEM_NFOCUS; // Focusable (collectable)
MEM_WriteInt(arrowAI+56, FLOATONE); // oCAIArrow.lifeTime // Set high lifetime to ensure item visibility
MEM_WriteInt(removePtr, 0); // Do not remove vob on AI destruction
MEM_WriteInt(ESP+8, _@(ARROWAI_REDIRECT)); // Divert the actual "return" value
} else if (MEM_ReadInt(arrowAI+56) == -1073741824) { // Marked as positive hit on npc: do not keep alive
MEM_WriteInt(arrowAI+56, FLOATNULL); // oCAIArrow.lifeTime
};
};
Details:
Benutzung
Zur Benutzung reicht es lediglich - neben Ikarus und LeGo (dort ist nur das HookEngine-Paket nötig) - den Hook in der Init_Global zu initialisieren. Ein Aufruf von initCollectableProjectiles(); genügt.
Wie geht das
Wenn ein Projektil (Pfeil oder Bolzen) verschossen wird, bekommt es eine KI. Die ist dafür zuständig, das Projektil "intelligent" zu machen, also fliegen zu lassen. Die Funktion, die dem Projektil das Fliegen beibringt wird jedes Frame aufgerufen, bis die KI abläuft (Pfeil kollidiert oder KI überschreitet eine gewisse Lebenszeit). Das Skript hier überprüft diese beiden Zustände bevor es die darunterliegenden Enginefunktionen (oCAIArrow::DoAI und oCAIArrowBase::DoAI) tun. Das Ganze sollte sehr performance-freundlich sein.
Gothic deaktivert den Fokus wenn ein Pfeil verschossen wird, sodass er nicht mehr aufgehoben werden kann. Ich habe einfach diesen Fokus wieder angestellt, sobald der Pfeil zum Halten kommt. Gothic will nun aber nach einer bestimmten Lebenszeit der KI den Pfeil aus der Welt löschen (klar, normalerweise konnte man den ja nicht mehr aufheben).
Die einfachste Möglichkeit das zu verhindern wäre, die Lebenszeit der KI auf -1 (unendlich) zu setzen. Das klappt wunderbar, hat aber den fatalen Nachteil, dass die KI eben unendlich lang weiterläuft. Wenn man also viele Pfeile verschiesst ist nicht garantiert, dass die Performance nicht darunter leidet oder Speicherlecks entstehen.
Um das zu verhindern, habe ich den Pfeil von seiner KI gelöst, gaukel Gothic aber vor die KI wäre noch aktiv, weil Gothic den Pfeil sonst löscht (in/nach oCAIArrow::DoAI). Das passiert über einen Rückgabewert (kein Returnwert, sondern call-by-refernce Argument), den ich nach redirect umleite, und den richtigen (removePtr) auf Null (= Pfeil nicht löschen) setze. Da die KI aber gelöst ist vom Pfeil merkt Gothic dann erst anschliessend, dass er sie löschen kann - der Pfeil bleibt so in der Welt während die KI gelöscht wird. Der Pfeil ist dann wieder ein ganz normales Item (ein Item ist der Pfeil sowie so die ganze Zeit). Soweit ich das nachverfolgen konnte, entsteht kein Speicherleck und die Variante ist "clean" und zuverlässig.
Ich habe das Skript für Gothic 2 geschrieben. Möchte es jemand für Gothic 1 benutzen, so müsste er die Adressen und Längen der Hooks anpassen. Allerdings unterstützt die benötigte LeGo-HookEngine meines Wissenes nach sowie so nur Gothic 2.
Einschränkungen
Wie oben erwähnt ist es natürlich etwas schade, dass die Pfeile nicht in Npcs stecken bleiben, sondern in deren Inventar wandern. Viel wichtiger zu beachten ist allerdings, dass auch die Pfeile ins Inventar wandern, die gar keinen Schaden anrichten. Bei tieferem Skill-Level von Bogen-/Armbrustschiessen, ist nicht etwa die Präzision schlechter, Gothic registriert einfach nicht jeden Treffer - rein physikalisch trifft ein Pfeil immer. Das bedeutet, dass im Inventar alle getroffenen Pfeile auftauchen. Im Video kann man das sehen: Der erste Pfeil verletzt den Wolf nicht und ruft auch keine Reaktion hervor (dennoch trifft er ihn), aber ist anschliessend trotzdem im Inventar. Dass nur Pfeile im Inventar landen die auch Schaden angerichtet haben, lässt sich einfach lösen - habe ich hier aber nicht, denn wo sollen die Pfeile hin, die treffen aber kein Schaden machen?
Das Skript entstand heute beim experimentieren - auf solche Details habe ich deshalb nicht wert gelegt. Schlimm finde ich es aber auch nicht.
Änderungen:
- 16-09-05: Skript, als auch Klassen überarbeitet (um weitere Sicherheitsabfrage erweitert, Skript ist kompakter) und weitere Erklärungen hinzugefügt.
- 16-09-08: Skript vereinfacht und Klassendokumentation rausgenommen, weil sie im Skript nur an ein, zwei Stellen referenziert wurden. Dafür lohnt es sich nicht extra für so ein kurzes Skript extra Klassen einzubinden - soll ja simpel und übersichtlich bleiben.
- 16-09-12: Fix zum besseren Aufsammeln: Steckengebliebene Projektile werden ein Stück nach hinten verschoben, so dass sie weiter herausstehen und besser fokussiert und aufgesammelt werden können.
- 16-09-12: Optionaler Code zum Fix für Metalrüstungen. Nicht mehr nötig
- 16-09-13: Komplette Überarbeitung: Jetzt ist es sehr stabil
- 16-10-13: Fix für einen Bug, der nach dem Neuladen zum Crash führt
Last edited by mud-freak; 02.07.2017 at 11:02.
Reason: Bitte das G2 Free Aim Skriptpaket benutzen!
-
Apprentice
Good thing Few months ago I wrote similiar script, but it was FPS hungry.
https://www.youtube.com/watch?v=_h2HBnu1e9s
-
Ein super Script! Ähnliche Idee hatte ich vor einigen Wochen, als wir über das FreeAim geschrieben hatten, auch schon. In Gothic 3 hatte PB das ja umgesetzt. Ganz tolles Feature wie ich finde, auch wenn das sicher einige Balancing Anpassungen bedarf bzw. auch einige coole Neuerungen wie das der Pfeil nach solchen Treffern etwas abgenutzt ist, weniger Schaden macht und somit erst wieder repariert werden muss. Aber das kann sich ja jeder nach Bedarf anpassen. Vielen Dank!!
-
Ich habe gerade das Skript und die Klassen etwas überarbeitet (kompakter gemacht und eine weitere Sicherheitsabfrage was die Argumente angeht hinzugefügt). Ich empfehle diese Überarbeitung des Skriptes, sowie der Klassen zu benutzen. Beides ist oben im Einleitungspost aktualisiert. Ich habe auch die Erklärungen zur Funktionsweise etwas nachgebessert.
Originally Posted by Rayzer
Nice, I wasn't sure whether that has actually been done before. The script I posted here is very FPS friendly as it only sits on top of the KI and only acts when the projectile stopped moving. Here should be absolutely no hit on performance.
Originally Posted by Fisk2033
Ganz tolles Feature wie ich finde, auch wenn das sicher einige Balancing Anpassungen bedarf bzw. auch einige coole Neuerungen wie das der Pfeil nach solchen Treffern etwas abgenutzt ist, weniger Schaden macht und somit erst wieder repariert werden muss. Aber das kann sich ja jeder nach Bedarf anpassen. Vielen Dank!!
Stimmt, dazu müsste man aber verschiedene Item-Instanzen haben. Denn projectile.name = "Abgenutzter Pfeil"; (o.Ä.) hilft nicht; beim aufsammeln wird die Instanz ins Inventar gelegt, solche Änderungen bleiben dann nicht bestehen.
Man kann aber einfach die Skript-Instanz des oCItems austauschen (dann muss man nicht das Item komplett ersetzen). Dabei müssen aber die Eigenschaften übernommen werden. Funktionieren tut dieses hier:
Code:
if (projectile.instanz == Hlp_GetInstanceID(ItRw_Arrow)) { // If projectile is an arrow
const int oCItem__SetByScriptInstance = 7421168; //0x713CF0
CALL_IntParam(0); // Alternatively the symbolID
CALL_zStringPtrParam("ItRw_UsedArrow"); // OR the instance name
CALL__thiscall(_@(projectile), oCItem__SetByScriptInstance);
}; // else if for crossbow bolts etc.
Ich habe in den letzten Tagen immer mal wieder ein paar Sachen am Skript verbessert (im Einleitungspost findet sich eine Art "Changelog").
Zum einen habe ich die Klassen rausgenommen, da sie nicht wirklich wichtig waren und das Skript jetzt kürzer und simpler ist, zum anderen habe ich einen Fix zum besseren Aufsammeln eingebaut. Das Problem war, dass Pfeile und Bolzen zu weit "drin" stecken bleiben und so schwieriger zu fokussieren und somit aufzusammeln sind. Die zusätzlichen Zeilen Code (im Skript oben bereits ergänzt) ziehen das Projektil wieder ein bisschen raus. Getestet mit herkömmlichen Pfeilen und Bolzen. (Hat man ein Projektil, das noch kürzer ist als ein Bolzen könnte es dazu führen, dass es in der Luft schwebt).
Ich habe ein Problem festgestellt: Trägt ein NPC eine Rüstung vom Materialtyp MAT_METAL (Metall), so prallt ein Projektil ab, wenn Gothic sie nicht als Treffer registriert (hängt von Talent ab). Das wird in diesem Skript hier nicht berücksichtigt, sodass ein solches abprallendes Projektil, trotzdem ins Inventar wandert. Um das ganze am geschicktesten zu umgehen, kann man mit folgendem Code das Abprallen für alle Npcs (egal welches Material deren Rüstungen haben) ausschalten (Abprallen mit unbelebten Gegenständen in der Welt bleibt erhalten). Einfach disableProjectileNpcBounce() in der init_global ausführen.
Code:
const int projectileBounceOffAdr = 6949734; //0x6A0B66
/* Never bounce off of npcs */
func void disableProjectileNpcBounce() {
MemoryProtectionOverride(projectileBounceOffAdr, 2);
MEM_WriteByte(projectileBounceOffAdr+1, /*60*/ 96); // jz to 0x6A0BC8
};
/* Restore default behavior */
func void resetProjectileNpcBounce() {
MemoryProtectionOverride(projectileBounceOffAdr, 2);
MEM_WriteByte(projectileBounceOffAdr+1, /*3B*/ 59); // jz to 0x6A0BA3
};
Last edited by mud-freak; 13.09.2016 at 20:09.
Reason: Mehrere Posts zusammengefügt.
-
Ich habe das Skript komplett überarbeitet. Jetzt ist es wesentlich verlässlicher und der Metalrüstungsfix ist nicht mehr nötig. Jetzt wird verlässlicher abgefragt, ob ein Pfeil in der Welt stecken oder liegen bleibt oder einen Npc getroffen hat. Wenn jemand das Skript schon benutzt, empfehle ich sehr, diese neue Überarbeitung statt dessen zu verwenden.
Last edited by mud-freak; 13.09.2016 at 20:10.
-
Hmmm i have one question. What is ARROWAI_REDIRECT? I don't have this varriable / constant ...
-
Originally Posted by Siemekk
Hmmm i have one question. What is ARROWAI_REDIRECT? I don't have this varriable / constant ...
Thanks for pointing that out, this is an error in my Code. I corrected it in the first post.
-
Ich hab es zwar schon erwähnt, aber jetzt trotzdem nochmal:
Einfach geniale Arbeit, die du hier abgeliefert hast!
Es fühlt sich einfach so gut an, wenn die Pfeile in Holz steckenbleiben und man sie wieder einsammeln kann.
Eine kleine Idee hierzu:
Projektile könnten beim Aufprall zerstört werden - je nach Material, gegen das sie knallen, ist die Wahrscheinlichkeit anders. Bolzen, die auf Metall treffen, werden in 80% der Fälle zerstört, in Holz vielleicht in 15% etc.
Auch, wenn man ein Tier ausnimmt, sollten ein paar Geschosse unwiederbringlich zerstört sein, finde ich (da lässt es sich vermutlich leichter einrichten, als beim Aufprall auf etwas ).
Risenmodkit
"Du sagst mir jetzt, was du weißt, oder es gibt ein paar auf's Maul, Paul!"
-
Originally Posted by Simon
Ich hab es zwar schon erwähnt, aber jetzt trotzdem nochmal:
Einfach geniale Arbeit, die du hier abgeliefert hast!
Es fühlt sich einfach so gut an, wenn die Pfeile in Holz steckenbleiben und man sie wieder einsammeln kann.
Danke, bitte. Es freut mich, wenn das Skript Verwendung findet!
Originally Posted by Simon
Projektile könnten beim Aufprall zerstört werden - je nach Material, gegen das sie knallen, ist die Wahrscheinlichkeit anders. Bolzen, die auf Metall treffen, werden in 80% der Fälle zerstört, in Holz vielleicht in 15% etc.
Auch, wenn man ein Tier ausnimmt, sollten ein paar Geschosse unwiederbringlich zerstört sein, finde ich (da lässt es sich vermutlich leichter einrichten, als beim Aufprall auf etwas ).
Gute Idee. Im freien Zielen Skript ist die Handhabung von Projektilen weit aus ausgereifter als in diesem Post (ob ich das hier noch einmal separat einpflege weiss ich noch nicht. Das hängt davon ab, ob Leute dieses Skript aber nicht freies Zielen verwenden möchten). Dort ist es durchaus schon möglich darüber zu entscheiden, ob ein Projetil tatsächlich im Inventar auftaucht. Abhängig machen kann man das dort...
- ...am getroffenen NPC,
- ...der Schusswaffe,
- ...des Projektils selbst,
- ...Talenten des Spielers
- ...möglichen Ausnehm-Werkzeugen,
- ...usw
Eine Kaputtgeh-Wahrscheinlichkeit kann man dort also auch einbauen.
Dass man es auch vom Material abhängig machen kann, ob ein Projektil stecken bleibt, kaputtgeht oder abprallt, geht sicher auch (denn diese Abfrage ist in der Engine schon vorhanden, siehe Holz vs. Metall). Damit habe ich mich aber noch nicht auseinander gesetzt.
EDIT: Das ist nun (im freien Zielen Skript) eingebaut: Das Kollisionsverhalten kann man nun an Hand des Oberflächenmaterials festlegen:
(a) Kaputt gehen, (b) Steckbleiben oder (c) Abprallen. Wenn man einen NPC trifft, wird das Material dessen Rüstung verwendet. Wahrscheinlichkeiten (z.B. ob ein Projektil zerschellt oder bestehen bleibt) kann man dort auch einbauen.
Last edited by mud-freak; 13.10.2016 at 09:53.
Reason: Kollisionsverhalten nach Material eingebaut
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
|
|