diff --git a/_work/data/Scripts/Content/GFA/_intern/controls.d b/_work/data/Scripts/Content/GFA/_intern/controls.d index 8a8ff50..fe0eacd 100644 --- a/_work/data/Scripts/Content/GFA/_intern/controls.d +++ b/_work/data/Scripts/Content/GFA/_intern/controls.d @@ -173,12 +173,9 @@ func void GFA_SetControlSchemeRanged(var int scheme) { MEM_Info(ConcatStrings(" OPT: GFA: Ranged-ctrl-scheme=", IntToString(scheme))); // To zSpy in style as options if (scheme > 0) { // Control scheme override: Skip jump to Gothic 2 controls - MEM_WriteByte(oCAIHuman__BowMode_g2ctrlCheck, ASMINT_OP_nop); - MEM_WriteByte(oCAIHuman__BowMode_g2ctrlCheck+1, ASMINT_OP_nop); - MEM_WriteByte(oCAIHuman__BowMode_g2ctrlCheck+2, ASMINT_OP_nop); - MEM_WriteByte(oCAIHuman__BowMode_g2ctrlCheck+3, ASMINT_OP_nop); - MEM_WriteByte(oCAIHuman__BowMode_g2ctrlCheck+4, ASMINT_OP_nop); - MEM_WriteByte(oCAIHuman__BowMode_g2ctrlCheck+5, ASMINT_OP_nop); + repeat(i, 6); var int i; + MEM_WriteByte(oCAIHuman__BowMode_g2ctrlCheck+i, ASMINT_OP_nop); + end; if (scheme == 2) { // Gothic 2 controls enabled: Mimic the Gothic 1 controls but change the keys @@ -352,11 +349,9 @@ func void GFA_DisableMagicDuringStrafing(var int on) { if (on) { // Disable magic combat during default strafing - MEM_WriteByte(oCNpc__EV_Strafe_magicCombat, ASMINT_OP_nop); // Remove call to oCNpc::FightAttackMagic() - MEM_WriteByte(oCNpc__EV_Strafe_magicCombat+1, ASMINT_OP_nop); - MEM_WriteByte(oCNpc__EV_Strafe_magicCombat+2, ASMINT_OP_nop); - MEM_WriteByte(oCNpc__EV_Strafe_magicCombat+3, ASMINT_OP_nop); - MEM_WriteByte(oCNpc__EV_Strafe_magicCombat+4, ASMINT_OP_nop); + repeat(i, 5); var int i; + MEM_WriteByte(oCNpc__EV_Strafe_magicCombat+i, ASMINT_OP_nop); // Remove call to oCNpc::FightAttackMagic() + end; } else { MEM_WriteByte(oCNpc__EV_Strafe_magicCombat, /*E8*/ 232); // Revert to default call MEM_WriteByte(oCNpc__EV_Strafe_magicCombat+1, /*A0*/ 160); @@ -487,6 +482,23 @@ func void GFA_FixOpenInventory() { }; +/* + * Modify the attack run turning to only allow it for the player. This function hooks oCNpc::EV_AttackRun() at an offset + * where the player can turn while performing an attack run. This function is only called for Gothic 2. + */ +func void GFA_FixNpcAttackRun() { + var C_Npc slf; slf = _^(ESI); + if (Npc_IsPlayer(slf)) { + const int call = 0; var int zero; + if (CALL_Begin(call)) { + CALL_IntParam(_@(zero)); + CALL__thiscall(_@(ECX), oCAIHuman__PC_Turnings); + call = CALL_End(); + }; + }; +}; + + /* * Reset spell FX when interrupting investing/casting by the default strafing. This function hooks oCNpc::EV_Strafe() at * an offset where the fight mode is checked. oCNpc::EV_Strafe() is only called for the player. diff --git a/_work/data/Scripts/Content/GFA/_intern/init.d b/_work/data/Scripts/Content/GFA/_intern/init.d index cfc6d03..70f89e1 100644 --- a/_work/data/Scripts/Content/GFA/_intern/init.d +++ b/_work/data/Scripts/Content/GFA/_intern/init.d @@ -62,11 +62,9 @@ func void GFA_InitFeatureFreeAiming() { // Aiming condition (detect aiming onset and overwrite aiming condition if GFA_STRAFING) MemoryProtectionOverride(oCAIHuman__BowMode_aimCondition, 5); - MEM_WriteByte(oCAIHuman__BowMode_aimCondition, ASMINT_OP_nop); // Erase standing condition to make room for hook - MEM_WriteByte(oCAIHuman__BowMode_aimCondition+1, ASMINT_OP_nop); - MEM_WriteByte(oCAIHuman__BowMode_aimCondition+2, ASMINT_OP_nop); - MEM_WriteByte(oCAIHuman__BowMode_aimCondition+3, ASMINT_OP_nop); - MEM_WriteByte(oCAIHuman__BowMode_aimCondition+4, ASMINT_OP_nop); + repeat(i, 5); var int i; + MEM_WriteByte(oCAIHuman__BowMode_aimCondition+i, ASMINT_OP_nop); // Erase condition to make room for hook + end; HookEngineF(oCAIHuman__BowMode_aimCondition, 5, GFA_RangedAimingCondition); // Replace condition with own // Gothic 2 controls @@ -163,13 +161,9 @@ func void GFA_InitFeatureCustomCollisions() { HookEngineF(oCAIArrow__ReportCollisionToAI_hitChc, 6, GFA_CC_ProjectileCollisionWithNpc); // Hit reg/coll on NPCs if (GOTHIC_BASE_VERSION == 1) { MemoryProtectionOverride(oCAIArrow__ReportCollisionToAI_destroyPrj, 7); // Disable destroying of projectiles - MEM_WriteByte(oCAIArrow__ReportCollisionToAI_destroyPrj, ASMINT_OP_nop); // Disable fixed destruction - MEM_WriteByte(oCAIArrow__ReportCollisionToAI_destroyPrj+1, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrow__ReportCollisionToAI_destroyPrj+2, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrow__ReportCollisionToAI_destroyPrj+3, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrow__ReportCollisionToAI_destroyPrj+4, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrow__ReportCollisionToAI_destroyPrj+5, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrow__ReportCollisionToAI_destroyPrj+6, ASMINT_OP_nop); + repeat(i, 7); var int i; + MEM_WriteByte(oCAIArrow__ReportCollisionToAI_destroyPrj+i, ASMINT_OP_nop); // Disable fixed destruction + end; HookEngineF(oCAIArrow__ReportCollisionToAI_collAll, 8, GFA_CC_ProjectileCollisionWithWorld); // Collision world MemoryProtectionOverride(oCAIArrow__ReportCollisionToAI_keepPlyStrp, 2); // Keep poly strip after coll MEM_WriteByte(oCAIArrow__ReportCollisionToAI_keepPlyStrp, /*EB*/ 235); // jmp @@ -178,20 +172,12 @@ func void GFA_InitFeatureCustomCollisions() { // Gothic 2 MemoryProtectionOverride(oCAIArrowBase__ReportCollisionToAI_PFXon1, 7); // Prevent too early setting of dust PFX MemoryProtectionOverride(oCAIArrowBase__ReportCollisionToAI_PFXon2, 7); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon1, ASMINT_OP_nop); // First occurrence - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon1+1, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon1+2, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon1+3, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon1+4, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon1+5, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon1+6, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon2, ASMINT_OP_nop); // Second occurrence - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon2+1, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon2+2, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon2+3, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon2+4, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon2+5, ASMINT_OP_nop); - MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon2+6, ASMINT_OP_nop); + repeat(i, 7); + MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon1+i, ASMINT_OP_nop); // First occurrence + end; + repeat(i, 7); + MEM_WriteByte(oCAIArrowBase__ReportCollisionToAI_PFXon2+i, ASMINT_OP_nop); // Second occurrence + end; HookEngineF(oCAIArrowBase__ReportCollisionToAI_collVob, 5, GFA_CC_ProjectileCollisionWithWorld); // Vobs HookEngineF(oCAIArrowBase__ReportCollisionToAI_collWld, 5, GFA_CC_ProjectileCollisionWithWorld); // Static world MemoryProtectionOverride(oCAIArrowBase__ReportCollisionToAI_collNpc, 2); // Set collision behavior on NPCs @@ -252,13 +238,9 @@ func void GFA_InitDamageBehavior() { func void GFA_InitFixDroppedProjectileAI() { MEM_Info("Initializing dropped projectiles AI bug fix."); MemoryProtectionOverride(oCAIVobMove__DoAI_stopMovement, 7); // First erase a call, to make room for hook - MEM_WriteByte(oCAIVobMove__DoAI_stopMovement, ASMINT_OP_nop); - MEM_WriteByte(oCAIVobMove__DoAI_stopMovement+1, ASMINT_OP_nop); - MEM_WriteByte(oCAIVobMove__DoAI_stopMovement+2, ASMINT_OP_nop); - MEM_WriteByte(oCAIVobMove__DoAI_stopMovement+3, ASMINT_OP_nop); - MEM_WriteByte(oCAIVobMove__DoAI_stopMovement+4, ASMINT_OP_nop); - MEM_WriteByte(oCAIVobMove__DoAI_stopMovement+5, ASMINT_OP_nop); - MEM_WriteByte(oCAIVobMove__DoAI_stopMovement+6, ASMINT_OP_nop); + repeat(i, 7); var int i; + MEM_WriteByte(oCAIVobMove__DoAI_stopMovement+i, ASMINT_OP_nop); + end; HookEngineF(oCAIVobMove__DoAI_stopMovement, 7, GFA_FixDroppedProjectileAI); // Re-write what has been overwritten }; @@ -269,15 +251,35 @@ func void GFA_InitFixDroppedProjectileAI() { func void GFA_InitFixOpenInventory() { MEM_Info("Initializing open inventory bug fix."); MemoryProtectionOverride(oCGame__HandleEvent_openInvCheck, 5); // First erase a call, to make room for hook - MEM_WriteByte(oCGame__HandleEvent_openInvCheck, ASMINT_OP_nop); - MEM_WriteByte(oCGame__HandleEvent_openInvCheck+1, ASMINT_OP_nop); - MEM_WriteByte(oCGame__HandleEvent_openInvCheck+2, ASMINT_OP_nop); - MEM_WriteByte(oCGame__HandleEvent_openInvCheck+3, ASMINT_OP_nop); - MEM_WriteByte(oCGame__HandleEvent_openInvCheck+4, ASMINT_OP_nop); + repeat(i, 5); var int i; + MEM_WriteByte(oCGame__HandleEvent_openInvCheck+i, ASMINT_OP_nop); + end; HookEngineF(oCGame__HandleEvent_openInvCheck, 5, GFA_FixOpenInventory); // Re-write what has been overwritten }; +/* + * Prevent the player from controlling the turning of NPCs that are performing an attack run. This inherent Gothic 2 bug + * becomes very obvious with free aiming and thus needs to be fixed. + * To still allow the player to turn while performing an attack run, the solution from the link below is extended, to + * squeeze in a check whether the character in question is the player. + * + * Inspired by: http://forum.worldofplayers.de/forum/threads/879891?p=14886885 + */ +func void GFA_InitFixNpcAttackRun() { + if (GOTHIC_BASE_VERSION != 2) { + return; + }; + + MEM_Info("Initializing NPC attack-run turning bug fix."); + MemoryProtectionOverride(oCNpc__EV_AttackRun_playerTurn, 7); // Erase call to oCAIHuman::PC_Turnings() + repeat(i, 7); var int i; + MEM_WriteByte(oCNpc__EV_AttackRun_playerTurn+i, ASMINT_OP_nop); + end; + HookEngineF(oCNpc__EV_AttackRun_playerTurn, 7, GFA_FixNpcAttackRun); // Re-write what has been overwritten +}; + + /* * Initializations to perform only once every session. This function overwrites memory protection at certain addresses, * and registers hooks and console commands, all depending on the selected features (see config\settings.d). The @@ -332,6 +334,9 @@ func int GFA_InitOnce() { // Fix open inventory bug GFA_InitFixOpenInventory(); + // Fix player turning NPCs on attack run + GFA_InitFixNpcAttackRun(); + // }; // Successfully initialized diff --git a/_work/data/Scripts/Content/GFA/_intern/offsets_G1.d b/_work/data/Scripts/Content/GFA/_intern/offsets_G1.d index 99a5cf6..2871d9e 100644 --- a/_work/data/Scripts/Content/GFA/_intern/offsets_G1.d +++ b/_work/data/Scripts/Content/GFA/_intern/offsets_G1.d @@ -110,6 +110,7 @@ const int oCAIHuman__BowMode_g2ctrlCheck = 0; const int oCAIHuman__BowMode_shootingKey = 6359374; //0x61094E // Not used for Gothic 1 const int oCAIHuman__MagicMode_turnToTarget = 4641584; //0x46D330 const int oCAIHuman__PC_ActionMove_aimingKey = 6373222; //0x613F66 // Not used for Gothic 1 +const int oCAIHuman__PC_Turnings = 6375424; //0x614800 // Not used for Gothic 1 const int zCCollObjectLevelPolys__s_oCollObjClass = 8861152; //0x8735E0 const int zCWorld__AdvanceClock = 6257280; //0x5F7A80 // Hook len 10 @@ -144,6 +145,7 @@ const int oCNpc__OnDamage_Anim_stumbleAniName = 7620508; //0x74479C // H const int oCNpc__OnDamage_Anim_gotHitAniName = 7620670; //0x74483E // Hook len 5 const int oCNpc__SetWeaponMode_player = 6907265; //0x696581 // Hook len 6 const int oCNpc__SetWeaponMode2_walkmode = 6905437; //0x695E5D // Hook len 6 +const int oCNpc__EV_AttackRun_playerTurn = 0; // Does not exist in Gothic 1 const int oCNpc__EV_Strafe_commonOffset = 7661795; //0x74E8E3 // Hook len 5 const int oCNpc__EV_Strafe_g2ctrl = 0; // Does not exist in Gothic 1 const int oCNpc__Interrupt_stopAnis = 6891924; //0x692994 // Hook len 5 diff --git a/_work/data/Scripts/Content/GFA/_intern/offsets_G2.d b/_work/data/Scripts/Content/GFA/_intern/offsets_G2.d index 0a3ed25..0f22608 100644 --- a/_work/data/Scripts/Content/GFA/_intern/offsets_G2.d +++ b/_work/data/Scripts/Content/GFA/_intern/offsets_G2.d @@ -110,6 +110,7 @@ const int oCAIHuman__BowMode_g2ctrlCheck = 6905643; //0x695F2B const int oCAIHuman__BowMode_shootingKey = 6906610; //0x6962F2 const int oCAIHuman__MagicMode_turnToTarget = 0; // Does not exist in Gothic 2 const int oCAIHuman__PC_ActionMove_aimingKey = 6922427; //0x69A0BB +const int oCAIHuman__PC_Turnings = 6924608; //0x69A940 const int zCCollObjectLevelPolys__s_oCollObjClass = 9274192; //0x8D8350 const int zCWorld__AdvanceClock = 6447328; //0x6260E0 // Hook len 10 @@ -144,6 +145,7 @@ const int oCNpc__OnDamage_Anim_stumbleAniName = 6784975; //0x6787CF // H const int oCNpc__OnDamage_Anim_gotHitAniName = 6785116; //0x67885C // Hook len 5 const int oCNpc__SetWeaponMode_player = 7575921; //0x739971 // Hook len 6 const int oCNpc__SetWeaponMode2_walkmode = 7574116; //0x739264 // Hook len 6 +const int oCNpc__EV_AttackRun_playerTurn = 7674197; //0x751955 // Hook len 7 const int oCNpc__EV_Strafe_commonOffset = 6831608; //0x683DF8 // Hook len 5 const int oCNpc__EV_Strafe_g2ctrl = 6832857; //0x6842D9 // Hook len 6 const int oCNpc__Interrupt_stopAnis = 7560261; //0x735C45 // Hook len 5