/* * G2 Free Aim v0.1.2 - Free aiming for the video game Gothic 2 by Piranha Bytes * Copyright (C) 2016 mud-freak (@szapp) * * This file is part of G2 Free Aim. * * * G2 Free Aim is free software: you can redistribute it and/or modify * it under the terms of the MIT License. * On redistribution this notice must remain intact and all copies must * identify the original author. * * G2 Free Aim is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MIT License for more details. * * You should have received a copy of the MIT License * along with G2 Free Aim. If not, see . * * * WARNING: Do not edit this file. All necessary adjustments can and should be performed in freeAim\config.d * You should not need to edit anything in this file. Possible updates and revisions will replace this file. * Proceed at you own risk: On modifying this file, free aiming will most certainly become unstable. */ /* Free aim settings, do not modify! Change the settings in freeAim\config.d */ const string FREEAIM_VERSION = "G2 Free Aim v0.1.2"; // Do not change under any circumstances const int FREEAIM_REUSE_PROJECTILES = 1; // Enable collection and re-using of shot projectiles const int FREEAIM_DISABLE_SPELLS = 0; // If true, free aiming is disabled for spells const int FREEAIM_DRAWTIME_MAX = 1200; // Max draw time (ms): When is the bow fully drawn const int FREEAIM_TRAJECTORY_ARC_MAX = 400; // Max time (ms) after which the trajectory drops off const float FREEAIM_ROTATION_SCALE = 0.16; // Turn rate. Non-weapon mode: 0.2 (zMouseRotationScale) const float FREEAIM_SCATTER_DEG = 2.2; // Maximum scatter radius in degrees const int FREEAIM_TRIGGER_COLL_FIX = 1; // Apply trigger collision fix (disable collision) const string FREEAIM_CAMERA = "CamModFreeAim"; // CCamSys_Def script instance for free aim const int FREEAIM_CAMERA_X_SHIFT = 0; // Camera is set to shoulderview (not recommended) const int FREEAIM_HITDETECTION_EXP = 0; // Additional hit detection test (EXPERIMENTAL) const int FREEAIM_DEBUG_WEAKSPOT = 0; // Visualize weakspot bbox and trajectory const int FREEAIM_DEBUG_TRACERAY = 0; // Visualize trace ray bboxes and trajectory const int FREEAIM_DEBUG_CONSOLE = 1; // Console command for debugging. Turn off in final mod const float FREEAIM_PROJECTILE_GRAVITY = 0.1; // The gravity decides how fast the projectile drops const int FREEAIM_DRAWTIME_READY = 650; // Time offset for readying the bow. Fixed by animation const int FREEAIM_DRAWTIME_RELOAD = 1110; // Time offset for reloading the bow. Fixed by animation const int FREEAIM_RETICLE_MIN_SIZE = 32; // Smallest reticle size in pixels const int FREEAIM_RETICLE_MAX_SIZE = 64; // Biggest reticle size in pixels const string FREEAIM_TRAIL_FX = "freeAim_TRAIL"; // Trailstrip FX. Should not be changed const string FREEAIM_BREAK_FX = "freeAim_DESTROY"; // FX of projectile breaking on impact with world const int FREEAIM_MAX_DIST = 5000; // 50m. Shooting/reticle adjustments. Do not change const int FREEAIM_ACTIVE_PREVFRAME = 0; // Internal. Do not change const int FREEAIM_FOCUS_SPELL_FREE = 0; // Internal. Do not change const int FREEAIM_FOCUS_COLLECTION = 1; // Internal. Do not change (change in ini-file) const int FREEAIM_ARROWAI_REDIRECT = 0; // Used to redirect call-by-reference var. Do not change const int FLOAT1C = 1120403456; // 100 as float const int FLOAT3C = 1133903872; // 300 as float const int FLOAT1K = 1148846080; // 1000 as float var int freeAimDebugWSBBox[6]; // Weaksopt boundingbox for debug visualization var int freeAimDebugWSTrj[6]; // Projectile trajectory for debug visualization var int freeAimDebugTRBBox[6]; // Trace ray intersection for debug visualization var int freeAimDebugTRTrj[6]; // Trace ray trajectory var int freeAimReticleHndl; // Holds the handle of the reticle var int freeAimBowDrawOnset; // Time onset of drawing the bow const int FREEAIM_G2CTRL_PREVFRAME = 0; // Internal. Do not change /* Helper class: Critical hit definitions */ class Weakspot { var string node; var int dimX; var int dimY; var int bDmg; }; /* Helper class: Reticle definitions */ class Reticle { var string texture; var int size; var int color; }; /* All addresses used (gothic2). In case of a gothic1 port: There are a lot of hardcoded address offsets in the code! */ const int zCVob__SetPositionWorld = 6404976; //0x61BB70 const int zCVob__GetRigidBody = 6285664; //0x5FE960 const int zCVob__TraceRay = 6291008; //0x5FFE40 const int zCArray_zCVob__IsInList = 7159168; //0x6D3D80 const int zCWorld__TraceRayNearestHit_Vob = 6430624; //0x621FA0 const int oCWorld__AddVobAsChild = 7863856; //0x77FE30 const int zCMaterial__vtbl = 8593940; //0x832214 const int zCTrigger_vtbl = 8627196; //0x83A3FC const int zCTriggerScript_vtbl = 8582148; //0x82F404 const int zString_CamModRanged = 9234704; //0x8CE910 const int zString_CamModMagic = 9235048; //0x8CEA68 const int oCAniCtrl_Human__Turn = 7005504; //0x6AE540 const int oCAniCtrl_Human__GetLayerAni = 7011712; //0x6AFD80 const int oCNpc__GetAngles = 6820528; //0x6812B0 const int oCNpc__SetFocusVob = 7547744; //0x732B60 const int oCNpc__SetEnemy = 7556032; //0x734BC0 const int oCNpc__GetModel = 7571232; //0x738720 const int oCItem___CreateNewInstance = 7423040; //0x714440 const int oCItem__InitByScript = 7412688; //0x711BD0 const int oCItem__InsertEffect = 7416896; //0x712C40 const int oCItem__RemoveEffect = 7416832; //0x712C00 const int oCMag_Book__GetSelectedSpell = 4683648; //0x477780 const int zCModel__SearchNode = 5758960; //0x57DFF0 const int zCModel__GetBBox3DNodeWorld = 5738736; //0x5790F0 const int zCModel__GetNodePositionWorld = 5738816; //0x579140 //const int zTBBox3D__Draw = 5529312; //0x545EE0 //const int zCLineCache__Line3D = 5289040; //0x50B450 //const int zlineCache = 9257720; //0x8D42F8 const int oCGame__s_bUseOldControls = 9118144; //0x8B21C0 const int mouseEnabled = 9248108; //0x8D1D6C const int mouseSensX = 9019720; //0x89A148 const int mouseDeltaX = 9246300; //0x8D165C const int projectileDeflectOffNpcAddr = 6949734; //0x6A0B66 //const int zCWorld__AdvanceClock = 6447328; //0x6260E0 // Hook length 10 const int oCAniCtrl_Human__InterpolateCombineAni = 7037296; //0x6B6170 // Hook length 5 const int oCAIArrow__SetupAIVob = 6951136; //0x6A10E0 // Hook length 6 const int oCAIArrow__CanThisCollideWith = 6952080; //0x6A1490 // Hook length 7 const int oCAIHuman__BowMode = 6905600; //0x695F00 // Hook length 6 const int oCAIArrowBase__DoAI = 6948416; //0x6A0640 // Hook length 7 const int onArrowHitNpcAddr = 6949832; //0x6A0BC8 // Hook length 5 const int onArrowHitVobAddr = 6949929; //0x6A0C29 // Hook length 5 const int onArrowHitStatAddr = 6949460; //0x6A0A54 // Hook length 5 const int onArrowCollVobAddr = 6949440; //0x6A0C18 // Hook length 5 const int onArrowCollStatAddr = 6949912; //0x6A0A40 // Hook length 5 const int onArrowHitChanceAddr = 6953483; //0x6A1A0B // Hook length 5 const int onArrowDamageAddr = 6953621; //0x6A1A95 // Hook length 7 const int onDmgAnimationAddr = 6774593; //0x675F41 // Hook length 9 const int oCNpcFocus__SetFocusMode = 7072800; //0x6BEC20 // Hook length 7 const int oCAIHuman__MagicMode = 4665296; //0x472FD0 // Hook length 7 const int oCSpell__Setup_484BA9 = 4737961; //0x484BA9 // Hook length 6 const int spellAutoTurnAddr = 4665539; //0x4730C3 // Hook length 6 const int mouseUpdate = 5062907; //0x4D40FB // Hook length 5 const int oCAIHuman__BowMode_695F2B = 6905643; //0x695F2B const int oCAIHuman__BowMode_6962F2 = 6906610; //0x6962F2 const int oCAIHuman___WalkCycle_6925892 = 6925892; //0x69AE44 /* Initialize free aim framework */ func void freeAim_Init() { const int hookFreeAim = 0; if (!hookFreeAim) { MEM_Info(""); // Copyright notice in zSpy var int s; s = SB_New(); SB(" "); SB(FREEAIM_VERSION); SB(", Copyright "); SBc(169 /* (C) */); SB(" 2016 mud-freak (@szapp)"); MEM_Info(SB_ToString()); SB_Destroy(); MEM_Info(" "); MEM_Info(" Released under the MIT License."); MEM_Info(" For more details see ."); MEM_Info(""); MEM_Call(freeAimInitConstants); // Customized settings MemoryProtectionOverride(oCAIHuman__BowMode_695F2B, 6); // G2 Controls 0F 84 60 04 00 00 jz loc_00696391 MemoryProtectionOverride(oCAIHuman__BowMode_6962F2, 2); // G2 Controls 6A 03 push 3 MemoryProtectionOverride(oCAIHuman___WalkCycle_6925892, 2); // G2 Ctrls 6A 05 push 5 CC_Register(freeAimVersion, "freeaim version", "print freeaim version info"); CC_Register(freeAimLicense, "freeaim license", "print freeaim license info"); CC_Register(freeAimInfo, "freeaim info", "print freeaim info"); HookEngineF(oCAniCtrl_Human__InterpolateCombineAni, 5, freeAimAnimation); // Update aiming animation HookEngineF(oCAIArrow__SetupAIVob, 6, freeAimSetupProjectile); // Set projectile direction and trajectory HookEngineF(oCAIHuman__BowMode, 6, freeAimManageReticle); // Manage the reticle (on/off) HookEngineF(oCNpcFocus__SetFocusMode, 7, freeAimSwitchMode); // Manage the reticle (on/off) and draw force HookEngineF(mouseUpdate, 5, freeAimManualRotation); // Update the player model rotation by mouse input HookEngineF(oCAIArrowBase__DoAI, 7, freeAimWatchProjectile); // AI loop for each projectile HookEngineF(onArrowDamageAddr, 7, freeAimDetectCriticalHit); // Critical hit detection HookEngineF(onArrowHitChanceAddr, 5, freeAimDoNpcHit); // Decide whether a projectile hits or not MemoryProtectionOverride(projectileDeflectOffNpcAddr, 2); // Collision behavior on npcs HookEngineF(onArrowCollVobAddr, 5, freeAimOnArrowCollide); // Collision behavior on non-npc vob material HookEngineF(onArrowCollStatAddr, 5, freeAimOnArrowCollide); // Collision behavior on static world material HookEngineF(onDmgAnimationAddr , 9, freeAimDmgAnimation); // Disable damage animation while aiming if (!FREEAIM_DISABLE_SPELLS) { HookEngineF(oCAIHuman__MagicMode, 7, freeAimSpellReticle); // Manage focus collection and reticle HookEngineF(oCSpell__Setup_484BA9, 6, freeAimSetupSpell); // Set spell fx direction and trajectory HookEngineF(spellAutoTurnAddr, 6, freeAimDisableSpellAutoTurn); // Prevent auto turning towards target }; if (FREEAIM_DEBUG_CONSOLE) || (FREEAIM_DEBUG_WEAKSPOT) || (FREEAIM_DEBUG_TRACERAY) { // Debug visualization HookEngineF(zCWorld__AdvanceClock, 10, freeAimVisualizeWeakspot); // FrameFunctions hook too early HookEngineF(zCWorld__AdvanceClock, 10, freeAimVisualizeTraceRay); if (FREEAIM_DEBUG_CONSOLE) { // Enable console command for debugging CC_Register(freeAimDebugWeakspot, "debug freeaim weakspot", "turn debug visualization on/off"); CC_Register(freeAimDebugTraceRay, "debug freeaim traceray", "turn debug visualization on/off"); }; }; if (FREEAIM_REUSE_PROJECTILES) { // Because of balancing issues, this is a constant and not a variable HookEngineF(onArrowHitNpcAddr, 5, freeAimOnArrowHitNpc); // Put projectile into inventory HookEngineF(onArrowHitVobAddr, 5, freeAimOnArrowGetStuck); // Keep projectile alive when stuck in vob HookEngineF(onArrowHitStatAddr, 5, freeAimOnArrowGetStuck); // Keep projectile alive when stuck in world }; if (FREEAIM_TRIGGER_COLL_FIX) { // Because by default all triggers react to objects, this is a setting HookEngineF(oCAIArrow__CanThisCollideWith, 7, freeAimTriggerCollisionCheck); // Fix trigger collision bug }; if (!MEM_GothOptExists("FREEAIM", "enabled")) { MEM_SetGothOpt("FREEAIM", "enabled", "1"); }; // If not set if (!MEM_GothOptExists("FREEAIM", "focusEnabled")) { MEM_SetGothOpt("FREEAIM", "focusEnabled", "1"); } else if (!STR_ToInt(MEM_GetGothOpt("FREEAIM", "focusEnabled"))) { FREEAIM_FOCUS_COLLECTION = 0; }; // No focuscollection (performance) not recommended r_DefaultInit(); // Start rng for aiming accuracy hookFreeAim = 1; }; MEM_Info(ConcatStrings(FREEAIM_VERSION, " initialized successfully.")); }; /* Return the active spell instance */ func MEMINT_HelperClass freeAimGetActiveSpellInst(var C_Npc npc) { if (Npc_GetActiveSpell(npc) == -1) { var C_Spell ret; ret = MEM_NullToInst(); MEMINT_StackPushInst(ret); return; }; var int magBookPtr; magBookPtr = MEM_ReadInt(_@(npc)+2324); //0x0914 oCNpc.mag_book const int call = 0; if (CALL_Begin(call)) { CALL__thiscall(_@(magBookPtr), oCMag_Book__GetSelectedSpell); call = CALL_End(); }; _^(CALL_RetValAsPtr()+128); //0x0080 oCSpell.C_Spell }; /* Return whether a spell is eligible for free aiming */ func int freeAimSpellEligible(var C_Spell spell) { if (FREEAIM_DISABLE_SPELLS) || (!_@(spell)) { return FALSE; }; if (spell.targetCollectAlgo != TARGET_COLLECT_FOCUS_FALLBACK_NONE) || (!spell.canTurnDuringInvest) || (!spell.canChangeTargetDuringInvest) { return FALSE; }; return TRUE; // All other cases }; /* Update internal settings when turning free aim on/off in the options */ func void freeAimUpdateSettings(var int on) { MEM_Info("Updating internal free aim settings"); MEM_InitGlobalInst(); // Important as this function will be called during level change, otherwise the game crashes if (on) { Focus_Ranged.npc_azi = 15.0; // Set stricter focus collection MEM_WriteString(zString_CamModRanged, STR_Upper(FREEAIM_CAMERA)); // New camera mode, upper case is important if (!FREEAIM_DISABLE_SPELLS) { MEM_WriteString(zString_CamModMagic, STR_Upper(FREEAIM_CAMERA)); }; FREEAIM_ACTIVE_PREVFRAME = 1; } else { Focus_Ranged.npc_azi = 45.0; // Reset ranged focus collection to standard Focus_Magic.npc_azi = 45.0; Focus_Magic.item_prio = -1; FREEAIM_FOCUS_SPELL_FREE = -1; MEM_WriteString(zString_CamModRanged, "CAMMODRANGED"); // Restore camera mode, upper case is important MEM_WriteString(zString_CamModMagic, "CAMMODMAGIC"); // Also for spells MEM_WriteByte(projectileDeflectOffNpcAddr, /*74*/ 116); // Reset to default collision behavior on npcs MEM_WriteByte(projectileDeflectOffNpcAddr+1, /*3B*/ 59); // jz to 0x6A0BA3 FREEAIM_ACTIVE_PREVFRAME = -1; }; }; /* Update internal settings for Gothic 2 controls */ func void freeAimUpdateSettingsG2Ctrl(var int on) { MEM_Info("Updating internal free aim settings for Gothic 2 controls"); if (on) { // Gothic 2 controls and free aiming enabled: Mimic the Gothic 1 controls but change the keys MEM_WriteByte(oCAIHuman__BowMode_695F2B, ASMINT_OP_nop); // Skip jump to Gothic 2 controls MEM_WriteByte(oCAIHuman__BowMode_695F2B+1, ASMINT_OP_nop); MEM_WriteByte(oCAIHuman__BowMode_695F2B+2, ASMINT_OP_nop); MEM_WriteByte(oCAIHuman__BowMode_695F2B+3, ASMINT_OP_nop); MEM_WriteByte(oCAIHuman__BowMode_695F2B+4, ASMINT_OP_nop); MEM_WriteByte(oCAIHuman__BowMode_695F2B+5, ASMINT_OP_nop); MEM_WriteByte(oCAIHuman__BowMode_6962F2+1, 5); // Overwrite shooting key to action button MEM_WriteByte(oCAIHuman___WalkCycle_6925892+1, /*19*/ 25); // Overwrite aiming key to secondary (parry) button FREEAIM_G2CTRL_PREVFRAME = 1; } else { // Gothic 2 controls or free aiming disabled: Revert to original Gothic 2 controls MEM_WriteByte(oCAIHuman__BowMode_695F2B, /*0F*/ 15); // Revert to G2 controls to default: jz loc_00696391 MEM_WriteByte(oCAIHuman__BowMode_695F2B+1, /*84*/ 132); MEM_WriteByte(oCAIHuman__BowMode_695F2B+2, /*60*/ 96); MEM_WriteByte(oCAIHuman__BowMode_695F2B+3, /*04*/ 4); MEM_WriteByte(oCAIHuman__BowMode_695F2B+4, /*00*/ 0); MEM_WriteByte(oCAIHuman__BowMode_695F2B+5, /*00*/ 0); MEM_WriteByte(oCAIHuman__BowMode_6962F2+1, 3); // Revert to default: push 3 MEM_WriteByte(oCAIHuman___WalkCycle_6925892+1, 5); // Revert to default: push 5 FREEAIM_G2CTRL_PREVFRAME = -1; }; }; /* Check whether free aiming should be activated */ func int freeAimIsActive() { if (!STR_ToInt(MEM_GetGothOpt("FREEAIM", "enabled"))) // Free aiming is disabled in the menu || (!MEM_ReadInt(mouseEnabled)) { // Mouse controls are disabled if (FREEAIM_ACTIVE_PREVFRAME != -1) { freeAimUpdateSettings(0); }; // Update internal settings (turn off) if (FREEAIM_G2CTRL_PREVFRAME != -1) { freeAimUpdateSettingsG2Ctrl(0); }; // Disable extended Gothic 2 Controls return 0; }; if (FREEAIM_ACTIVE_PREVFRAME != 1) { freeAimUpdateSettings(1); }; // Update internal settings (turn on) // Everything below is only reached if free aiming is enabled (but not necessarily active) if (MEM_Game.pause_screen) { return 0; }; // Only when playing if (!InfoManager_HasFinished()) { return 0; }; // Not in dialogs if (!Npc_IsInFightMode(hero, FMODE_FAR)) && (!Npc_IsInFightMode(hero, FMODE_MAGIC)) { return 0; }; // Everything below is only reached if free aiming is enabled and active (player is in respective fight mode) var int keyStateAiming1; var int keyStateAiming2; // Depending on control scheme either action or blocking key if (MEM_ReadInt(oCGame__s_bUseOldControls)) { keyStateAiming1 = MEM_KeyState(MEM_GetKey("keyAction")); // A bit much, but the keys are also needed below keyStateAiming2 = MEM_KeyState(MEM_GetSecondaryKey("keyAction")); if (FREEAIM_G2CTRL_PREVFRAME != -1) { freeAimUpdateSettingsG2Ctrl(0); }; // Disable extended Gothic 2 controls } else { keyStateAiming1 = MEM_KeyState(MEM_GetKey("keyParade")); keyStateAiming2 = MEM_KeyState(MEM_GetSecondaryKey("keyParade")); if (FREEAIM_G2CTRL_PREVFRAME != 1) { freeAimUpdateSettingsG2Ctrl(1); }; // Enable extended Gothic 2 controls }; var int keyPressed; keyPressed = (keyStateAiming1 == KEY_PRESSED) || (keyStateAiming1 == KEY_HOLD) || (keyStateAiming2 == KEY_PRESSED) || (keyStateAiming2 == KEY_HOLD); // Pressing or holding the aiming key if (Npc_IsInFightMode(hero, FMODE_MAGIC)) { if (FREEAIM_DISABLE_SPELLS) { return 0; }; // If free aiming for spells is disabled if (MEM_ReadInt(oCGame__s_bUseOldControls)) && (!keyPressed) { return 0; }; // G1 controls require action key var C_Spell spell; spell = freeAimGetActiveSpellInst(hero); if (!freeAimSpellEligible(spell)) { // Check if the active spell supports free aiming if (FREEAIM_FOCUS_SPELL_FREE != -1) { Focus_Magic.npc_azi = 45.0; // Reset ranged focus collection Focus_Magic.item_prio = -1; FREEAIM_FOCUS_SPELL_FREE = -1; }; return 0; }; if (FREEAIM_FOCUS_SPELL_FREE != 1) { Focus_Magic.npc_azi = 15.0; // Set stricter focus collection Focus_Magic.item_prio = 0; FREEAIM_FOCUS_SPELL_FREE = 1; }; return FMODE_MAGIC; }; if (!keyPressed) { return 0; }; // If aiming key is not pressed or held // Get onset for drawing the bow - right when pressing down the aiming key if (keyStateAiming1 == KEY_PRESSED) || (keyStateAiming2 == KEY_PRESSED) { freeAimBowDrawOnset = MEM_Timer.totalTime + FREEAIM_DRAWTIME_READY; }; return FMODE_FAR; }; /* Return texture file name for an animated texture. numFrames files must exist with the postfix '_[frameNo].tga' */ func string freeAimAnimateReticleByTime(var string fileName, var int fps, var int numFrames) { var int frameTime; frameTime = 1000/fps; // Time of one frame var int cycle; cycle = (MEM_Timer.totalTime % (frameTime*numFrames)) / frameTime; // Cycle through [0, numFrames] var string prefix; prefix = STR_SubStr(fileName, 0, STR_Len(fileName)-4); // Base name (without extension) var string postfix; if (cycle < 10) { postfix = ConcatStrings("0", IntToString(cycle)); } else { postfix = IntToString(cycle); }; return ConcatStrings(ConcatStrings(ConcatStrings(prefix, "_"), postfix), ".TGA"); }; /* Return texture file name for an animated texture. numFrames files must exist with the postfix '_[frameNo].tga' */ func string freeAimAnimateReticleByPercent(var string fileName, var int percent, var int numFrames) { var int cycle; cycle = roundf(mulf(mkf(percent), divf(mkf(numFrames-1), FLOAT1C))); var string prefix; prefix = STR_SubStr(fileName, 0, STR_Len(fileName)-4); // Base name (without extension) var string postfix; if (cycle < 10) { postfix = ConcatStrings("0", IntToString(cycle)); } else { postfix = IntToString(cycle); }; return ConcatStrings(ConcatStrings(ConcatStrings(prefix, "_"), postfix), ".TGA"); }; /* Hide reticle */ func void freeAimRemoveReticle() { if (Hlp_IsValidHandle(freeAimReticleHndl)) { View_Close(freeAimReticleHndl); }; }; /* Draw reticle */ func void freeAimInsertReticle(var int reticlePtr) { var Reticle reticle; reticle = _^(reticlePtr); var int size; if (!Hlp_StrCmp(reticle.texture, "")) { size = (((FREEAIM_RETICLE_MAX_SIZE-FREEAIM_RETICLE_MIN_SIZE)*(reticle.size))/100)+FREEAIM_RETICLE_MIN_SIZE; if (size > FREEAIM_RETICLE_MAX_SIZE) { size = FREEAIM_RETICLE_MAX_SIZE; } else if (size < FREEAIM_RETICLE_MIN_SIZE) { size = FREEAIM_RETICLE_MIN_SIZE; }; var zCView screen; screen = _^(MEM_Game._zCSession_viewport); if (!Hlp_IsValidHandle(freeAimReticleHndl)) { // Create reticle if it does not exist freeAimReticleHndl = View_CreateCenterPxl(screen.psizex/2, screen.psizey/2, size, size); View_SetTexture(freeAimReticleHndl, reticle.texture); View_SetColor(freeAimReticleHndl, reticle.color); View_Open(freeAimReticleHndl); } else { if (!Hlp_StrCmp(View_GetTexture(freeAimReticleHndl), reticle.texture)) { // Update its texture View_SetTexture(freeAimReticleHndl, reticle.texture); }; if (View_GetColor(freeAimReticleHndl) != reticle.color) { // Update its color View_SetColor(freeAimReticleHndl, reticle.color); }; var zCView crsHr; crsHr = _^(getPtr(freeAimReticleHndl)); if (crsHr.psizex != size) || (screen.psizex/2 != centerX) { // Update its size and re-position it to center var int centerX; centerX = screen.psizex/2; View_ResizePxl(freeAimReticleHndl, size, size); View_MoveToPxl(freeAimReticleHndl, screen.psizex/2-(size/2), screen.psizey/2-(size/2)); }; if (!crsHr.isOpen) { View_Open(freeAimReticleHndl); }; }; } else { freeAimRemoveReticle(); }; }; /* Attach an FX to the aim vob */ func void freeAimAttachFX(var string effectInst) { var int vobPtr; vobPtr = MEM_SearchVobByName("AIMVOB"); if (!vobPtr) { return; }; MEM_WriteString(vobPtr+564, effectInst); // oCItem.effect const int call = 0; if (CALL_Begin(call)) { CALL__thiscall(_@(vobPtr), oCItem__InsertEffect); call = CALL_End(); }; }; /* Detach the FX of the aim vob */ func void freeAimDetachFX() { var int vobPtr; vobPtr = MEM_SearchVobByName("AIMVOB"); if (!vobPtr) { return; }; if (Hlp_StrCmp(MEM_ReadString(vobPtr+564), "")) { return; }; const int call = 0; if (CALL_Begin(call)) { CALL__thiscall(_@(vobPtr), oCItem__RemoveEffect); call = CALL_End(); }; MEM_WriteString(vobPtr+564, ""); // oCItem.effect }; /* Decide when to draw reticle or when to hide it */ func void freeAimManageReticle() { if (!freeAimIsActive()) { freeAimDetachFX(); freeAimRemoveReticle(); }; }; /* Switching between weapon modes (sometimes called several times in a row) */ func void freeAimSwitchMode() { freeAimBowDrawOnset = MEM_Timer.totalTime + FREEAIM_DRAWTIME_READY; // Reset draw force onset freeAimManageReticle(); }; /* Mouse handling for manually turning the player model by mouse input */ func void freeAimManualRotation() { if (!freeAimIsActive()) { return; }; var int deltaX; deltaX = mulf(mkf(MEM_ReadInt(mouseDeltaX)), MEM_ReadInt(mouseSensX)); // Get mouse change in x if (deltaX == FLOATNULL) { return; }; // Only rotate if there was movement along x position deltaX = mulf(deltaX, castToIntf(FREEAIM_ROTATION_SCALE)); // Turn rate var int hAniCtrl; hAniCtrl = MEM_ReadInt(_@(hero)+2432); // oCNpc.anictrl const int call = 0; var int null; if (CALL_Begin(call)) { CALL_IntParam(_@(null)); // 0 = disable turn animation (there is none while aiming anyways) CALL_FloatParam(_@(deltaX)); CALL__thiscall(_@(hAniCtrl), oCAniCtrl_Human__Turn); call = CALL_End(); }; }; /* Manipulate the position of the aim vob (only for spells) */ func void freeAimManipulateAimVobPos(var int posPtr) { var int spell; spell = Npc_GetActiveSpell(hero); if (spell == -1) { return; }; MEM_PushIntParam(spell); MEM_Call(freeAimShiftAimVob); var int pushed; pushed = MEM_PopIntResult(); if (pushed) { pushed = mkf(pushed); // Amount to push the aim vob along the out vector of the camera var zMAT4 camPos; camPos = _^(MEM_ReadInt(MEM_ReadInt(MEMINT_oGame_Pointer_Address)+20)+60); MEM_WriteInt(posPtr+0, addf(MEM_ReadInt(posPtr+0), mulf(camPos.v0[2], pushed))); MEM_WriteInt(posPtr+4, addf(MEM_ReadInt(posPtr+4), mulf(camPos.v1[2], pushed))); MEM_WriteInt(posPtr+8, addf(MEM_ReadInt(posPtr+8), mulf(camPos.v2[2], pushed))); }; }; /* Create and set aim vob to position */ func int freeAimSetupAimVob(var int posPtr) { var int vobPtr; vobPtr = MEM_SearchVobByName("AIMVOB"); // Arrow needs target vob if (!vobPtr) { // Does not exist MEM_Info("freeAimSetupAimVob: Creating aim vob."); // Should be printed only once ever CALL__cdecl(oCItem___CreateNewInstance); // This actually allocates the memory, so no need to care about freeing vobPtr = CALL_RetValAsPtr(); MEM_WriteString(vobPtr+16, "AIMVOB"); // zCVob._zCObject_objectName CALL_PtrParam(_@(MEM_Vobtree)); CALL_PtrParam(vobPtr); CALL__thiscall(_@(MEM_World), oCWorld__AddVobAsChild); MEM_WriteInt(vobPtr+260, 3105); // zCVob.bitfield[0] (ignored by trace ray, no collision) }; MEM_CopyBytes(_@(hero)+60, vobPtr+60, 64); // Include rotation freeAimManipulateAimVobPos(posPtr); // Shift the aim vob (if desired) const int call4 = 0; // Set position to aim vob if (CALL_Begin(call4)) { CALL_PtrParam(_@(posPtr)); // Update aim vob position CALL__thiscall(_@(vobPtr), zCVob__SetPositionWorld); call4 = CALL_End(); }; return vobPtr; }; /* Shoot aim-tailored trace ray. Do no use for other purposes. This function is customized for aiming. */ func int freeAimRay(var int distance, var int focusType, var int vobPtr, var int posPtr, var int distPtr, var int trueDistPtr) { // Flags: (VOB_IGNORE_NO_CD_DYN | POLY_IGNORE_TRANSP | VOB_IGNORE_PROJECTILES | POLY_TEST_WATER) const int flags = (1<<0) | (1<<8) | (1<<9) | (1<<14); // Do not change (will make trace ray unstable) var zMAT4 camPos; camPos = _^(MEM_ReadInt(MEM_ReadInt(MEMINT_oGame_Pointer_Address)+20)+60); //0=right, 2=out, 3=pos var int herPtr; herPtr = _@(hero); // Shift the start point for the trace ray beyond the player model. Necessary, because if zooming out, // (1) there might be something between camera and hero and (2) the maximum aiming distance is off. var int dist; dist = sqrtf(addf(addf( // Distance between camera and player model (does not care about cam offset) sqrf(subf(MEM_ReadInt(herPtr+72), camPos.v0[3])), sqrf(subf(MEM_ReadInt(herPtr+88), camPos.v1[3]))), sqrf(subf(MEM_ReadInt(herPtr+104), camPos.v2[3])))); if (FREEAIM_CAMERA_X_SHIFT) { // Shifting the camera (shoulderview) is not recommended. Aiming is harder + less fps? // This makes the distance mentioned above more complex and requires the calculation of a point-line distance // For illustration: http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html var int line[6]; // Line with two points along the camera right vector at the level of the player model line[0] = subf(MEM_ReadInt(herPtr+72), mulf(camPos.v0[0], FLOAT1K)); // Left of player model line[1] = subf(MEM_ReadInt(herPtr+88), mulf(camPos.v1[0], FLOAT1K)); line[2] = subf(MEM_ReadInt(herPtr+104), mulf(camPos.v2[0], FLOAT1K)); line[3] = addf(MEM_ReadInt(herPtr+72), mulf(camPos.v0[0], FLOAT1K)); // Right of player model line[4] = addf(MEM_ReadInt(herPtr+88), mulf(camPos.v1[0], FLOAT1K)); line[5] = addf(MEM_ReadInt(herPtr+104), mulf(camPos.v2[0], FLOAT1K)); var int u[3]; var int v[3]; // Subtract both points of the line from the camera position u[0] = subf(camPos.v0[3], line[0]); v[0] = subf(camPos.v0[3], line[3]); u[1] = subf(camPos.v1[3], line[1]); v[1] = subf(camPos.v1[3], line[4]); u[2] = subf(camPos.v2[3], line[2]); v[2] = subf(camPos.v2[3], line[5]); var int crossProd[3]; // Cross-product crossProd[0] = subf(mulf(u[1], v[2]), mulf(u[2], v[1])); crossProd[1] = subf(mulf(u[2], v[0]), mulf(u[0], v[2])); crossProd[2] = subf(mulf(u[0], v[1]), mulf(u[1], v[0])); dist = sqrtf(addf(addf(sqrf(crossProd[0]), sqrf(crossProd[1])), sqrf(crossProd[2]))); dist = divf(dist, mkf(2000)); // Divide area of triangle by length between the points on the line }; var int traceRayVec[6]; traceRayVec[0] = addf(camPos.v0[3], mulf(camPos.v0[2], dist)); // Start ray from here traceRayVec[1] = addf(camPos.v1[3], mulf(camPos.v1[2], dist)); traceRayVec[2] = addf(camPos.v2[3], mulf(camPos.v2[2], dist)); traceRayVec[3] = mulf(camPos.v0[2], mkf(distance)); // Direction-/to-vector of ray traceRayVec[4] = mulf(camPos.v1[2], mkf(distance)); traceRayVec[5] = mulf(camPos.v2[2], mkf(distance)); var int fromPosPtr; fromPosPtr = _@(traceRayVec); var int dirPosPtr; dirPosPtr = _@(traceRayVec)+12; var int worldPtr; worldPtr = _@(MEM_World); const int call = 0; if (CALL_Begin(call)) { CALL_IntParam(_@(flags)); // Trace ray flags CALL_PtrParam(_@(herPtr)); // Ignore player model CALL_PtrParam(_@(dirPosPtr)); // Trace ray direction CALL__fastcall(_@(worldPtr), _@(fromPosPtr), zCWorld__TraceRayNearestHit_Vob); call = CALL_End(); }; var int found; found = CALL_RetValAsInt(); // Did the trace ray hit if (!found) && (!MEM_World.foundVob) { // Fix the intersection if there was no hit (trace ray is inconsistent) MEM_World.foundIntersection[0] = addf(traceRayVec[0], traceRayVec[3]); MEM_World.foundIntersection[1] = addf(traceRayVec[1], traceRayVec[4]); MEM_World.foundIntersection[2] = addf(traceRayVec[2], traceRayVec[5]); }; var int foundFocus; foundFocus = 0; // Is the focus vob in the trace ray vob list var int potentialVob; potentialVob = MEM_ReadInt(herPtr+2476); // oCNpc.focus_vob // Focus vob by focus collection if (potentialVob) { // Check if collected focus matches the desired focus type var int runDetailedTraceRay; runDetailedTraceRay = 0; // Second trace ray only if focus vob is reasonable if (!focusType) { // No focus vob (still a trace ray though) foundFocus = 0; } else if (focusType != TARGET_TYPE_ITEMS) && (Hlp_Is_oCNpc(potentialVob)) { // Validate focus vob, if it is npc var C_Npc target; target = _^(potentialVob); MEM_PushInstParam(target); // Function is not defined yet at time of parsing: MEM_Call(C_NpcIsUndead); // C_NpcIsUndead(target); var int npcIsUndead; npcIsUndead = MEM_PopIntResult(); if ((focusType == TARGET_TYPE_NPCS) // Any npc || ((focusType == TARGET_TYPE_ORCS) && target.guild > GIL_SEPERATOR_ORC) // Only focus orcs || ((focusType == TARGET_TYPE_HUMANS) && target.guild < GIL_SEPERATOR_HUM) // Only focus humans || ((focusType == TARGET_TYPE_UNDEAD) && npcIsUndead)) // Only focus undead npcs && (!Npc_IsInState(target, ZS_Unconscious)) // Do not allow focusing npcs that are down && (!Npc_IsInState(target, ZS_MagicSleep)) && (!Npc_IsDead(target)) { var int potVobPtr; potVobPtr = _@(potentialVob); var int voblist; voblist = _@(MEM_World.traceRayVobList_array); const int call2 = 0; if (CALL_Begin(call2)) { // More complicated for npcs: Check if npc is in trace ray vob list CALL_PtrParam(_@(potVobPtr)); // Explanation: Npcs are never HIT by a trace ray (only collected) CALL__thiscall(_@(voblist), zCArray_zCVob__IsInList); call2 = CALL_End(); }; runDetailedTraceRay = CALL_RetValAsInt(); // Will perform detailed trace ray if npc was in vob list }; } else if (focusType <= TARGET_TYPE_ITEMS) && (Hlp_Is_oCItem(potentialVob)) { runDetailedTraceRay = 1; // Will perform detailed trace ray }; if (runDetailedTraceRay) { // If focus collection is reasonable, run a more detailed examination // zCWorld::TraceRayNearestHit (0x621D82 in g2) flags = (1<<0) | (1<<2); // (zTRACERAY_VOB_IGNORE_NO_CD_DYN | zTRACERAY_VOB_BBOX) // Important! var int trRep; trRep = MEM_Alloc(40); // sizeof_zTTraceRayReport const int call3 = 0; if (CALL_Begin(call3)) { CALL_PtrParam(_@(trRep)); // zTTraceRayReport CALL_IntParam(_@(flags)); // Trace ray flags CALL_PtrParam(_@(dirPosPtr)); // Trace ray direction CALL_PtrParam(_@(fromPosPtr)); // Start vector CALL__thiscall(_@(potentialVob), zCVob__TraceRay); // This is a vob specific trace ray call3 = CALL_End(); }; if (CALL_RetValAsInt()) { // Got a hit: Update trace ray report MEM_World.foundVob = potentialVob; MEM_CopyWords(trRep+12, _@(MEM_World.foundIntersection), 3); // 0x0C zVEC3 foundFocus = potentialVob; // Confirmed focus vob }; MEM_Free(trRep); // Free the report }; }; if (foundFocus != potentialVob) { // If focus vob changed by the validation above const int call4 = 0; // Set the focus vob properly: reference counter if (CALL_Begin(call4)) { CALL_PtrParam(_@(foundFocus)); // If no valid focus was found, this will remove the focus (foundFocus == 0) CALL__thiscall(_@(herPtr), oCNpc__SetFocusVob); call4 = CALL_End(); }; }; if (MEM_ReadInt(herPtr+1176)) { //0x0498 oCNpc.enemy const int call5 = 0; var int null; // Remove the enemy properly: reference counter if (CALL_Begin(call5)) { CALL_PtrParam(_@(null)); // Always remove oCNpc.enemy. Target will be set to aimvob when shooting CALL__thiscall(_@(herPtr), oCNpc__SetEnemy); call5 = CALL_End(); }; }; // Debug visualization if (FREEAIM_DEBUG_TRACERAY) { freeAimDebugTRBBox[0] = subf(MEM_World.foundIntersection[0], mkf(10)); freeAimDebugTRBBox[1] = subf(MEM_World.foundIntersection[1], mkf(10)); freeAimDebugTRBBox[2] = subf(MEM_World.foundIntersection[2], mkf(10)); freeAimDebugTRBBox[3] = addf(freeAimDebugTRBBox[0], mkf(20)); freeAimDebugTRBBox[4] = addf(freeAimDebugTRBBox[1], mkf(20)); freeAimDebugTRBBox[5] = addf(freeAimDebugTRBBox[2], mkf(20)); freeAimDebugTRTrj[0] = traceRayVec[0]; freeAimDebugTRTrj[1] = traceRayVec[1]; freeAimDebugTRTrj[2] = traceRayVec[2]; freeAimDebugTRTrj[3] = addf(traceRayVec[0], traceRayVec[3]); freeAimDebugTRTrj[4] = addf(traceRayVec[1], traceRayVec[4]); freeAimDebugTRTrj[5] = addf(traceRayVec[2], traceRayVec[5]); }; // Write call-by-reference variables if (vobPtr) { MEM_WriteInt(vobPtr, MEM_World.foundVob); }; if (posPtr) { MEM_CopyWords(_@(MEM_World.foundIntersection), posPtr, 3); }; if (distPtr) { // Distance between intersection and player model distance = sqrtf(addf(addf( sqrf(subf(MEM_World.foundIntersection[0], traceRayVec[0])), sqrf(subf(MEM_World.foundIntersection[1], traceRayVec[1]))), sqrf(subf(MEM_World.foundIntersection[2], traceRayVec[2])))); MEM_WriteInt(distPtr, distance); }; if (trueDistPtr) { // Distance between intersection and camera distance = sqrtf(addf(addf( sqrf(subf(MEM_World.foundIntersection[0], camPos.v0[3])), sqrf(subf(MEM_World.foundIntersection[1], camPos.v1[3]))), sqrf(subf(MEM_World.foundIntersection[2], camPos.v2[3])))); MEM_WriteInt(trueDistPtr, distance); }; return found; }; /* Internal helper function for freeAimGetReticleRanged() for ranged combat */ func void freeAimGetReticleRanged_(var int target, var int distance, var int returnPtr) { var C_Npc targetNpc; var int talent; var C_Item weapon; // Retrieve target npc, weapon and talent if (Npc_IsInFightMode(hero, FMODE_FAR)) { weapon = Npc_GetReadiedWeapon(hero); } else if (Npc_HasEquippedRangedWeapon(hero)) { weapon = Npc_GetEquippedRangedWeapon(hero); } else { MEM_Error("freeAimGetReticleRanged_: No valid weapon equipped/readied!"); return; }; // Should never happen if (weapon.flags & ITEM_BOW) { talent = hero.HitChance[NPC_TALENT_BOW]; } // Bow talent else if (weapon.flags & ITEM_CROSSBOW) { talent = hero.HitChance[NPC_TALENT_CROSSBOW]; } // Crossbow talent else { MEM_Error("freeAimGetReticleRanged_: No valid weapon equipped/readied!"); return; }; if (Hlp_Is_oCNpc(target)) { targetNpc = _^(target); } else { targetNpc = MEM_NullToInst(); }; // Call customized function MEM_PushInstParam(targetNpc); MEM_PushInstParam(weapon); MEM_PushIntParam(talent); MEM_PushIntParam(distance); MEM_PushIntParam(returnPtr); MEM_Call(freeAimGetReticleRanged); // freeAimGetReticleRanged(targetNpc, weapon, talent, distance, returnPtr); }; /* Update aiming animation. Hook oCAniCtrl_Human::InterpolateCombineAni */ func void freeAimAnimation() { if (freeAimIsActive() != FMODE_FAR) { return; }; var int herPtr; herPtr = _@(hero); var int distance; var int target; if (FREEAIM_FOCUS_COLLECTION) { // Set focus npc if there is a valid one under the reticle freeAimRay(FREEAIM_MAX_DIST, TARGET_TYPE_NPCS, _@(target), 0, _@(distance), 0); // Shoot ray and retrieve info distance = roundf(divf(mulf(distance, FLOAT1C), mkf(FREEAIM_MAX_DIST))); // Distance scaled between [0, 100] } else { // More performance friendly. Here, there will be NO focus, otherwise it gets stuck on npcs. const int call4 = 0; var int null; // Set the focus vob properly: reference counter if (CALL_Begin(call4)) { CALL_PtrParam(_@(null)); // This will remove the focus CALL__thiscall(_@(herPtr), oCNpc__SetFocusVob); call4 = CALL_End(); }; const int call5 = 0; // Remove the enemy properly: reference counter if (CALL_Begin(call5)) { CALL_PtrParam(_@(null)); // Always remove oCNpc.enemy. Target will be set to aimvob when shooting CALL__thiscall(_@(herPtr), oCNpc__SetEnemy); call5 = CALL_End(); }; distance = 25; // No distance check ever. Set it to medium distance target = 0; // No focus target ever }; var int autoAlloc[7]; var Reticle reticle; reticle = _^(_@(autoAlloc)); // Gothic takes care of freeing this ptr MEM_CopyWords(_@s(""), _@(autoAlloc), 5); // reticle.texture (reset string) // Do not show reticle by default reticle.color = -1; // Do not set color by default reticle.size = 75; // Medium size by default freeAimGetReticleRanged_(target, distance, _@(reticle)); // Retrieve reticle specs freeAimInsertReticle(_@(reticle)); // Draw/update reticle var zMAT4 camPos; camPos = _^(MEM_ReadInt(MEM_ReadInt(MEMINT_oGame_Pointer_Address)+20)+60); //0=right, 2=out, 3=pos var int pos[3]; // The position is calculated from the camera, not the player model pos[0] = addf(camPos.v0[3], mulf(camPos.v0[2], mkf(FREEAIM_MAX_DIST))); pos[1] = addf(camPos.v1[3], mulf(camPos.v1[2], mkf(FREEAIM_MAX_DIST))); pos[2] = addf(camPos.v2[3], mulf(camPos.v2[2], mkf(FREEAIM_MAX_DIST))); // Get aiming angles var int angleX; var int angXptr; angXptr = _@(angleX); var int angleY; var int angYptr; angYptr = _@(angleY); var int posPtr; posPtr = _@(pos); // So many pointers because it is a recyclable call const int call3 = 0; if (CALL_Begin(call3)) { CALL_PtrParam(_@(angYptr)); CALL_PtrParam(_@(angXptr)); // X angle not needed CALL_PtrParam(_@(posPtr)); CALL__thiscall(_@(herPtr), oCNpc__GetAngles); call3 = CALL_End(); }; if (lf(absf(angleY), 1048576000)) { // Prevent multiplication with too small numbers. Would result in aim twitching if (lf(angleY, FLOATNULL)) { angleY = -1098907648; } // -0.25 else { angleY = 1048576000; }; // 0.25 }; // This following paragraph is essentially "copied" from oCAIHuman::BowMode (0x695F00 in g2) angleY = negf(subf(mulf(angleY, 1001786197), FLOATHALF)); // Scale and flip Y [-90° +90°] to [+1 0] if (lef(angleY, FLOATNULL)) { angleY = FLOATNULL; } // Maximum aim height (straight up) else if (gef(angleY, 1065353216)) { angleY = 1065353216; }; // Minimum aim height (down) // New aiming coordinates. Overwrite the arguments passed to oCAniCtrl_Human::InterpolateCombineAni MEM_WriteInt(ESP+4, FLOATHALF); // Always aim at center (x angle) MEM_WriteInt(ESP+8, angleY); }; /* Internal helper function for freeAimGetDrawForce() */ func int freeAimGetDrawForce_() { var int talent; var C_Item weapon; // Retrieve the weapon first to distinguish between (cross-)bow talent if (Npc_IsInFightMode(hero, FMODE_FAR)) { weapon = Npc_GetReadiedWeapon(hero); } else if (Npc_HasEquippedRangedWeapon(hero)) { weapon = Npc_GetEquippedRangedWeapon(hero); } else { MEM_Error("freeAimGetDrawForce_: No valid weapon equipped/readied!"); return -1; }; // Should never happen if (weapon.flags & ITEM_BOW) { talent = hero.HitChance[NPC_TALENT_BOW]; } // Bow talent else if (weapon.flags & ITEM_CROSSBOW) { talent = hero.HitChance[NPC_TALENT_CROSSBOW]; } // Crossbow talent else { MEM_Error("freeAimGetDrawForce_: No valid weapon equipped/readied!"); return -1; }; // Call customized function MEM_PushInstParam(weapon); MEM_PushIntParam(talent); MEM_Call(freeAimGetDrawForce); // freeAimGetDrawForce(weapon, talent); var int drawForce; drawForce = MEM_PopIntResult(); if (drawForce > 100) { drawForce = 100; } else if (drawForce < 0) { drawForce = 0; }; // Must be in [0, 100] return drawForce; }; /* Internal helper function for freeAimGetAccuracy() */ func int freeAimGetAccuracy_() { var int talent; var C_Item weapon; // Retrieve the weapon first to distinguish between (cross-)bow talent if (Npc_IsInFightMode(hero, FMODE_FAR)) { weapon = Npc_GetReadiedWeapon(hero); } else if (Npc_HasEquippedRangedWeapon(hero)) { weapon = Npc_GetEquippedRangedWeapon(hero); } else { MEM_Error("freeAimGetAccuracy_: No valid weapon equipped/readied!"); return -1; }; // Should never happen if (weapon.flags & ITEM_BOW) { talent = hero.HitChance[NPC_TALENT_BOW]; } // Bow talent else if (weapon.flags & ITEM_CROSSBOW) { talent = hero.HitChance[NPC_TALENT_CROSSBOW]; } // Crossbow talent else { MEM_Error("freeAimGetAccuracy_: No valid weapon equipped/readied!"); return -1; }; // Call customized function MEM_PushInstParam(weapon); MEM_PushIntParam(talent); MEM_Call(freeAimGetAccuracy); // freeAimGetAccuracy(weapon, talent); var int accuracy; accuracy = MEM_PopIntResult(); if (accuracy < 1) { accuracy = 1; } else if (accuracy > 100) { accuracy = 100; }; // Limit to [1, 100] // Div by 0! return accuracy; }; /* Internal helper function for freeAimScaleInitialDamage() */ func int freeAimScaleInitialDamage_(var int basePointDamage) { var int talent; var C_Item weapon; // Retrieve the weapon first to distinguish between (cross-)bow talent if (Npc_IsInFightMode(hero, FMODE_FAR)) { weapon = Npc_GetReadiedWeapon(hero); } else if (Npc_HasEquippedRangedWeapon(hero)) { weapon = Npc_GetEquippedRangedWeapon(hero); } else { MEM_Error("freeAimScaleInitialDamage_: No valid weapon equipped/readied!"); return basePointDamage; }; if (weapon.flags & ITEM_BOW) { talent = hero.HitChance[NPC_TALENT_BOW]; } // Bow talent else if (weapon.flags & ITEM_CROSSBOW) { talent = hero.HitChance[NPC_TALENT_CROSSBOW]; } // Crossbow talent else { MEM_Error("freeAimScaleInitialDamage_: No valid weapon equipped/readied!"); return basePointDamage; }; // Call customized function MEM_PushIntParam(basePointDamage); MEM_PushInstParam(weapon); MEM_PushIntParam(talent); MEM_Call(freeAimScaleInitialDamage); // freeAimScaleInitialDamage(basePointDamage, weapon, talent); basePointDamage = MEM_PopIntResult(); if (basePointDamage < 0) { basePointDamage = 0; }; // No negative damage return basePointDamage; }; /* Set the projectile direction and trajectory. Hook oCAIArrow::SetupAIVob */ func void freeAimSetupProjectile() { var int projectile; projectile = MEM_ReadInt(ESP+4); // First argument is the projectile var C_Npc shooter; shooter = _^(MEM_ReadInt(ESP+8)); // Second argument is shooter if (FREEAIM_ACTIVE_PREVFRAME != 1) || (!Npc_IsPlayer(shooter)) { return; }; // Only if player and if fa WAS active // 1st: Set base damage of projectile // oCItem.damage[DAM_INDEX_POINT]; var int baseDamage; baseDamage = MEM_ReadStatArr(projectile+364, DAM_INDEX_POINT); var int newBaseDamage; newBaseDamage = freeAimScaleInitialDamage_(baseDamage); MEM_WriteStatArr(projectile+364, DAM_INDEX_POINT, newBaseDamage); // 2nd: Manipulate aiming accuracy (scatter): Rotate target position (azimuth, elevation) var int distance; freeAimRay(FREEAIM_MAX_DIST, TARGET_TYPE_NPCS, 0, 0, 0, _@(distance)); // Trace ray intersection var int accuracy; accuracy = freeAimGetAccuracy_(); // Change the accuracy calculation in that function, not here! if (accuracy > 100) { accuracy = 100; } else if (accuracy < 1) { accuracy = 1; }; // Prevent devision by zero var int bias; bias = castToIntf(FREEAIM_SCATTER_DEG); var int slope; slope = negf(divf(castToIntf(FREEAIM_SCATTER_DEG), FLOAT1C)); var int angleMax; angleMax = roundf(mulf(addf(mulf(slope, mkf(accuracy)), bias), FLOAT1K)); // y = slope*acc+bias var int angleY; angleY = fracf(r_MinMax(-angleMax, angleMax), 1000); // Degrees azimuth angleMax = roundf(sqrtf(subf(sqrf(mkf(angleMax)), sqrf(mulf(angleY, FLOAT1K))))); // sqrt(angleMax^2-angleY^2) var int angleX; angleX = fracf(r_MinMax(-angleMax, angleMax), 1000); // Degrees elevation (restrict to circle) var zMAT4 camPos; camPos = _^(MEM_ReadInt(MEM_ReadInt(MEMINT_oGame_Pointer_Address)+20)+60); //0=right, 2=out, 3=pos var int pos[3]; pos[0] = FLOATNULL; pos[1] = FLOATNULL; pos[2] = distance; SinCosApprox(Print_ToRadian(angleX)); // Rotate around x-axis (elevation scatter) pos[1] = mulf(negf(pos[2]), sinApprox); // y*cos - z*sin = y' pos[2] = mulf(pos[2], cosApprox); // y*sin + z*cos = z' SinCosApprox(Print_ToRadian(angleY)); // Rotate around y-axis (azimuth scatter) pos[0] = mulf(pos[2], sinApprox); // x*cos + z*sin = x' pos[2] = mulf(pos[2], cosApprox); // -x*sin + z*cos = z' var int newPos[3]; // Rotation (translation into local coordinate system of camera) newPos[0] = addf(addf(mulf(camPos.v0[0], pos[0]), mulf(camPos.v0[1], pos[1])), mulf(camPos.v0[2], pos[2])); newPos[1] = addf(addf(mulf(camPos.v1[0], pos[0]), mulf(camPos.v1[1], pos[1])), mulf(camPos.v1[2], pos[2])); newPos[2] = addf(addf(mulf(camPos.v2[0], pos[0]), mulf(camPos.v2[1], pos[1])), mulf(camPos.v2[2], pos[2])); pos[0] = addf(camPos.v0[3], newPos[0]); pos[1] = addf(camPos.v1[3], newPos[1]); pos[2] = addf(camPos.v2[3], newPos[2]); // 3rd: Set projectile drop-off (by draw force) const int call2 = 0; if (CALL_Begin(call2)) { CALL__thiscall(_@(projectile), zCVob__GetRigidBody); // Get ridigBody this way, it will be properly created call2 = CALL_End(); }; var int rBody; rBody = CALL_RetValAsInt(); // zCRigidBody* var int drawForce; drawForce = freeAimGetDrawForce_(); // Modify the draw force in that function, not here! var int gravityMod; gravityMod = FLOATONE; // Gravity only modified on short draw time if (drawForce < 25) { gravityMod = castToIntf(3.0); }; // Very short draw time increases gravity var int dropTime; dropTime = (drawForce*(FREEAIM_TRAJECTORY_ARC_MAX*100))/10000; FF_ApplyOnceExtData(freeAimDropProjectile, dropTime, 1, rBody); // When to hit the projectile with gravity freeAimBowDrawOnset = MEM_Timer.totalTime + FREEAIM_DRAWTIME_RELOAD; // Reset draw timer MEM_WriteInt(rBody+236, mulf(castToIntf(FREEAIM_PROJECTILE_GRAVITY), gravityMod)); // Set gravity (but not enabled) if (Hlp_Is_oCItem(projectile)) && (Hlp_StrCmp(MEM_ReadString(projectile+564), "")) { // Projectile has no FX MEM_WriteString(projectile+564, FREEAIM_TRAIL_FX); // Set trail strip fx for better visibility const int call3 = 0; if (CALL_Begin(call3)) { CALL__thiscall(_@(projectile), oCItem__InsertEffect); call3 = CALL_End(); }; }; // 4th: Setup the aim vob var int vobPtr; vobPtr = freeAimSetupAimVob(_@(pos)); // Print info to zSpy var int s; s = SB_New(); SB("freeAimSetupProjectile: "); SB("drawforce="); SBi(drawForce); SB("% "); SB("accuracy="); SBi(accuracy); SB("% "); SB("scatter="); SB(STR_Prefix(toStringf(angleX), 5)); SBc(176 /* deg */); SB("/"); SB(STR_Prefix(toStringf(angleY), 5)); SBc(176 /* deg */); SB(" "); SB("init-basedamage="); SBi(newBaseDamage); SB("/"); SBi(baseDamage); MEM_Info(SB_ToString()); SB_Destroy(); MEM_WriteInt(ESP+12, vobPtr); // Overwrite the third argument (target vob) passed to oCAIArrow::SetupAIVob }; /* This function is timed by draw force and is responsible for applying gravity to a projectile */ func void freeAimDropProjectile(var int rigidBody) { if (!rigidBody) || (!MEM_ReadInt(rigidBody)) { return; }; if (MEM_ReadInt(rigidBody+188) == FLOATNULL) // zCRigidBody.velocity[3] && (MEM_ReadInt(rigidBody+192) == FLOATNULL) && (MEM_ReadInt(rigidBody+196) == FLOATNULL) { return; }; // Do not add gravity if projectile already stopped moving MEM_WriteByte(rigidBody+256, 1); // Turn on gravity (zCRigidBody.bitfield) }; /* Internal helper function for freeAimHitRegNpc() */ func int freeAimHitRegNpc_(var C_Npc target) { var C_Item weapon; weapon = MEM_NullToInst(); // Daedalus pseudo locals if (Npc_IsInFightMode(hero, FMODE_FAR)) { weapon = Npc_GetReadiedWeapon(hero); } else if (Npc_HasEquippedRangedWeapon(hero)) { weapon = Npc_GetEquippedRangedWeapon(hero); }; var int material; material = -1; // No armor if (Npc_HasEquippedArmor(target)) { var C_Item armor; armor = Npc_GetEquippedArmor(target); material = armor.material; }; // Call customized function MEM_PushInstParam(target); MEM_PushInstParam(weapon); MEM_PushIntParam(material); MEM_Call(freeAimHitRegNpc); // freeAimHitRegNpc(target, weapon, material); return MEM_PopIntResult(); }; /* Internal helper function for freeAimHitRegWld() */ func int freeAimHitRegWld_(var C_Npc shooter, var int material, var string texture) { var C_Item weapon; weapon = MEM_NullToInst(); // Daedalus pseudo locals if (Npc_IsInFightMode(shooter, FMODE_FAR)) { weapon = Npc_GetReadiedWeapon(shooter); } else if (Npc_HasEquippedRangedWeapon(shooter)) { weapon = Npc_GetEquippedRangedWeapon(shooter); }; // Call customized function MEM_PushInstParam(shooter); MEM_PushInstParam(weapon); MEM_PushIntParam(material); MEM_PushStringParam(texture); MEM_Call(freeAimHitRegWld); // freeAimHitRegWld(shooter, weapon, material, texture); return MEM_PopIntResult(); }; /* Determine the hit chance. For the player it's always 100%. True hit chance is calculated in freeAimGetAccuracy() */ func void freeAimDoNpcHit() { var int hitChance; hitChance = MEM_ReadInt(ESP+24); // esp+1ACh+194h var C_Npc target; target = _^(MEM_ReadInt(ESP+28)); // esp+1ACh+190h // oCNpc* var C_Npc shooter; shooter = _^(MEM_ReadInt(EBP+92)); // ebp+5Ch // oCNpc* var int projectile; projectile = MEM_ReadInt(EBP+88); // ebp+58h // oCItem* if (FREEAIM_ACTIVE_PREVFRAME != 1) || (!Npc_IsPlayer(shooter)) { // Default hitchance for npcs or if fa is disabled MEM_WriteByte(projectileDeflectOffNpcAddr, /*74*/ 116); // Reset to default collision behavior on npcs MEM_WriteByte(projectileDeflectOffNpcAddr+1, /*3B*/ 59); // jz to 0x6A0BA3 return; }; var int intersection; intersection = 1; // Hit registered (positive hit determined by the engine at this point) if (FREEAIM_HITDETECTION_EXP) { // Additional hit detection test (EXPERIMENTAL). Will lead to some hits not detected intersection = 0; // Check here if "any" point along the line of the projectile direction lies inside the bbox var zTBBox3D targetBBox; targetBBox = _^(_@(target)+124); // oCNpc.bbox3D var int dir[3]; // Direction of collision line along the right-vector of projectile (projectile flies sideways) dir[0] = MEM_ReadInt(projectile+60); dir[1] = MEM_ReadInt(projectile+76); dir[2] = MEM_ReadInt(projectile+92); var int line[6]; // Collision line line[0] = addf(MEM_ReadInt(projectile+ 72), mulf(dir[0], FLOAT3C)); // Start 3m behind the projectile line[1] = addf(MEM_ReadInt(projectile+ 88), mulf(dir[1], FLOAT3C)); // So far because of bbox at close range line[2] = addf(MEM_ReadInt(projectile+104), mulf(dir[2], FLOAT3C)); var int i; i=0; var int iter; iter = 700/5; // 7meters while(i <= iter); i += 1; // Walk along the line in steps of 5cm line[3] = subf(line[0], mulf(dir[0], mkf(i*5))); // Next point along the collision line line[4] = subf(line[1], mulf(dir[1], mkf(i*5))); line[5] = subf(line[2], mulf(dir[2], mkf(i*5))); if (lef(targetBBox.mins[0], line[3])) && (lef(targetBBox.mins[1], line[4])) && (lef(targetBBox.mins[2], line[5])) && (gef(targetBBox.maxs[0], line[3])) && (gef(targetBBox.maxs[1], line[4])) && (gef(targetBBox.maxs[2], line[5])) { intersection = 1; break; }; // Current point is inside the bbox end; }; var int hit; if (intersection) { // By default this is always true var int collision; collision = freeAimHitRegNpc_(target); // 0=destroy, 1=stuck, 2=deflect if (collision == 2) { // Deflect (no damage) MEM_WriteByte(projectileDeflectOffNpcAddr, ASMINT_OP_nop); // Skip npc armor collision check, deflect always MEM_WriteByte(projectileDeflectOffNpcAddr + 1, ASMINT_OP_nop); hit = FALSE; } else { MEM_WriteByte(projectileDeflectOffNpcAddr, /*74*/ 116); // Jump beyond armor collision check, deflect never MEM_WriteByte(projectileDeflectOffNpcAddr+1, /*60*/ 96); // jz to 0x6A0BC8 if (!collision) { // Destroy (no damage) MEM_WriteInt(projectile+816, -1); // Delete item instance (it will not be put into the directory) hit = FALSE; } else { // Collide (damage) hit = TRUE; }; }; } else { // Destroy the projectile if it did not physically hit MEM_WriteByte(projectileDeflectOffNpcAddr, /*74*/ 116); // Jump beyond the armor collision check, deflect never MEM_WriteByte(projectileDeflectOffNpcAddr+1, /*60*/ 96); // jz to 0x6A0BC8 MEM_WriteInt(projectile+816, -1); // Delete item instance (it will not be put into the directory) hit = FALSE; }; MEM_WriteInt(ESP+24, hit*100); // Player always hits = 100% }; /* Arrow collides with world (static or non-npc vob). Either destroy, deflect or collide */ func void freeAimOnArrowCollide() { var oCItem projectile; projectile = _^(MEM_ReadInt(ESI+60)); // esi+3Ch var C_Npc shooter; shooter = _^(MEM_ReadInt(esi+92)); // esi+5Ch var int matobj; matobj = MEM_ReadInt(ECX); // zCMaterial* or zCPolygon* if (MEM_ReadInt(matobj) != zCMaterial__vtbl) { matobj = MEM_ReadInt(matobj+24); }; // Static world: Read zCPolygon var int material; material = MEM_ReadInt(matobj+64); var string texture; texture = ""; if (MEM_ReadInt(matobj+52)) { // For the case that the material has no assigned texture (which should not happen) texture = MEM_ReadString(MEM_ReadInt(matobj+52)+16); // zCMaterial.texture._zCObject_objectName }; var int collision; collision = freeAimHitRegWld_(shooter, material, texture); // 0=destroy, 1=stay, 2=deflect if (collision == 1) { // Collide EDI = material; // Sets the condition at 0x6A0A45 and 0x6A0C1A to true: Projectile stays } else { EDI = -1; // Sets the condition at 0x6A0A45 and 0x6A0C1A to false: Projectile deflects if (!collision) { if (FF_ActiveData(freeAimDropProjectile, _@(projectile._zCVob_rigidBody))) { FF_RemoveData(freeAimDropProjectile, _@(projectile._zCVob_rigidBody)); }; if (FREEAIM_REUSE_PROJECTILES) { // Destroy Wld_StopEffect(FREEAIM_BREAK_FX); // Sometimes collides several times Wld_PlayEffect(FREEAIM_BREAK_FX, projectile, projectile, 0, 0, 0, FALSE); MEM_WriteInt(ESI+56, -1073741824); // oCAIArrow.lifeTime // Mark this AI for freeAimWatchProjectile() }; }; }; }; /* Arrow gets stuck in npc: put projectile instance into inventory and let ai die */ func void freeAimOnArrowHitNpc() { var oCItem projectile; projectile = _^(MEM_ReadInt(ESI+88)); var C_Npc victim; victim = _^(EDI); // Call customized function MEM_PushIntParam(projectile.instanz); MEM_PushInstParam(victim); MEM_Call(freeAimGetUsedProjectileInstance); // freeAimGetUsedProjectileInstance(projectile.instanz, victim); var int projInst; projInst = MEM_PopIntResult(); if (projInst > 0) { CreateInvItem(victim, projInst); }; // Put respective instance in inventory if (FF_ActiveData(freeAimDropProjectile, _@(projectile._zCVob_rigidBody))) { FF_RemoveData(freeAimDropProjectile, _@(projectile._zCVob_rigidBody)); }; MEM_WriteInt(ESI+56, -1073741824); // oCAIArrow.lifeTime // Mark this AI for freeAimWatchProjectile() }; /* Arrow gets stuck in static or dynamic world (non-npc): keep ai alive */ func void freeAimOnArrowGetStuck() { var int projectilePtr; projectilePtr = MEM_ReadInt(ESI+88); var oCItem projectile; projectile = _^(projectilePtr); if (FF_ActiveData(freeAimDropProjectile, _@(projectile._zCVob_rigidBody))) { FF_RemoveData(freeAimDropProjectile, _@(projectile._zCVob_rigidBody)); }; // 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); }; /* Fix trigger collision bug. Taken from http://forum.worldofplayers.de/forum/threads/1126551/page10?p=20894916 */ func void freeAimTriggerCollisionCheck() { var int vobPtr; vobPtr = ESP+4; var int shooter; shooter = MEM_ReadInt(ECX+92); var int vtbl; vtbl = MEM_ReadInt(MEM_ReadInt(vobPtr)); if (vtbl != zCTrigger_vtbl) && (vtbl != zCTriggerScript_vtbl) { return; }; // It is no Trigger var zCTrigger trigger; trigger = _^(MEM_ReadInt(vobPtr)); if (trigger.bitfield & zCTrigger_bitfield_respondToObject) && (trigger.bitfield & zCTrigger_bitfield_reactToOnTouch) { return; }; // Object-reacting trigger MEM_WriteInt(vobPtr, shooter); // The engine ignores the shooter }; /* Once a projectile stopped moving keep it alive */ func void freeAimWatchProjectile() { var int arrowAI; arrowAI = ECX; // oCAIArrow* var int projectilePtr; projectilePtr = MEM_ReadInt(ESP+4); // oCItem* var int removePtr; removePtr = MEM_ReadInt(ESP+8); // int* (call-by-reference argument) if (!projectilePtr) { return; }; // oCItem* might not exist var oCItem projectile; projectile = _^(projectilePtr); if (!projectile._zCVob_rigidBody) { return; }; // zCRigidBody* might not exist the first time // Reset projectile gravity (zCRigidBody.gravity) after collision (oCAIArrow.collision) if (MEM_ReadInt(arrowAI+52)) { MEM_WriteInt(projectile._zCVob_rigidBody+236, FLOATONE); }; // Set gravity to default if (!FREEAIM_REUSE_PROJECTILES) { return; }; // Normal projectile handling // If the projectile stopped moving (and did not hit npc) if (MEM_ReadInt(arrowAI+56) != -1073741824) && !(projectile._zCVob_bitfield[0] & zCVob_bitfield0_physicsEnabled) { if (FF_ActiveData(freeAimDropProjectile, _@(projectile._zCVob_rigidBody))) { FF_RemoveData(freeAimDropProjectile, _@(projectile._zCVob_rigidBody)); }; if (Hlp_StrCmp(projectile.effect, FREEAIM_TRAIL_FX)) { // Remove trail strip fx const int call2 = 0; if (CALL_Begin(call2)) { CALL__thiscall(_@(projectilePtr), oCItem__RemoveEffect); call2 = CALL_End(); }; }; var C_Npc emptyNpc; emptyNpc = MEM_NullToInst(); // Call customized function MEM_PushIntParam(projectile.instanz); MEM_PushInstParam(emptyNpc); MEM_Call(freeAimGetUsedProjectileInstance); // freeAimGetUsedProjectileInstance(projectile.instanz, emptyNpc); var int projInst; projInst = MEM_PopIntResult(); if (projInst > 0) { // Will be -1 on invalid item if (projInst != projectile.instanz) { // Only change the instance if different const int call3 = 0; const int one = 1; if (CALL_Begin(call3)) { CALL_IntParam(_@(one)); // Amount CALL_PtrParam(_@(projInst)); // Instance ID CALL__thiscall(_@(projectilePtr), oCItem__InitByScript); call3 = CALL_End(); }; }; projectile.flags = projectile.flags &~ ITEM_NFOCUS; // Focusable 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, _@(FREEAIM_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 }; }; /* Disable damage animation. Taken from http://forum.worldofplayers.de/forum/threads/1474431?p=25057480#post25057480 */ func void freeAimDmgAnimation() { var C_Npc victim; victim = _^(ECX); if (Npc_IsPlayer(victim)) && (freeAimIsActive()) { EAX = 0; }; // Disable damage animation while aiming }; /* Visualize the bounding boxes of the trace ray its trajectory for debugging */ func void freeAimVisualizeTraceRay() { if (!FREEAIM_DEBUG_TRACERAY) { return; }; if (freeAimDebugTRBBox[0]) { // Visualize intersection bounding box var int cGreenPtr; cGreenPtr = _@(zCOLOR_GREEN); var int bboxPtr; bboxPtr = _@(freeAimDebugTRBBox); const int call = 0; if (CALL_Begin(call)) { CALL_PtrParam(_@(cGreenPtr)); CALL__thiscall(_@(bboxPtr), zTBBox3D__Draw); call = CALL_End(); }; }; if (freeAimDebugTRTrj[0]) { // Visualize trace ray trajectory var int pos1Ptr; pos1Ptr = _@(freeAimDebugTRTrj); var int pos2Ptr; pos2Ptr = _@(freeAimDebugTRTrj)+12; const int call2 = 0; var int null; if (CALL_Begin(call2)) { CALL_IntParam(_@(null)); CALL_IntParam(_@(zCOLOR_GREEN)); CALL_PtrParam(_@(pos2Ptr)); CALL_PtrParam(_@(pos1Ptr)); CALL__thiscall(_@(zlineCache), zCLineCache__Line3D); call2 = CALL_End(); }; }; }; /* Visualize the bounding box of the weakspot and the projectile trajectory for debugging */ func void freeAimVisualizeWeakspot() { if (!FREEAIM_DEBUG_WEAKSPOT) { return; }; if (freeAimDebugWSBBox[0]) { // Visualize weak spot bounding box var int cRedPtr; cRedPtr = _@(zCOLOR_RED); var int bboxPtr; bboxPtr = _@(freeAimDebugWSBBox); const int call = 0; if (CALL_Begin(call)) { CALL_PtrParam(_@(cRedPtr)); CALL__thiscall(_@(bboxPtr), zTBBox3D__Draw); call = CALL_End(); }; }; if (freeAimDebugWSTrj[0]) { // Visualize projectile trajectory var int pos1Ptr; pos1Ptr = _@(freeAimDebugWSTrj); var int pos2Ptr; pos2Ptr = _@(freeAimDebugWSTrj)+12; const int call2 = 0; var int null; if (CALL_Begin(call2)) { CALL_IntParam(_@(null)); CALL_IntParam(_@(zCOLOR_RED)); CALL_PtrParam(_@(pos2Ptr)); CALL_PtrParam(_@(pos1Ptr)); CALL__thiscall(_@(zlineCache), zCLineCache__Line3D); call2 = CALL_End(); }; }; }; /* Internal helper function for freeAimCriticalHitEvent() */ func void freeAimCriticalHitEvent_(var C_Npc target) { var C_Item weapon; weapon = MEM_NullToInst(); // Daedalus pseudo locals if (Npc_IsInFightMode(hero, FMODE_FAR)) { weapon = Npc_GetReadiedWeapon(hero); } else if (Npc_HasEquippedRangedWeapon(hero)) { weapon = Npc_GetEquippedRangedWeapon(hero); }; // Call customized function MEM_PushInstParam(target); MEM_PushInstParam(weapon); MEM_Call(freeAimCriticalHitEvent); // freeAimCriticalHitEvent(target, weapon); }; /* Internal helper function for freeAimCriticalHitDef() */ func void freeAimCriticalHitDef_(var C_Npc target, var int damage, var int returnPtr) { var C_Item weapon; weapon = MEM_NullToInst(); // Daedalus pseudo locals if (Npc_IsInFightMode(hero, FMODE_FAR)) { weapon = Npc_GetReadiedWeapon(hero); } else if (Npc_HasEquippedRangedWeapon(hero)) { weapon = Npc_GetEquippedRangedWeapon(hero); }; // Call customized function MEM_PushInstParam(target); MEM_PushInstParam(weapon); MEM_PushIntParam(damage); MEM_PushIntParam(returnPtr); MEM_Call(freeAimCriticalHitDef); // freeAimCriticalHitDef(target, weapon, damage, returnPtr); MEM_WriteString(returnPtr, STR_Upper(MEM_ReadString(returnPtr))); // Nodes are always upper case if (lf(MEM_ReadInt(returnPtr+28), FLOATNULL)) { MEM_WriteInt(returnPtr+28, FLOATNULL); }; // Correct negative damage }; /* Console function to enable/disable weak spot debug output */ func string freeAimDebugWeakspot(var string command) { FREEAIM_DEBUG_WEAKSPOT = !FREEAIM_DEBUG_WEAKSPOT; if (FREEAIM_DEBUG_WEAKSPOT) { return "Debug weak spot on."; } else { return "Debug weak spot off."; }; }; /* Console function to enable/disable trace ray debug output */ func string freeAimDebugTraceRay(var string command) { FREEAIM_DEBUG_TRACERAY = !FREEAIM_DEBUG_TRACERAY; if (FREEAIM_DEBUG_TRACERAY) { return "Debug trace ray on."; } else { return "Debug trace ray off."; }; }; /* Console function to show freeAim version */ func string freeAimVersion(var string command) { return FREEAIM_VERSION; }; /* Console function to show freeAim license */ func string freeAimLicense(var string command) { var int s; s = SB_New(); SB(FREEAIM_VERSION); SB(", Copyright "); SBc(169 /* (C) */); SB(" 2016 mud-freak (@szapp)"); SBc(13); SBc(10); SB(""); SBc(13); SBc(10); SB("Released under the MIT License."); SBc(13); SBc(10); SB("For more details see ."); SBc(13); SBc(10); var string ret; ret = SB_ToString(); SB_Destroy(); return ret; }; /* Console function to show freeAim info */ func string freeAimInfo(var string command) { const string onOff[2] = {"off", "on"}; var int s; s = SB_New(); SB(FREEAIM_VERSION); SBc(13); SBc(10); SB("Enabled: "); SB(MEM_ReadStatStringArr(onOff, STR_ToInt(MEM_GetGothOpt("FREEAIM", "enabled")))); SBc(13);SBc(10); SB("Focus: "); SB(MEM_ReadStatStringArr(onOff, FREEAIM_FOCUS_COLLECTION)); SBc(13); SBc(10); SB("Reuse projectiles: "); SB(MEM_ReadStatStringArr(onOff, FREEAIM_REUSE_PROJECTILES)); SBc(13); SBc(10); SB("Free aim for spells: "); SB(MEM_ReadStatStringArr(onOff, !FREEAIM_DISABLE_SPELLS)); SBc(13); SBc(10); var string ret; ret = SB_ToString(); SB_Destroy(); return ret; }; /* Detect critical hits and increase base damage. Modify the weak spot in freeAimCriticalHitDef() */ func void freeAimDetectCriticalHit() { var int damagePtr; damagePtr = ESP+228; // esp+1ACh+C8h // zREAL* var int target; target = MEM_ReadInt(ESP+28); // esp+1ACh+190h // oCNpc* var int projectile; projectile = MEM_ReadInt(EBP+88); // ebp+58h // oCItem* var C_Npc shooter; shooter = _^(MEM_ReadInt(EBP+92)); // ebp+5Ch // oCNpc* if (FREEAIM_ACTIVE_PREVFRAME != 1) || (!Npc_IsPlayer(shooter)) { return; }; // Only if player and if fa WAS active var C_Npc targetNpc; targetNpc = _^(target); // Get model from target npc const int call = 0; if (CALL_Begin(call)) { CALL__thiscall(_@(target), oCNpc__GetModel); call = CALL_End(); }; var int model; model = CALL_RetValAsPtr(); // Get weak spot node from target model var int autoAlloc[8]; var Weakspot weakspot; weakspot = _^(_@(autoAlloc)); // Gothic takes care of freeing this ptr MEM_CopyWords(_@s(""), _@(autoAlloc), 5); // weakspot.node (reset string) freeAimCriticalHitDef_(targetNpc, MEM_ReadInt(damagePtr), _@(weakspot)); // Retrieve weakspot specs var int nodeStrPtr; nodeStrPtr = _@(weakspot); if (Hlp_StrCmp(MEM_ReadString(nodeStrPtr), "")) { return; }; // No critical node defined const int call2 = 0; if (CALL_Begin(call2)) { CALL_PtrParam(_@(nodeStrPtr)); CALL__thiscall(_@(model), zCModel__SearchNode); call2 = CALL_End(); }; var int node; node = CALL_RetValAsPtr(); if (!node) { MEM_Warn("freeAimDetectCriticalHit: Node not found!"); return; }; if (weakspot.dimX == -1) && (weakspot.dimY == -1) { // Retrieve the bbox by model if (MEM_ReadInt(node+8)) { // node->nodeVisual // If the node has a dedicated visual, retrieve bbox // Get the bbox of the node (although zCModelNodeInst has a zTBBox3D property, it is empty the first time) CALL_PtrParam(node); CALL_RetValIsStruct(24); // sizeof_zTBBox3D // No recyclable call possible CALL__thiscall(model, zCModel__GetBBox3DNodeWorld); var int nodeBBoxPtr; nodeBBoxPtr = CALL_RetValAsPtr(); MEM_CopyWords(nodeBBoxPtr, _@(freeAimDebugWSBBox), 6); // zTBBox3D MEM_Free(nodeBBoxPtr); // Free memory } else { MEM_Error("freeAimDetectCriticalHit: Node has no boundingbox!"); return; }; } else if (weakspot.dimX < 0) || (weakspot.dimY < 0) { // Bbox dimensions must be positive MEM_Error("freeAimDetectCriticalHit: Boundingbox dimensions illegal!"); return; } else { // Create bbox by dimensions weakspot.dimX /= 2; weakspot.dimY /= 2; // Get the position of the node (although zCModelNodeInst has a position property, it is empty the first time) CALL_PtrParam(node); CALL_RetValIsStruct(12); // sizeof_zVEC3 // No recyclable call possible bc of structure CALL__thiscall(model, zCModel__GetNodePositionWorld); var int nodPosPtr; nodPosPtr = CALL_RetValAsInt(); var int nodePos[3]; MEM_CopyWords(nodPosPtr, _@(nodePos), 3); MEM_Free(nodPosPtr); // Free memory freeAimDebugWSBBox[0] = subf(nodePos[0], mkf(weakspot.dimX)); // Build an own bbox by the passed node dimensions freeAimDebugWSBBox[1] = subf(nodePos[1], mkf(weakspot.dimY)); freeAimDebugWSBBox[2] = subf(nodePos[2], mkf(weakspot.dimX)); freeAimDebugWSBBox[3] = addf(nodePos[0], mkf(weakspot.dimX)); freeAimDebugWSBBox[4] = addf(nodePos[1], mkf(weakspot.dimY)); freeAimDebugWSBBox[5] = addf(nodePos[2], mkf(weakspot.dimX)); }; // The internal engine functions are not accurate enough for detecting a shot through a bbox // Instead check here if "any" point along the line of projectile direction lies inside the bbox of the node var int dir[3]; // Direction of collision line along the right-vector of the projectile (projectile flies sideways) dir[0] = MEM_ReadInt(projectile+60); dir[1] = MEM_ReadInt(projectile+76); dir[2] = MEM_ReadInt(projectile+92); freeAimDebugWSTrj[0] = addf(MEM_ReadInt(projectile+ 72), mulf(dir[0], FLOAT3C)); // Start 3m behind the projectile freeAimDebugWSTrj[1] = addf(MEM_ReadInt(projectile+ 88), mulf(dir[1], FLOAT3C)); // So far bc bbox at close range freeAimDebugWSTrj[2] = addf(MEM_ReadInt(projectile+104), mulf(dir[2], FLOAT3C)); var int intersection; intersection = 0; // Critical hit detected var int i; i=0; var int iter; iter = 700/5; // 7meters: Max distance from model bbox edge to node bbox (e.g. troll) while(i <= iter); i += 1; // Walk along the line in steps of 5cm freeAimDebugWSTrj[3] = subf(freeAimDebugWSTrj[0], mulf(dir[0], mkf(i*5))); // Next point along the collision line freeAimDebugWSTrj[4] = subf(freeAimDebugWSTrj[1], mulf(dir[1], mkf(i*5))); freeAimDebugWSTrj[5] = subf(freeAimDebugWSTrj[2], mulf(dir[2], mkf(i*5))); if (lef(freeAimDebugWSBBox[0], freeAimDebugWSTrj[3])) && (lef(freeAimDebugWSBBox[1], freeAimDebugWSTrj[4])) && (lef(freeAimDebugWSBBox[2], freeAimDebugWSTrj[5])) && (gef(freeAimDebugWSBBox[3], freeAimDebugWSTrj[3])) && (gef(freeAimDebugWSBBox[4], freeAimDebugWSTrj[4])) && (gef(freeAimDebugWSBBox[5], freeAimDebugWSTrj[5])) { intersection = 1; }; // Current point is inside the node bbox, but stay in loop for debugging the line end; var int s; s = SB_New(); // Print info to zSpy SB("freeAimDetectCriticalHit: "); SB("criticalhit="); SBi(intersection); SB(" "); SB("basedamage="); SBi(roundf(weakspot.bDmg)); SB("/"); SBi(roundf(MEM_ReadInt(damagePtr))); SB(" "); SB("ciriticalnode='"); SB(weakspot.node); SB("' "); SB(" ("); SBi(weakspot.dimX); SB("x"); SBi(weakspot.dimY); SB(")"); MEM_Info(SB_ToString()); SB_Destroy(); if (intersection) { // Critical hit detected freeAimCriticalHitEvent_(targetNpc); // Use this function to add an event, e.g. a print or a sound MEM_WriteInt(damagePtr, weakspot.bDmg); // Base damage not final damage }; }; /* Set the spell fx direction and trajectory. Hook oCSpell::Setup */ func void freeAimSetupSpell() { var int casterPtr; casterPtr = MEM_ReadInt(EBP+52); //0x0034 oCSpell.spellCasterNpc if (!casterPtr) { return; }; // No caster var C_Npc caster; caster = _^(casterPtr); if (FREEAIM_ACTIVE_PREVFRAME != 1) || (!Npc_IsPlayer(caster)) { return; }; // Only if player and if fa WAS active var C_Spell spell; spell = _^(EBP+128); //0x0080 oCSpell.C_Spell if (!freeAimSpellEligible(spell)) { return; }; // Only with eligible spells var int focusType; // No focus display for TARGET_COLLECT_NONE (still focus collection though) if (!spell.targetCollectAlgo) { focusType = 0; } else { focusType = spell.targetCollectType; }; var int pos[3]; freeAimRay(spell.targetCollectRange, focusType, 0, _@(pos), 0, 0); var int vobPtr; vobPtr = freeAimSetupAimVob(_@(pos)); // Setup the aim vob MEM_WriteInt(ESP+4, vobPtr); // Overwrite target vob }; /* Internal helper function for freeAimGetReticleSpell() for magic combat */ func void freeAimGetReticleSpell_(var int target, var C_Spell spellInst, var int distance, var int returnPtr) { var C_Npc targetNpc; var int spellID; var int spellLvl; var int isScroll; var int manaInvested; spellID = Npc_GetActiveSpell(hero); spellLvl = Npc_GetActiveSpellLevel(hero); isScroll = Npc_GetActiveSpellIsScroll(hero); manaInvested = MEM_ReadInt(_@(spellInst)-56); // 0x0048 oCSpell.manaInvested if (Hlp_Is_oCNpc(target)) { targetNpc = _^(target); } else { targetNpc = MEM_NullToInst(); }; // Call customized function MEM_PushInstParam(targetNpc); MEM_PushIntParam(spellID); MEM_PushInstParam(spellInst); MEM_PushIntParam(spellLvl); MEM_PushIntParam(isScroll); MEM_PushIntParam(manaInvested); MEM_PushIntParam(distance); MEM_PushIntParam(returnPtr); MEM_Call(freeAimGetReticleSpell); // freeAimGetReticleSpell(target, spellID, spellInst, spellLvl, isScroll, ...); }; /* Manage reticle style and focus collection for magic combat */ func void freeAimSpellReticle() { if (!freeAimIsActive()) { freeAimRemoveReticle(); return; }; // Only with eligible spells var C_Spell spell; spell = freeAimGetActiveSpellInst(hero); var int distance; var int target; if (FREEAIM_FOCUS_COLLECTION) && (spell.targetCollectRange > 0) { // Set focus npc if there is a valid one var int focusType; // No focus display for TARGET_COLLECT_NONE (still focus collection though) if (!spell.targetCollectAlgo) || (spell.targetCollectAzi <= 0) || (spell.targetCollectElev <= 0) { focusType = 0; } else { focusType = spell.targetCollectType; }; freeAimRay(spell.targetCollectRange, focusType, _@(target), 0, _@(distance), 0); // Shoot ray distance = roundf(divf(mulf(distance, FLOAT1C), mkf(spell.targetCollectRange))); // Distance scaled to [0, 100] } else { // More performance friendly. Here, there will be NO focus, otherwise it gets stuck on npcs. var int herPtr; herPtr = _@(hero); const int call2 = 0; var int null; // Set the focus vob properly: reference counter if (CALL_Begin(call2)) { CALL_PtrParam(_@(null)); // This will remove the focus CALL__thiscall(_@(herPtr), oCNpc__SetFocusVob); call2 = CALL_End(); }; if (!MEM_ReadInt(herPtr+1176)) { //0x0498 oCNpc.enemy const int call3 = 0; // Remove the enemy properly: reference counter if (CALL_Begin(call3)) { CALL_PtrParam(_@(null)); // Always remove oCNpc.enemy. Target will be set to aimvob when shooting CALL__thiscall(_@(herPtr), oCNpc__SetEnemy); call3 = CALL_End(); }; }; distance = 25; // No distance check ever. Set it to medium distance target = 0; // No focus target ever }; var int autoAlloc[7]; var Reticle reticle; reticle = _^(_@(autoAlloc)); // Gothic takes care of freeing this ptr MEM_CopyWords(_@s(""), _@(autoAlloc), 5); // reticle.texture (reset string) // Do not show reticle by default reticle.color = -1; // Do not set color by default reticle.size = 75; // Medium size by default freeAimGetReticleSpell_(target, spell, distance, _@(reticle)); // Retrieve reticle specs freeAimInsertReticle(_@(reticle)); // Draw/update reticle }; /* Disable auto turning towards the target for free aiming spells */ func void freeAimDisableSpellAutoTurn() { var int herPtr; herPtr = _@(hero); if (freeAimIsActive() && MEM_ReadInt(herPtr+1176)) { //0x0498 oCNpc.enemy const int call3 = 0; var int null; // Remove the enemy properly: reference counter if (CALL_Begin(call3)) { CALL_PtrParam(_@(null)); // Always remove oCNpc.enemy. Target will be set to aimvob when shooting CALL__thiscall(_@(herPtr), oCNpc__SetEnemy); // This disables turning towards the target call3 = CALL_End(); }; }; };