mahlendur/_work/Data/Scripts/Content/freeAim/_intern.d
Daniel Heße 892a40ff0f Initial commit with Subversion-codebase from 13/05/2022.
Cleaned up the mess, at this time, only viable data get synced (_work directory for Gothic-data, miranda for mdc-scripts.
Need to symlink files to a working directory.
2022-05-13 17:38:52 +02:00

1357 lines
81 KiB
D

/*
* 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.
* <http://github.com/szapp/g2freeAim>
*
* 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 <http://opensource.org/licenses/MIT>.
*
*
* 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(" <http://github.com/szapp/g2freeAim>");
MEM_Info(" Released under the MIT License.");
MEM_Info(" For more details see <http://opensource.org/licenses/MIT>.");
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("<http://github.com/szapp/g2freeAim>"); SBc(13); SBc(10);
SB("Released under the MIT License."); SBc(13); SBc(10);
SB("For more details see <http://opensource.org/licenses/MIT>."); 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();
};
};
};