From e276749c790d35c8b7a79a3509f35d7cc7b0efe7 Mon Sep 17 00:00:00 2001 From: VuceticBranislav <24853106+VuceticBranislav@users.noreply.github.com> Date: Sat, 3 Feb 2024 12:43:47 +0100 Subject: [PATCH] feat(Era): Update Era source code to 3.9.10. Commit: 3c7f910 Signed-off-by: VuceticBranislav <24853106+VuceticBranislav@users.noreply.github.com> --- Era/Heroes.pas | 20 +++++++++++++- Era/Tweaks.pas | 75 ++++++++++++++++++++++++++++++++++++++++++++------ README.md | 2 +- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/Era/Heroes.pas b/Era/Heroes.pas index ca2c461..7ea58a0 100644 --- a/Era/Heroes.pas +++ b/Era/Heroes.pas @@ -1057,6 +1057,14 @@ TTextTable = class IsTwoLevelMap: boolean; end; // .record TGameManager + PBattleStack = ^TBattleStack; + TBattleStack = packed record + Unk1: array [0..$34 - 1] of byte; + MonType: integer; // +0x34 + Pos: integer; // +0x38 + Unk2: array [$3C..$548 - 1] of byte; + end; // .record TBattleStack + PPCombatManager = ^PCombatManager; PCombatManager = ^TCombatManager; TCombatManager = packed record @@ -1099,7 +1107,9 @@ TTextTable = class // _Dlg_* dlg; // + 0x132FC // _byte_ field_13300[3564]; - end; // .TCombatManager + + function GetActiveStack: PBattleStack; + end; // .record TCombatManager PPAdvManager = ^PAdvManager; PAdvManager = ^TAdvManager; @@ -1363,6 +1373,14 @@ procedure TNetData.Send (aDestPlayerId: integer); PatchApi.Call(FASTCALL_, Ptr($5549E0), [@Self, aDestPlayerId, 0, 1]); end; +function TCombatManager.GetActiveStack: PBattleStack; +Type + TGetActiveStackMethod = function (CombatMgr: PCombatManager): PBattleStack; cdecl; + +begin + result := TGetActiveStackMethod(Ptr($75AF06))(@Self); +end; + procedure SendNetData (DestPlayerId, MsgId: integer; {n} Data: pointer; DataSize: integer); var NetDataBuf: UtilsB2.TArrayOfByte; diff --git a/Era/Tweaks.pas b/Era/Tweaks.pas index 13306c4..0926f10 100644 --- a/Era/Tweaks.pas +++ b/Era/Tweaks.pas @@ -96,6 +96,7 @@ function RandomRangeWithFreeParam (MinValue, MaxValue, FreeParam: integer): inte fRangeMax: integer; fCombatActionId: integer; fFreeParam: integer; + fAttemptParam: integer; end; TBattleDeterministicRng = class (FastRand.TRng) @@ -106,7 +107,7 @@ TBattleDeterministicRng = class (FastRand.TRng) fCombatActionIdPtr: pinteger; fFreeParamPtr: pinteger; - procedure UpdateState (RangeMin, RangeMax: integer); + procedure UpdateState (RangeMin, RangeMax: integer; AttemptIndex: integer = 1); public constructor Create (CombatIdPtr, CombatRoundPtr, CombatActionIdPtr, FreeParamPtr: pinteger); @@ -167,7 +168,7 @@ constructor TBattleDeterministicRng.Create (CombatIdPtr, CombatRoundPtr, CombatA Self.fFreeParamPtr := FreeParamPtr; end; -procedure TBattleDeterministicRng.UpdateState (RangeMin, RangeMax: integer); +procedure TBattleDeterministicRng.UpdateState (RangeMin, RangeMax: integer; AttemptIndex: integer = 1); begin Self.fState.fCombatRound := Crypto.Tm32Encode(Self.fCombatRoundPtr^); Self.fState.fRangeMin := RangeMin; @@ -175,6 +176,7 @@ procedure TBattleDeterministicRng.UpdateState (RangeMin, RangeMax: integer); Self.fState.fRangeMax := RangeMax; Self.fState.fCombatActionId := Crypto.Tm32Encode(Self.fCombatActionIdPtr^ + 1147022261); Self.fState.fFreeParam := Crypto.Tm32Encode(Self.fFreeParamPtr^ + 641013956); + Self.fState.fAttemptParam := 1709573561 + AttemptIndex * 39437491; end; procedure TBattleDeterministicRng.Seed (NewSeed: integer); @@ -189,6 +191,15 @@ function TBattleDeterministicRng.Random: integer; end; function TBattleDeterministicRng.RandomRange (MinValue, MaxValue: integer): integer; +const + MAX_UNBIAS_ATTEMPTS = 100; + +var + RangeLen: cardinal; + BiasedRangeLen: cardinal; + MaxUnbiasedValue: cardinal; + i: integer; + begin if MinValue >= MaxValue then begin result := MinValue; @@ -199,7 +210,23 @@ function TBattleDeterministicRng.RandomRange (MinValue, MaxValue: integer): inte result := Crypto.FastHash(@Self.fState, sizeof(Self.fState)); if (MinValue > Low(integer)) or (MaxValue < High(integer)) then begin - result := MinValue + integer(cardinal(result) mod cardinal(MaxValue - MinValue + 1)); + i := 2; + RangeLen := cardinal(MaxValue - MinValue) + 1; + BiasedRangeLen := High(cardinal) mod RangeLen + 1; + + if BiasedRangeLen = RangeLen then begin + BiasedRangeLen := 0; + end; + + MaxUnbiasedValue := High(cardinal) - BiasedRangeLen; + + while (cardinal(result) > MaxUnbiasedValue) and (i <= MAX_UNBIAS_ATTEMPTS) do begin + Inc(Self.fState.fAttemptParam, 39437491); + result := Crypto.FastHash(@Self.fState, sizeof(Self.fState)); + Inc(i); + end; + + result := MinValue + integer(cardinal(result) mod RangeLen); end; end; @@ -860,6 +887,8 @@ function Hook_WoGBeforeBattleAction (Context: Core.PHookContext): longbool; stdc BattleMgr: Heroes.PCombatManager; begin + Inc(CombatActionId); + BattleMgr := Heroes.CombatManagerPtr^; CombatOrigStackActionInfo.Action := BattleMgr.Action; @@ -870,6 +899,33 @@ function Hook_WoGBeforeBattleAction (Context: Core.PHookContext): longbool; stdc result := Core.EXEC_DEF_CODE; end; +function Hook_WoGBeforeBattleAction_HandleEnchantress (Context: Core.PHookContext): longbool; stdcall; +const + LOCAL_ACTING_MON_TYPE = -$2C; + +var + BattleMgr: Heroes.PCombatManager; + +begin + BattleMgr := Heroes.CombatManagerPtr^; + Erm.v[997] := CombatRound; + Erm.FireErmEvent(TRIGGER_BG0); + + // Monster type could be changed by script, use the one from combat manager + pinteger(Context.EBP + LOCAL_ACTING_MON_TYPE)^ := BattleMgr.GetActiveStack().MonType; + + result := Core.EXEC_DEF_CODE; +end; + +function Hook_WoGCallAfterBattleAction (Context: Core.PHookContext): longbool; stdcall; +begin + Erm.v[997] := CombatRound; + Erm.FireErmEvent(TRIGGER_BG1); + + Context.RetAddr := Ptr($75D317); + result := not Core.EXEC_DEF_CODE; +end; + function Hook_SendBattleAction_CopyActionParams (Context: Core.PHookContext): longbool; stdcall; begin Context.EAX := CombatOrigStackActionInfo.ActionParam2; @@ -935,11 +991,6 @@ function Hook_OnCombatRound_End (Context: Core.PHookContext): longbool; stdcall; var RngId: integer = 0; // Holds random generation attempt ID, auto resets at each reseeding. Used for debugging purposes -procedure OnBeforeBattleAction (Event: GameExt.PEvent); stdcall; -begin - Inc(CombatActionId); -end; - procedure Hook_SRand (OrigFunc: pointer; Seed: integer); stdcall; var CallerAddr: pointer; @@ -2139,6 +2190,13 @@ procedure OnAfterWoG (Event: GameExt.PEvent); stdcall; ApiJack.HookCode(Ptr($75C69E), @Hook_WoGBeforeBattleAction); ApiJack.HookCode(Ptr($47883B), @Hook_SendBattleAction_CopyActionParams); + (* Trigger OnBeforeBattleAction before Enchantress, Hell Steed and creature experience mass spell processing *) + ApiJack.HookCode(Ptr($75C96C), @Hook_WoGBeforeBattleAction_HandleEnchantress); + Core.p.WriteDataPatch(Ptr($75CB26), [myAStr('9090909090909090909090')]); + + (* Use CombatRound in OnAfterBattleAction trigger for v997 *) + ApiJack.HookCode(Ptr($75D306), @Hook_WoGCallAfterBattleAction); + (* Send and receive unique identifier for each battle to use in deterministic PRNG in multiplayer *) ApiJack.HookCode(Ptr($763796), @Hook_ZvsAdd2Send); ApiJack.HookCode(Ptr($763BA4), @Hook_ZvsGet4Receive); @@ -2202,7 +2260,6 @@ procedure OnAfterVfsInit (Event: GameExt.PEvent); stdcall; EventMan.GetInstance.On('OnAfterVfsInit', OnAfterVfsInit); EventMan.GetInstance.On('OnAfterWoG', OnAfterWoG); EventMan.GetInstance.On('OnBattleReplay', OnBattleReplay); - EventMan.GetInstance.On('OnBeforeBattleAction', OnBeforeBattleAction); EventMan.GetInstance.On('OnBeforeBattleReplay', OnBeforeBattleReplay); EventMan.GetInstance.On('OnBeforeBattleUniversal', OnBeforeBattleUniversal); EventMan.GetInstance.On('OnGenerateDebugInfo', OnGenerateDebugInfo); diff --git a/README.md b/README.md index 7d4ac26..375a391 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Contains ERA and VFS projects as well as B2 library.

Original commits containing code are from ethernidee repositories:
- ERA: 9ddfbbf
+ ERA: 3c7f910
VFS: 5e4a7f0
B2: 001251e