diff --git a/README.md b/README.md index 0ddbd90..1a5a271 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Features **Examples** -Here short list of some examples which are easily implementable (or already included) in `config.d`. There is a lot +Here short list of some examples which are easily implementable (or already included) in `config/`. There is a lot more freedom, however. - Change the way the draw force (projectile gravity) is calculated (by weapon stats, skill level, ..) @@ -111,13 +111,13 @@ Customization As the mechanisms of the script are rather complex, the script is divided into two parts in `_work\data\Scripts\Content\freeAim\`. One part is the core functionality and should **not** be edited: `_intern.d`. -The other one holds all possible customizations and can be freely adjusted and changed: `config.d`. +The other one holds all possible customizations and can be freely adjusted and changed: `config/`. The customization is mostly implemented by calling outsourced functions. These are functions you can freely adjust. The only restrictions are the function signature and the type of return value. What happens inside the functions is fully up to you. Other binary settings are offered as constants that can simply be changed. -In the file `config.d` the following things can be adjusted. For more details see `config.d` and read the in-line +In the files `config/` the following things can be adjusted. For more details see `config/` and read the in-line comments of each listed function. There are a lot of possibilities and a lot of information is already provided to each function (in form of function arguments). For some examples and ideas see **Examples** above. @@ -344,7 +344,7 @@ Q: **Why does my game crash?** A: See **"It does not work? What now?"**. Additionally, it will be essential to describe your configuration, ideally by -attaching your `freeAim/config.d`. +attaching your files in `freeAim/config/`. Q: **What is the deal with the license? May I use g2freeAim in my modification?** @@ -397,7 +397,7 @@ include the following information: - Output when entering `freeaim info` into the F2-ingame-console. - zSpy Log. - The Access Violation message (if applicable). - - Your configuration (attach the file `freeAim/config.d`). + - Your configuration (attach the files in `freeAim/config/`). - Used versions of Ikarus/LeGo. - If possible: Your own attempts to isolate and reproduce the bug. diff --git a/_work/data/Scripts/Content/freeAim/_intern.d b/_work/data/Scripts/Content/freeAim/_intern.d deleted file mode 100644 index 00452a0..0000000 --- a/_work/data/Scripts/Content/freeAim/_intern.d +++ /dev/null @@ -1,1297 +0,0 @@ -/* - * 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 freeAimDebugTRPrevVob; // Trace ray detected vob bbox pointer -var int freeAimReticleHndl; // Holds the handle of the reticle -var int freeAimBowDrawOnset; // Time onset of drawing the bow - -/* 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 - -/* 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 - 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; - }; -}; - -/* 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 - || (!MEM_ReadInt(oCGame__s_bUseOldControls)) { // Classic gothic 1 controls are disabled - if (FREEAIM_ACTIVE_PREVFRAME != -1) { freeAimUpdateSettings(0); }; // Update internal settings (turn off) - 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 keyStateAction1; keyStateAction1 = MEM_KeyState(MEM_GetKey("keyAction")); // A bit much, but needed below - var int keyStateAction2; keyStateAction2 = MEM_KeyState(MEM_GetSecondaryKey("keyAction")); - if (keyStateAction1 != KEY_PRESSED) && (keyStateAction1 != KEY_HOLD) // Only while pressing the action button - && (keyStateAction2 != KEY_PRESSED) && (keyStateAction2 != KEY_HOLD) { return 0; }; - if (Npc_IsInFightMode(hero, FMODE_MAGIC)) { - if (FREEAIM_DISABLE_SPELLS) { return 0; }; // If free aiming for spells is disabled - 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; - }; - // Get onset for drawing the bow - right when pressing down the action key - if (keyStateAction1 == KEY_PRESSED) || (keyStateAction2 == 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 | POLY_TEST_WATER | VOB_IGNORE_PROJECTILES - var int flags; 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(5)); - freeAimDebugTRBBox[1] = subf(MEM_World.foundIntersection[1], mkf(5)); - freeAimDebugTRBBox[2] = subf(MEM_World.foundIntersection[2], mkf(5)); - freeAimDebugTRBBox[3] = addf(freeAimDebugTRBBox[0], mkf(10)); - freeAimDebugTRBBox[4] = addf(freeAimDebugTRBBox[1], mkf(10)); - freeAimDebugTRBBox[5] = addf(freeAimDebugTRBBox[2], mkf(10)); - MEM_CopyWords(_@(traceRayVec), _@(freeAimDebugTRTrj), 3); - freeAimDebugTRTrj[3] = addf(traceRayVec[0], traceRayVec[3]); - freeAimDebugTRTrj[4] = addf(traceRayVec[1], traceRayVec[4]); - freeAimDebugTRTrj[5] = addf(traceRayVec[2], traceRayVec[5]); - if (MEM_World.foundVob) { freeAimDebugTRPrevVob = MEM_World.foundVob+124; } else { freeAimDebugTRPrevVob = 0; }; - }; - // 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 a bounding box in 3D space */ -func void freeAimVisualizeBBox(var int bboxPtr, var int color) { - var int cPtr; cPtr = _@(color); - const int call = 0; - if (CALL_Begin(call)) { - CALL_PtrParam(_@(cPtr)); - CALL__thiscall(_@(bboxPtr), zTBBox3D__Draw); - call = CALL_End(); - }; -}; - -/* Visualize a line in 3D space */ -func void freeAimVisualizeLine(var int pos1Ptr, var int pos2Ptr, var int color) { - const int call = 0; var int null; - if (CALL_Begin(call)) { - CALL_IntParam(_@(null)); - CALL_IntParam(_@(color)); - CALL_PtrParam(_@(pos2Ptr)); - CALL_PtrParam(_@(pos1Ptr)); - CALL__thiscall(_@(zlineCache), zCLineCache__Line3D); - call = CALL_End(); - }; -}; - -/* Visualize the bounding boxes of the trace ray its trajectory for debugging */ -func void freeAimVisualizeTraceRay() { - if (!FREEAIM_DEBUG_TRACERAY) { return; }; - if (freeAimDebugTRBBox[0]) { freeAimVisualizeBBox(_@(freeAimDebugTRBBox), zCOLOR_GREEN); }; - if (freeAimDebugTRTrj[0]) { freeAimVisualizeLine(_@(freeAimDebugTRTrj), _@(freeAimDebugTRTrj)+12, zCOLOR_GREEN); }; - if (freeAimDebugTRPrevVob) { freeAimVisualizeBBox(freeAimDebugTRPrevVob, zCOLOR_GREEN); }; -}; - -/* Visualize the bounding box of the weakspot and the projectile trajectory for debugging */ -func void freeAimVisualizeWeakspot() { - if (!FREEAIM_DEBUG_WEAKSPOT) { return; }; - if (freeAimDebugWSBBox[0]) { freeAimVisualizeBBox(_@(freeAimDebugWSBBox), zCOLOR_RED); }; - if (freeAimDebugWSTrj[0]) { freeAimVisualizeLine(_@(freeAimDebugWSTrj), _@(freeAimDebugWSTrj)+12, zCOLOR_RED); }; -}; - -/* 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(); - }; - }; -}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/activeSpell.d b/_work/data/Scripts/Content/freeAim/_intern/activeSpell.d new file mode 100644 index 0000000..9337980 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/activeSpell.d @@ -0,0 +1,48 @@ +/* + * Identifying active spell instances and determine if they are eligible for free aiming + * + * 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 . + */ + +/* 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 +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/aimRay.d b/_work/data/Scripts/Content/freeAim/_intern/aimRay.d new file mode 100644 index 0000000..8c5c3cb --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/aimRay.d @@ -0,0 +1,182 @@ +/* + * Aim-specific trace ray and focus collection + * + * 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 . + */ + +/* 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 | POLY_TEST_WATER | VOB_IGNORE_PROJECTILES + var int flags; 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(5)); + freeAimDebugTRBBox[1] = subf(MEM_World.foundIntersection[1], mkf(5)); + freeAimDebugTRBBox[2] = subf(MEM_World.foundIntersection[2], mkf(5)); + freeAimDebugTRBBox[3] = addf(freeAimDebugTRBBox[0], mkf(10)); + freeAimDebugTRBBox[4] = addf(freeAimDebugTRBBox[1], mkf(10)); + freeAimDebugTRBBox[5] = addf(freeAimDebugTRBBox[2], mkf(10)); + MEM_CopyWords(_@(traceRayVec), _@(freeAimDebugTRTrj), 3); + freeAimDebugTRTrj[3] = addf(traceRayVec[0], traceRayVec[3]); + freeAimDebugTRTrj[4] = addf(traceRayVec[1], traceRayVec[4]); + freeAimDebugTRTrj[5] = addf(traceRayVec[2], traceRayVec[5]); + if (MEM_World.foundVob) { freeAimDebugTRPrevVob = MEM_World.foundVob+124; } else { freeAimDebugTRPrevVob = 0; }; + }; + // 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; +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/aimVob.d b/_work/data/Scripts/Content/freeAim/_intern/aimVob.d new file mode 100644 index 0000000..10a714b --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/aimVob.d @@ -0,0 +1,87 @@ +/* + * Definition and manipulation of aim vob (targeting system) + * + * 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 . + */ + +/* 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 +}; + +/* 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; +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/ccommands.d b/_work/data/Scripts/Content/freeAim/_intern/ccommands.d new file mode 100644 index 0000000..7be1b8d --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/ccommands.d @@ -0,0 +1,63 @@ +/* + * Definition of all console commands + * + * 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 . + */ + +/* 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; +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/classes.d b/_work/data/Scripts/Content/freeAim/_intern/classes.d new file mode 100644 index 0000000..4ca0d1d --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/classes.d @@ -0,0 +1,37 @@ +/* + * Helper classes + * + * 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 . + */ + +/* 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; +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/collectable.d b/_work/data/Scripts/Content/freeAim/_intern/collectable.d new file mode 100644 index 0000000..6df95c1 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/collectable.d @@ -0,0 +1,97 @@ +/* + * Collectable projectiles + * + * 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 . + */ + +/* 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 + }; +}; + +/* 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); +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/collision.d b/_work/data/Scripts/Content/freeAim/_intern/collision.d new file mode 100644 index 0000000..1cbdba2 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/collision.d @@ -0,0 +1,152 @@ +/* + * Projectile collision behavior + * + * 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 . + */ + +/* 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() + }; + }; + }; +}; + +/* 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 +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/const.d b/_work/data/Scripts/Content/freeAim/_intern/const.d new file mode 100644 index 0000000..df1a6b9 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/const.d @@ -0,0 +1,100 @@ +/* + * Constants + * + * 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 . + */ + +/* Free aim settings, do not modify! Change the settings in freeAim\config\settings.d */ +const string FREEAIM_VERSION = "G2 Free Aim v0.1.2"; // Do not change under any circumstances +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 for debug visualization +var int freeAimDebugTRPrevVob; // Trace ray detected vob bbox pointer for debugging +var int freeAimReticleHndl; // Holds the handle of the reticle +var int freeAimBowDrawOnset; // Time onset of drawing the bow + +/* 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 diff --git a/_work/data/Scripts/Content/freeAim/_intern/controls.d b/_work/data/Scripts/Content/freeAim/_intern/controls.d new file mode 100644 index 0000000..165fec8 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/controls.d @@ -0,0 +1,44 @@ +/* + * Input and controls manipulation + * + * 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 . + */ + +/* 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(); + }; +}; + +/* 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 +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/criticalHit.d b/_work/data/Scripts/Content/freeAim/_intern/criticalHit.d new file mode 100644 index 0000000..c83c477 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/criticalHit.d @@ -0,0 +1,138 @@ +/* + * Critical hits for projectiles + * + * 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 . + */ + +/* 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 +}; + +/* 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 + }; +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/debug.d b/_work/data/Scripts/Content/freeAim/_intern/debug.d new file mode 100644 index 0000000..290401e --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/debug.d @@ -0,0 +1,61 @@ +/* + * Debugging visualizations + * + * 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 . + */ + +/* Visualize a bounding box in 3D space */ +func void freeAimVisualizeBBox(var int bboxPtr, var int color) { + var int cPtr; cPtr = _@(color); + const int call = 0; + if (CALL_Begin(call)) { + CALL_PtrParam(_@(cPtr)); + CALL__thiscall(_@(bboxPtr), zTBBox3D__Draw); + call = CALL_End(); + }; +}; + +/* Visualize a line in 3D space */ +func void freeAimVisualizeLine(var int pos1Ptr, var int pos2Ptr, var int color) { + const int call = 0; var int null; + if (CALL_Begin(call)) { + CALL_IntParam(_@(null)); + CALL_IntParam(_@(color)); + CALL_PtrParam(_@(pos2Ptr)); + CALL_PtrParam(_@(pos1Ptr)); + CALL__thiscall(_@(zlineCache), zCLineCache__Line3D); + call = CALL_End(); + }; +}; + +/* Visualize the bounding boxes of the trace ray its trajectory for debugging */ +func void freeAimVisualizeTraceRay() { + if (!FREEAIM_DEBUG_TRACERAY) { return; }; + if (freeAimDebugTRBBox[0]) { freeAimVisualizeBBox(_@(freeAimDebugTRBBox), zCOLOR_GREEN); }; + if (freeAimDebugTRTrj[0]) { freeAimVisualizeLine(_@(freeAimDebugTRTrj), _@(freeAimDebugTRTrj)+12, zCOLOR_GREEN); }; + if (freeAimDebugTRPrevVob) { freeAimVisualizeBBox(freeAimDebugTRPrevVob, zCOLOR_GREEN); }; +}; + +/* Visualize the bounding box of the weakspot and the projectile trajectory for debugging */ +func void freeAimVisualizeWeakspot() { + if (!FREEAIM_DEBUG_WEAKSPOT) { return; }; + if (freeAimDebugWSBBox[0]) { freeAimVisualizeBBox(_@(freeAimDebugWSBBox), zCOLOR_RED); }; + if (freeAimDebugWSTrj[0]) { freeAimVisualizeLine(_@(freeAimDebugWSTrj), _@(freeAimDebugWSTrj)+12, zCOLOR_RED); }; +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/init.d b/_work/data/Scripts/Content/freeAim/_intern/init.d new file mode 100644 index 0000000..8603ceb --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/init.d @@ -0,0 +1,144 @@ +/* + * Initialization + * + * 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 . + */ + +/* 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(""); + 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.")); +}; + +/* 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; + }; +}; + +/* 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 + || (!MEM_ReadInt(oCGame__s_bUseOldControls)) { // Classic gothic 1 controls are disabled + if (FREEAIM_ACTIVE_PREVFRAME != -1) { freeAimUpdateSettings(0); }; // Update internal settings (turn off) + 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 keyStateAction1; keyStateAction1 = MEM_KeyState(MEM_GetKey("keyAction")); // A bit much, but needed below + var int keyStateAction2; keyStateAction2 = MEM_KeyState(MEM_GetSecondaryKey("keyAction")); + if (keyStateAction1 != KEY_PRESSED) && (keyStateAction1 != KEY_HOLD) // Only while pressing the action button + && (keyStateAction2 != KEY_PRESSED) && (keyStateAction2 != KEY_HOLD) { return 0; }; + if (Npc_IsInFightMode(hero, FMODE_MAGIC)) { + if (FREEAIM_DISABLE_SPELLS) { return 0; }; // If free aiming for spells is disabled + 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; + }; + // Get onset for drawing the bow - right when pressing down the action key + if (keyStateAction1 == KEY_PRESSED) || (keyStateAction2 == KEY_PRESSED) { + freeAimBowDrawOnset = MEM_Timer.totalTime + FREEAIM_DRAWTIME_READY; }; + return FMODE_FAR; +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/ranged.d b/_work/data/Scripts/Content/freeAim/_intern/ranged.d new file mode 100644 index 0000000..92fedf2 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/ranged.d @@ -0,0 +1,216 @@ +/* + * Ranged combat mechanics + * + * 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 . + */ + +/* 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) +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/reticle.d b/_work/data/Scripts/Content/freeAim/_intern/reticle.d new file mode 100644 index 0000000..171390e --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/reticle.d @@ -0,0 +1,130 @@ +/* + * Reticle handling + * + * 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 . + */ + +/* 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(); }; +}; + +/* 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(); +}; + +/* 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"); +}; + +/* 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); +}; + +/* 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, ...); +}; diff --git a/_work/data/Scripts/Content/freeAim/_intern/spell.d b/_work/data/Scripts/Content/freeAim/_intern/spell.d new file mode 100644 index 0000000..24929a6 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/_intern/spell.d @@ -0,0 +1,88 @@ +/* + * Magic combat mechanics + * + * 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 . + */ + +/* 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(); + }; + }; +}; + +/* 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 +}; + +/* 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 +}; diff --git a/_work/data/Scripts/Content/freeAim/config.d b/_work/data/Scripts/Content/freeAim/config.d deleted file mode 100644 index d4c6cd7..0000000 --- a/_work/data/Scripts/Content/freeAim/config.d +++ /dev/null @@ -1,343 +0,0 @@ -/* - * 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 . - * - * - * Customizability: - * - Show weakspot debug visualization by default: FREEAIM_DEBUG_WEAKSPOT - * - Show trace ray debug visualization by default: FREEAIM_DEBUG_TRACERAY - * - Apply trigger collision fix (disable coll.): FREEAIM_TRIGGER_COLL_FIX - * - Allow freeAim console commands (cheats): FREEAIM_DEBUG_CONSOLE - * - Maximum bow draw time (ms): FREEAIM_DRAWTIME_MAX - * - Disable free aiming for spells (yes/no): FREEAIM_DISABLE_SPELLS - * - Collect and re-use shot projectiles (yes/no): FREEAIM_REUSE_PROJECTILES - * - Projectile instance for re-using: freeAimGetUsedProjectileInstance(instance, targetNpc) - * - Draw force (gravity/drop-off) calculation: freeAimGetDrawForce(weapon, talent) - * - Accuracy calculation: freeAimGetAccuracy(weapon, talent) - * - Reticle style (texture, color, size): freeAimGetReticleRanged(target, weapon, talent, distance) - * - Reticle style for spells: freeAimGetReticleSpell(target, spellID, spellInst, spellLevel, ..) - * - Hit registration on npcs (e.g. friendly-fire): freeAimHitRegNpc(target, weapon, material) - * - Hit registration on world: freeAimHitRegWld(shooter, weapon, material, texture) - * - Change the base damage at time of shooting: freeAimScaleInitialDamage(basePointDamage, weapon, talent) - * - Critical hit calculation (position, damage): freeAimCriticalHitDef(target, weapon, damage) - * - Critical hit event (print, sound, xp, ...): freeAimCriticalHitEvent(target, weapon) - * Advanced (modification not recommended): - * - Scatter radius for accuracy: FREEAIM_SCATTER_DEG - * - Camera view (shoulder view): FREEAIM_CAMERA and FREEAIM_CAMERA_X_SHIFT - * - Max time before projectile drop-off: FREEAIM_TRAJECTORY_ARC_MAX - * - Gravity of projectile after drop-off: FREEAIM_PROJECTILE_GRAVITY - * - Turn speed while aiming: FREEAIM_ROTATION_SCALE - * - Additional hit detection test (EXPERIMENTAL): FREEAIM_HITDETECTION_EXP - * - Shift the aim vob: freeAimShiftAimVob(spellID) - */ - -/* Initialize fixed settings. This function is called once at the beginning of each session. Set the constants here */ -func void freeAimInitConstants() { - // If you want to change a setting, uncomment the respective line. These are the default values. - // FREEAIM_REUSE_PROJECTILES = 1; // Enable collection and re-using of shot projectiles - // FREEAIM_DISABLE_SPELLS = 0; // If true, free aiming is disabled for spells (not for ranged) - // FREEAIM_DRAWTIME_MAX = 1200; // Max draw time (ms): When is the bow fully drawn - // FREEAIM_DEBUG_CONSOLE = 1; // Console commands for debugging. Set to zero in final mod - // FREEAIM_DEBUG_WEAKSPOT = 0; // Visualize weakspot bbox and trajectory by default - // FREEAIM_DEBUG_TRACERAY = 0; // Visualize trace ray bboxes and trajectory by default - // FREEAIM_TRIGGER_COLL_FIX = 1; // Apply trigger collision fix (disable collision) - // Modifying any line below is not recommended! - // FREEAIM_SCATTER_DEG = 2.2; // Maximum scatter radius in degrees - // FREEAIM_TRAJECTORY_ARC_MAX = 400; // Max time (ms) after which the trajectory drops off - // FREEAIM_PROJECTILE_GRAVITY = 0.1; // The gravity decides how fast the projectile drops - // FREEAIM_CAMERA = "CamModFreeAim"; // CCamSys_Def script instance for free aim - // FREEAIM_CAMERA_X_SHIFT = 0; // One, if camera is set to shoulderview, s.a. (not recommended) - // FREEAIM_ROTATION_SCALE = 0.16; // Turn rate. Non-weapon mode is 0.2 (zMouseRotationScale) - // FREEAIM_HITDETECTION_EXP = 0; // Additional hit detection (EXPERIMENTAL) -}; - -/* Modify this function to alter the draw force calculation. Scaled between 0 and 100 (percent) */ -func int freeAimGetDrawForce(var C_Item weapon, var int talent) { - var int drawTime; drawTime = MEM_Timer.totalTime - freeAimBowDrawOnset; - // Possibly incorporate more factors like e.g. a quick-draw talent, weapon-specific stats, ... - if (weapon.flags & ITEM_CROSSBOW) { return 100; }; // Always full draw force on crossbows - // For now the draw time is scaled by a maximum. Replace FREEAIM_DRAWTIME_MAX by a variable for a quick-draw talent - var int drawForce; drawForce = (100 * drawTime) / FREEAIM_DRAWTIME_MAX; - if (drawForce < 0) { drawForce = 0; } else if (drawForce > 100) { drawForce = 100; }; // Respect the ranges - return drawForce; -}; - -/* Modify this function to alter accuracy calculation. Scaled between 0 and 100 (percent) */ -func int freeAimGetAccuracy(var C_Item weapon, var int talent) { - // Add any other factors here e.g. weapon-specific accuracy stats, weapon spread, accuracy talent, ... - // Check if bow or crossbow with (weapon.flags & ITEM_BOW) or (weapon.flags & ITEM_CROSSBOW) - // Here the talent is scaled by draw force: draw force=100% => accuracy=talent; draw force=0% => accuracy=talent/2 - var int drawForce; drawForce = freeAimGetDrawForce(weapon, talent); // Already scaled to [0, 100] - var int accuracy; accuracy = (talent-talent/2)*drawForce/100+talent/2; - if (accuracy < 0) { accuracy = 0; } else if (accuracy > 100) { accuracy = 100; }; // Respect the ranges - return accuracy; -}; - -// This a list of available reticle textures. Some of them are animated as indicated. Animated textures can be passed to -// the following functions: -// reticle.texture = freeAimAnimateReticleByTime(textureFileName, framesPerSecond, numberOfFrames) -// reticle.texture = freeAimAnimateReticleByPercent(textureFileName, 100, numberOfFrames) // Where 100 is a percentage -const string RETICLE_DOT = "RETICLEDOT.TGA"; -const string RETICLE_CROSSTWO = "RETICLECROSSTWO.TGA"; -const string RETICLE_CROSSTHREE = "RETICLECROSSTHREE.TGA"; -const string RETICLE_CROSSFOUR = "RETICLECROSSFOUR.TGA"; -const string RETICLE_X = "RETICLEX.TGA"; -const string RETICLE_CIRCLE = "RETICLECIRCLE.TGA"; -const string RETICLE_CIRCLECROSS = "RETICLECIRCLECROSS.TGA"; -const string RETICLE_DOUBLECIRCLE = "RETICLEDOUBLECIRCLE.TGA"; // Can be animated (rotation) 10 Frames [00..09] -const string RETICLE_PEAK = "RETICLEPEAK.TGA"; -const string RETICLE_NOTCH = "RETICLENOTCH.TGA"; // Can be animated (expanding) 17 Frames [00..16] -const string RETICLE_TRI_IN = "RETICLETRIIN.TGA"; // Can be animated (expanding) 17 Frames [00..16] -const string RETICLE_TRI_IN_DOT = "RETICLETRIINDOT.TGA"; // Can be animated (expanding) 17 Frames [00..16] -const string RETICLE_TRI_OUT_DOT = "RETICLETRIOUTDOT.TGA"; // Can be animated (expanding) 17 Frames [00..16] -const string RETICLE_DROP = "RETICLEDROP.TGA"; // Can be animated (expanding) 8 Frames [00..07] -const string RETICLE_FRAME = "RETICLEFRAME.TGA"; -const string RETICLE_EDGES = "RETICLEEDGES.TGA"; -const string RETICLE_BOWL = "RETICLEBOWL.TGA"; -const string RETICLE_HORNS = "RETICLEHORNS.TGA"; -const string RETICLE_BOLTS = "RETICLEBOLTS.TGA"; -const string RETICLE_BLAZE = "RETICLEBLAZE.TGA"; // Can be animated (flames) 10 Frames [00..09] -const string RETICLE_WHIRL = "RETICLEWHIRL.TGA"; // Can be animated (rotation) 10 Frames [00..09] -const string RETICLE_BRUSH = "RETICLEBRUSH.TGA"; -const string RETICLE_SPADES = "RETICLESPADES.TGA"; -const string RETICLE_SQUIGGLE = "RETICLESQUIGGLE.TGA"; - -/* Modify this function to alter the reticle texture, color and size (scaled between 0 and 100) for ranged combat. */ -func void freeAimGetReticleRanged(var C_Npc target, var C_Item weapon, var int talent, var int dist, var int rtrnPtr) { - var Reticle reticle; reticle = _^(rtrnPtr); - // Color (do not set the color to preserve the original texture color) - if (Hlp_IsValidNpc(target)) { // The argument 'target' might be empty! - var int att; att = Npc_GetAttitude(target, hero); - if (att == ATT_FRIENDLY) { reticle.color = Focusnames_Color_Friendly(); } - else if (att == ATT_HOSTILE) { reticle.color = Focusnames_Color_Hostile(); }; - }; - // Size (scale between [0, 100]: 0 is smallest, 100 is biggest) - reticle.size = -dist + 100; // Inverse aim distance: bigger for closer range: 100 for closest, 0 for most distance - // reticle.size = -freeAimGetDrawForce(weapon, talent) + 100; // Or inverse draw force: bigger for less draw force - // reticle.size = -freeAimGetAccuracy(weapon, talent) + 100; // Or inverse accuracy: bigger with lower accuracy - // More sophisticated customization is also possible: change the texture by draw force, the size by accuracy, ... - if (weapon.flags & ITEM_BOW) { // Change reticle texture by drawforce (irrespective of the reticle size set above) - var int drawForce; drawForce = freeAimGetDrawForce(weapon, talent); // Already scaled between [0, 100] - reticle.texture = freeAimAnimateReticleByPercent(RETICLE_NOTCH, drawForce, 17); // Animate reticle by draw force - } else if (weapon.flags & ITEM_CROSSBOW) { // Change reticle texture by distance - reticle.size = 75; // Keep the size fixed here - reticle.texture = freeAimAnimateReticleByPercent(RETICLE_DROP, dist, 8); // Animate reticle by distance - }; -}; - -/* Modify this function to alter the reticle texture, color and size (scaled between 0 and 100) for magic combat. */ -func void freeAimGetReticleSpell(var C_Npc target, var int spellID, var C_Spell spellInst, var int spellLevel, - var int isScroll, var int manaInvested, var int dist, var int rtrnPtr) { - var Reticle reticle; reticle = _^(rtrnPtr); - // 1. Texture (needs to be set, otherwise reticle will not be displayed) - // if (spellInst.spellType == SPELL_GOOD) { reticle.texture = RETICLE_CIRCLECROSS; } - // else if (spellInst.spellType == SPELL_NEUTRAL) { reticle.texture = RETICLE_CIRCLECROSS; } - // else if (spellInst.spellType == SPELL_BAD) { reticle.texture = RETICLE_CIRCLECROSS; }; - // 2. Color (do not set the color to preserve the original texture color) - if (Hlp_IsValidNpc(target)) { // The argument 'target' might be empty! - var int att; att = Npc_GetAttitude(target, hero); - if (att == ATT_FRIENDLY) { reticle.color = Focusnames_Color_Friendly(); } - else if (att == ATT_HOSTILE) { reticle.color = Focusnames_Color_Hostile(); }; - }; - // 3. Size (scale between [0, 100]: 0 is smallest, 100 is biggest) - reticle.size = -dist + 100; // Inverse aim distance: bigger for closer range: 100 for closest, 0 for most distance - // More sophisticated customization is also possible: change the texture by spellID, the size by spellLevel, ... - // Size by spell level for invest spells (e.g. increase size by invest level) - // if (spellLevel < 2) { reticle.size = 75; } - // else if (spellLevel >= 2) { reticle.size = 100; }; - // Different reticle for scrolls - // if (isScroll) { reticle.color = RGBA(125, 200, 250, 255); }; // Light blue - // Scale size by the amount of mana invested - // reticle.size = manaInvested; // This should be scaled between [0, 100] - // One possibility is to set the reticle texture by grouping the spells, as it is done below - // Ice spells - if (spellID == SPL_Icebolt) - || (spellID == SPL_IceCube) - || (spellID == SPL_IceLance) { - reticle.texture = RETICLE_SPADES; - } // Water spells - else if (spellID == SPL_WaterFist) - || (spellID == SPL_Inflate) - || (spellID == SPL_Geyser) - || (spellID == SPL_Waterwall) { - reticle.texture = freeAimAnimateReticleByTime(RETICLE_WHIRL, 30, 10); // Animate reticle with 30 FPS (10 Frames) - } // Fire spells - else if (spellID == SPL_Firebolt) - || (spellID == SPL_InstantFireball) - || (spellID == SPL_ChargeFireball) - || (spellID == SPL_Pyrokinesis) - || (spellID == SPL_Firestorm) { - reticle.texture = RETICLE_HORNS; - } // Electric spells - else if (spellID == SPL_Zap) - || (spellID == SPL_LightningFlash) - || (spellID == SPL_ChargeZap) { - reticle.texture = freeAimAnimateReticleByTime(RETICLE_BLAZE, 15, 10); // Animate reticle with 15 FPS (10 Frames) - } // Paladin spells - else if (spellID == SPL_PalHolyBolt) - || (spellID == SPL_PalRepelEvil) - || (spellID == SPL_PalDestroyEvil) { - reticle.texture = RETICLE_FRAME; - } // Evil spells - else if (spellID == SPL_BreathOfDeath) - || (spellID == SPL_MasterOfDisaster) - || (spellID == SPL_Energyball) - || (spellID == SPL_Skull) { - reticle.texture = RETICLE_BOWL; - } else { - reticle.texture = RETICLE_EDGES; // Set this as "default" texture here (if none of the conditions above is met) - }; -}; - -/* Modify this function to disable hit registration on npcs, e.g. 'ineffective' ranged weapons, no friendly-fire, ... */ -func int freeAimHitRegNpc(var C_Npc target, var C_Item weapon, var int material) { - // Valid return values are: - const int DESTROY = 0; // No hit reg (no damage), projectile is destroyed - const int COLLIDE = 1; // Hit reg (damage), projectile is put into inventory - const int DEFLECT = 2; // No hit reg (no damage), projectile is repelled - // The argument 'material' holds the material of the armor (of the target), -1 for no armor equipped - // For armors of npcs the materials are defined as in Constants.d (MAT_METAL, MAT_WOOD, ...) - if (target.aivar[AIV_PARTYMEMBER]) // Disable friendly-fire - && (target.aivar[AIV_LASTTARGET] != Hlp_GetInstanceID(hero)) { return DESTROY; }; - // if (material == MAT_METAL) && (Hlp_Random(100) < 20) { return DEFLECT; }; // Metal armors may be more durable - // The weapon can also be considered (e.g. ineffective weapons). Make use of 'weapon' for that - // Caution: Weapon may have been unequipped already at this time (unlikely)! Use Hlp_IsValidItem(weapon) - // if (Hlp_IsValidItem(weapon)) && (weapon.ineffective) { return DEFLECT; }; // Special case for weapon property - return COLLIDE; // Usually all shots on npcs should be registered, see freeAimGetAccuracy() above -}; - -/* Modify this function to disable hit registration on the world, e.g. deflection of metal, stuck in wood, ... */ -func int freeAimHitRegWld(var C_Npc shooter, var C_Item weapon, var int material, var string texture) { - // This function, unlike freeAimHitRegNpc() and all other functions here, is also called for npc shooters! - // Valid return values are: - const int DESTROY = 0; // Projectile is destroyed on impact - const int COLLIDE = 1; // Projectile gets stuck in the surface - const int DEFLECT = 2; // Projectile is repelled - // Note: The materials of the world are defined differently (than the familiar item-materials): - const int METAL = 1; - const int STONE = 2; - const int WOOD = 3; - const int EARTH = 4; - const int WATER = 5; - const int SNOW = 6; - const int UNDEF = 0; - if (material == WOOD) { return COLLIDE; }; // Projectiles stay stuck in wood (default in gothic) - if (Hlp_StrCmp(texture, "MOWOBOWMARK01.TGA")) { return COLLIDE; }; // Condition by surface texture - // if (Npc_IsPlayer(shooter)) ... // Keep in mind that this function is also called for npc shooters - if (material == STONE) && (Hlp_Random(100) < 20) { return DESTROY; }; // The projectile might break on impact - // The example in the previous line can also be treated in freeAimGetUsedProjectileInstance() below - // The weapon can also be considered (e.g. ineffective weapons). Make use of 'weapon' for that - // Caution: Weapon may have been unequipped already at this time (unlikely)! Use Hlp_IsValidItem(weapon) - // if (Hlp_IsValidItem(weapon)) && (weapon.ineffective) { return DEFLECT; }; // Special case for weapon property - return DEFLECT; // Projectiles deflect off of all other surfaces -}; - -/* Modify this function to alter the base damage of projectiles at time of shooting (only DAM_POINT) */ -func int freeAimScaleInitialDamage(var int basePointDamage, var C_Item weapon, var int talent) { - // This function should not be necessary, all damage specifications should be set in the item scripts. However, - // here the initial damage (DAM_POINT) may be scaled by draw force, accuracy, ... - // Check if bow or crossbow with (weapon.flags & ITEM_BOW) or (weapon.flags & ITEM_CROSSBOW) - // Here the damage is scaled by draw force: draw force=100% => baseDamage; draw force=0% => baseDamage/2 - var int drawForce; drawForce = freeAimGetDrawForce(weapon, talent); // Already scaled to [0, 100] - drawForce = 50 * drawForce / 100 + 50; // Re-scale the drawforce to [50, 100] - return (basePointDamage * drawForce) / 100; // Scale initial point damage by draw force -}; - -/* Modify this function to define a critical hit by weak spot (e.g. head node for headshot), its size and the damage */ -func void freeAimCriticalHitDef(var C_Npc target, var C_Item weapon, var int damage, var int rtrnPtr) { - var Weakspot weakspot; weakspot = _^(rtrnPtr); - // This function is dynamic: It is called on every hit and the weakspot and damage can be calculated individually - // Possibly incorporate weapon-specific stats, headshot talent, dependency on target, ... - // The damage may depend on the target npc (e.g. different damage for monsters). Make use of 'target' argument - // if (target.guild < GIL_SEPERATOR_HUM) { }; // E.g. special case for humans - // The weapon can also be considered (e.g. weapon specific damage). Make use of 'weapon' for that - // Caution: Weapon may have been unequipped already at this time (unlikely)! Use Hlp_IsValidItem(weapon) to check - // if (Hlp_IsValidItem(weapon)) && (weapon.certainProperty > 10) { }; // E.g. special case for weapon property - // The damage is a float and represents the new base damage (damage of weapon), not the final damage! - if (target.guild < GIL_SEPERATOR_HUM) { // Humans: head shot - weakspot.node = "Bip01 Head"; // Upper/lower case is not important, but spelling and spaces are - weakspot.dimX = -1; // Retrieve from model (works only on humans and only for head node!) - weakspot.dimY = -1; - weakspot.bDmg = mulf(damage, castToIntf(2.0)); // Double the base damage. This is a float - // } else if (target.aivar[AIV_MM_REAL_ID] == ID_TROLL) { - // weakspot.node = "Bip01 R Finger0"; // Difficult to hit when the troll attacks - // weakspot.dimX = 100; // 100x100cm size - // weakspot.dimY = 100; - // weakspot.bDmg = mulf(damage, castToIntf(1.75)); - // } else if (target.aivar[AIV_MM_REAL_ID] == ... - // ... - } else if (target.guild == GIL_BLOODFLY) || (target.guild == GIL_MEATBUG) { // Models that don't have a head node - weakspot.node = ""; // Disable critical hits this way - } else { // Default - weakspot.node = "Bip01 Head"; - weakspot.dimX = 50; // 50x50cm size - weakspot.dimY = 50; - weakspot.bDmg = mulf(damage, castToIntf(2.0)); // Double the base damage. This is a float - }; -}; - -/* Use this function to create an event when getting a critical hit, e.g. print or sound jingle, leave blank for none */ -func void freeAimCriticalHitEvent(var C_Npc target, var C_Item weapon) { - // The event may depend on the target npc (e.g. different sound for monsters). Make use of 'target' argument - // if (target.guild < GIL_SEPERATOR_HUM) { }; // E.g. special case for humans - // The critical hits could also be counted here to give an xp reward after 25 headshots - // The weapon can also be considered (e.g. weapon specific print). Make use of 'weapon' for that - // Caution: Weapon may have been unequipped already at this time (unlikely)! Use Hlp_IsValidItem(weapon) to check - // if (Hlp_IsValidItem(weapon)) && (weapon.certainProperty > 10) { }; // E.g. special case for weapon property - // Simple screen notification - // PrintS("Critical hit"); - // Shooter-like hit marker - var int hitmark; - if (!Hlp_IsValidHandle(hitmark)) { // Create hitmark if it does not exist - var zCView screen; screen = _^(MEM_Game._zCSession_viewport); - hitmark = View_CreateCenterPxl(screen.psizex/2, screen.psizey/2, 64, 64); - View_SetTexture(hitmark, freeAimAnimateReticleByPercent(RETICLE_TRI_IN, 100, 7)); // Retrieve 7th frame of ani - }; - View_Open(hitmark); - FF_ApplyExtData(View_Close, 300, 1, hitmark); - // Sound notification - Snd_Play3D(target, "FREEAIM_CRITICALHIT"); -}; - -/* Modify this function to exchange (or remove) the projectile after shooting for re-using, e.g. used arrow */ -func int freeAimGetUsedProjectileInstance(var int projectileInst, var C_Npc inventoryNpc) { - // By returning zero, the projectile is completely removed (e.g. retrieve-projectile-talent not learned yet) - // The argument inventoryNpc holds the npc in whose inventory it will be put, or is empty if it landed in the world - // if (projectileInst == Hlp_GetInstanceID(ItRw_Arrow)) { // Exchange the instance for a "used" one - // if (!Hlp_IsValidItem(ItRw_UsedArrow)) { Wld_InsertItem(ItRw_UsedArrow, MEM_FARFARAWAY); }; // Initialize! - // projectileInst = Hlp_GetInstanceID(ItRw_UsedArrow); - // }; - if (Hlp_IsValidNpc(inventoryNpc)) { // Projectile hit npc and will be put into their inventory - if (Npc_IsPlayer(inventoryNpc)) { return 0; }; // Do not put projectiles in player inventory - // if (inventoryNpc.guild < GIL_SEPERATOR_HUM) { return 0; }; // Remove projectile when it hits humans - // if (PLAYER_TALENT_TAKEANIMALTROPHY[REUSE_Arrow] == FALSE) { return 0; }; // Retrieve-projectile-talent - // if (!Npc_HasItems(hero, ItMi_ArrowTool)) { return 0; }; // Player needs tool to remove the projectile - // if (Hlp_Random(100) < 50) { return 0; }; // Chance of retrieval - return projectileInst; // For now it is just preserved (is put in the inventory as is) - } else { // Projectile did not hit npc and landed in world - // if (PLAYER_TALENT_REUSE_ARROW == FALSE) { return 0; }; // Reuse-projectile-talent - return projectileInst; // For now it is just preserved (leave it in the world as is) - }; -}; - -/* Shift the aimvob along the camera out vector for spells (if you don't know what this is, you don't need it) */ -func int freeAimShiftAimVob(var int spellID) { - // if (spellID == ...) { return -100; }; // Push the aim vob 100 cm away from any wall - return 0; -}; diff --git a/_work/data/Scripts/Content/freeAim/config/collectable.d b/_work/data/Scripts/Content/freeAim/config/collectable.d new file mode 100644 index 0000000..884497e --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/config/collectable.d @@ -0,0 +1,28 @@ +/* + * This file contains all configurations for collectable projectiles. + */ + +/* + * When collecting projectiles is enabled (FREEAIM_REUSE_PROJECTILES == 1), this function is called whenever a + * projectile (arrows and bolts) hits an npc or stops in the world. This function is useful to replace the projectile. + * Return value is a item instance. When returning zero, the projectile is destroyed. + */ +func int freeAimGetUsedProjectileInstance(var int projectileInst, var C_Npc inventoryNpc) { + // By returning zero, the projectile is completely removed (e.g. retrieve-projectile-talent not learned yet) + // The argument inventoryNpc holds the npc in whose inventory it will be put, or is empty if it landed in the world + // if (projectileInst == Hlp_GetInstanceID(ItRw_Arrow)) { // Exchange the instance for a "used" one + // if (!Hlp_IsValidItem(ItRw_UsedArrow)) { Wld_InsertItem(ItRw_UsedArrow, MEM_FARFARAWAY); }; // Initialize! + // projectileInst = Hlp_GetInstanceID(ItRw_UsedArrow); + // }; + if (Hlp_IsValidNpc(inventoryNpc)) { // Projectile hit npc and will be put into their inventory + if (Npc_IsPlayer(inventoryNpc)) { return 0; }; // Do not put projectiles in player inventory + // if (inventoryNpc.guild < GIL_SEPERATOR_HUM) { return 0; }; // Remove projectile when it hits humans + // if (PLAYER_TALENT_TAKEANIMALTROPHY[REUSE_Arrow] == FALSE) { return 0; }; // Retrieve-projectile-talent + // if (!Npc_HasItems(hero, ItMi_ArrowTool)) { return 0; }; // Player needs tool to remove the projectile + // if (Hlp_Random(100) < 50) { return 0; }; // Chance of retrieval + return projectileInst; // For now it is just preserved (is put in the inventory as is) + } else { // Projectile did not hit npc and landed in world + // if (PLAYER_TALENT_REUSE_ARROW == FALSE) { return 0; }; // Reuse-projectile-talent + return projectileInst; // For now it is just preserved (leave it in the world as is) + }; +}; diff --git a/_work/data/Scripts/Content/freeAim/config/collision.d b/_work/data/Scripts/Content/freeAim/config/collision.d new file mode 100644 index 0000000..a0cc706 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/config/collision.d @@ -0,0 +1,53 @@ +/* + * This file contains all configurations for collision and hit registration of projectiles. + */ + +/* + * This function is called every time an npc is hit by a projectile (arrows and bolts). It can be used to define the + * collision behavior (or disabling hit registration) on npcs based on different criteria. + * Ideas: 'ineffective' ranged weapons, armor materials immune to arrows, no friendly-fire + */ +func int freeAimHitRegNpc(var C_Npc target, var C_Item weapon, var int material) { + // Valid return values are: + const int DESTROY = 0; // No hit reg (no damage), projectile is destroyed + const int COLLIDE = 1; // Hit reg (damage), projectile is put into inventory + const int DEFLECT = 2; // No hit reg (no damage), projectile is repelled + // The argument 'material' holds the material of the armor (of the target), -1 for no armor equipped + // For armors of npcs the materials are defined as in Constants.d (MAT_METAL, MAT_WOOD, ...) + if (target.aivar[AIV_PARTYMEMBER]) // Disable friendly-fire + && (target.aivar[AIV_LASTTARGET] != Hlp_GetInstanceID(hero)) { return DESTROY; }; + // if (material == MAT_METAL) && (Hlp_Random(100) < 20) { return DEFLECT; }; // Metal armors may be more durable + // The weapon can also be considered (e.g. ineffective weapons). Make use of 'weapon' for that + // Caution: Weapon may have been unequipped already at this time (unlikely)! Use Hlp_IsValidItem(weapon) + // if (Hlp_IsValidItem(weapon)) && (weapon.ineffective) { return DEFLECT; }; // Special case for weapon property + return COLLIDE; // Usually all shots on npcs should be registered, see freeAimGetAccuracy() above +}; + +/* + * This function is called every time the world (static or vobs) is hit by a projectile (arrows ans bolts). It can be + * used to define the collision behavior for different materials or surface textures. + * Note: Unlike freeAimHitRegNpc() and all other functions here, this function is also called for npc shooters! + */ +func int freeAimHitRegWld(var C_Npc shooter, var C_Item weapon, var int material, var string texture) { + // Valid return values are: + const int DESTROY = 0; // Projectile is destroyed on impact + const int COLLIDE = 1; // Projectile gets stuck in the surface + const int DEFLECT = 2; // Projectile is repelled + // Note: The materials of the world are defined differently (than the familiar item-materials): + const int METAL = 1; + const int STONE = 2; + const int WOOD = 3; + const int EARTH = 4; + const int WATER = 5; + const int SNOW = 6; + const int UNDEF = 0; + if (material == WOOD) { return COLLIDE; }; // Projectiles stay stuck in wood (default in gothic) + if (Hlp_StrCmp(texture, "MOWOBOWMARK01.TGA")) { return COLLIDE; }; // Condition by surface texture + // if (Npc_IsPlayer(shooter)) ... // Keep in mind that this function is also called for npc shooters + if (material == STONE) && (Hlp_Random(100) < 20) { return DESTROY; }; // The projectile might break on impact + // The example in the previous line can also be treated in freeAimGetUsedProjectileInstance() below + // The weapon can also be considered (e.g. ineffective weapons). Make use of 'weapon' for that + // Caution: Weapon may have been unequipped already at this time (unlikely)! Use Hlp_IsValidItem(weapon) + // if (Hlp_IsValidItem(weapon)) && (weapon.ineffective) { return DEFLECT; }; // Special case for weapon property + return DEFLECT; // Projectiles deflect off of all other surfaces +}; diff --git a/_work/data/Scripts/Content/freeAim/config/criticalHit.d b/_work/data/Scripts/Content/freeAim/config/criticalHit.d new file mode 100644 index 0000000..8fd2601 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/config/criticalHit.d @@ -0,0 +1,159 @@ +/* + * This file contains all configurations for critical hits for bows and crossbows. + */ + +/* + * This function is called every time (any kind of) npc is hit by a projectile (arrows and bolts) to determine, whether + * a critical hit occured. This function returns a defintion of the critical hit area (weakspot) based on the npc that + * it hit or the weapon used. A weakspot is defined by its bone, size and modified damage. + * Here, preliminary weakspots for almost all Gothic 2 monsters are defined (all headshots). + */ +func void freeAimCriticalHitDef(var C_Npc target, var C_Item weapon, var int damage, var int rtrnPtr) { + var Weakspot weakspot; weakspot = _^(rtrnPtr); + // This function is dynamic: It is called on every hit and the weakspot and damage can be calculated individually + // Possibly incorporate weapon-specific stats, headshot talent, dependency on target, ... + // The damage may depend on the target npc (e.g. different damage for monsters). Make use of 'target' argument + // if (target.guild < GIL_SEPERATOR_HUM) { }; // E.g. special case for humans + // The weapon can also be considered (e.g. weapon specific damage). Make use of 'weapon' for that + // Caution: Weapon may have been unequipped already at this time (unlikely)! Use Hlp_IsValidItem(weapon) to check + // if (Hlp_IsValidItem(weapon)) && (weapon.certainProperty > 10) { }; // E.g. special case for weapon property + // The damage is a float and represents the new base damage (damage of weapon), not the final damage! + weakspot.node = "Bip01 Head"; // Upper/lower case is not important, but spelling and spaces are + weakspot.bDmg = mulf(damage, castToIntf(1.5)); // Increase the base damage. This is a float + if (target.guild < GIL_SEPERATOR_HUM) + || ((target.guild > GIL_SEPERATOR_ORC) && (target.guild < GIL_DRACONIAN)) + || (target.guild == GIL_ZOMBIE) + || (target.guild == GIL_SUMMONEDZOMBIE) { + // Here is also room for story-dependent exceptions (e.g. a specific npc may have a different weak spot) + weakspot.dimX = -1; // Retrieve from model (works only on humanoids and only for head node!) + weakspot.dimY = -1; + } else if (target.guild == GIL_BLOODFLY) // Bloodflys and meatbugs don't have a head node + || (target.guild == GIL_MEATBUG) + || (target.guild == GIL_STONEGUARDIAN) // Stoneguardians have too large heads (head node is not centered) + || (target.guild == GIL_SUMMONEDGUARDIAN) + || (target.guild == GIL_STONEGOLEM) // Same for golems + || (target.guild == GIL_FIREGOLEM) + || (target.guild == GIL_ICEGOLEM) + || (target.guild == GIL_SUMMONED_GOLEM) + || (target.guild == GIL_SWAMPGOLEM) + || (target.guild == GIL_SKELETON) // Skeletons are only bones, there is no critical hit + || (target.guild == GIL_SUMMONED_SKELETON) + || (target.guild == GIL_SKELETON_MAGE) + || (target.guild == GIL_GOBBO_SKELETON) + || (target.guild == GIL_SUMMONED_GOBBO_SKELETON) + || (target.guild == GIL_SHADOWBEAST_SKELETON) { + weakspot.node = ""; // Disable critical hits this way + } else if (target.aivar[AIV_MM_REAL_ID] == ID_BLATTCRAWLER) { + weakspot.node = "ZM_Fuehler_01"; // Blattcrawler has a tiny head that is not centered. ZM_Fuehler_01 is, though + weakspot.dimX = 45; + weakspot.dimY = 35; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_BLOODHOUND) { + weakspot.dimX = 55; + weakspot.dimY = 50; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_ORCBITER) { + weakspot.dimX = 45; + weakspot.dimY = 40; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_RAZOR) || (target.aivar[AIV_MM_REAL_ID] == ID_DRAGONSNAPPER) { + weakspot.dimX = 40; + weakspot.dimY = 40; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_GARGOYLE) { // Both panther and fire beast + weakspot.dimX = 65; + weakspot.dimY = 60; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_SNAPPER) { + weakspot.dimX = 40; + weakspot.dimY = 35; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_TROLL) { + weakspot.dimX = 90; + weakspot.dimY = 100; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_TROLL_BLACK) { + weakspot.dimX = 70; + weakspot.dimY = 80; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_KEILER) { + weakspot.dimX = 60; + weakspot.dimY = 65; + } else if (target.aivar[AIV_MM_REAL_ID] == ID_WARG) || (target.aivar[AIV_MM_REAL_ID] == ID_ICEWOLF) { + weakspot.dimX = 40; + weakspot.dimY = 45; + } else if (target.guild == GIL_SWAMPSHARK) { + weakspot.node = "ZS_MOUTH"; // Hard to hit + weakspot.dimX = 30; + weakspot.dimY = 30; + } else if (target.guild == GIL_ALLIGATOR) { + weakspot.dimX = 80; + weakspot.dimY = 60; + } else if (target.guild == GIL_GIANT_RAT) { // All rats (desert, swamp, normal) + weakspot.dimX = 40; + weakspot.dimY = 35; + } else if (target.guild == GIL_GOBBO) { + weakspot.dimX = 25; + weakspot.dimY = 25; + } else if (target.guild == GIL_DEMON) || (target.guild == GIL_SUMMONED_DEMON) { // Both demon and demon lord + weakspot.dimX = 35; + weakspot.dimY = 40; + } else if (target.guild == GIL_DRACONIAN) { + weakspot.dimX = 40; + weakspot.dimY = 40; + } else if (target.guild == GIL_DRAGON) { + weakspot.dimX = 60; + weakspot.dimY = 70; + } else if (target.guild == GIL_WARAN) { + weakspot.dimX = 50; + weakspot.dimY = 50; + } else if (target.guild == GIL_GIANT_BUG) { + weakspot.dimX = 30; + weakspot.dimY = 40; + } else if (target.guild == GIL_HARPY) { + weakspot.dimX = 25; + weakspot.dimY = 25; + } else if (target.guild == GIL_LURKER) { + weakspot.dimX = 30; + weakspot.dimY = 30; + } else if (target.guild == GIL_MINECRAWLER) { + weakspot.dimX = 50; + weakspot.dimY = 50; + } else if (target.guild == GIL_MOLERAT) { + weakspot.dimX = 35; + weakspot.dimY = 30; + } else if (target.guild == GIL_SCAVENGER) { + weakspot.dimX = 35; + weakspot.dimY = 40; + } else if (target.guild == GIL_SHADOWBEAST) { + weakspot.dimX = 60; + weakspot.dimY = 60; + } else if (target.guild == GIL_SHEEP) { + weakspot.dimX = 20; + weakspot.dimY = 25; + } else if (target.guild == GIL_WOLF) || (target.guild == GIL_SUMMONED_WOLF) { + weakspot.dimX = 25; + weakspot.dimY = 40; + } else { // Default size for any non-listed monster + weakspot.dimX = 50; // 50x50cm size + weakspot.dimY = 50; + }; +}; + +/* + * This function is called when a critical hit occurred and can be used to print something to the screen, play a sound + * jingle or, as done here by default, show a hitmarker. Leave this function blank for no event. + */ +func void freeAimCriticalHitEvent(var C_Npc target, var C_Item weapon) { + // The event may depend on the target npc (e.g. different sound for monsters). Make use of 'target' argument + // if (target.guild < GIL_SEPERATOR_HUM) { }; // E.g. special case for humans + // The critical hits could also be counted here to give an xp reward after 25 headshots + // The weapon can also be considered (e.g. weapon specific print). Make use of 'weapon' for that + // Caution: Weapon may have been unequipped already at this time (unlikely)! Use Hlp_IsValidItem(weapon) to check + // if (Hlp_IsValidItem(weapon)) && (weapon.certainProperty > 10) { }; // E.g. special case for weapon property + // Simple screen notification + // PrintS("Critical hit"); + // Shooter-like hit marker + var int hitmark; + if (!Hlp_IsValidHandle(hitmark)) { // Create hitmark if it does not exist + var zCView screen; screen = _^(MEM_Game._zCSession_viewport); + hitmark = View_CreateCenterPxl(screen.psizex/2, screen.psizey/2, 64, 64); + View_SetTexture(hitmark, freeAimAnimateReticleByPercent(RETICLE_TRI_IN, 100, 7)); // Retrieve 7th frame of ani + }; + View_Open(hitmark); + FF_ApplyExtData(View_Close, 300, 1, hitmark); + // Sound notification + Snd_Play3D(target, "FREEAIM_CRITICALHIT"); +}; diff --git a/_work/data/Scripts/Content/freeAim/config/ranged.d b/_work/data/Scripts/Content/freeAim/config/ranged.d new file mode 100644 index 0000000..a880080 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/config/ranged.d @@ -0,0 +1,52 @@ +/* + * This file contains all configurations for ranged combat (bows and crossbows). + */ + +/* + * This function is called at the point of shooting a bow or a crossbow. The return value scales the gravity of the + * projectile in percent, where 0 is fast gravity drop-off and 100 is the straightest shot possible. Regardless of the + * percentage, however, all shots are impacted by gravity at the latest after FREEAIM_TRAJECTORY_ARC_MAX milliseconds. + * Here, bows are scaled with draw time, whereas crossbows always have 100% draw force (they are mechanical). + * This function is also well-suited to be used by the other functions of this file defined below. + */ +func int freeAimGetDrawForce(var C_Item weapon, var int talent) { + var int drawTime; drawTime = MEM_Timer.totalTime - freeAimBowDrawOnset; + // Possibly incorporate more factors like e.g. a quick-draw talent, weapon-specific stats, ... + if (weapon.flags & ITEM_CROSSBOW) { return 100; }; // Always full draw force on crossbows + // For now the draw time is scaled by a maximum. Replace FREEAIM_DRAWTIME_MAX by a variable for a quick-draw talent + var int drawForce; drawForce = (100 * drawTime) / FREEAIM_DRAWTIME_MAX; + if (drawForce < 0) { drawForce = 0; } else if (drawForce > 100) { drawForce = 100; }; // Respect the ranges + return drawForce; +}; + +/* + * This function is called at the point of shooting a bow or a crossbow. The return value scales the accuracy of the + * projectile in percent, where 0 is maximum scattering and 100 is precisely on target. + * Here, the accuracy is scaled by talent and by draw force (see function above). + */ +func int freeAimGetAccuracy(var C_Item weapon, var int talent) { + // Add any other factors here e.g. weapon-specific accuracy stats, weapon spread, accuracy talent, ... + // Check if bow or crossbow with (weapon.flags & ITEM_BOW) or (weapon.flags & ITEM_CROSSBOW) + // Here the talent is scaled by draw force: draw force=100% => accuracy=talent; draw force=0% => accuracy=talent/2 + var int drawForce; drawForce = freeAimGetDrawForce(weapon, talent); // Already scaled to [0, 100] + var int accuracy; accuracy = (talent-talent/2)*drawForce/100+talent/2; + if (accuracy < 0) { accuracy = 0; } else if (accuracy > 100) { accuracy = 100; }; // Respect the ranges + return accuracy; +}; + +/* + * This function is called at the point of shooting a bow or crossbow. It may be used to alter the base damage at time + * of shooting (only DAM_POINT damage). This should never be necessary, as all damage specifications should be set in + * the item script of the weapon. However, here the initial damage may be scaled by draw force or accuracy (see + * functions above). The return value is the base damage (equivalent to the damage in the item script of the weapon). + * Here, the damage is scaled by draw force to yield less damage when the bow is only briefly drawn. + */ +func int freeAimScaleInitialDamage(var int basePointDamage, var C_Item weapon, var int talent) { + // This function should not be necessary, all damage specifications should be set in the item scripts. However, + // here the initial damage (DAM_POINT) may be scaled by draw force, accuracy, ... + // Check if bow or crossbow with (weapon.flags & ITEM_BOW) or (weapon.flags & ITEM_CROSSBOW) + // Here the damage is scaled by draw force: draw force=100% => baseDamage; draw force=0% => baseDamage/2 + var int drawForce; drawForce = freeAimGetDrawForce(weapon, talent); // Already scaled to [0, 100] + drawForce = 50 * drawForce / 100 + 50; // Re-scale the drawforce to [50, 100] + return (basePointDamage * drawForce) / 100; // Scale initial point damage by draw force +}; diff --git a/_work/data/Scripts/Content/freeAim/config/reticle.d b/_work/data/Scripts/Content/freeAim/config/reticle.d new file mode 100644 index 0000000..b797c8c --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/config/reticle.d @@ -0,0 +1,133 @@ +/* + * This file contains all configurations for reticles. + */ + +/* + * This a list of available reticle textures by default. Feel free to extend this list with your own textures. Some of + * them are animated as indicated. Animated textures can be passed to the following functions: + * reticle.texture = freeAimAnimateReticleByTime(textureFileName, framesPerSecond, numberOfFrames) + * reticle.texture = freeAimAnimateReticleByPercent(textureFileName, 100, numberOfFrames) // Where 100 is a percentage + */ +const string RETICLE_DOT = "RETICLEDOT.TGA"; +const string RETICLE_CROSSTWO = "RETICLECROSSTWO.TGA"; +const string RETICLE_CROSSTHREE = "RETICLECROSSTHREE.TGA"; +const string RETICLE_CROSSFOUR = "RETICLECROSSFOUR.TGA"; +const string RETICLE_X = "RETICLEX.TGA"; +const string RETICLE_CIRCLE = "RETICLECIRCLE.TGA"; +const string RETICLE_CIRCLECROSS = "RETICLECIRCLECROSS.TGA"; +const string RETICLE_DOUBLECIRCLE = "RETICLEDOUBLECIRCLE.TGA"; // Can be animated (rotation) 10 Frames [00..09] +const string RETICLE_PEAK = "RETICLEPEAK.TGA"; +const string RETICLE_NOTCH = "RETICLENOTCH.TGA"; // Can be animated (expanding) 17 Frames [00..16] +const string RETICLE_TRI_IN = "RETICLETRIIN.TGA"; // Can be animated (expanding) 17 Frames [00..16] +const string RETICLE_TRI_IN_DOT = "RETICLETRIINDOT.TGA"; // Can be animated (expanding) 17 Frames [00..16] +const string RETICLE_TRI_OUT_DOT = "RETICLETRIOUTDOT.TGA"; // Can be animated (expanding) 17 Frames [00..16] +const string RETICLE_DROP = "RETICLEDROP.TGA"; // Can be animated (expanding) 8 Frames [00..07] +const string RETICLE_FRAME = "RETICLEFRAME.TGA"; +const string RETICLE_EDGES = "RETICLEEDGES.TGA"; +const string RETICLE_BOWL = "RETICLEBOWL.TGA"; +const string RETICLE_HORNS = "RETICLEHORNS.TGA"; +const string RETICLE_BOLTS = "RETICLEBOLTS.TGA"; +const string RETICLE_BLAZE = "RETICLEBLAZE.TGA"; // Can be animated (flames) 10 Frames [00..09] +const string RETICLE_WHIRL = "RETICLEWHIRL.TGA"; // Can be animated (rotation) 10 Frames [00..09] +const string RETICLE_BRUSH = "RETICLEBRUSH.TGA"; +const string RETICLE_SPADES = "RETICLESPADES.TGA"; +const string RETICLE_SQUIGGLE = "RETICLESQUIGGLE.TGA"; + +/* + * This function is called continuously while aiming with a ranged weapon (bows and crossbows). It allows defining the + * reticle texture, size and color at any point in time while aiming, based on a variety of properties. Reticle size is + * represented as a percentage (100 is biggest size, 0 is smallest). + * Here, the size is scaled by aiming distance. As indicated by the in-line comments basing the size (or color) on the + * functions freeAimGetDrawForce and freeAimGetAccuracy is also possible. + */ +func void freeAimGetReticleRanged(var C_Npc target, var C_Item weapon, var int talent, var int dist, var int rtrnPtr) { + var Reticle reticle; reticle = _^(rtrnPtr); + // Color (do not set the color to preserve the original texture color) + if (Hlp_IsValidNpc(target)) { // The argument 'target' might be empty! + var int att; att = Npc_GetAttitude(target, hero); + if (att == ATT_FRIENDLY) { reticle.color = Focusnames_Color_Friendly(); } + else if (att == ATT_HOSTILE) { reticle.color = Focusnames_Color_Hostile(); }; + }; + // Size (scale between [0, 100]: 0 is smallest, 100 is biggest) + reticle.size = -dist + 100; // Inverse aim distance: bigger for closer range: 100 for closest, 0 for most distance + // reticle.size = -freeAimGetDrawForce(weapon, talent) + 100; // Or inverse draw force: bigger for less draw force + // reticle.size = -freeAimGetAccuracy(weapon, talent) + 100; // Or inverse accuracy: bigger with lower accuracy + // More sophisticated customization is also possible: change the texture by draw force, the size by accuracy, ... + if (weapon.flags & ITEM_BOW) { // Change reticle texture by drawforce (irrespective of the reticle size set above) + var int drawForce; drawForce = freeAimGetDrawForce(weapon, talent); // Already scaled between [0, 100] + reticle.texture = freeAimAnimateReticleByPercent(RETICLE_NOTCH, drawForce, 17); // Animate reticle by draw force + } else if (weapon.flags & ITEM_CROSSBOW) { // Change reticle texture by distance + reticle.size = 75; // Keep the size fixed here + reticle.texture = freeAimAnimateReticleByPercent(RETICLE_DROP, dist, 8); // Animate reticle by distance + }; +}; + +/* + * This function is called continuously while aiming with a spells. It allows defining the reticle texture, size and + * color at any point in time while aiming, based on a variety of spell properties. Reticle size is represented as a + * percentage (100 is biggest size, 0 is smallest). + * Here, the size is scaled by aiming distance. As indicated by the in-line comments basing the size (or color) on the + * any provided spell property is easily possible. + */ +func void freeAimGetReticleSpell(var C_Npc target, var int spellID, var C_Spell spellInst, var int spellLevel, + var int isScroll, var int manaInvested, var int dist, var int rtrnPtr) { + var Reticle reticle; reticle = _^(rtrnPtr); + // 1. Texture (needs to be set, otherwise reticle will not be displayed) + // if (spellInst.spellType == SPELL_GOOD) { reticle.texture = RETICLE_CIRCLECROSS; } + // else if (spellInst.spellType == SPELL_NEUTRAL) { reticle.texture = RETICLE_CIRCLECROSS; } + // else if (spellInst.spellType == SPELL_BAD) { reticle.texture = RETICLE_CIRCLECROSS; }; + // 2. Color (do not set the color to preserve the original texture color) + if (Hlp_IsValidNpc(target)) { // The argument 'target' might be empty! + var int att; att = Npc_GetAttitude(target, hero); + if (att == ATT_FRIENDLY) { reticle.color = Focusnames_Color_Friendly(); } + else if (att == ATT_HOSTILE) { reticle.color = Focusnames_Color_Hostile(); }; + }; + // 3. Size (scale between [0, 100]: 0 is smallest, 100 is biggest) + reticle.size = -dist + 100; // Inverse aim distance: bigger for closer range: 100 for closest, 0 for most distance + // More sophisticated customization is also possible: change the texture by spellID, the size by spellLevel, ... + // Size by spell level for invest spells (e.g. increase size by invest level) + // if (spellLevel < 2) { reticle.size = 75; } + // else if (spellLevel >= 2) { reticle.size = 100; }; + // Different reticle for scrolls + // if (isScroll) { reticle.color = RGBA(125, 200, 250, 255); }; // Light blue + // Scale size by the amount of mana invested + // reticle.size = manaInvested; // This should be scaled between [0, 100] + // One possibility is to set the reticle texture by grouping the spells, as it is done below + // Ice spells + if (spellID == SPL_Icebolt) + || (spellID == SPL_IceCube) + || (spellID == SPL_IceLance) { + reticle.texture = RETICLE_SPADES; + } // Water spells + else if (spellID == SPL_WaterFist) + || (spellID == SPL_Inflate) + || (spellID == SPL_Geyser) + || (spellID == SPL_Waterwall) { + reticle.texture = freeAimAnimateReticleByTime(RETICLE_WHIRL, 30, 10); // Animate reticle with 30 FPS (10 Frames) + } // Fire spells + else if (spellID == SPL_Firebolt) + || (spellID == SPL_InstantFireball) + || (spellID == SPL_ChargeFireball) + || (spellID == SPL_Pyrokinesis) + || (spellID == SPL_Firestorm) { + reticle.texture = RETICLE_HORNS; + } // Electric spells + else if (spellID == SPL_Zap) + || (spellID == SPL_LightningFlash) + || (spellID == SPL_ChargeZap) { + reticle.texture = freeAimAnimateReticleByTime(RETICLE_BLAZE, 15, 10); // Animate reticle with 15 FPS (10 Frames) + } // Paladin spells + else if (spellID == SPL_PalHolyBolt) + || (spellID == SPL_PalRepelEvil) + || (spellID == SPL_PalDestroyEvil) { + reticle.texture = RETICLE_FRAME; + } // Evil spells + else if (spellID == SPL_BreathOfDeath) + || (spellID == SPL_MasterOfDisaster) + || (spellID == SPL_Energyball) + || (spellID == SPL_Skull) { + reticle.texture = RETICLE_BOWL; + } else { + reticle.texture = RETICLE_EDGES; // Set this as "default" texture here (if none of the conditions above is met) + }; +}; diff --git a/_work/data/Scripts/Content/freeAim/config/settings.d b/_work/data/Scripts/Content/freeAim/config/settings.d new file mode 100644 index 0000000..3ee4075 --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/config/settings.d @@ -0,0 +1,21 @@ +/* + * This file contains all basic settings. Values deviating too far from the default values should either be avoided or + * tested thoroughly. + */ + +const int FREEAIM_REUSE_PROJECTILES = 1; // Enable collection and re-using of shot projectiles +const int FREEAIM_DISABLE_SPELLS = 0; // Disable free aiming for spells (ranged uneffected) +const int FREEAIM_DRAWTIME_MAX = 1200; // Max draw time (ms): When is the bow fully drawn +const int FREEAIM_DEBUG_CONSOLE = 1; // Enable console commands (debugging). Disable in final mod +const int FREEAIM_DEBUG_WEAKSPOT = 0; // Show weakspot debugging visualization by default +const int FREEAIM_DEBUG_TRACERAY = 0; // Show trace ray debugging visualization by default +const int FREEAIM_TRIGGER_COLL_FIX = 1; // Apply trigger collision fix (disable collision) + +// Modifying any line below is not recommended! +const float FREEAIM_SCATTER_DEG = 2.2; // Maximum scatter radius in degrees for ranged accuracy +const int FREEAIM_TRAJECTORY_ARC_MAX = 400; // Max time (ms) after which the trajectory drops off +const float FREEAIM_PROJECTILE_GRAVITY = 0.1; // Gravity of projectile after FREEAIM_TRAJECTORY_ARC_MAX ms +const string FREEAIM_CAMERA = "CamModFreeAim"; // CCamSys_Def script instance for free aim +const int FREEAIM_CAMERA_X_SHIFT = 0; // Camera is set to shoulderview, s.a. (not recommended) +const float FREEAIM_ROTATION_SCALE = 0.16; // Turn rate. Non-weapon mode is 0.2 (zMouseRotationScale) +const int FREEAIM_HITDETECTION_EXP = 0; // Additional hit detection test (EXPERIMENTAL) diff --git a/_work/data/Scripts/Content/freeAim/config/spell.d b/_work/data/Scripts/Content/freeAim/config/spell.d new file mode 100644 index 0000000..fec26fe --- /dev/null +++ b/_work/data/Scripts/Content/freeAim/config/spell.d @@ -0,0 +1,15 @@ +/* + * This file contains all configurations for magic combat (spells). + */ + +/* + * This function is called continuously while aiming with spells to correct the aim vob position. The return value is + * interpreted as centimeters, the aim vob should be shifted along the camera out vector (viewing angle). This function + * should never be of use and should be adjusted for individual spells only. Usually, no spell requires the aim vob to + * be shifted. Exceptions are spells that utilize the aim vob as target to spawn FX on it with the functions + * freeAimAttachFX and freeAimDetachFX. + */ +func int freeAimShiftAimVob(var int spellID) { + // if (spellID == ...) { return -100; }; // Push the aim vob 100 cm away from any wall towards the player + return 0; +}; diff --git a/_work/data/Scripts/Content/freeAim/freeAim.src b/_work/data/Scripts/Content/freeAim/freeAim.src index dc55a7a..ae2f0d4 100644 --- a/_work/data/Scripts/Content/freeAim/freeAim.src +++ b/_work/data/Scripts/Content/freeAim/freeAim.src @@ -1,2 +1,26 @@ -_intern.d -config.d +_intern\const.d +_intern\classes.d +config\settings.d + +_intern\activeSpell.d +_intern\init.d +_intern\controls.d +_intern\ccommands.d +_intern\debug.d +_intern\aimVob.d +_intern\aimRay.d +_intern\reticle.d + +_intern\ranged.d +_intern\spell.d + +_intern\collision.d +_intern\collectable.d +_intern\criticalHit.d + +config\ranged.d +config\spell.d +config\reticle.d +config\collision.d +config\collectable.d +config\criticalHit.d diff --git a/_work/data/Scripts/System/PFX/PfxInstFreeAim.d b/_work/data/Scripts/System/PFX/PfxInstFreeAim.d index fb9b57d..9bdccfc 100644 --- a/_work/data/Scripts/System/PFX/PfxInstFreeAim.d +++ b/_work/data/Scripts/System/PFX/PfxInstFreeAim.d @@ -1,5 +1,5 @@ /* - * Free aim projectile trail strip for increase visibility + * Projectile trail strip for increased visibility * * G2 Free Aim v0.1.2 - Free aiming for the video game Gothic 2 by Piranha Bytes * Copyright (C) 2016 mud-freak (@szapp) diff --git a/_work/data/Scripts/System/SFX/SfxInstFreeAim.d b/_work/data/Scripts/System/SFX/SfxInstFreeAim.d index e19cddc..5cda011 100644 --- a/_work/data/Scripts/System/SFX/SfxInstFreeAim.d +++ b/_work/data/Scripts/System/SFX/SfxInstFreeAim.d @@ -1,5 +1,5 @@ /* - * Free aim critical hit sound + * Critical hit sound for projectiles * * G2 Free Aim v0.1.2 - Free aiming for the video game Gothic 2 by Piranha Bytes * Copyright (C) 2016 mud-freak (@szapp) @@ -23,5 +23,5 @@ INSTANCE FREEAIM_CRITICALHIT (C_SFX_DEF) { file = "BOW_FIRE_02.WAV"; - vol = 90; + vol = 60; }; diff --git a/_work/data/Scripts/System/VisualFX/VisualFxFreeAim.d b/_work/data/Scripts/System/VisualFX/VisualFxFreeAim.d index c70a9a5..cce473d 100644 --- a/_work/data/Scripts/System/VisualFX/VisualFxFreeAim.d +++ b/_work/data/Scripts/System/VisualFX/VisualFxFreeAim.d @@ -1,5 +1,5 @@ /* - * Free aim projectile trail strip for increase visibility + * Projectile trail strip for increased visibility * * G2 Free Aim v0.1.2 - Free aiming for the video game Gothic 2 by Piranha Bytes * Copyright (C) 2016 mud-freak (@szapp)