diff --git a/_work/data/Scripts/Content/GFA/_intern/collectable.d b/_work/data/Scripts/Content/GFA/_intern/collectable.d index 8aefc48..609f952 100644 --- a/_work/data/Scripts/Content/GFA/_intern/collectable.d +++ b/_work/data/Scripts/Content/GFA/_intern/collectable.d @@ -46,8 +46,10 @@ func void GFA_RP_KeepProjectileInWorld() { }; // Always keep the projectile alive, set infinite life time - MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, -1082130432); // -1 - projectile._zCVob_visualAlpha = FLOATONE; // Fully visible + if (gef(MEM_ReadInt(arrowAI+oCAIArrowBase_lifeTime_offset), FLOATNULL)) { + MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATONE_NEG); + projectile._zCVob_visualAlpha = FLOATONE; // Fully visible + }; // Check if the projectile stopped moving if (!(projectile._zCVob_bitfield[0] & zCVob_bitfield0_physicsEnabled)) { diff --git a/_work/data/Scripts/Content/GFA/_intern/collision.d b/_work/data/Scripts/Content/GFA/_intern/collision.d index d3d0956..d1ec824 100644 --- a/_work/data/Scripts/Content/GFA/_intern/collision.d +++ b/_work/data/Scripts/Content/GFA/_intern/collision.d @@ -121,11 +121,8 @@ func void GFA_CC_ProjectileStuck(var int projectilePtr) { * It is called for both Gothic 1 and Gothic 2. */ func void GFA_CC_ProjectileDestroy(var int arrowAI) { - if (GOTHIC_BASE_VERSION == 1) { - MEM_WriteInt(arrowAI+oCAIArrow_destroyProjectile_offset, 1); // Gothic 1 - } else { - MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATNULL); // Gothic 2 - }; + MEM_WriteInt(arrowAI+oCAIArrow_destroyProjectile_offset, 1); + MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATNULL); }; @@ -227,7 +224,9 @@ func void GFA_CC_ProjectileCollisionWithNpc() { if (collision == DEFLECT) { var oCItem projectile; projectile = _^(MEM_ReadInt(arrowAI+oCAIArrowBase_hostVob_offset)); GFA_CC_ProjectileDeflect(projectile._zCVob_rigidBody); - MEM_WriteInt(arrowAI+oCAIArrow_destroyProjectile_offset, -1); // Mark as deflecting, such that it is ignored + if (GFA_Flags & GFA_REUSE_PROJECTILES) { + MEM_WriteInt(arrowAI+oCAIArrow_destroyProjectile_offset, -1); // Mark as deflecting to ignored it + }; } else { MEM_WriteInt(arrowAI+oCAIArrow_destroyProjectile_offset, 1); // Destroy projectile on impact }; @@ -495,7 +494,7 @@ func void GFA_CC_FadeProjectileVisibility() { if (!(projectile.bitfield[0] & zCVob_bitfield0_physicsEnabled)) { var int arrowAI; arrowAI = ESI; // oCAIArrow* var int lifeTime; lifeTime = MEM_ReadInt(arrowAI+oCAIArrowBase_lifeTime_offset); - if (lifeTime == -1082130432) { // -1 + if (lifeTime == FLOATONE_NEG) { MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATONE); }; }; diff --git a/_work/data/Scripts/Content/GFA/_intern/const.d b/_work/data/Scripts/Content/GFA/_intern/const.d index 084ec40..15ed0bd 100644 --- a/_work/data/Scripts/Content/GFA/_intern/const.d +++ b/_work/data/Scripts/Content/GFA/_intern/const.d @@ -31,10 +31,11 @@ const string GFA_VERSION = "Gothic Free Aim v1.0.1"; const int GFA_LEGO_FLAGS = LeGo_HookEngine // For initializing all hooks - | LeGo_FrameFunctions // For projectile gravity | LeGo_ConsoleCommands // For console commands and debugging + | LeGo_View // For drawing reticle | LeGo_Random // For scattering and other uses of random numbers - | LeGo_Draw3D; // For debug visualizations + | LeGo_Draw3D // For debug visualizations (may be removed) + | LeGo_FrameFunctions; // For hitmarker in config (may be removed) var int GFA_Flags; // Flags for initialization of GFA const int GFA_RANGED = 1<<0; // Free aiming for ranged combat (bow and crossbow) @@ -156,6 +157,7 @@ var int GFA_DebugBoneOBBox; // Handle of bone or /* Numerical constants */ +const int FLOATONE_NEG = -1082130432; // -1 as float const int FLOAT1C = 1120403456; // 100 as float const int FLOAT3C = 1133903872; // 300 as float const int FLOAT1K = 1148846080; // 1000 as float diff --git a/_work/data/Scripts/Content/GFA/_intern/init.d b/_work/data/Scripts/Content/GFA/_intern/init.d index 02ce2d1..d1f7345 100644 --- a/_work/data/Scripts/Content/GFA/_intern/init.d +++ b/_work/data/Scripts/Content/GFA/_intern/init.d @@ -47,6 +47,10 @@ func void GFA_InitFeatureFreeAiming() { HookEngineF(oCAIHuman__BowMode_notAiming, 6, GFA_RangedIdle); // Fix focus collection while not aiming HookEngineF(oCAIHuman__BowMode_interpolateAim, 5, GFA_RangedAiming); // Interpolate aiming animation HookEngineF(oCAIArrow__SetupAIVob, 6, GFA_SetupProjectile); // Setup projectile trajectory (shooting) + writeNOP(oCAIArrowBase__DoAI_setLifeTime, 7); + MEM_WriteByte(oCAIArrowBase__DoAI_setLifeTime, /*85*/ 133); // test eax, eax + MEM_WriteByte(oCAIArrowBase__DoAI_setLifeTime+1, /*C0*/ 192); + HookEngineF(oCAIArrowBase__DoAI_setLifeTime, 7, GFA_EnableProjectileGravity); // Enable gravity after some time HookEngineF(oCAIArrow__ReportCollisionToAI_collAll, 8, GFA_ResetProjectileGravity); // Reset gravity on impact HookEngineF(oCAIArrow__ReportCollisionToAI_hitChc, 6, GFA_OverwriteHitChance); // Manipulate hit chance MemoryProtectionOverride(oCAIHuman__CheckFocusVob_ranged, 1); // Prevent toggling focus in ranged combat @@ -304,13 +308,10 @@ func int GFA_InitOnce() { /* - * Initializations to perfrom on every game start, level change and loading of saved games. This function is called from + * Initializations to perform on every game start, level change and loading of saved games. This function is called from * GFA_Init(). */ func void GFA_InitAlways() { - // Pause frame functions when in menu - Timer_SetPauseInMenu(1); - // Retrieve trace ray interval: Recalculate trace ray intersection every x ms GFA_AimRayInterval = STR_ToInt(MEM_GetGothOpt("GFA", "focusUpdateIntervalMS")); if (GFA_AimRayInterval > 500) { diff --git a/_work/data/Scripts/Content/GFA/_intern/offsets_G1.d b/_work/data/Scripts/Content/GFA/_intern/offsets_G1.d index e9e4ff7..70c1c1f 100644 --- a/_work/data/Scripts/Content/GFA/_intern/offsets_G1.d +++ b/_work/data/Scripts/Content/GFA/_intern/offsets_G1.d @@ -129,6 +129,7 @@ const int oCAIArrow__DoAI_rtn = 6395210; //0x61954A // H const int oCAIArrow__ReportCollisionToAI_collAll = 6395474; //0x619652 // Hook len 8 const int oCAIArrow__ReportCollisionToAI_hitChc = 6395775; //0x61977F // Hook len 6 const int oCAIArrow__ReportCollisionToAI_damage = 6395861; //0x6197D5 // Hook len 7 +const int oCAIArrowBase__DoAI_setLifeTime = 6393028; //0x618CC4 // Hook len 7 const int oCAIArrowBase__ReportCollisionToAI_hitNpc = 6395866; //0x6197DA // Hook len 5 const int oCAIArrowBase__ReportCollisionToAI_hitVob = 0; // Does not exist in Gothic 1 const int oCAIArrowBase__ReportCollisionToAI_hitWld = 0; // Does not exist in Gothic 1 diff --git a/_work/data/Scripts/Content/GFA/_intern/offsets_G2.d b/_work/data/Scripts/Content/GFA/_intern/offsets_G2.d index a1f7e63..2f437eb 100644 --- a/_work/data/Scripts/Content/GFA/_intern/offsets_G2.d +++ b/_work/data/Scripts/Content/GFA/_intern/offsets_G2.d @@ -129,6 +129,7 @@ const int oCAIArrow__DoAI_rtn = 6952073; //0x6A1489 // H const int oCAIArrow__ReportCollisionToAI_collAll = 6949315; //0x6A09C3 // Hook len 8 const int oCAIArrow__ReportCollisionToAI_hitChc = 6953482; //0x6A1A0A // Hook len 6 const int oCAIArrow__ReportCollisionToAI_damage = 6953711; //0x6A1AEF // Hook len 7 +const int oCAIArrowBase__DoAI_setLifeTime = 6949017; //0x6A0899 // Hook len 7 const int oCAIArrowBase__ReportCollisionToAI_hitNpc = 6949832; //0x6A0BC8 // Hook len 5 const int oCAIArrowBase__ReportCollisionToAI_hitVob = 6949929; //0x6A0C29 // Hook len 5 const int oCAIArrowBase__ReportCollisionToAI_hitWld = 6949460; //0x6A0A54 // Hook len 5 @@ -193,7 +194,7 @@ const int oCAIArrowBase_hostVob_offset = 60; //0x003C const int oCAIArrowBase_creatingImpactFX_offset = 64; //0x0040 const int oCAIArrowBase_hasHit_offset = 84; //0x0054 const int oCAIArrow_origin_offset = 92; //0x005C -const int oCAIArrow_destroyProjectile_offset = 96; //0x0060 // Not used for Gothic 2 +const int oCAIArrow_destroyProjectile_offset = 96; //0x0060 const int zCRigidBody_mass_offset = 0; //0x0000 const int zCRigidBody_xPos_offset = 80; //0x0050 diff --git a/_work/data/Scripts/Content/GFA/_intern/rangedShooting.d b/_work/data/Scripts/Content/GFA/_intern/rangedShooting.d index 0cf86db..0a88263 100644 --- a/_work/data/Scripts/Content/GFA/_intern/rangedShooting.d +++ b/_work/data/Scripts/Content/GFA/_intern/rangedShooting.d @@ -387,8 +387,11 @@ func void GFA_SetupProjectile() { // Calculate the air time at which to apply the gravity, by the maximum air time GFA_TRAJECTORY_ARC_MAX. Because // drawForce is a percentage, GFA_TRAJECTORY_ARC_MAX is first multiplied by 100 and later divided by 10000 var int dropTime; dropTime = (drawForce*(GFA_TRAJECTORY_ARC_MAX*100))/10000; - // Create a timed frame function to apply the gravity to the projectile after the calculated air time - FF_ApplyOnceExtData(GFA_EnableProjectileGravity, dropTime, 1, rBody); + if (dropTime < 2) { + dropTime = 2; // Timer value should not be 1 or 0, otherwise gravity is never applied + }; + // Use life time to apply gravity after the calculated air time, see GFA_EnableProjectileGravity() + MEM_WriteInt(ECX+oCAIArrowBase_lifeTime_offset, negf(mkf(dropTime))); // Set the gravity to the projectile. Again: The gravity does not take effect until it is activated MEM_WriteInt(rBody+zCRigidBody_gravity_offset, mulf(castToIntf(GFA_PROJECTILE_GRAVITY), gravityMod)); @@ -493,31 +496,72 @@ func void GFA_SetupProjectile() { /* - * This is a frame function timed by draw force and is responsible for applying gravity to a projectile after a certain - * air time as determined in GFA_SetupProjectile(). The gravity is merely turned on, the gravity strength itself is set - * in GFA_SetupProjectile(). + * This function applies gravity to a projectile after a certain air time as determined in GFA_SetupProjectile(). The + * gravity is merely turned on, the gravity strength itself is set in GFA_SetupProjectile(). This function hooks + * oCAIArrowBase::DoAI() at an address where the life time and projectile visibility is decreased. With EAX it is + * later determined whether to decrease the life timer and visibility. */ -func void GFA_EnableProjectileGravity(var int rigidBody) { - if (!rigidBody) { - return; - }; - - // Check validity of the zCRigidBody pointer by its first class variable (value is always 10.0). This is necessary - // for loading a saved game, as the pointer will not point to a zCRigidBody address anymore. - if (roundf(MEM_ReadInt(rigidBody+zCRigidBody_mass_offset)) != 10) { +func void GFA_EnableProjectileGravity() { + var int projectilePtr; projectilePtr = EBP; + if (!projectilePtr) { + EAX = TRUE; return; }; - - // Do not add gravity if projectile already stopped moving - if (MEM_ReadInt(rigidBody+zCRigidBody_velocity_offset) == FLOATNULL) // zCRigidBody.velocity[3] - && (MEM_ReadInt(rigidBody+zCRigidBody_velocity_offset+4) == FLOATNULL) - && (MEM_ReadInt(rigidBody+zCRigidBody_velocity_offset+8) == FLOATNULL) { - return; + var zCVob projectile; projectile = _^(projectilePtr); // oCItem* + + var int arrowAI; arrowAI = ESI; // oCAIArrow* + var int lifeTime; lifeTime = MEM_ReadInt(arrowAI+oCAIArrowBase_lifeTime_offset); + // Gravity counter: lifeTime < -1 + // Mid-flight: lifeTime = -1 + // Visibility counter: 0 <= lifeTime <= 1 + + if (gef(lifeTime, FLOATNULL)) { + // lifeTime >= 0: Decrease visibility? + if (MEM_ReadInt(arrowAI+oCAIArrow_destroyProjectile_offset) == 1) + || (!(GFA_Flags & GFA_REUSE_PROJECTILES)) { + // Yes (destroy or no collectable feature) + if (gf(lifeTime, FLOATONE)) { + // Reset life time to 1 + MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATONE); + }; + // Decrease life time and visibility (default behavior) + EAX = TRUE; + } else { + // No (collectable feature). Reset life time to -1 + MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATONE_NEG); + EAX = FALSE; + }; + } else if (!(projectile.bitfield[0] & zCVob_bitfield0_physicsEnabled)) { + // Stopped moving: Decrease visibility? + if (GFA_Flags & GFA_REUSE_PROJECTILES) { + // No (collectable feature). Reset life time to -1 + MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATONE_NEG); + EAX = FALSE; + } else { + // Yes (no collectable feature). Reset life time to 1 + MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATONE); + // Decrease life time and visibility (default behavior) + EAX = TRUE; + }; + } else if (lf(lifeTime, FLOATONE_NEG)) { + // lifeTime < -1: Continue counting flight time until gravity drop + lifeTime = addf(lifeTime, MEM_Timer.frameTimeFloat); + if (gef(lifeTime, FLOATONE_NEG)) { + lifeTime = FLOATONE_NEG; + // Apply gravity. Reset life time to -1 + var int rigidBody; rigidBody = projectile.rigidBody; // zCRigidBody* + if (rigidBody) { + var int bitfield; bitfield = MEM_ReadByte(rigidBody+zCRigidBody_bitfield_offset); + MEM_WriteByte(rigidBody+zCRigidBody_bitfield_offset, bitfield | zCRigidBody_bitfield_gravityActive); + }; + }; + MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, lifeTime); + // Do not decrease visibility + EAX = FALSE; + } else { // Mid-flight: lifeTime == -1.0 + // Do not decrease visibility + EAX = FALSE; }; - - // Turn on gravity - var int bitfield; bitfield = MEM_ReadByte(rigidBody+zCRigidBody_bitfield_offset); - MEM_WriteByte(rigidBody+zCRigidBody_bitfield_offset, bitfield | zCRigidBody_bitfield_gravityActive); }; @@ -530,18 +574,17 @@ func void GFA_EnableProjectileGravity(var int rigidBody) { func void GFA_ResetProjectileGravity() { var int arrowAI; arrowAI = MEMINT_SwitchG1G2(ESI, ECX); var oCItem projectile; projectile = _^(MEM_ReadInt(arrowAI+oCAIArrowBase_hostVob_offset)); - if (!projectile._zCVob_rigidBody) { - return; - }; var int rigidBody; rigidBody = projectile._zCVob_rigidBody; - - // Better safe than writing to an invalid address - if (FF_ActiveData(GFA_EnableProjectileGravity, rigidBody)) { - FF_RemoveData(GFA_EnableProjectileGravity, rigidBody); + if (!rigidBody) { + return; }; // Reset projectile gravity (zCRigidBody.gravity) after collision (oCAIArrow.collision) to default MEM_WriteInt(rigidBody+zCRigidBody_gravity_offset, FLOATONE); + if (lf(MEM_ReadInt(arrowAI+oCAIArrowBase_lifeTime_offset), FLOATONE_NEG)) { + // Reset gravity timer + MEM_WriteInt(arrowAI+oCAIArrowBase_lifeTime_offset, FLOATONE_NEG); + }; // Remove trail strip FX if (GOTHIC_BASE_VERSION == 1) {