Which is probably too late if you cleanly want to change the damage
Yes, and it's tricky enough anyway (the registered perception (none by default) is used instead of PLAYER_PERC_ASSESSMAGIC if a script state is active on the player).
"Unter diesen schwierigen Umständen bin ich mir sicher, daß diese guten Menschen meinen augenblicklichen Bedarf an deren Gold verstehen werden." -- Connor
I think I managed to get what I needed other way. Instead editing damage formula I added aditional damage with B_MagicHurtNpc function placed in C_CanNpcCollideWithSpell. It seems to be working for now, although further testings will show if there is no problems with it. Well, thanks everyone for help
Ich habe eine neue Version geschrieben, die die Funktion DMG_OnDmg um einige weitere nützliche Informationen bereichert. Gerade wer akkurat und einfach mit Magie umgehen möchte wird es damit simpler haben.
Code:
// Look for the function "DMG_OnDmg" to modify
class oSDamageDescriptor {
var int validFields; // zDWORD 0x00
var int attackerVob; // zCVob* 0x04
var int attackerNpc; // oCNpc* 0x08
var int hitVob; // zCVob* 0x0C
var int hitPfx; // oCVisualFX* 0x10
var int itemWeapon; // oCItem* 0x14
var int spellID; // zDWORD 0x18
var int spellCat; // zDWORD 0x1C
var int spellLevel; // zDWORD 0x20
var int dmgMode; // zDWORD 0x24
var int weaponMode; // zDWORD 0x28
var int dmgArray[8]; // zDWORD[8] 0x2C Vermutlich vor Abzug der Rüstungswerte
var int dmgTotal; // zREAL 0x4C // Nach Abzug der Rüstungswerte?
var int dmgMultiplier; // zREAL 0x50
var int locationHit[3]; // zVEC3 0x54
var int directionFly[3]; // zVEC3 0x58
var string visualFXStr; // zSTRING 0x5C
var int duration; // zREAL 0x60
var int interval; // zREAL 0x64
var int dmgPerInterval; // zREAL 0x68
var int dmgDontKill; // zBOOL 0x6C
var int bitfield; // 1 -> Once, 2 -> finished, 4 -> isDead, 8 -> isUnconscious
var int azimuth; // zREAL 0x74
var int elevation; // zREAL 0x78
var int timeCurrent; // zREAL 0x7C
var int dmgReal; // zREAL 0x80
var int dmgEffective; // zREAL 0x84
var int dmgArrayEffective[8]; // zDWORD[8] 0x104 // Vermutlich nach Abzug der Rüstungswerte, ohne Mindestschaden
var int vobParticleFX; // zCVob* 0x108
var int particleFX; // zCParticleFX* 0x10C
var int visualFX; // zCVisualFX* 0x110
};
func int DMG_OnDmg(var int victimPtr, var int attackerPtr, var int dmg, var int dmgDescriptorPtr) {
var oSDamageDescriptor dmgDesc; dmgDesc = _^(dmgDescriptorPtr);
// Diese Funktion anpassen, wenn ihr den Schaden verändern wollt! 'dmg' ist der von Gothic berechnete Schaden
return dmg;
};
var int _DMG_DmgDesc;
func void _DMG_OnDmg_Post() {
EDI = DMG_OnDmg(EBP, MEM_ReadInt(MEM_ReadInt(ESP+644)+8), EDI, _DMG_DmgDesc);
};
func void _DMG_OnDmg_Pre() {
_DMG_DmgDesc = ESI; // I'm preeeeetty sure it won't get moved in the meantime...
};
func void InitDamage() {
const int dmg = 0;
if (dmg) { return; };
HookEngineF(6736583/*0x66CAC7*/, 5, _DMG_OnDmg_Post);
const int oCNpc__OnDamage_Hit = 6710800;
HookEngineF(oCNpc__OnDamage_Hit, 7, _DMG_OnDmg_Pre);
dmg = 1;
};
Nicht alle Änderungen am DamageDescriptor-Objekt werden in der DMG_OnDmg auch wirksam sein, da sie eventuell bereits verarbeitet wurden. Da heißt im Zweifelsfall nur testen. Einige Änderungen können auch schon in der _DMG_OnDmg_Pre() durchgeführt werden, zu diesem Zeitpunkt sollte noch keines der Werte verarbeitet worden sein.
In ZSpy kommt es vor, dass durch Zauber/Magieschaden ein Stacktrace getriggert wird. Das Spiel stürzt nicht ab. Kennt jemand diese Fehlermeldung und kann diese reproduzieren?
Momentan kommt es in meiner Mod/Testversion vor, dass bei Ladevorgängen das Spiel abstürzt. Die Fehlermeldungen sind nicht eindeutig und ich kann die Abstürze auf keinen konkreten Scripteingriff zurückführen. Deshalb suche ich an allen Stellen.
Getestet habe ich das unter den Bedingungen:
frische Gothic-Installation, keine weiteren Scriptveränderungen
Ikarus 1.2
Lego 2.5
B_DmgOnDmg.d
im Fallbeispiel: Held trifft Ork-Krieger mit einem Feuerball. Ereignisse: Stacktrace-Meldung in ZSpy. Die Schadensberechnung wird korrekt ausgeführt, auch unter Eingriffen bei den Spells über den dmgDesc.
func void INIT_GLOBAL()
{
// wird fuer jede Welt aufgerufen (vor INIT_<LevelName>)
Game_InitGerman();
LeGo_Init(LeGo_All & ~LeGo_Bloodsplats);
InitDamage();
};
unveränderte B_Dmg_OnDmg.d
Spoiler:(zum lesen bitte Text markieren)
Code:
// Look for the function "DMG_OnDmg" to modify
class oSDamageDescriptor {
var int validFields; // zDWORD 0x00
var int attackerVob; // zCVob* 0x04
var int attackerNpc; // oCNpc* 0x08
var int hitVob; // zCVob* 0x0C
var int hitPfx; // oCVisualFX* 0x10
var int itemWeapon; // oCItem* 0x14
var int spellID; // zDWORD 0x18
var int spellCat; // zDWORD 0x1C
var int spellLevel; // zDWORD 0x20
var int dmgMode; // zDWORD 0x24
var int weaponMode; // zDWORD 0x28
var int dmgArray[8]; // zDWORD[8] 0x2C Vermutlich vor Abzug der Rüstungswerte
var int dmgTotal; // zREAL 0x4C // Nach Abzug der Rüstungswerte?
var int dmgMultiplier; // zREAL 0x50
var int locationHit[3]; // zVEC3 0x54
var int directionFly[3]; // zVEC3 0x58
var string visualFXStr; // zSTRING 0x5C
var int duration; // zREAL 0x60
var int interval; // zREAL 0x64
var int dmgPerInterval; // zREAL 0x68
var int dmgDontKill; // zBOOL 0x6C
var int bitfield; // 1 -> Once, 2 -> finished, 4 -> isDead, 8 -> isUnconscious
var int azimuth; // zREAL 0x74
var int elevation; // zREAL 0x78
var int timeCurrent; // zREAL 0x7C
var int dmgReal; // zREAL 0x80
var int dmgEffective; // zREAL 0x84
var int dmgArrayEffective[8]; // zDWORD[8] 0x104 // Vermutlich nach Abzug der Rüstungswerte, ohne Mindestschaden
var int vobParticleFX; // zCVob* 0x108
var int particleFX; // zCParticleFX* 0x10C
var int visualFX; // zCVisualFX* 0x110
};
func int DMG_OnDmg(var int victimPtr, var int attackerPtr, var int dmg, var int dmgDescriptorPtr) {
var oSDamageDescriptor dmgDesc; dmgDesc = _^(dmgDescriptorPtr);
var c_npc attackerNpc; attackerNpc = _^(attackerptr);
var c_npc victimNpc; victimNpc = _^(victimPtr);
// Diese Funktion anpassen, wenn ihr den Schaden verändern wollt! 'dmg' ist der von Gothic berechnete Schaden
return dmg;
};
var int _DMG_DmgDesc;
func void _DMG_OnDmg_Post() {
EDI = DMG_OnDmg(EBP, MEM_ReadInt(MEM_ReadInt(ESP+644)+8), EDI, _DMG_DmgDesc);
};
func void _DMG_OnDmg_Pre() {
_DMG_DmgDesc = ESI; // I'm preeeeetty sure it won't get moved in the meantime...
};
func void InitDamage() {
const int dmg = 0;
if (dmg) { return; };
HookEngineF(6736583/*0x66CAC7*/, 5, _DMG_OnDmg_Post);
const int oCNpc__OnDamage_Hit = 6710800;
HookEngineF(oCNpc__OnDamage_Hit, 7, _DMG_OnDmg_Pre);
dmg = 1;
};
var c_npc attackerNpc; attackerNpc = _^(attackerptr);
das ist einfach eine Abgekürzte Schreibweise für:
Code:
var c_npc attackerNpc; attackerNpc = MEM_PTRTOINST(attackerptr);
Die Warnung sagt dir also das du versuchst aus einem ungültigen pointer eine Instanz zu basteln.
attackerptr kann hier ungültig sein. Beispielsweise wenn du den hero in eine Stachelfalle steuerst. Da nimmt er zwar Schaden, aber es gibt keinen Schadensverursacher.
Warum in deinem "Held trifft Ork-Krieger mit einem Feuerball"-Szenario einer der pointer ungültig ist weiß ich jetzt aus dem Stegreif nicht.
Ganz platt gesagt: Solange es funktioniert kannst du die Warnung ignorieren.
Du solltest dir aber bewusst sein das die c_npc Variablen eventuell nicht auf einen validen NPC zeigen.
Falls du auf irgendwelche Eigenschaften vom attackerNpc zugreifen willst solltest du also Hlp_IsValidNPC benutzen um abzufragen ob da überhaupt was brauchbares drinsteht.
Wenn dich einfach nur die Warnung stört müsstest du das auch so machen können:
Code:
func int DMG_OnDmg(var int victimPtr, var int attackerPtr, var int dmg, var int dmgDescriptorPtr) {
var oSDamageDescriptor dmgDesc; dmgDesc = _^(dmgDescriptorPtr);
var c_npc attackerNpc;
if(Hlp_Is_oCNpc(attackerptr)){
attackerNpc = _^(attackerptr);
} else{
attackerNpc = MEM_NullToInst();
};
var c_npc victimNpc;
if(Hlp_Is_oCNpc(victimptr)){
victimNpc = _^(attackerptr);
} else{
victimNpc = MEM_NullToInst();
};
// Diese Funktion anpassen, wenn ihr den Schaden verändern wollt! 'dmg' ist der von Gothic berechnete Schaden
return dmg;
};
MEM_NullToInst() ist halt gezielt dazu da.
Wenn du aber dann sowieso Hlp_Is_oCNpc benutzt könntest du das auch so strukturieren das du in der Funktion erst Sachen machst bei denen attackerNpc egal ist, dann aus der Funktion springst falls !Hlp_Is_oCNpc(attackerptr) und erst danach attackerNpc eine Instanz zuweist und Abfragen machst bei denen du auf Eigenschaften von attackerNpc zugreifen willst.
Geändert von Cryp18Struct (07.05.2018 um 23:17 Uhr)
That's because in some cases there is no attacker (hitting the ground after falling from the ledge for example). Before you assign pointer to instance, check if variable returns pointer instead null.
Code:
if (attackerPtr) {
var c_npc attacker; attacker = _^(attackerPtr);
};
var c_npc attackerNpc; attackerNpc = _^(attackerptr);
das ist einfach eine Abgekürzte Schreibweise für:
Code:
var c_npc attackerNpc; attackerNpc = MEM_PTRTOINST(attackerptr);
Die Warnung sagt dir also das du versuchst aus einem ungültigen pointer eine Instanz zu basteln.
attackerptr kann hier ungültig sein. Beispielsweise wenn du den hero in eine Stachelfalle steuerst. Da nimmt er zwar Schaden, aber es gibt keinen Schadensverursacher.
Warum in deinem "Held trifft Ork-Krieger mit einem Feuerball"-Szenario einer der pointer ungültig ist weiß ich jetzt aus dem Stegreif nicht.
Ganz platt gesagt: Solange es funktioniert kannst du die Warnung ignorieren.
Du solltest dir aber bewusst sein das die c_npc Variablen eventuell nicht auf einen validen NPC zeigen.
Falls du auf irgendwelche Eigenschaften vom attackerNpc zugreifen willst solltest du also Hlp_IsValidNPC benutzen um abzufragen ob da überhaupt was brauchbares drinsteht.
Wenn dich einfach nur die Warnung stört müsstest du das auch so machen können:
Code:
func int DMG_OnDmg(var int victimPtr, var int attackerPtr, var int dmg, var int dmgDescriptorPtr) {
var oSDamageDescriptor dmgDesc; dmgDesc = _^(dmgDescriptorPtr);
var c_npc attackerNpc;
if(Hlp_Is_oCNpc(attackerptr)){
attackerNpc = _^(attackerptr);
} else{
attackerNpc = MEM_NullToInst();
};
var c_npc victimNpc;
if(Hlp_Is_oCNpc(victimptr)){
victimNpc = _^(attackerptr);
} else{
victimNpc = MEM_NullToInst();
};
// Diese Funktion anpassen, wenn ihr den Schaden verändern wollt! 'dmg' ist der von Gothic berechnete Schaden
return dmg;
};
MEM_NullToInst() ist halt gezielt dazu da.
Wenn du aber dann sowieso Hlp_Is_oCNpc benutzt könntest du das auch so strukturieren das du in der Funktion erst Sachen machst bei denen attackerNpc egal ist, dann aus der Funktion springst falls !Hlp_Is_oCNpc(attackerptr) und erst danach attackerNpc eine Instanz zuweist und Abfragen machst bei denen du auf Eigenschaften von attackerNpc zugreifen willst.
Hallo Cryp18Struct,
danke für die schnelle Antwort! Das hatte ich mir gedacht, dass der Schadenverursacher in einigen Fällen nicht bekannt ist und damit auf einen ungültigen (leeren) Pointer verwiesen wird.
Dein Input und die zwei Lösungsansätze haben mir direkt geholfen, ich konnte meine Scripte jetzt korrekt einbauen!
Vielen Dank nochmal!
Zitat von Rayzer
That's because in some cases there is no attacker (hitting the ground after falling from the ledge for example). Before you assign pointer to instance, check if variable returns pointer instead null.
Code:
if (attackerPtr) {
var c_npc attacker; attacker = _^(attackerPtr);
};
Ist es möglich in G2 die Schadensberechnung auf die G1 Schadensberechnung umzustellen, aber nicht zur Gänze?
Also normaler Treffer soll im Nahkampf bedeuten Schaden = Stärke + Waffenschaden - Rüstungsschaden und Volltreffer = Doppelter Schaden.
Im Fernkampf auch so, nur halt das anders als in G1 auch das Geschick zum Schaden addiert wird.
Ist es möglich in G2 die Schadensberechnung auf die G1 Schadensberechnung umzustellen, aber nicht zur Gänze?
Also normaler Treffer soll im Nahkampf bedeuten Schaden = Stärke + Waffenschaden - Rüstungsschaden und Volltreffer = Doppelter Schaden.
Im Fernkampf auch so, nur halt das anders als in G1 auch das Geschick zum Schaden addiert wird.
Der Post scheint irgendwie untergegangen zu sein, sorry.
Möglich ist es auf jeden Fall. Du müsstest herausfinden, ob es sich um Nah- oder Fernkampf handelt (im dmgDescriptor steht ein Pointer auf die Waffe, das solltest du prüfen können) und dann eigentlich nur den entsprechenden Schaden berechnen. Falls du es nach so langer Zeit doch noch versuchst, kannst du ja mal berichten, was dir konkret Probleme bereitet
Der Post scheint irgendwie untergegangen zu sein, sorry.
Möglich ist es auf jeden Fall. Du müsstest herausfinden, ob es sich um Nah- oder Fernkampf handelt (im dmgDescriptor steht ein Pointer auf die Waffe, das solltest du prüfen können) und dann eigentlich nur den entsprechenden Schaden berechnen. Falls du es nach so langer Zeit doch noch versuchst, kannst du ja mal berichten, was dir konkret Probleme bereitet
Coole Sache, danke für Rückmeldung.
Ich trau mich da irgendwie nicht so ganz drüber, es hätte nicht zufällig jemand Lust das für mich zu machen?
So ich hab jetzt angefangen und Ikarus und LeGo mitsamt diesem Damage Skript installiert.
Jetzt stehe ich allerdings schon an, da ich leider keinerlei Ahnung habe wie ich diese Funktion nun modizifieren soll/muss, damit der Gothic 1 Damage statt dem Gothic 2 Damage berechnet wird oder die kritischen Treffer doppelten Schaden machen.
Code:
func int DMG_OnDmg(var int victimPtr, var int attackerPtr, var int dmg, var int dmgDescriptorPtr) {
var oSDamageDescriptor dmgDesc; dmgDesc = _^(dmgDescriptorPtr);
var c_npc attackerNpc; attackerNpc = _^(attackerptr);
var c_npc victimNpc; victimNpc = _^(victimPtr);
// Diese Funktion anpassen, wenn ihr den Schaden verändern wollt! 'dmg' ist der von Gothic berechnete Schaden
return dmg;
};
Könnte mir bitte jemand der schon erfolgreich eine neue Schadensberechnung gemacht, paar Tipps geben?
Mach dir doch erst einmal klar, wie die Schadensberechnung in Gothic 1 überhaupt funktioniert.
Zweitens solltest du dir überlegen, wie sich die Schadensberechnung in Gothic 2 verhält.
Es gibt Insgesamt Ohne Waffe, Einhand, Zweihand, Fernkampf und Magie. Du wirst für jede Waffenart eine andere Berechnung durchführen müssen (Tipp: Verwende if, else if, else ). Magieschaden dürfte gleich sein, kannst du also ignorieren. Ich weiß aber gerade nicht, ob der Mindestschaden in Gothic 2 auch für Magie gilt. Da du eh deine eigene Schadensberechnung machst, kannst du den Mindestschaden aber auch ausschalten: NPC_MINIMAL_DAMAGE = 0 in AI_Constants.d
Der Einfachheit solltest du erst einmal nur eine Waffenart implementieren. Das verringert die Komplexität beim Implementieren enorm.
Für Einhand wäre die Formel (kein Volltreffer): Stärke + Waffenschaden - Rüstungsschutz
Die Stärke nimmst du vom Angreifer, den Rüstungsschutz vom Opfer und um den Waffenschaden zu bestimmen, musst du erst einmal die gezogene Waffe des Angreifers ermitteln:
Code:
var int weapon; weapon = Npc_GetReadiedWeapon(attackerNpc);
Wir wissen an dieser Stelle noch nicht, ob der Angreifer überhaupt eine Waffe in Händen hält, also ein Sicherheitscheck mit Hlp_IsValiditem.
Als nächstes müssen wir noch prüfen, ob die Waffe eine Einhandwaffe ist. Das geht, indem wir schauen ob das ITEM_KAT_NF flag gesetzt ist und entweder ITEM_SWD oder ITEM_AXE gesetzt ist:
Code:
var int isOneHand;
isOneHand = weapon.flags & ITEM_KAT_NF;
isOneHand = isOneHand && (weapon.flags & ITEM_SWD || weapon.flags & ITEM_AXE);
Die Bedingung, die wir brauchen, um die Einhandschadensberechung durchführen zu können, sieht also so aus:
Code:
var int isOneHand;
isOneHand = weapon.flags & ITEM_KAT_NF;
isOneHand = isOneHand && (weapon.flags & ITEM_SWD || weapon.flags & ITEM_AXE);
if (Hlp_IsValidItem(weapon) && isOneHand) {
// Hier berechnest du den Schaden für Einhandwaffen
};
Der Schaden lässt sich jetzt ganz einfach berechnen.
EDIT: Man muss allerdings noch berücksichtigen, dass Äxte Blunt-Schaden machen, während Schwerter Edge verwenden!
func int DMG_OnDmg(var int victimPtr, var int attackerPtr, var int dmg, var int dmgDescriptorPtr) {
var oSDamageDescriptor dmgDesc; dmgDesc = _^(dmgDescriptorPtr);
var c_npc attackerNpc; attackerNpc = _^(attackerptr);
var c_npc victimNpc; victimNpc = _^(victimPtr);
// Diese Funktion anpassen, wenn ihr den Schaden verändern wollt! 'dmg' ist der von Gothic berechnete Schaden
var int weapon; weapon = Npc_GetReadiedWeapon(attackerNpc);
var int isOneHand;
isOneHand = weapon.flags & ITEM_KAT_NF;
isOneHand = isOneHand && (weapon.flags & ITEM_SWD || weapon.flags & ITEM_AXE);
if (Hlp_IsValidItem(weapon) && isOneHand) {
if (weapon.flags & ITEM_SWD) {
dmg = attackerNpc.attribute[ATR_STRENGTH] + weapon.damage[DAM_EDGE] - victimNpc.protection[PROT_EDGE];
} else {
dmg = attackerNpc.attribute[ATR_STRENGTH] + weapon.damage[DAM_BLUNT] - victimNpc.protection[PROT_BLUNT];
};
};
return dmg;
};
Ich denke damit hast du jetzt genügend Material, um Volltreffer für Einhandwaffen und die restlichen Waffenarten selbst zu implementieren.
Ich sollte noch dazu sagen, dass ich den Code nicht getestet habe (Fehler können also möglich sein)
Du möchtest vermutlich entscheiden können, ob es Nahkampf, Fernkampf, Magie oder anderer Schaden war. Wenn du nur Nahkampf und Fernkampf anpassen möchtest, dann könntest du z.B. abfragen, ob der attackerNpc eine Nahkampf oder Fernkampfwaffe gezogen hat oder im Fistmode (letzteres vielleicht ausprobieren) ist. Wenn nicht (else) dann einfach mit return dmg; den von Gothic errechneten Schaden zurückgeben / anwenden / verwenden.
Du kannst dir ja die Stärke-, Rüstungs- und Waffenwerte schnappen, die in dmgDesc, attackerNpc, victimNpc stehen.
Falls in der Tat Nah- oder Fernkampf, dann mit diesen Werten den Schaden so berechnen, wie du ihn haben möchtest. Anschließend z.B. mit einer Zufallszahl (Hlp_Random) entscheiden, ob es ein Volltreffer gibt und ggf. den Schaden verdoppeln.
Am Ende dann return deine_variable;, wobei deine_variable den Wert des errechneten Schadens hat.