diff --git a/IMPLEMENTATION_CHEAT_SHEET.md b/IMPLEMENTATION_CHEAT_SHEET.md index 97b2e5cf8..53c796467 100644 --- a/IMPLEMENTATION_CHEAT_SHEET.md +++ b/IMPLEMENTATION_CHEAT_SHEET.md @@ -8,7 +8,7 @@ Note, that Ghidra and reccmp use slightly different disassembly styles, so there ## Boundaries -Certain instructions work as boundaries between different code segments, preventing reordering of instructions and aiding in identifying the possible structure. +Certain instructions work as boundaries between different code segments, preventing reordering of instructions and aiding in identifying the possible structure. ### Functions @@ -39,6 +39,7 @@ it is a clean indication of a known function body. The source of the other funct allowing the compiler to optimize the register usage. #### Detecting inlined functions + The compiler (and/or linker) likely inlined some functions. This mainly happens if the compilation unit (in our case function body) can "see" the contents of another compilation unit (function body). Detecting whether this has occurred is challenging. So far, we have found one pattern that predicts it (occurs in OpenSHC::Global::WinMain). @@ -89,6 +90,7 @@ which usually also cascades in further code changes. ### Conditionals Instructions can only be pulled: + - before a condition block if every block has the same instruction at the start. - after a condition block if every block that does not return has the same instruction at the end. @@ -106,7 +108,7 @@ Within a boundary the compiler schedules freely, so do not copy the instruction This matters most for long runs of independent stores into one object, as in `AICState::setAICParameters_NN`: the whole body is `this->aics[aicIndex]. = ;` over ~130 fields, and the compiler emits those `mov dword ptr [eax + ], ` instructions in an order of its own. Rewriting all 16 of -those functions so their statements followed the original's store order made every one of them *worse* +those functions so their statements followed the original's store order made every one of them _worse_ (e.g. `setAICParameters_14` 82.4% -> 50.0%). That does not make the order unrecoverable. The compiler moves only the stores whose value sits in a **register**: @@ -123,7 +125,7 @@ are pulled up in front of the reload, and stores of a newly loaded constant are 3. Compile and move the few statements that still sit elsewhere next to their neighbour in the original. All 16 `setAICParameters_NN` went from 75-95% to 100% this way, with no type or header change; the earlier attempt -failed because it copied the *instruction* order, hoisted stores included. Which constant lands in which register +failed because it copied the _instruction_ order, hoisted stores included. Which constant lands in which register then follows by itself - it was a consequence of the statement order, not of register pressure. ## Structure @@ -135,6 +137,7 @@ at the usage of a structure that the compiler optimized. If not contained in the examples here yet, AI is usually good at identifying them. The most prominent example are modulo operations so far: + ```cpp if ((int)SEC_RNG.currentNumber1 % 3 == 0) { DAT_SoundEffectsHelperData1.field6_0x34 = 1; @@ -143,7 +146,9 @@ else { DAT_SoundEffectsHelperData1.field6_0x34 = (-(uint)((int)SEC_RNG.currentNumber1 % 3 != 1) & 0xfffffffe) + 2; } ``` + is roughly: + ```cpp int const randomNumber = SEC_RNG::instance.currentNumber1 % 3; if (randomNumber == 0) @@ -155,13 +160,16 @@ else ``` Or another modulo: + ```cpp uVar1 = (int)SEC_RNG.currentNumber1 & 0x80000003; if ((int)uVar1 < 0) { uVar1 = (uVar1 - 1 | 0xfffffffc) + 1; } ``` + being + ```cpp SEC_RNG::ptr->currentNumber1 % 4 ``` @@ -196,7 +204,7 @@ SEC_RNG::ptr->currentNumber1 % 4 every use took `getTargetableBuildingForPlayerID` from 65% to 100%. - **A constant index held in a local stops the per-struct multiply being shared.** When the original recomputes `playerID * 0x39f4` in every basic block (`mov eax, ecx; imul eax, eax, 0x39f4` three times, with `playerID` staying - in its register) while we multiply once, the array index in the source was a *variable* that the optimizer only + in its register) while we multiply once, the array index in the source was a _variable_ that the optimizer only later found to be constant. With a variable index MSVC factors each access by its own element size (`playerID * 0x1cfa` for a `short` array, `* 0xe7d` for an `int` array), so the three accesses are three different expressions and nothing is common. Write `int tribeIndex = 11;` and index with it: @@ -220,7 +228,7 @@ SEC_RNG::ptr->currentNumber1 % 4 (`removeOrganismsAndSetMoveDestinationPairs` 97.9% -> 100%). - Where two arguments are loaded on either side of a store (`mov ecx, [x]` ... `mov [stance], 1` ... `mov eax, [y]`), give each a local and put the statements in that order: `int x = pd.someX; tribe.unitStance = ...; int y = pd.someY;` - (`makeUnitsGoDefensiveAndBackToSomeLocation` 95.7% -> 100%). Without a store in between, the *declaration order* + (`makeUnitsGoDefensiveAndBackToSomeLocation` 95.7% -> 100%). Without a store in between, the _declaration order_ of two such locals picks which one gets `ecx` (`sendUnitsToAttackBreachedCastle` 98.7% -> 100%). - The opposite does happen for the operands of a single statement: in `SHC_3BB0A8C1_0x00522520` the original evaluates `unitID / 16` and `unitID % 16` into locals before the compound assignment. If a `|=`/`&=` statement with computed index @@ -244,6 +252,13 @@ SEC_RNG::ptr->currentNumber1 % 4 `if ((x & MASK) == VALUE) { return TRUE; } else { return FALSE; }`, with locals for the computed index and the loaded byte, reached 100%. Together with the range-check case above this means: folded expression and explicit `TRUE`/`FALSE` branches are two separate candidates, measure both. +- A field read that every arm of an if/else-if chain needs is often read **inside each arm** in the original, not + hoisted above the chain. When the original shows the same load repeated once per arm (three `movsx [ebx + 0x24a]` + for one `unit.tribeID`, each followed by its own `test`/`jz` to the loop's continue trampoline), move the read and + its guard into every arm instead of declaring one local before the chain. This is the same duplicate-the-shared- + block rule as for `LAB_*` targets with several predecessors, applied to a read rather than a block, and it was + worth +33 points on its own (SHC_3BB0A8C1_0x004CFCD0 `aiCommandSiegeEngineTribes`, 59.4% -> 92.7%). Spell the last + arm's condition out as `else if (type == UT_S_SHIELD)` too when the assembly tests it, rather than a bare `else`. - A switch with fewer then 4 cases is often simplified and uses subtractions in assembly to compare the value: ``` MOV EAX,[DAT_SoundEffectsHelperData1.SEC_Section1079. volumeLevel] @@ -255,7 +270,7 @@ SEC_RNG::ptr->currentNumber1 % 4 JNZ LAB_0047c386 ``` Once 4 are reached, the switch is probably converted to a table lookup. - This structure shows up as if-else chain in the decompiler, so do not be confused and check the assembly if the conditions are repeated checks of the same value. + This structure shows up as if-else chain in the decompiler, so do not be confused and check the assembly if the conditions are repeated checks of the same value. - A switch is in the end just a structure with a scope that allows to jump to certain cases. Inside this scope, other structures are fully legal. This is allowed, for example: ```cpp switch(value) { @@ -284,6 +299,7 @@ SEC_RNG::ptr->currentNumber1 % 4 Loops are very often presented in the decompiler as a pointer being incremented or decremented. The pointer is often created by getting the address to an array before the loop. There is also often a condition variable present that is incremented or decremented with the direction not necessarily matching the direction of the pointer increment. Example: + ```cpp int _soundIndex = 1; if (1 < DAT_SoundSystemState.loadedSoundsCountAndIndex_0x316c) { @@ -300,6 +316,7 @@ if (1 < DAT_SoundSystemState.loadedSoundsCountAndIndex_0x316c) { ``` It should always be attempted to simplify this logic to a default for-block with array index access: + ```cpp for (int soundIndex = 1; soundIndex < this->loadedSoundsCountAndIndex_0x316c; ++soundIndex) { if (this->soundFileCurrSampleNum_0x28c[soundIndex] < 0 || !this->samplePaused_0x31f4[soundIndex]) { @@ -313,17 +330,18 @@ for (int soundIndex = 1; soundIndex < this->loadedSoundsCountAndIndex_0x316c; ++ This covers a lot of cases. Exceptions are: - - when the logic has not initial condition check before the loop body. This suggests the usage of a do-while loop. - - when the update parts of the pointer and index are not the last instructions before the loop condition. This indicates manual handling like in a while loop. - - when it might lack a counter variable and the index loop simply does not match. This case might indicate a pointer increment loop. - Another sign for this could be the storing of the start of an array before the array is incremented. The main pointer is then often reset using this temporary. This may should up in opcodes like this: - ``` - MOV EDX, dword ptr [ESP + 0x20] <-- - MOV dword ptr [ESI + 0xc], EBX - MOV dword ptr [ESI + 0x24], EBX - MOV dword ptr [ESP + 0x20], EDX <-- - ``` - Note the seemingly redundant storing. This structure is followed by a loop where the main array is incremented and a temp used to reset it. + +- when the logic has not initial condition check before the loop body. This suggests the usage of a do-while loop. +- when the update parts of the pointer and index are not the last instructions before the loop condition. This indicates manual handling like in a while loop. +- when it might lack a counter variable and the index loop simply does not match. This case might indicate a pointer increment loop. + Another sign for this could be the storing of the start of an array before the array is incremented. The main pointer is then often reset using this temporary. This may should up in opcodes like this: + ``` + MOV EDX, dword ptr [ESP + 0x20] <-- + MOV dword ptr [ESI + 0xc], EBX + MOV dword ptr [ESI + 0x24], EBX + MOV dword ptr [ESP + 0x20], EDX <-- + ``` + Note the seemingly redundant storing. This structure is followed by a loop where the main array is incremented and a temp used to reset it. If you see a repeating logic structure, that, for example, increments by a value in its logic every repeat, you might have found an unrolled loop. Therefore, try to reproduce the logic in loop form and see how the compiler behaves. @@ -340,7 +358,7 @@ worth the last 8-10% on the whole `Map::Version::UpgradeMapUnitsTo_*` family (0x unrolled body, write `for (int i = 1; i != 2500; ++i)` instead of `i < 2500` (SHC_3BB0A8C1_0x0053B340, 53% -> 91%). - A loop whose early exit returns the same value as the code after the loop was a `break`, not a `return`. The tell is - callee-saved registers pushed *after* the loop's entry test and one shared `mov eax, result` behind the loop, where + callee-saved registers pushed _after_ the loop's entry test and one shared `mov eax, result` behind the loop, where an early `return result;` makes us push everything in the prologue (`assignRequiredIdleEngineersToNewTribe`, `addEngineersToSelection`, both ~88% -> 100%). - Do not add a guard the loop condition already expresses. `if (id == 0) return 0;` in front of @@ -350,27 +368,102 @@ unrolled body, write `for (int i = 1; i != 2500; ++i)` instead of `i < 2500` (SH - **Several exits that end in the same statement were usually one statement.** MSVC copies a short shared tail (a store or a call followed by the epilogue) into each predecessor, so three `mov word ptr [tribe.unitStance], 2` - + `ret` blocks in the original do not mean three assignments in the source. Writing the assignment once after an - `if / else if` ladder instead of `stance = X; return;` in every arm changes how often each value is *used*, and the - use count is what ranks values for the callee-saved registers - so this is the fix when a function matches - instruction for instruction except that two registers are swapped (`tribeID` in `ebx` and the tribe offset in `ebp` - on one side, the reverse on the other). `giveMoveCommandToSortieUnits` 79.6% -> 100%, `instructTribe166ToMove` - 81.8% -> 100%, `assignUnitToATribe` 86.1% -> 100% (one `addUnitToTribe(unitID, tribeID)` after the branches - instead of three calls). When one of the arms must skip a call the others share, a `bool` set in the arms and - tested once afterwards compiles away and gives the single call site the original has. + - `ret` blocks in the original do not mean three assignments in the source. Writing the assignment once after an + `if / else if` ladder instead of `stance = X; return;` in every arm changes how often each value is _used_, and the + use count is what ranks values for the callee-saved registers - so this is the fix when a function matches + instruction for instruction except that two registers are swapped (`tribeID` in `ebx` and the tribe offset in `ebp` + on one side, the reverse on the other). `giveMoveCommandToSortieUnits` 79.6% -> 100%, `instructTribe166ToMove` + 81.8% -> 100%, `assignUnitToATribe` 86.1% -> 100% (one `addUnitToTribe(unitID, tribeID)` after the branches + instead of three calls). When one of the arms must skip a call the others share, a `bool` set in the arms and + tested once afterwards compiles away and gives the single call site the original has. - A search loop whose "found a free slot" exit must skip the check that follows the loop has its end test **inside the body**: `for (;; i++) { if (i >= count) { if (best == 0) return 0; break; } ... if (free) { best = create(); - bestIndex = i; break; } ... }`. A `for (i < count)` loop with the check after it tests `best` on both paths, and +bestIndex = i; break; } ... }`. A `for (i < count)` loop with the check after it tests `best` on both paths, and duplicating the tail into the create path changes the frame. With the test in the body the compiler rotates it to the bottom and lays the blocks out as the original does (`getSmallestPatrolTribe` 65.2% -> 100%). The same - function needed `int i = 0;` declared *first*, before the other zero-initialised locals, to get the original's + function needed `int i = 0;` declared _first_, before the other zero-initialised locals, to get the original's `xor` order - declaration order did not matter anywhere else it was tried. - `mov edx, esi; add esi, 1` at the **top** of a loop body, with the body then indexing by the copy, is a `while` loop that takes the index and advances the counter first: `while (i < count) { int index = i; i++; if (...[index]...) - { ...; return; } }`. A `for (...; i++)` with the same body does not produce it. Together with `int i = 0;` +{ ...; return; } }`. A `for (...; i++)` with the same body does not produce it. Together with `int i = 0;` declared where the original's `xor esi, esi` sits (here before an earlier early-return test, which then compares against that zero register) this took `generateSiegeCreationInformation` from 39.4% to 100%. +- The `setAICParameters_01..16` family (0x004C6D60 and up) is a long run of independent constant stores into one + `aics[aicIndex]` element, and it sits at 74-95% because the original hoists repeated constants into registers in a + particular order while we materialise them in another; every actual field store already matches, and the field + offsets check out. Statement order is **not** the lever: `reorder_search.py` on `setAICParameters_15` tried all 9 + independent runs (lengths 42, 29, 21, 14, 6, 4, 3, 3, 3) over 27 builds and kept nothing, leaving it at exactly + 74.92%. MSVC schedules those stores the same way whatever order the source lists them in, which is the + _Statement Order_ note above measured on a large case. Record the percentage and move on. +- Where the original compares a memory operand against a **zero register** (`xor ebp,ebp` once, then + `cmp dword ptr [..], ebp` and `mov dword ptr [..], ebp`) and we emit `test r,r` / `cmp ..,0`, that is **not** a + source-level difference and **not** a size-vs-speed flag. Using a zero register saves a byte per memory + comparison, so `/Os` looks like the explanation, but compiling with `/O2 /Os` per file (verified present in the + compile command) left `getTargetableBuildingForPlayerID`, `getSmallestPatrolTribe` and + `aiShouldAttackOrWaitForTeamCoordination` at _exactly_ their previous percentages. The choice cascades into a + different register assignment and sometimes one extra spill of the loop counter, which is why these functions + stall in the 43-65% band with an otherwise instruction-for-instruction match. Treat it as allocator noise and + record it; it shows up across `AI::AICState` (`aiRecruitUnits`, `aiGiveRaidInstructions`, + `generateSiegeCreationInformation`, `getTargetableBuildingForPlayerID`, `getSmallestPatrolTribe`). +- The "find the smallest tribe, or create one if a slot is stale" family all have the same shape: a loop whose + create-path wants to reach the function's shared store-and-return tail while skipping the tail's + `selected == 0` guard, which the original does with a jump into the middle of that block. There is no goto-free + form that is right for all of them, so **measure each one**; the three measured so far disagree: + - `getSmallestPatrolTribe` (0x004CCAF0): `break` into the shared tail is best at **65.2%**; duplicating the + stores into the create path drops it to 48.3%. + - `smallestTribeOfUnitType` (0x004CC990): the reverse - duplicating the stores gives **41.9%**, a `goto` into + the tail would give ~55%, and `break` into the shared tail was measured at 17%. + - `addUnitToSmallestBehaviourTypeTribe` (0x004CCD20): `break` into the shared tail, **64.5%**; neither hoisting + the `smallestSize` initialiser to the top (where the original's own `mov [esp+0x24],0x3e8` sits) nor reusing + the loop variable for the slot index moved it at all - both exact ties. + So the predecessor-count rule tells you a duplicate _may_ be needed, not that it will help; `try_styles.py` is + the only way to settle it. + +- A loop whose early exit returns the same value as the code after the loop was a `break`, not a `return`. The tell is + callee-saved registers pushed _after_ the loop's entry test and one shared `mov eax, result` behind the loop, where + an early `return result;` makes us push everything in the prologue (`assignRequiredIdleEngineersToNewTribe`, + `addEngineersToSelection`, both ~88% -> 100%). +- Do not add a guard the loop condition already expresses. `if (id == 0) return 0;` in front of + `for (; id != 0; id = next(id))` moved one `push` across a branch; without it the compiler derives the same early + exit itself and places it where the original does (`aiRequiresExtraOxtethers`). Likewise put the block the + original keeps inline first: `if (best > 20) { store; return 1; } return 0;` rather than the inverted guard. + +### Reading Ghidra against the assembly + +Ghidra's decompilation shows the original's *variable and loop structure*, and whether copying a given piece of it +back into the source helps is **not predictable - measure every one with reccmp, never with `quick_diff`'s ratio.** +Measured on `AI::AICState` (2026-10-06), the same change went both ways: + +- Giving the engineer-tribe block of `aiCreateSiegeUnits` the single physical copy the original has + (`LAB_004d22d3`, two predecessors) was **+1.3 points** by reccmp - while `quick_diff` scored it 2 points *worse*. +- Doing the same to the three duplicated render/sprintf calls of `renderDebugDataAiInfo`, which Ghidra shows as one + call with a selected string and colour, cost **10 points** (72.3% -> 62.0%). + +`quick_diff`'s difflib ratio cannot judge a restructuring: it rewards alignment, and a different-but-better block +layout shows up as a worse ratio. Use it for local edits, and build + reccmp for anything that moves a block. +Several other shapes Ghidra showed were measured worse and are not worth retrying: the +`do { i = slot; slot++; ... } while` inner loop in `aiCreateSiegeUnits`, a `unitType` stack local there, and the +countdown loop in `computeEnemyKeepApproachTile`. + +What Ghidra *is* reliable for: + +- **Comparison literals.** `if (count >= 4) count = 3;` against the original's `cmp edi, 3; jle` says the source + wrote `> 3`. Worth 0.6% on its own and it is free (`aiCreateSiegeUnits`). +- **Where a value is computed.** `_aicOffset = (_aiType + ~AIT_NULL) * 0x2a4` appearing once at the top of the + decompilation, against our `aics[aiType - 1]` at each use, says the source had `int aicIndex = pd.aiType - 1;` + as its own statement (+1%). +- **Arms with identical bodies.** Ghidra renders `if (t == UT_E_ENGINEER) { b = guild; } else if (t == UT_E_LADDER) + { b = guild; }` where we had written `t == ENGINEER || t == LADDER`. Splitting the `||` into two arms with the + same body took `aiRecruitUnits` from 41.6% to 43.4%. +- **The number of calls.** `push_pattern.py` counts the calls whose argument set-up it can see on each side; more on + our side than the original's means we have duplicated a block the original keeps once. That count, not the difflib + ratio, is what identified the `aiCreateSiegeUnits` engineer block correctly. Its per-call `pushes/interleaved` + columns answer the other half: a contiguous push run means the arguments were already in locals, pushes + interleaved with loads mean the expression was written inline at the call. +- **Field types and flat array access.** `*(int *)((int)&DAT_AICState + (index + aicIndex * 0xa9) * 4 + 0x184)` is + `(&aics[aicIndex].DefUnit1)[index]` - the AIC block indexed as a flat `int` array, 169 ints per entry. The + per-field struct access and this flat form appear in the same function and both are real. ### GOTO @@ -474,6 +567,7 @@ However, there are also C functions that can be replaced by intrinsics. The foll In cases where the decompiler seems to perform one of these actions via simple instructions, one can also try one of these functions. Example `strcpy`: + ```cpp char* pcVar2 = filePath; do { @@ -484,6 +578,7 @@ do { ``` Example `strcmp`: + ```cpp int iVar2 = 9; int bVar7 = true; @@ -518,11 +613,13 @@ The return value optimization may appear if a function returns an object, but in The actual function signature will only have the object as value return. Example in Ghidra: + ```cpp std::string* paths_getDocumentsFolderString(std::string* out, bool param_2); ``` Actual signature: + ```cpp std::string paths_getDocumentsFolderString(bool param_2); ``` diff --git a/cmake/compiler-flags-gl.txt b/cmake/compiler-flags-gl.txt index d0c81b8d5..fe4000b4b 100644 --- a/cmake/compiler-flags-gl.txt +++ b/cmake/compiler-flags-gl.txt @@ -16,4 +16,5 @@ src/OpenSHC/Audio/SFX/SFXState/playUnitSpeech.cpp src/OpenSHC/Game/GameStateStructures/getSellResourceAmount.cpp src/OpenSHC/Map/TileMapState/determineBuildingPlacementRotation.cpp src/OpenSHC/Map/Navigation/DirectionAlgorithmState/getMouseVectorLengthBasedOnDirection.cpp -src/OpenSHC/Input/MouseState/updateRightDragCameraControl.cpp \ No newline at end of file +src/OpenSHC/Input/MouseState/updateRightDragCameraControl.cpp +src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp diff --git a/src/OpenSHC/AI/AICState.func.hpp b/src/OpenSHC/AI/AICState.func.hpp index 42f3f8974..6ef9588e9 100644 --- a/src/OpenSHC/AI/AICState.func.hpp +++ b/src/OpenSHC/AI/AICState.func.hpp @@ -631,7 +631,7 @@ namespace AI { decideOnTribeAttackLocation; MACRO_FUNCTION_RESOLVER( - void (AICState::*)(int), false, Address::SHC_3BB0A8C1_0x004D1D60, &AICState::sellExcessGoods) + void (AICState::*)(int), true, Address::SHC_3BB0A8C1_0x004D1D60, &AICState::sellExcessGoods) sellExcessGoods; MACRO_FUNCTION_RESOLVER(void (AICState::*)(int, ResourceType, int), false, Address::SHC_3BB0A8C1_0x004D1F00, @@ -715,7 +715,7 @@ namespace AI { makeOutpostTribesAttack; MACRO_FUNCTION_RESOLVER( - void (AICState::*)(int), false, Address::SHC_3BB0A8C1_0x004D39B0, &AICState::buyRequiredGoods) + void (AICState::*)(int), true, Address::SHC_3BB0A8C1_0x004D39B0, &AICState::buyRequiredGoods) buyRequiredGoods; MACRO_FUNCTION_RESOLVER( diff --git a/src/OpenSHC/AI/AICState/Constructor_AICState.cpp b/src/OpenSHC/AI/AICState/Constructor_AICState.cpp new file mode 100644 index 000000000..2eedf70e1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/Constructor_AICState.cpp @@ -0,0 +1,14 @@ +#include "../AICState.func.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3940 + AICState* AICState::Constructor_AICState() + { + MACRO_CALL_MEMBER(AICState_Func::setInitialAIC, this)(); + this->aiTauntResponseTimeWindow = 0; + return this; + } +} +} diff --git a/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp b/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp new file mode 100644 index 000000000..bdd9f7312 --- /dev/null +++ b/src/OpenSHC/AI/AICState/abortAttackAndGoIdle.cpp @@ -0,0 +1,23 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D45E0 + void AICState::abortAttackAndGoIdle(int playerID) + { + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::removeSiegeBuildings, DAT_BuildingsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp b/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp new file mode 100644 index 000000000..bd48cbe1c --- /dev/null +++ b/src/OpenSHC/AI/AICState/addBuildingToTargetableBuildings.cpp @@ -0,0 +1,30 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameMode.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDA50 + void AICState::addBuildingToTargetableBuildings(int buildingID) + { + int owner = DAT_BuildingsState::instance.buildings[buildingID].owner; + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) + return; + if (DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker >= 100) + return; + if (DAT_BuildingsState::instance.buildings[buildingID].fireDuration != 0) + return; + + DAT_GameState::instance.playerDataArray[owner] + .top100TargetableBuildings[DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker] + = buildingID; + DAT_GameState::instance.playerDataArray[owner].top100TargetableBuildingsTracker++; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp b/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp new file mode 100644 index 000000000..ce614c098 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addEngineersToSelection.cpp @@ -0,0 +1,44 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/States/UnitState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC680 + int AICState::addEngineersToSelection(int playerID, int unitCount) + { + int tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (unitCount <= 0) + break; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_E_ENGINEER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 10) + continue; + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].state.generic == Map::Units::States::US_STAND_UPUnk) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + unitCount--; + } + return tribeID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp b/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp new file mode 100644 index 000000000..48bc61b6a --- /dev/null +++ b/src/OpenSHC/AI/AICState/addEngineersToTribe.cpp @@ -0,0 +1,33 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4040 + void AICState::addEngineersToTribe(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_E_ENGINEER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 10) + continue; + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + continue; + + MACRO_CALL_MEMBER(AICState_Func::addUnitToItsTribe, this)(unitID, 10); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp b/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp new file mode 100644 index 000000000..5a8527736 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addRallyPointForTribe.cpp @@ -0,0 +1,57 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFA10 + void AICState::addRallyPointForTribe(int tribeID) + { + DAT_TribesState::instance.tribes[tribeID].rallyPointCount = 0; + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == OpenSHC::Map::Units::ULS_INVISIBLE) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::getTeamsDifferent, DAT_GameState::ptr)( + DAT_TribesState::instance.tribes[tribeID].owner, DAT_UnitsState::instance.units[unitID].owner) + != FALSE) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_CATAPULT + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TREBUCHET + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_FBALLISTA) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeNextRallyPointDestination, + DAT_PathFindingState::ptr)( + -1, DAT_UnitsState::instance.units[unitID].x, DAT_UnitsState::instance.units[unitID].y); + DAT_TribesState::instance.tribes[tribeID] + .rallyPointArray[DAT_TribesState::instance.tribes[tribeID].rallyPointCount][0] + = DAT_PathFindingState::instance.ALG_ResultX; + DAT_TribesState::instance.tribes[tribeID] + .rallyPointArray[DAT_TribesState::instance.tribes[tribeID].rallyPointCount][1] + = DAT_PathFindingState::instance.ALG_ResultY; + DAT_TribesState::instance.tribes[tribeID].rallyPointCount++; + if (DAT_TribesState::instance.tribes[tribeID].rallyPointCount >= 9) + break; + } + + if (DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex < 0) + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = 0; + if (DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex + >= DAT_TribesState::instance.tribes[tribeID].rallyPointCount) + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp new file mode 100644 index 000000000..0cf637150 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToItsTribe.cpp @@ -0,0 +1,27 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D27E0 + void AICState::addUnitToItsTribe(int unitID, int aiUnitBehaviourType) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = aiUnitBehaviourType; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::addUnitToSmallestBehaviourTypeTribe, this)( + owner, unitID, aiUnitBehaviourType); + if (tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp new file mode 100644 index 000000000..5d3e5ddf3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestBehaviourTypeTribe.cpp @@ -0,0 +1,82 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCD20 + int AICState::addUnitToSmallestBehaviourTypeTribe( + undefined4 playerID, undefined4 unitID, undefined4 aiUnitBehaviourType) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + + int aicIndex = aiType - 1; + int groupsCount; + switch (DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]) { + case 15: + groupsCount = this->aics[aicIndex].AttUnitPatrolGroupsCount; + break; + case 186: + groupsCount = this->aics[aicIndex].AttUnitBackupGroupsCount; + break; + case 190: + groupsCount = this->aics[aicIndex].AttUnitSiegeDefGroupsCount; + break; + case 192: + groupsCount = this->aics[aicIndex].AttMainGroupsCount; + break; + default: + groupsCount = 1; + break; + } + + int selectedTribeID = 0; + int selectedSlot = 0; + if (groupsCount <= 1) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[DAT_SkirmishDefinedData::instance + .AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[DAT_SkirmishDefinedData::instance + .AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]]) + return tribeID; + selectedTribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedSlot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; + } else { + int smallestSize = 1000; + for (int slot = DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10]; slot + < DAT_SkirmishDefinedData::instance.AIUnitBehaviourTypeMapping[aiUnitBehaviourType - 10] + groupsCount; + slot++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[slot]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[slot]) { + selectedTribeID = MACRO_CALL_MEMBER( + Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedSlot = slot; + break; + } + if (DAT_TribesState::instance.tribes[tribeID].size < smallestSize) { + smallestSize = DAT_TribesState::instance.tribes[tribeID].size; + selectedTribeID = tribeID; + selectedSlot = slot; + } + } + } + if (selectedTribeID <= 0) + return 0; + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[selectedSlot] = selectedTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[selectedSlot] + = DAT_TribesState::instance.tribes[selectedTribeID].uid; + return selectedTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp b/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp new file mode 100644 index 000000000..e92b28080 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitToSmallestPatrolTribe.cpp @@ -0,0 +1,27 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2730 + void AICState::addUnitToSmallestPatrolTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + int aicIndex = DAT_GameState::instance.playerDataArray[owner].aiType - 1; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 4; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::getSmallestPatrolTribe, this)( + owner, this->aics[aicIndex].OuterPatrolGroupsCount); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp b/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp new file mode 100644 index 000000000..5e0cbafd8 --- /dev/null +++ b/src/OpenSHC/AI/AICState/addUnitsToTribe166.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC720 + void AICState::addUnitsToTribe166(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 6) + continue; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[166]; + if (DAT_UnitsState::instance.units[unitID].tribeID == tribeID) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp b/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp new file mode 100644 index 000000000..4857156f9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAddUnitToMoatDiggerTribe.cpp @@ -0,0 +1,31 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC840 + void AICState::aiAddUnitToMoatDiggerTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 5; + int tribeID = DAT_GameState::instance.playerDataArray[owner].aiTribeIDs[10]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[owner].aiTribeUIDs[10]) { + tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(owner); + DAT_GameState::instance.playerDataArray[owner].aiTribeIDs[10] = tribeID; + DAT_GameState::instance.playerDataArray[owner].aiTribeUIDs[10] + = DAT_TribesState::instance.tribes[tribeID].uid; + } + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp b/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp new file mode 100644 index 000000000..6821d3933 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAssignMoatDiggers.cpp @@ -0,0 +1,60 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Units; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3F20 + void AICState::aiAssignMoatDiggers(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + int aicIndex = aiType - 1; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType0 <= 0) + return; + + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].isSelectable_OR_matchTime == 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + + if (DAT_UnitsState::instance.units[unitID].unitType == UT_LORD + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_ENGINEER + || DAT_UnitsState::instance.units[unitID].unitType == UT_TUNNELER + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_TREBUCHET + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_MANGONEL + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_TOWER + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_BATTERINGRAM + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_SHIELD + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_BALLISTA + || DAT_UnitsState::instance.units[unitID].unitType == UT_S_FBALLISTA) + continue; + + if (this->aics[aicIndex].DefDiggingUnitMax == 0 + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_ARCHER + || DAT_UnitsState::instance.units[unitID].unitType == UT_E_XBOW + || DAT_UnitsState::instance.units[unitID].someUnitStat4 == 0) + MACRO_CALL_MEMBER(AICState_Func::assignUnitToATribe, this)(unitID); + else + MACRO_CALL_MEMBER(AICState_Func::aiAddUnitToMoatDiggerTribe, this)(unitID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp b/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp new file mode 100644 index 000000000..bd7a41dec --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiAssignUnitToDefensiveTribe.cpp @@ -0,0 +1,26 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2790 + void AICState::aiAssignUnitToDefensiveTribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 2; + int tribeID = MACRO_CALL_MEMBER(AICState_Func::getDefensiveTribeForUnit, this)(owner, unitID); + if (tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp b/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp new file mode 100644 index 000000000..2d81632a7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiBuyAndSellGoods.cpp @@ -0,0 +1,28 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D48F0 + void AICState::aiBuyAndSellGoods(int playerID) + { + int marketID = DAT_GameState::instance.playerDataArray[playerID].marketplace.id; + DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk = 0; + if (marketID != 0) + DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk = 1; + if (DAT_GameState::instance.playerDataArray[playerID].hasMarketUnk == 0) + return; + + DAT_GameState::instance.playerDataArray[playerID].willBuy + = DAT_GameState::instance.playerDataArray[playerID].willBuy ^ TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].willBuy == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sellExcessGoods, this)(playerID); + return; + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::buyRequiredGoods, this)(playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp b/src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp new file mode 100644 index 000000000..890450502 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiChoiceAttForceRallyPercentage.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEC80 + BOOLEnum AICState::aiChoiceAttForceRallyPercentage(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = aiType - 1; + + int activeTribes = 0; + int rallyingTribes = 0; + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + activeTribes++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) + rallyingTribes++; + } + } + + if (activeTribes <= 0) + return FALSE; + return rallyingTribes * 100 / activeTribes >= this->aics[aicIndex].AttForceRallyPercentage; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp new file mode 100644 index 000000000..a7da31806 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandSiegeEngineTribes.cpp @@ -0,0 +1,105 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Units; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFCD0 + void AICState::aiCommandSiegeEngineTribes(int playerID) + { + int shieldTargetTribeIDs[3]; + shieldTargetTribeIDs[0] = 0; + shieldTargetTribeIDs[1] = 0; + shieldTargetTribeIDs[2] = 0; + int targetCount = 0; + for (int i = 186; i < 189; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i]) + continue; + shieldTargetTribeIDs[targetCount] = tribeID; + targetCount++; + } + + int shieldCount = 0; + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != UT_S_TOWER + && DAT_UnitsState::instance.units[unitID].unitType != UT_S_BATTERINGRAM + && DAT_UnitsState::instance.units[unitID].unitType != UT_S_SHIELD) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + continue; + + if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_TOWER) { + int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; + if (tribeID == 0) + continue; + if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.wideValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x414); + } else if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_BATTERINGRAM) { + int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; + if (tribeID == 0) + continue; + if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.gateValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x3f6); + else if (*(int*)((int)DAT_TroopValueState::instance.attackInfo.wideValuesArray + playerID * 0x177bc - 4) + != 0) + MACRO_CALL_MEMBER(TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, STBT_0x413); + } else if (DAT_UnitsState::instance.units[unitID].unitType == UT_S_SHIELD) { + int tribeID = DAT_UnitsState::instance.units[unitID].tribeID; + if (tribeID == 0) + continue; + if (shieldCount >= 3) + continue; + + int targetTribeID = shieldTargetTribeIDs[shieldCount]; + DAT_TribesState::instance.tribes[tribeID].field56_0x1f2 = targetTribeID; + DAT_TribesState::instance.tribes[tribeID].uid2 = DAT_TribesState::instance.tribes[targetTribeID].uid; + DAT_TribesState::instance.tribes[targetTribeID].tribeID = tribeID; + int targetUnitID = DAT_TribesState::instance.tribes[targetTribeID].selectionTargetUnitID; + int tile = DAT_UnitsState::instance.units[targetUnitID].tile; + shieldCount++; + if (tile <= 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + MACRO_CALL_MEMBER(TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, x, y, 0, 0, Instructions::UMSE_0); + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::calculatePreferredRelativeOrientation, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, x, y); + DAT_TribesState::instance.tribes[tribeID].orientation + = DAT_DirectionAlgorithmState::instance.orientation; + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp new file mode 100644 index 000000000..265f1d75d --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe11StandGroundToGate.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF470 + void AICState::aiCommandTribe11StandGroundToGate(int playerID) + { + int tribeIndex = 11; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) + return; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + return; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_STAND_GROUND; + if (MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::moveTribeToNearbyGatehouse, DAT_TroopValueState::ptr)( + tribeID) + == 0) + MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::moveTowardsParticularUnits, DAT_TroopValueState::ptr)( + tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp new file mode 100644 index 000000000..818b68d3c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe12AggressiveApproach.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF2E0 + void AICState::aiCommandTribe12AggressiveApproach(int playerID) + { + int tribeIndex = 12; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) + return; + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (targetUnitID == 0) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, -1, -1, 10000, FALSE); + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 200) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 150) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 100) + == FALSE + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findDestinationCostLowerThan6, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 50) + == FALSE) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, DAT_PathFindingState::instance.ALG_ResultX, DAT_PathFindingState::instance.ALG_ResultY, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp b/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp new file mode 100644 index 000000000..fe8539329 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandTribe13DefensiveWave.cpp @@ -0,0 +1,33 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF270 + void AICState::aiCommandTribe13DefensiveWave(int playerID) + { + int tribeIndex = 13; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) + return; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + return; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f4); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp b/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp new file mode 100644 index 000000000..a1b457e72 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCommandWallAssaultTribes.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF660 + void AICState::aiCommandWallAssaultTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + + for (int i = 0; i < 2; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[190 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 190]) + continue; + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp new file mode 100644 index 000000000..403a5138e --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiCreateSiegeUnits.cpp @@ -0,0 +1,234 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Buildings::BuildingType; + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2000 + void AICState::aiCreateSiegeUnits(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != OpenSHC::Map::Buildings::BT_ENGINEERSGUILD) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + + for (int siegeIndex = 0; siegeIndex < 4; siegeIndex++) { + int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot]; + if (locationCount > 3) + locationCount = 3; + + for (int i = 0; i < locationCount; i++) { + int location = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots + [DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot][i]; + if (location <= 0) + continue; + + int unitID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID; + int uid + = DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid; + int buildingID = DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID; + + if (unitID != 0 && DAT_UnitsState::instance.units[unitID].uid != uid) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + continue; + } + if (buildingID != 0 && DAT_BuildingsState::instance.buildings[buildingID].uid != uid) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unitID + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].unknown + = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + continue; + } + + if (unitID == 0 && buildingID == 0) { + if (((short)DAT_TileMapState::instance.BuildingLayer[location]) == 0) { + if (siegeIndex < 2) + continue; + } else { + if (siegeIndex >= 2) + continue; + if (DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .buildingType + != OpenSHC::Map::Buildings::BT_TOWER4 + && DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .buildingType + != OpenSHC::Map::Buildings::BT_TOWER5) + continue; + } + + int requiredGold + = DAT_BuildingsState::instance + .buildingCosts[DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType] + .requiredGold; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay++; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + + this->aics[aicIndex].defSiegeEngineGoldThreshold + < requiredGold) + continue; + if (DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .delay + < this->aics[aicIndex].defSiegeEngineBuildDelay) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[location]; + int x = location - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (siegeIndex < 2) { + int spawnedUnitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::spawnUnit, + DAT_UnitsState::ptr)(playerID, playerID, x * 8 + 4, y * 8 + 4, + DAT_BuildingsState::instance + .buildings[((short)DAT_TileMapState::instance.BuildingLayer[location])] + .terrainHeightUnk, + ((UnitType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .unitType)); + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID = spawnedUnitID; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].uid + = DAT_UnitsState::instance.units[spawnedUnitID].uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = 0; + MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::processPlacementResourceLossForBuildingType, + DAT_BuildingsState::ptr)(playerID, + (BuildingType)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .buildingType, + 0); + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)( + playerID, x, y, + (MappersEnum)(ushort)DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex] + .commandBuildingType, + 3, 15); + if (DAT_TileMapState::instance.buildingPlacementFail == FALSE) { + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + -= requiredGold; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .unitID = 0; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .uid + = DAT_BuildingsState::instance.buildings[DAT_TileMapState::instance.placedBuildingID] + .uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][i] + .buildingID = DAT_TileMapState::instance.placedBuildingID; + } + } + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][i].delay + = 0; + return; + } + if (unitID != 0 && buildingID == 0) { + int remainingEngineers + = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getRemainingRequiredEngineers, + DAT_UnitsState::ptr)(unitID); + if (remainingEngineers <= 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated < remainingEngineers) { + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + continue; + } + int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, remainingEngineers); + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + if (targetUnitID == 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func:: + calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + 0) + == 0) + continue; + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x41c; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_MAN_SIEGE_EQUIPMENT, unitID, + DAT_UnitsState::instance.units[unitID].uid, 0); + return; + } + + int requiredEngineers = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::getRequiredEngineerCountForSiegeBuilding, + DAT_BuildingsState::ptr)(buildingID); + if (DAT_BuildingsState::instance.buildings[buildingID].currentEmployeeCount == requiredEngineers) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].engineerCountRelated >= requiredEngineers) { + int tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignRequiredIdleEngineersToNewTribe, + this)(playerID, requiredEngineers); + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)(tribeID, + OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + return; + } + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = TRUE; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiFarmChoice.cpp b/src/OpenSHC/AI/AICState/aiFarmChoice.cpp new file mode 100644 index 000000000..46898001b --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiFarmChoice.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB180 + int AICState::aiFarmChoice(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + return (&this->aics[aiType - 1].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex]; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp new file mode 100644 index 000000000..218454530 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveOuterPatrolCommand.cpp @@ -0,0 +1,88 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D28B0 + void AICState::aiGiveOuterPatrolCommand(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == 0) + return; + int aicIndex = aiType - 1; + int i = 0; + int groupsCount = this->aics[aicIndex].OuterPatrolGroupsCount; + if (groupsCount <= 0) + return; + + DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker++; + if (DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker + < this->aics[aicIndex].OuterPatrolRallyDelay) + return; + DAT_GameState::instance.playerDataArray[playerID].outerPatrolRallyDelayTracker = 0; + + for (; i < groupsCount; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[170 + i]; + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 170]) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk > 10) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageShootingUnk > 10) + continue; + if (DAT_TribesState::instance.tribes[tribeID].percentageAttackingUnk > 10) + continue; + + int tile; + if (this->aics[aicIndex].OuterPatrolGroupsMove != 0) { + int buildingID = MACRO_CALL_MEMBER(AICState_Func::getTargetableBuildingForPlayerID, this)(playerID, i); + if (buildingID != 0) { + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { + buildingID + = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } + } else { + int buildingID + = MACRO_CALL_MEMBER(AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + if (buildingID == 0) + continue; + tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } + if (tile == 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (MACRO_CALL_MEMBER(AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) != FALSE) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + else + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp new file mode 100644 index 000000000..113ea823f --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiGiveRaidInstructions.cpp @@ -0,0 +1,147 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2A70 + void AICState::aiGiveRaidInstructions(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == 0) + return; + int i = 0; + int aicIndex = aiType - 1; + + DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter++; + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter + >= this->aics[aicIndex].RaidRetargetDelay) + DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter = 0; + + int totalRaidingTroopsUnk = DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk; + if (totalRaidingTroopsUnk == 0) + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown > 0) + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown--; + + for (; i < 6; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[180 + i]; + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 180]) + continue; + + int buildingID = 0; + int playerID_askerUnk = DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk; + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + playerID_askerUnk) + != 0) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, + this)(playerID_askerUnk, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + } else if (DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown == 0 + && totalRaidingTroopsUnk < MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(aicIndex, playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)(tribeID, i, TRUE) + != FALSE) + continue; + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, this)(playerID, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::selectBuildingFromAListOfBuildingTypes, this)(playerID); + } else if (DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 182 + || DAT_SkirmishDefinedData::instance.TribeTypeID[i] == 181) { + if (DAT_GameState::instance.playerDataArray[playerID].raidRetargetDelayCounter != 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, FALSE) + != FALSE) + continue; + buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectRandomBuildingIDFromListOfBuildingTypes, + this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk, i); + if (buildingID == 0) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + } else { + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, FALSE) + != FALSE) + continue; + + int targetBuildingID = DAT_TribesState::instance.tribes[tribeID].targetBuildingID; + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0 + && MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::checkTribeActivityPercentages, this)( + tribeID, i, TRUE) + != FALSE) { + if (DAT_GameState::instance.mapAndTime.yearChanged != 0) + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + continue; + } + + targetBuildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, + this)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk); + if (targetBuildingID == 0) { + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 0; + continue; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, targetBuildingID, + DAT_BuildingsState::instance.buildings[targetBuildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = targetBuildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[targetBuildingID].uid; + DAT_GameState::instance.playerDataArray[playerID].someTotalDefenseTroopsRelatedCountdown = 100; + continue; + } + + if (buildingID == 0) + continue; + int tile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + if (tile == 0) + continue; + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) + == FALSE) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp b/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp new file mode 100644 index 000000000..835f96773 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiOfTypeInCurrentGame.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0580 + BOOLEnum AICState::aiOfTypeInCurrentGame(int aicIndex) + { + for (int playerID = 1; playerID < 9; playerID++) { + if (DAT_GameSynchronyState::instance.currentAIArray[playerID] == 0) + continue; + if (DAT_GameState::instance.playerDataArray[playerID].lordKilledByPlayerID != 0) + continue; + if (aicIndex != DAT_GameState::instance.playerDataArray[playerID].aiType - 1) + continue; + return TRUE; + } + return FALSE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp b/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp new file mode 100644 index 000000000..6165dd546 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiReassignTunnelersToTribe.cpp @@ -0,0 +1,35 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D40B0 + void AICState::aiReassignTunnelersToTribe(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_TUNNELER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 15) + continue; + + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(AICState_Func::addUnitToItsTribe, this)(unitID, 15); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp b/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp new file mode 100644 index 000000000..8c7e889a5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitEngineerForOilDuty.cpp @@ -0,0 +1,71 @@ +#include "OpenSHC/AI/AICState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2500 + void AICState::aiRecruitEngineerForOilDuty(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] < 30) + return; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != Map::Buildings::BT_ENGINEERSGUILD) + return; + int smelterID = DAT_GameState::instance.playerDataArray[playerID].oilSmelter.id; + if (smelterID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[smelterID].buildingType != Map::Buildings::BT_OILSMELTER) + return; + int amount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[1] + 1; + if (amount <= 1) + return; + + if (DAT_BuildingsState::instance.buildings[smelterID].resources[Game::Resources::RT_PITCH] <= 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_PITCH] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_PITCH] = 4; + return; + } + if (MACRO_CALL_MEMBER(AICState_Func::checksAndGenerateAITribesForPlayerIfNotExisting, this)( + playerID, amount, TRUE) + == FALSE) + return; + + int tribeID = MACRO_CALL_MEMBER(AICState_Func::checksAndGenerateAITribesForPlayerIfNotExisting, this)( + playerID, amount, FALSE); + int unitID = MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + Map::Units::UT_E_ENGINEER, DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id, playerID, + 0); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + if (unitID == 0) + return; + + DAT_UnitsState::instance.units[unitID].engineerRelatedUnk = 1; + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + DAT_TribesState::instance.tribes[tribeID].tribeType = Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = Map::Units::STBT_0x41d; + MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)(tribeID, + Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, smelterID, + DAT_BuildingsState::instance.buildings[smelterID].uid, 0); + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp b/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp new file mode 100644 index 000000000..ba7ee99f2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitEngineers.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC520 + void AICState::aiRecruitEngineers(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + if (DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired == FALSE) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + if (this->aics[aiType - 1].defSiegeEngineGoldThreshold + + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + < 30) + return; + int guildID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + if (guildID <= 0) + return; + if (DAT_BuildingsState::instance.buildings[guildID].buildingType != Map::Buildings::BT_ENGINEERSGUILD) + return; + + MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + Map::Units::UT_E_ENGINEER, guildID, playerID, 0); + DAT_GameState::instance.playerDataArray[playerID].isEngineerRequired = FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp new file mode 100644 index 000000000..c3ae30af2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitSortieMeleeUnits.cpp @@ -0,0 +1,69 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD690 + void AICState::aiRecruitSortieMeleeUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].SortieUnitMeleeMin < 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType7 + >= this->aics[aicIndex].SortieUnitMeleeMin) + return; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + UnitType unitType = (UnitType)this->aics[aicIndex].SortieUnitMelee; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER) + return; + + int buildingID; + if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType < 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment > 0 + && DAT_UnitsState::instance.euroUnitAcquisitionFailReason == 2) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + return; + } + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 7; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::createTribeForUnitType, this)(playerID, 167)); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp new file mode 100644 index 000000000..951822739 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitSortieRangedUnits.cpp @@ -0,0 +1,70 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD560 + void AICState::aiRecruitSortieRangedUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].SortieUnitRangedMin < 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6 + >= DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 / 2 + + this->aics[aicIndex].SortieUnitRangedMin) + return; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + UnitType unitType = (UnitType)this->aics[aicIndex].SortieUnitRanged; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER) + return; + + int buildingID; + if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType < 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment > 0 + && DAT_UnitsState::instance.euroUnitAcquisitionFailReason == 2) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + return; + } + + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 6; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::createTribeForUnitType, this)(playerID, 166)); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp new file mode 100644 index 000000000..2f59cee0d --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRecruitUnits.cpp @@ -0,0 +1,201 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3AE0 + void AICState::aiRecruitUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + bool isExtremeTrail = false; + if (DAT_GameCore::instance.gameMode_2 == OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER + && DAT_GameCore::instance.isSkirmishTrail == TRUE + && DAT_GameCore::instance.currentTrailType == OpenSHC::Game::TT_EXTREME) + isExtremeTrail = true; + + int recruitInterval = (&this->aics[aicIndex] + .RecruitInterval)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int recruitCount = recruitInterval != 0 ? 1 : 2; + if (isExtremeTrail) + recruitCount++; + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + recruitCount = 4; + } else { + DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker++; + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker < recruitInterval) + return; + } + + DAT_GameState::instance.playerDataArray[playerID].aiRecruitIntervalTracker = 0; + if (DAT_GameState::instance.playerDataArray[playerID].idlePeasantsCount <= 0) + return; + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + return; + + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + int defTotal = this->aics[aicIndex].DefTotal; + if (isExtremeTrail) + defTotal = defTotal * 4 / 3; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) + defTotal = defTotal * 4; + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk >= defTotal) + return; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk >= MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(aicIndex, playerID)) + return; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING + && DAT_GameState::instance.playerDataArray[playerID].aiPlayerState != 0) { + return; + } + + int recruitDiggingUnit = 0; + if (this->aics[aicIndex].DefDiggingUnitMax != 0 + && MACRO_CALL_MEMBER( + OpenSHC::Map::TileMapState_Func::countUnfinishedMoatTilesForPlayer, DAT_TileMapState::ptr)(playerID) + != 0) { + int diggerTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[10]; + if (diggerTribeID == 0 + || DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[10] + != DAT_TribesState::instance.tribes[diggerTribeID].uid + || DAT_TribesState::instance.tribes[diggerTribeID].size < this->aics[aicIndex].DefDiggingUnitMax) + recruitDiggingUnit = 1; + } + + UnitType unitType = (UnitType)0; + int behaviourType = 0; + for (int i = 0; i < recruitCount; i++) { + if (recruitDiggingUnit && (SEC_RNG::instance.currentNumber2 & 1) != 0) + recruitDiggingUnit = 0; + + if (recruitDiggingUnit) { + if (DAT_GameState::instance.playerDataArray[playerID].barracks.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id == 0) + return; + unitType = (UnitType)this->aics[aicIndex].DefDiggingUnit; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex > 7) + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; + if ((&this->aics[aicIndex] + .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex = 0; + unitType = (UnitType)(&this->aics[aicIndex] + .DefUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex]; + DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex + = DAT_GameState::instance.playerDataArray[playerID].aiDefUnitChoiceIndex + 1; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex > 7) + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; + if ((&this->aics[aicIndex] + .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex = 0; + unitType = (UnitType)(&this->aics[aicIndex] + .RaidUnit1)[DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex]; + DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex + = DAT_GameState::instance.playerDataArray[playerID].aiRaidUnitChoiceIndex + 1; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING) { + behaviourType = MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::randomlySelectAttackUnitTypeToRecruit, this)(playerID); + unitType = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::getUnitTypeForUnitBehaviourType, this)( + playerID, (AIUnitBehaviourType)behaviourType); + } + if (unitType == (UnitType)0) + return; + + int buildingID; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_E_LADDER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_TUNNELER) + buildingID = DAT_GameState::instance.playerDataArray[playerID].tunnelersGuild.id; + else if (unitType == OpenSHC::Map::Units::UT_E_MONK) + buildingID = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_CATHEDRAL); + else if (unitType < 70) + buildingID = DAT_GameState::instance.playerDataArray[playerID].barracks.id; + else + buildingID = DAT_GameState::instance.playerDataArray[playerID].mercenaryPost.id; + if (buildingID == 0) + return; + + int unitID; + if (unitType == OpenSHC::Map::Units::UT_E_ENGINEER || unitType == OpenSHC::Map::Units::UT_E_LADDER + || unitType == OpenSHC::Map::Units::UT_TUNNELER || unitType == OpenSHC::Map::Units::UT_E_MONK + || unitType >= 70) + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + else + unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::euroRecruit, DAT_UnitsState::ptr)( + unitType, buildingID, playerID, 0); + + if (unitID == 0) { + if (this->aics[aicIndex].tradeAmountEquipment <= 0) + return; + if (DAT_UnitsState::instance.euroUnitAcquisitionFailReason != 2) + return; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] + = this->aics[aicIndex].tradeAmountEquipment; + else + DAT_GameState::instance.playerDataArray[playerID] + .resourcesToAcquireArray[DAT_UnitsState::instance.euroUnitRequiredResource] = 5; + return; + } + + if (recruitDiggingUnit) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAddUnitToMoatDiggerTribe, this)(unitID); + else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk + < this->aics[aicIndex].DefWalls) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::assignUnitToATribe, this)(unitID); + else + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToSmallestPatrolTribe, this)(unitID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAssignUnitToDefensiveTribe, this)(unitID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_ATTACKING) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitToItsTribe, this)(unitID, behaviourType); + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp b/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp new file mode 100644 index 000000000..1d2272d15 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRegisterTribeAndAssignTarget.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1950 + void AICState::aiRegisterTribeAndAssignTarget(int tribeID, int tribeUID) + { + if (DAT_TribesState::instance.tribes[tribeID].size > 0) { + if (DAT_GameState::instance.playerDataArray[DAT_TribesState::instance.tribes[tribeID].owner].aiType + == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 1000; i++) { + int existingTribeID = this->tribeIDArray[i]; + if (existingTribeID != 0 + && DAT_TribesState::instance.tribes[existingTribeID].uid == this->tribeUIDArray[i]) + continue; + + this->tribeIDArray[i] = tribeID; + this->tribeUIDArray[i] = tribeUID; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTargetForTribeBasedOnTargetChoiceLogic, this)( + DAT_TribesState::instance.tribes[tribeID].owner, tribeID); + return; + } + return; + } + DAT_TribesState::instance.tribes[tribeID].tribeState = 3; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp b/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp new file mode 100644 index 000000000..799e96e0c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRequiresExtraOxtethers.cpp @@ -0,0 +1,63 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB3A0 + undefined4 AICState::aiRequiresExtraOxtethers(int playerID) + { + int quarryID + = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY); + int highestQuarryID = 0; + int highestStonePerTether = 0; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_OXTETHER, 1) + >= 10) + return 0; + + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = 0; + for (; quarryID != 0; + quarryID = MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::findNextBuildingForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, OpenSHC::Map::Buildings::BT_QUARRY, quarryID)) { + int stone = DAT_BuildingsState::instance + .buildings[(short)DAT_BuildingsState::instance.buildings[quarryID].quarryStockpileID] + .resources[Game::Resources::RT_STONE]; + int tetherCount = 0; + for (int i = 0; i < 3; i++) { + int tetherID = DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[i]; + if (tetherID == 0) + continue; + if (DAT_BuildingsState::instance.buildings[tetherID].buildingType + == OpenSHC::Map::Buildings::BT_OXTETHER + && DAT_BuildingsState::instance.buildings[tetherID].oxtetherLinkedQuarryID == quarryID) + tetherCount++; + else + DAT_BuildingsState::instance.buildings[quarryID].quarryLinkedOxTethers[i] = 0; + } + + if (tetherCount <= 0) { + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = quarryID; + return 1; + } + if (stone / tetherCount > highestStonePerTether) { + highestQuarryID = quarryID; + highestStonePerTether = stone / tetherCount; + } + } + + if (highestStonePerTether > 20) { + DAT_GameState::instance.playerDataArray[playerID].highestLoadedQuarryUnk = highestQuarryID; + return 1; + } + return 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp b/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp new file mode 100644 index 000000000..7cdacce2c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiRetreatUnits.cpp @@ -0,0 +1,46 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE4D0 + void AICState::aiRetreatUnits(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (tribeType == 190) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else if (tribeType == 13) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::selectionContainsHorses, + DAT_TribesState::ptr)(tribeID) + != FALSE) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + else + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp new file mode 100644 index 000000000..5f0124873 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldAttackOrWaitForTeamCoordination.cpp @@ -0,0 +1,81 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDCF0 + BOOLEnum AICState::aiShouldAttackOrWaitForTeamCoordination(int playerID) + { + int otherPlayerID = 1; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) + return TRUE; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] + .totalTroopValue + <= 100) + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops + < this->aics[aicIndex].AttForceBase + + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel == 0) + return TRUE; + + int readyTeamMembers = 0; + int unreadyTeamMembers = 0; + for (; otherPlayerID < 9; otherPlayerID++) { + if (otherPlayerID == playerID) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + otherPlayerID) + == 0) + continue; + if (MACRO_CALL_MEMBER(OpenSHC::Synchrony::GameSynchronyState_Func::isAIPlayer, DAT_GameSynchronyState::ptr)( + otherPlayerID) + == FALSE) + continue; + if (DAT_GameState::instance.mapAndTime.playerTeams[otherPlayerID] + != DAT_GameState::instance.mapAndTime.playerTeams[playerID]) + continue; + + if (DAT_GameState::instance.playerDataArray[otherPlayerID].aiPlayerState != 0) + return TRUE; + if (DAT_GameState::instance.playerDataArray[otherPlayerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[otherPlayerID].totalAttackingEngineerTroops + >= this->aics[DAT_GameState::instance.playerDataArray[otherPlayerID].aiType - 1].AttForceBase + + DAT_GameState::instance.playerDataArray[otherPlayerID].currentWaveRandomAttackingStrength) + readyTeamMembers++; + else + unreadyTeamMembers++; + } + + if (readyTeamMembers >= DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel) + return TRUE; + if (unreadyTeamMembers == 0) + return TRUE; + + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience++; + if (DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience > 48 + && DAT_GameCore::instance.gameMode_2 != OpenSHC::Game::GM_CAMPAIGN_MISSION) + return TRUE; + return FALSE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp new file mode 100644 index 000000000..02ab1e06c --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildFarm.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB220 + BOOLEnum AICState::aiShouldBuildFarm(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return FALSE; + int aicIndex = aiType - 1; + + int farmCount + = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countFarms, DAT_BuildingsState::ptr)(playerID, 1); + if (farmCount <= 0) + return TRUE; + if (farmCount >= this->aics[aicIndex].maxFarms) + return FALSE; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / farmCount + >= this->aics[aicIndex].populationPerFarm; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp new file mode 100644 index 000000000..1f0c072e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildIronMine.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB4D0 + uint AICState::aiShouldBuildIronMine(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerIronmine <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_IRONMINE, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxIronmines) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerIronmine; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp new file mode 100644 index 000000000..5cf71d7e0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildPitchrig.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB540 + uint AICState::aiShouldBuildPitchrig(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerPitchrig <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_PITCHRIG, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxPitchrigs) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerPitchrig; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp new file mode 100644 index 000000000..2fcefd9ab --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildQuarry.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB330 + uint AICState::aiShouldBuildQuarry(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerQuarry <= 0) + return 0; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_QUARRY, 1); + if (count <= 0) + count = 1; + else if (count >= this->aics[aicIndex].maxQuarries) + return 0; + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerQuarry; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp b/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp new file mode 100644 index 000000000..7d223c5a9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiShouldBuildWoodcutter.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB290 + BOOLEnum AICState::aiShouldBuildWoodcutter(int playerID) + { + if (DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) + return FALSE; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return FALSE; + int aicIndex = aiType - 1; + if (this->aics[aicIndex].populationPerWoodcutter <= 0) + return FALSE; + + int count = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::countBuildingsForPlayer, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_WOODCUTTERSHUT, 1); + if (count <= 0) { + count = 1; + } else { + if (count >= this->aics[aicIndex].maxWoodcutters) + return FALSE; + if (count >= 6 && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 90) + return FALSE; + if (count >= 3 && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 30) + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].currentPopulation / count + >= this->aics[aicIndex].populationPerWoodcutter; + } +} +} diff --git a/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp b/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp new file mode 100644 index 000000000..cd1b58035 --- /dev/null +++ b/src/OpenSHC/AI/AICState/aiUpdateTaxesAndRations.cpp @@ -0,0 +1,91 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CAEA0 + void AICState::aiUpdateTaxesAndRations(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + int popularity = DAT_GameState::instance.playerDataArray[playerID].popularity; + if (popularity <= this->aics[aicIndex].criticalPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 2; + else if (popularity <= this->aics[aicIndex].lowestPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 1; + else if (popularity >= this->aics[aicIndex].highestPopularity) + DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 2) { + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 2; + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 4) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 8) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 3; + else + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 1) { + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > this->aics[aicIndex].taxesMin) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting--; + else + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = this->aics[aicIndex].taxesMin; + + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + else if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 10) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + else + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 3; + if (this->aics[aicIndex].doubleRationsFoodThreshold != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood + >= this->aics[aicIndex].doubleRationsFoodThreshold) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPopularityDecisionValue == 0) { + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting < this->aics[aicIndex].taxesMax) { + int lowerTaxesLimit; + if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 15) + lowerTaxesLimit = 6; + else if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 10) + lowerTaxesLimit = 5; + else + lowerTaxesLimit = 4; + + if (DAT_GameState::instance.playerDataArray[playerID].storedPopularityPercent <= popularity) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting++; + else if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > lowerTaxesLimit) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting--; + } else { + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = this->aics[aicIndex].taxesMax; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalFood <= 10) { + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + } else { + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 2; + if (this->aics[aicIndex].doubleRationsFoodThreshold != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood + >= this->aics[aicIndex].doubleRationsFoodThreshold) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } + } + + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting < 0) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 0; + if (DAT_GameState::instance.playerDataArray[playerID].taxesSetting > 11) + DAT_GameState::instance.playerDataArray[playerID].taxesSetting = 11; + if (DAT_GameState::instance.playerDataArray[playerID].rationsSetting < 0) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 0; + if (DAT_GameState::instance.playerDataArray[playerID].rationsSetting > 4) + DAT_GameState::instance.playerDataArray[playerID].rationsSetting = 4; + } +} +} diff --git a/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp b/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp new file mode 100644 index 000000000..2418ab3e9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/assignRequiredIdleEngineersToNewTribe.cpp @@ -0,0 +1,49 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/States/UnitState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC5A0 + int AICState::assignRequiredIdleEngineersToNewTribe(int playerID, int count) + { + int tribeID + = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (count <= 0) + break; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != Map::Units::UT_E_ENGINEER) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 22) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 10) + continue; + if (DAT_UnitsState::instance.units[unitID].engineerRelatedUnk != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].state.generic != 105 + && DAT_UnitsState::instance.units[unitID].state.generic != 2 + && DAT_UnitsState::instance.units[unitID].state.generic != Map::Units::States::US_IDLEUnk) + continue; + + if (DAT_UnitsState::instance.units[unitID].tribeID != 0) + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + count--; + } + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp new file mode 100644 index 000000000..547e164cf --- /dev/null +++ b/src/OpenSHC/AI/AICState/assignUnitToATribe.cpp @@ -0,0 +1,41 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2660 + void AICState::assignUnitToATribe(int unitID) + { + int owner = DAT_UnitsState::instance.units[unitID].owner; + if (DAT_GameState::instance.playerDataArray[owner].aiType == AITA_NULL) + return; + + AIVUnitType aivUnitType; + if (DAT_GameState::instance.playerDataArray[owner].aivCurrentPauseIndex == 1 + && DAT_GameState::instance.playerDataArray[owner] + .aivPauses[DAT_GameState::instance.playerDataArray[owner].aivCurrentPauseIndex] + > 0) + aivUnitType = MACRO_CALL_MEMBER(AICState_Func::getUnitTypeIndexForUnitID, this)(unitID, 1); + else + aivUnitType = MACRO_CALL_MEMBER(AICState_Func::getUnitTypeIndexForUnitID, this)(unitID, 0); + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = 1; + int tribeID; + if (DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlotLocationCount[aivUnitType] <= 0) { + if (MACRO_CALL_MEMBER(AICState_Func::unitIDIsRangedOrArmored, this)(unitID) != 0) + tribeID = MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 0); + else + tribeID = MACRO_CALL_MEMBER(AICState_Func::createTribeForUnitType, this)(owner, 1); + } else { + tribeID = MACRO_CALL_MEMBER(AICState_Func::smallestTribeOfUnitType, this)(owner, aivUnitType); + } + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/buyGoods.cpp b/src/OpenSHC/AI/AICState/buyGoods.cpp index 7075afd56..479c46eb4 100644 --- a/src/OpenSHC/AI/AICState/buyGoods.cpp +++ b/src/OpenSHC/AI/AICState/buyGoods.cpp @@ -2,6 +2,7 @@ #include "OpenSHC/Game/GameStateStructures.func.hpp" #include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" #include "OpenSHC/Globals/DAT_BuildingsState.hpp" #include "OpenSHC/Globals/DAT_GameState.hpp" @@ -12,10 +13,11 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CC000 BOOLEnum AICState::buyGoods(int playerID, ResourceType resourceType, int amount) { - int const cost = MACRO_CALL_MEMBER(Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( + int cost = MACRO_CALL_MEMBER(Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( playerID, resourceType, amount); if (MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::processResourceGain, DAT_BuildingsState::ptr)( - playerID, resourceType, amount)) { + playerID, resourceType, amount) + != FALSE) { DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] -= cost; DAT_GameState::instance.playerDataArray[playerID].marketGold -= cost; MACRO_CALL_MEMBER(Game::GameStateStructures_Func::displayPlayerTradeVisualEffect, DAT_GameState::ptr)( @@ -24,6 +26,5 @@ namespace AI { } return FALSE; } - } } diff --git a/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp b/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp new file mode 100644 index 000000000..6fde84ca6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/buyRequiredGoods.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D39B0 + void AICState::buyRequiredGoods(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + for (int i = 0; i < 25; i++) { + int resourceType = DAT_SkirmishDefinedData::instance.ResourceAcquisitionPreferenceOrder[i]; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && i < 12) + continue; + + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker != 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] < 20) + resourceType = Game::Resources::RT_WOOD; + + int amount = DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[resourceType]; + if (amount == 0) + continue; + if ((i > 3 + || DAT_GameState::instance.playerDataArray[playerID].popularity + >= this->aics[aicIndex].criticalPopularity) + && DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + && DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) + continue; + + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + < MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::getBuyPrice, DAT_GameState::ptr)( + playerID, resourceType, amount)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::requestGoods, this)( + playerID, (ResourceType)resourceType, amount * 2); + continue; + } + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::buyGoods, this)( + playerID, (ResourceType)resourceType, amount) + != FALSE) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[resourceType] = 0; + return; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp b/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp new file mode 100644 index 000000000..2b8922be6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/canNavigateUnitsFromTileToTargetTile.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD250 + BOOLEnum AICState::canNavigateUnitsFromTileToTargetTile(int tribeID, int tile) + { + int owner = DAT_TribesState::instance.tribes[tribeID].owner; + int fromArea = (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile]; + return MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)( + owner, fromArea, (short)DAT_TileMapState::instance.PathConnectionLayer[tile], 0) + != 0; + } +} +} diff --git a/src/OpenSHC/AI/AICState/checkTribeActivityPercentages.cpp b/src/OpenSHC/AI/AICState/checkTribeActivityPercentages.cpp new file mode 100644 index 000000000..8bf0b8883 --- /dev/null +++ b/src/OpenSHC/AI/AICState/checkTribeActivityPercentages.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD070 + BOOLEnum AICState::checkTribeActivityPercentages(int tribeID, BOOLEnum ignoreShooting, BOOLEnum includeMoving) + { + if (includeMoving != FALSE && DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk > 10) { + return TRUE; + } + if (DAT_TribesState::instance.tribes[tribeID].percentageShootingUnk > 10 && ignoreShooting != TRUE) { + return TRUE; + } + return DAT_TribesState::instance.tribes[tribeID].percentageAttackingUnk > 10; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp b/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp new file mode 100644 index 000000000..00347b846 --- /dev/null +++ b/src/OpenSHC/AI/AICState/checksAndGenerateAITribesForPlayerIfNotExisting.cpp @@ -0,0 +1,43 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCFB0 + int AICState::checksAndGenerateAITribesForPlayerIfNotExisting(int playerID, int maxAmount, BOOLEnum checkOnly) + { + int offset = 0; + int baseOffset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[1]; + for (; offset < maxAmount; offset++) { + if (offset >= 10) { + return 0; + } + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[baseOffset + offset]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[baseOffset + offset]) { + if (checkOnly != FALSE) { + return 1; + } + int newTribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[baseOffset + offset] = (short)newTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[baseOffset + offset] + = DAT_TribesState::instance.tribes[newTribeID].uid; + return newTribeID; + } + } + return 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp new file mode 100644 index 000000000..94ed39aaf --- /dev/null +++ b/src/OpenSHC/AI/AICState/chooseRandomBuildingIDFromPlayersArrayOfBuildings.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDB20 + int AICState::chooseRandomBuildingIDFromPlayersArrayOfBuildings(int playerID) + { + int chosenBuildingID = 0; + int chosenPriority = 46; + for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { + int rng = SEC_RNG::instance.currentNumber2 & 7; + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; + if (buildingID == 0) + continue; + + for (int j = 0; j < chosenPriority; j++) { + if (rng <= 1) { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet3[j]) + continue; + } else if (rng <= 3) { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet2[j]) + continue; + } else { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + != DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) + continue; + } + + chosenBuildingID = buildingID; + chosenPriority = j; + } + } + return chosenBuildingID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp b/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp new file mode 100644 index 000000000..8e7e23178 --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearOutdatedAITribes.cpp @@ -0,0 +1,39 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF870 + void AICState::clearOutdatedAITribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + if (i == 8) + continue; + + int tribeCount = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeCount; + int tribeType = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeType; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j] = 0; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType] = 0; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp b/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp new file mode 100644 index 000000000..9eb65d0be --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearPlayerDataInformationChunk.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CAC70 + void AICState::clearPlayerDataInformationChunk() + { + for (int i = 0; i < 9; i++) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1 + || DAT_GameSynchronyState::instance.currentAIArray[i] == 0) + MACRO_CALL(OpenSHC::OS_Func::_memset)(&DAT_GameState::instance.playerDataArray[i].aiFarmIndex, 0, 3748); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp b/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp new file mode 100644 index 000000000..09533eb02 --- /dev/null +++ b/src/OpenSHC/AI/AICState/clearRallyFlagOnAllAIAttackTribes.cpp @@ -0,0 +1,37 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE790 + void AICState::clearRallyFlagOnAllAIAttackTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp b/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp new file mode 100644 index 000000000..83e39cbc1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/commandFourTribesToMove.cpp @@ -0,0 +1,40 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF7C0 + void AICState::commandFourTribesToMove(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + int destIndex = 0; + for (int i = 0; i < 3; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[186 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 186]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_STAND_GROUND; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex].xOffset, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex].yOffset, + 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + destIndex++; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp b/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp new file mode 100644 index 000000000..4a8f8f26d --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeAttackPossibilities.cpp @@ -0,0 +1,38 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2EC0 + void AICState::computeAttackPossibilities(int playerID) + { + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIInfoLayer); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackPrimeInfo, this)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::recomputeTargetedBuildingTilesArray, + DAT_TroopValueState::ptr)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::recomputeAttackInfo, DAT_TroopValueState::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::computeSiegeSpotScores, DAT_TroopValueState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setScale3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setTown2, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setGate2, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setWide3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TroopValueState_Func::setMoat3, DAT_TroopValueState::ptr)(1, playerID); + MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::calculateAttackVectorsToCampFireOfPlayer, + DAT_GameState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp new file mode 100644 index 000000000..375733aef --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeEnemyKeepApproachTile.cpp @@ -0,0 +1,85 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE200 + void AICState::computeEnemyKeepApproachTile(int playerID) + { + int keepY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; + this->aiBorderTilesIndex = 0; + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 8000, '\0', this->aiBorderTiles); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 1); + + for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { + int tile = this->aiBorderTiles[i].tile; + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[playerID].campground.xEntry, + keepY, + tile + - DAT_ViewportRenderState::instance + .translationMatrix[DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]] + .addXgetTile, + DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]); + this->aiBorderTiles[i].distance = DAT_TileMapState::instance.unitDeathHeatMap[tile] + + DAT_DirectionAlgorithmState::instance.distanceHigh; + } + + int shortestDistanceTile = 0; + int shortestDistance = 100000; + for (int i = 0; i < (int)this->aiBorderTilesIndex; i++) { + if (this->aiBorderTiles[i].distance < shortestDistance) { + shortestDistanceTile = this->aiBorderTiles[i].tile; + shortestDistance = this->aiBorderTiles[i].distance; + } + } + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = 0; + if (shortestDistanceTile <= 0) + return; + + short y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[shortestDistanceTile]; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY = y; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile = shortestDistanceTile; + int x = shortestDistanceTile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX = x; + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x = x; + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y + = DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY; + + if ((short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX] + != 0) + MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)( + (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].x, + DAT_GameState::instance.mapAndTime.signpostEntryData[playerID - 1].y, -1, -1, 2000, FALSE); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::storeDestinationOptionsUnk, + DAT_PathFindingState::ptr)(playerID - 1); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/computeNervousness.cpp b/src/OpenSHC/AI/AICState/computeNervousness.cpp new file mode 100644 index 000000000..65e1fe7bc --- /dev/null +++ b/src/OpenSHC/AI/AICState/computeNervousness.cpp @@ -0,0 +1,47 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3780 + void AICState::computeNervousness(int playerID) + { + int troopValueNearKeep + = DAT_TroopValueState::instance.attackInfo.playerTotalTroopValueOfTroopsNearKeep[playerID]; + int enemyTroopValue = DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue; + + int isNotNervous = 1; + int margin; + if (enemyTroopValue < 50) + margin = -20; + else if (enemyTroopValue < 100) + margin = 0; + else if (enemyTroopValue < 200) + margin = 20; + else if (enemyTroopValue < 400) + margin = 100; + else if (enemyTroopValue < 600) + margin = 200; + else if (enemyTroopValue < 800) + margin = 300; + else + margin = 500; + + if (troopValueNearKeep > enemyTroopValue + margin) { + isNotNervous = 0; + } else if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue == 0) { + if (DAT_GameState::instance.playerDataArray[playerID] + .totalEnemyTroopValueByPlayerID[DAT_GameSynchronyState::instance.currentPlayerSlotID] + > troopValueNearKeep) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playNervous1BikFromPlayer, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playRequestHelpBikFromPlayer, this)(playerID); + } + DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue = isNotNervous; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp b/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp new file mode 100644 index 000000000..4cad5f5ed --- /dev/null +++ b/src/OpenSHC/AI/AICState/countSiegeEngineUnits.cpp @@ -0,0 +1,36 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFBC0 + int AICState::countSiegeEngineUnits(int playerID) + { + int count = 0; + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == OpenSHC::Map::Units::ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + continue; + + if (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_TREBUCHET + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_TOWER + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_BATTERINGRAM + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + count++; + } + return count; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp b/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp new file mode 100644 index 000000000..4395e208d --- /dev/null +++ b/src/OpenSHC/AI/AICState/createTribeForUnitType.cpp @@ -0,0 +1,26 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC910 + int AICState::createTribeForUnitType(int playerID, int unitSelectionTypeIndex) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[unitSelectionTypeIndex]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[unitSelectionTypeIndex]) + return tribeID; + + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[unitSelectionTypeIndex] = tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[unitSelectionTypeIndex] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp new file mode 100644 index 000000000..48f809905 --- /dev/null +++ b/src/OpenSHC/AI/AICState/decideOnTribeAttackLocation.cpp @@ -0,0 +1,154 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1A00 + void AICState::decideOnTribeAttackLocation(uint tribeID) + { + if (MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, TRUE) != FALSE) { + DAT_TribesState::instance.tribes[tribeID].counter = 0; + return; + } + + int targetPlayer = DAT_TribesState::instance.tribes[tribeID].targetPlayer_outpost; + if (DAT_TribesState::instance.tribes[tribeID].randomNumber == 0) { + DAT_TribesState::instance.tribes[tribeID].randomNumber = SEC_RNG::instance.currentNumber2; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + } + + DAT_TribesState::instance.tribes[tribeID].counter++; + if (DAT_TribesState::instance.tribes[tribeID].counter + == DAT_TribesState::instance.tribes[tribeID].randomNumber % 6 + 7) { + DAT_TribesState::instance.tribes[tribeID].counter = 0; + byte moatWasHit = 0; + int x = 0; + int y = 0; + int unitID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::getUnitIDForIndexInTribe, DAT_TribesState::ptr)(tribeID, 0); + if (unitID != 0 + && MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::pathFindingToAttackCastleIncludingMoat, + DAT_PathFindingState::ptr)(DAT_TribesState::instance.tribes[tribeID].owner, targetPlayer, + DAT_UnitsState::instance.units[unitID].x, DAT_UnitsState::instance.units[unitID].y, &moatWasHit, &x, + &y)) { + if (moatWasHit != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, (UnitInstructionType)35, + DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile + x, 0, 0); + return; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_FILL_MOAT, x, y, 1000); + return; + } + } + + if (DAT_TribesState::instance.tribes[tribeID].tribeSubtype1 != 182 + && DAT_TribesState::instance.tribes[tribeID].tribeSubtype1 != 185) { + if (MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, FALSE) != FALSE) + return; + + int targetBuildingID = DAT_TribesState::instance.tribes[tribeID].targetBuildingID; + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0 + && MACRO_CALL_MEMBER(AICState_Func::checkTribeActivityPercentages, this)(tribeID, FALSE, TRUE) + != FALSE) { + if (DAT_GameState::instance.mapAndTime.yearChanged != 0) + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + return; + } + + int buildingID + = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, + DAT_GameState::ptr)(targetPlayer); + if (buildingID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_ATTACK_BUILDING, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = buildingID; + DAT_TribesState::instance.tribes[tribeID].targetBuildingUID + = DAT_BuildingsState::instance.buildings[buildingID].uid; + return; + } + + int destinationX = DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + DAT_TribesState::instance.tribes[tribeID].targetBuildingID = 0; + int destinationY = DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, + DAT_ViewportRenderState::instance.translationMatrix[destinationY].addXgetTile + destinationX) + == FALSE) + return; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, destinationX, destinationY, 1); + return; + } + + int buildingID = MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::selectARandomBuildingOwnedByPlayer, + DAT_GameState::ptr)(targetPlayer); + int destinationTile; + if (buildingID == 0) { + destinationTile + = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } else if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + destinationTile = DAT_BuildingsState::instance.buildings[buildingID].buildingEntryX + + DAT_ViewportRenderState::instance + .translationMatrix[DAT_BuildingsState::instance.buildings[buildingID].buildingEntryY] + .addXgetTile; + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::pathfindingForAttacksUnk, + DAT_PathFindingState::ptr)(tribeID, buildingID, 1, + (short)DAT_TileMapState::instance.PathConnectionLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile], + targetPlayer); + destinationTile = DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile1; + if (DAT_PathFindingState::instance.searchQueue.destinationsArray[0].tile2OrAHelper == 0) + destinationTile + = DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[targetPlayer].campground.yEntry] + .addXgetTile + + DAT_GameState::instance.playerDataArray[targetPlayer].campground.xEntry; + } + + if (destinationTile == 0) + return; + + int destinationY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[destinationTile]; + int destinationX + = destinationTile - DAT_ViewportRenderState::instance.translationMatrix[destinationY].addXgetTile; + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)( + tribeID, destinationTile) + == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, targetPlayer); + return; + } + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, destinationX, destinationY, 1); + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp b/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp new file mode 100644 index 000000000..4ce6d8fee --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyAnyBuildingInCategory.cpp @@ -0,0 +1,97 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Buildings/BuildingLogicalState.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" + +namespace OpenSHC { +namespace AI { + + using namespace OpenSHC::Map::Buildings; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0280 + void AICState::destroyAnyBuildingInCategory(int playerID, AINervousDestroyBuildingCategory buildingCategory) + { + for (int buildingID = 1; buildingID < DAT_BuildingsState::instance.maxBuildingsCount; buildingID++) { + if (DAT_BuildingsState::instance.buildings[buildingID].logicalState == 0) + continue; + if (DAT_BuildingsState::instance.buildings[buildingID].logicalState == BLS_REMOVE) + continue; + if (DAT_BuildingsState::instance.buildings[buildingID].owner != playerID) + continue; + + bool matches = true; + if (buildingCategory == AINDBC_FEAR_FACTOR_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CESSPIT + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BURNINGSTAKE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GIBBET + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DUNGEON + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STRETCHINGRACK + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_RACKFLOGGING + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHOPPINGBLOCK + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DUNKINGSTOOL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DOGCAGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STATUE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_SHRINE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DANCINGBEAR + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_POND + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GALLOWS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STOCKS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WITCHHOIST + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MAYPOLE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GARDEN; + else if (buildingCategory == AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_FLETCHER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BLACKSMITH + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_POLETURNER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_ARMOURER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TANNER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_INN + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_APOTHECARY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHAPEL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CHURCH + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_CATHEDRAL; + else if (buildingCategory == AINDBC_GATEHOUSES_TOWERS_GUILDS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GATEHOUSELARGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_GATEHOUSESMALL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DRAWBRIDGE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER1 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER2 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER3 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER4 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TOWER5 + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_ENGINEERSGUILD + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_TUNNELERSGUILD + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_OILSMELTER; + else if (buildingCategory == AINDBC_BARRACKS_MERCPOST_STABLES) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MERCENARYPOST + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BARRACKS + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_STABLES; + else if (buildingCategory == AINDBC_WATER_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WELL + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WATERPOT; + else if (buildingCategory == AINDBC_RESOURCE_BUILDINGS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_OXTETHER + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_IRONMINE + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_PITCHRIG + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BAKERY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_BREWERY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_QUARRY + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_MILL; + else if (buildingCategory == AINDBC_FARMS) + matches = DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_WHEATFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_HOPFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_APPLEFARM + || DAT_BuildingsState::instance.buildings[buildingID].buildingType == BT_DAIRYFARM; + + if (!matches) + continue; + + MACRO_CALL_MEMBER(BuildingsState_Func::giveBackResourceForDestroyedBuilding, DAT_BuildingsState::ptr)( + buildingID, playerID, 50); + MACRO_CALL_MEMBER(BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(buildingID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp b/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp new file mode 100644 index 000000000..afa234ad5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyBuildingIfNoWorker.cpp @@ -0,0 +1,45 @@ +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Game/GameMode.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB940 + undefined4 AICState::destroyBuildingIfNoWorker(int buildingID) + { + short countdown = DAT_BuildingsState::instance.buildings[buildingID].unknownTickRelatedValue; + int owner = DAT_BuildingsState::instance.buildings[buildingID].owner; + if (countdown <= 0) + return 0; + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[owner] != -1) + return 0; + if (DAT_GameSynchronyState::instance.currentAIArray[owner] == 0) + return 0; + if (DAT_GameSynchronyState::instance.currentGameMode == Game::GM_SOLITARY) + return 0; + + if (DAT_BuildingsState::instance.buildings[buildingID].workers[0] != 0) { + if (DAT_GameState::instance.mapAndTime.monthTicks == owner) + DAT_BuildingsState::instance.buildings[buildingID].unknownTickRelatedValue = countdown - 1; + return 0; + } + if (countdown < 20) + return 0; + + MACRO_CALL_MEMBER(AIVState_Func::resetCountdownInHeatMap, DAT_AIVState::ptr)( + (short)DAT_BuildingsState::instance.buildings[buildingID].x, + (short)DAT_BuildingsState::instance.buildings[buildingID].y); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(buildingID, owner, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(buildingID); + return 1; + } +} +} diff --git a/src/OpenSHC/AI/AICState/destroyHouse.cpp b/src/OpenSHC/AI/AICState/destroyHouse.cpp new file mode 100644 index 000000000..acaa30e30 --- /dev/null +++ b/src/OpenSHC/AI/AICState/destroyHouse.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB810 + BOOLEnum AICState::destroyHouse(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentPopulation + > DAT_GameState::instance.playerDataArray[playerID].populationCap) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentPopulation <= 8) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].averagePopulationGrowthUnk <= 20) + return FALSE; + + int hovelID = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_HOVEL); + if (hovelID == 0) + return FALSE; + + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(hovelID, playerID, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)(hovelID); + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp b/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp new file mode 100644 index 000000000..2ec92a0c9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/determineAIPlayerAttackRequestResponse.cpp @@ -0,0 +1,36 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D38C0 + BOOLEnum AICState::determineAIPlayerAttackRequestResponse(int playerID, undefined4 param_2, int requestedByPlayerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue != 0) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotAttack1BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops + < this->aics[aicIndex].AttForceSupportAllyThreshold) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotAttack2BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + MACRO_CALL_MEMBER(AICState_Func::playWillAttackBikFromPlayerToPlayer, this)(playerID, requestedByPlayerID); + return TRUE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp b/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp new file mode 100644 index 000000000..46df0cb27 --- /dev/null +++ b/src/OpenSHC/AI/AICState/determineAIPlayerHelp.cpp @@ -0,0 +1,35 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3850 + BOOLEnum AICState::determineAIPlayerHelp(int playerID, int requestedByPlayerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return FALSE; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].isNotNervousByEnemyTroopValue != 0) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotHelp1BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + < this->aics[aicIndex].AttForceSupportAllyThreshold) { + MACRO_CALL_MEMBER(AICState_Func::playWillNotHelp2BikFromPlayerToPlayer, this)( + playerID, requestedByPlayerID); + return FALSE; + } + + MACRO_CALL_MEMBER(AICState_Func::playWillHelpBikFromPlayerToPlayer, this)(playerID, requestedByPlayerID); + return TRUE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/digNearestMoat.cpp b/src/OpenSHC/AI/AICState/digNearestMoat.cpp new file mode 100644 index 000000000..a8f101b79 --- /dev/null +++ b/src/OpenSHC/AI/AICState/digNearestMoat.cpp @@ -0,0 +1,54 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD160 + void AICState::digNearestMoat(int playerID) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[10]; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[10]) + return; + if (DAT_TribesState::instance.tribes[tribeID].size <= 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].percentageSomething > 80) + return; + + if (MACRO_CALL_MEMBER(Map::TileMapState_Func::countUnfinishedMoatTilesForPlayer, DAT_TileMapState::ptr)( + playerID) + <= 0) { + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + return; + } + + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + int tile = MACRO_CALL_MEMBER(Map::TileMapState_Func::findNearestFriendlyMoatTileForDigging, + DAT_TileMapState::ptr)(playerID, targetUnitID, 1); + if (tile <= -1) + return; + + MACRO_CALL_MEMBER(Map::TileMapState_Func::setXYBasedOnMoatID, DAT_TileMapState::ptr)( + tile, 1, DAT_UnitsState::instance.units[targetUnitID].x, DAT_UnitsState::instance.units[targetUnitID].y); + if (MACRO_CALL_MEMBER(Map::Units::TribesState_Func::updateTribeRallyFlags, DAT_TribesState::ptr)(tribeID) != 0) + return; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)(tribeID, + Map::Units::UIT_DIG_MOAT, DAT_TileMapState::instance.ALG_MoatXResult, + DAT_TileMapState::instance.ALG_MoatYResult, 0); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_STAND_GROUND; + } +} +} diff --git a/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp b/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp new file mode 100644 index 000000000..19151eaa5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/disbandAIPlayerSiegeUnits.cpp @@ -0,0 +1,43 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/States/UnitState.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFB20 + void AICState::disbandAIPlayerSiegeUnits(int playerID) + { + for (int unitID = 1; unitID < 2500; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState == OpenSHC::Map::Units::ULS_INVISIBLE) + continue; + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_CATAPULT + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TREBUCHET + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_TOWER + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_BATTERINGRAM + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_SHIELD + && DAT_UnitsState::instance.units[unitID].unitType != OpenSHC::Map::Units::UT_S_FBALLISTA) + continue; + if (DAT_UnitsState::instance.units[unitID].unknownSiegeTentRelated02 != 3) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == 21) + continue; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)( + DAT_UnitsState::instance.units[unitID].tribeID, OpenSHC::Map::Units::UIT_EXIT_SIEGE_EQUIPMENT, unitID, + DAT_UnitsState::instance.units[unitID].uid, 0); + DAT_UnitsState::instance.units[unitID].state.generic = OpenSHC::Map::Units::States::US_DISAPPEAR; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp new file mode 100644 index 000000000..803bd3d6c --- /dev/null +++ b/src/OpenSHC/AI/AICState/findAttackTribePositionAndPathToDefenses.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE930 + void AICState::findAttackTribePositionAndPathToDefenses(int playerID) + { + int x = 0; + int y = 0; + for (int i = 0; i < 11; i++) { + int targetUnitID = 0; + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + x = DAT_UnitsState::instance.units[targetUnitID].x; + y = DAT_UnitsState::instance.units[targetUnitID].y; + break; + } + if (targetUnitID != 0) + break; + } + + DAT_GameState::instance.playerDataArray[playerID].someTile = 0; + if (x == 0) + return; + + int tile = MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::tracePathToFriendlyDefensesAndReturnTileUnk, + DAT_PathFindingState::ptr)(playerID, x, y); + if (tile <= 0) + return; + + int tileY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + DAT_GameState::instance.playerDataArray[playerID].someTile = tile; + DAT_GameState::instance.playerDataArray[playerID].someY = tileY; + DAT_GameState::instance.playerDataArray[playerID].someX + = tile - DAT_ViewportRenderState::instance.translationMatrix[tileY].addXgetTile; + } +} +} diff --git a/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp new file mode 100644 index 000000000..dbc9260e1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/generateSiegeCreationInformation.cpp @@ -0,0 +1,55 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/GameMode.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC420 + void AICState::generateSiegeCreationInformation(int playerID, int buildingID, int unitID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int i = 0; + if (DAT_GameSynchronyState::instance.currentGameMode == OpenSHC::Game::GM_SOLITARY) + return; + + int siegeIndex; + if (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_TREBUCHET) + siegeIndex = 2; + else if (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + siegeIndex = 3; + else + return; + + int slot = DAT_SkirmishDefinedData::instance.SiegeEngineMetaInfoArray[siegeIndex].slot; + int locationCount = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[slot]; + while (i < locationCount) { + int index = i; + i++; + if (DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlots[slot][index] > 0 + && buildingID + == DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .buildingID) { + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][index].unitID + = unitID; + DAT_GameState::instance.playerDataArray[playerID].aiSiegeCreationInformation[siegeIndex][index].uid + = DAT_UnitsState::instance.units[unitID].uid; + DAT_GameState::instance.playerDataArray[playerID] + .aiSiegeCreationInformation[siegeIndex][index] + .buildingID = 0; + return; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp b/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp index b76f6d0d0..de9383a30 100644 --- a/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp +++ b/src/OpenSHC/AI/AICState/getAIBuildInterval.cpp @@ -1,5 +1,4 @@ -#include "../AICState.hpp" - +#include "OpenSHC/AI/AICState.hpp" #include "OpenSHC/AI/AITypeA.hpp" #include "OpenSHC/Globals/DAT_GameState.hpp" @@ -9,14 +8,11 @@ namespace AI { // FUNCTION: STRONGHOLDCRUSADER 0x004CB120 int AICState::getAIBuildInterval(int playerID) - { - int const aiType = DAT_GameState::ptr->playerDataArray[playerID].aiType; - if (aiType == AITA_NULL) { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) return 0; - } - - return this->aics[(aiType - 1)].buildInterval; + return this->aics[aiType - 1].buildInterval; } } } diff --git a/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp b/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp new file mode 100644 index 000000000..c4ad3be31 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getCurrentDesiredAttackRaidUnitCount.cpp @@ -0,0 +1,24 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D12A0 + int AICState::getCurrentDesiredAttackRaidUnitCount(int aiType, int playerID) + { + if (DAT_GameCore::instance.gameMode_2 == Game::GM_SKIRMISH_AND_MULTIPLAYER + && DAT_GameCore::instance.isSkirmishTrail == TRUE + && DAT_GameCore::instance.currentTrailType == Game::TT_EXTREME) + return (DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + + this->aics[aiType].RaidUnitsBase) + * 4 / 3; + return DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + + this->aics[aiType].RaidUnitsBase; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp b/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp new file mode 100644 index 000000000..fd1ac266c --- /dev/null +++ b/src/OpenSHC/AI/AICState/getDefensiveTribeForUnit.cpp @@ -0,0 +1,53 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCBE0 + int AICState::getDefensiveTribeForUnit(int playerID, int unitID) + { + int index; + for (index = 0; index < 20; index++) { + if (DAT_UnitsState::instance.units[unitID].unitType + == DAT_SkirmishDefinedData::instance.DefenseTribesUnitTypeArray[index] + || DAT_UnitsState::instance.units[unitID].unitTypeToChangeInto + == DAT_SkirmishDefinedData::instance.DefenseTribesUnitTypeArray[index]) + break; + } + + int tribeIndex; + if (index < 1) + tribeIndex = 180; + else if (index < 2) + tribeIndex = 181; + else if (index < 7) + tribeIndex = 182; + else if (index < 12) + tribeIndex = 183; + else if (index < 15) + tribeIndex = 184; + else if (index < 20) + tribeIndex = 185; + else + return 0; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) + return tribeID; + + tribeID = MACRO_CALL_MEMBER(Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex] = tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex] + = DAT_TribesState::instance.tribes[tribeID].uid; + return tribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp b/src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp new file mode 100644 index 000000000..5ab4f3163 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getResourceRebuildDelay.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB150 + int AICState::getResourceRebuildDelay(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + return this->aics[aiType - 1].resourceRebuildDelay; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp b/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp new file mode 100644 index 000000000..09ce8c0d5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getSmallestPatrolTribe.cpp @@ -0,0 +1,49 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CCAF0 + int AICState::getSmallestPatrolTribe(int playerID, int tribeCount) + { + int i = 0; + int selectedTribeID = 0; + int selectedIndex = 0; + int lowestSize = 1000; + + for (;; i++) { + if (i >= tribeCount) { + if (selectedTribeID == 0) + return 0; + break; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[170 + i]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 170]) { + selectedTribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + selectedIndex = i; + break; + } + + if (DAT_TribesState::instance.tribes[tribeID].size < lowestSize) { + lowestSize = DAT_TribesState::instance.tribes[tribeID].size; + selectedTribeID = tribeID; + selectedIndex = i; + } + } + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[selectedIndex + 170] = selectedTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[selectedIndex + 170] + = DAT_TribesState::instance.tribes[selectedTribeID].uid; + return selectedTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp b/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp new file mode 100644 index 000000000..83fa94d22 --- /dev/null +++ b/src/OpenSHC/AI/AICState/getTargetableBuildingForPlayerID.cpp @@ -0,0 +1,47 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDBC0 + int AICState::getTargetableBuildingForPlayerID(int playerID, int param_2) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == 0) + return 0; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (this->aics[aicIndex].OuterPatrolGroupsCount <= 0) + return 0; + if (DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker <= 0) + return 0; + + int step = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker + / this->aics[aicIndex].OuterPatrolGroupsCount; + DAT_GameState::instance.playerDataArray[playerID].someCounter2++; + if (DAT_GameState::instance.playerDataArray[playerID].someCounter2 + >= DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker) + DAT_GameState::instance.playerDataArray[playerID].someCounter2 = 0; + int index = (step * param_2 + DAT_GameState::instance.playerDataArray[playerID].someCounter2) + % DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[index]; + index++; + if (index >= DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker) + index = 0; + if (buildingID == 0) + continue; + + for (int j = 0; j < 8; j++) { + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType + == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) + return buildingID; + } + } + return 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp b/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp new file mode 100644 index 000000000..fd1ea735f --- /dev/null +++ b/src/OpenSHC/AI/AICState/getUnitTypeForUnitBehaviourType.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC250 + UnitType AICState::getUnitTypeForUnitBehaviourType(int playerID, AIUnitBehaviourType unitBehaviourType) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return (UnitType)0; + int aicIndex = aiType - 1; + + if (unitBehaviourType == OpenSHC::AI::AIUBT_ENGINEERING) + return OpenSHC::Map::Units::UT_E_ENGINEER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_DIGGING) + return (UnitType)this->aics[aicIndex].AttDiggingUnit; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ASSASSINATION) + return OpenSHC::Map::Units::UT_A_ASSASSIN; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ATTUNIT2) + return (UnitType)this->aics[aicIndex].AttUnit2; + if (unitBehaviourType == OpenSHC::AI::AIUBT_LADDERING) + return OpenSHC::Map::Units::UT_E_LADDER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_TUNNELING) + return OpenSHC::Map::Units::UT_TUNNELER; + if (unitBehaviourType == OpenSHC::AI::AIUBT_PATROLLING) + return (UnitType)this->aics[aicIndex].AttUnitPatrol; + if (unitBehaviourType == OpenSHC::AI::AIUBT_BACKUPING) + return (UnitType)this->aics[aicIndex].AttUnitBackup; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ENGAGING) + return (UnitType)this->aics[aicIndex].AttUnitEngage; + if (unitBehaviourType == OpenSHC::AI::AIUBT_SIEGEDEFENSE) + return (UnitType)this->aics[aicIndex].AttUnitSiegeDef; + if (unitBehaviourType == OpenSHC::AI::AIUBT_ATTUNITMAIN) { + if ((&this->aics[aicIndex] + .AttUnitMain1)[DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex] + == 0) + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; + if (DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex >= 4) + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = 0; + + int choice = DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex; + UnitType unitType = (UnitType)(&this->aics[aicIndex].AttUnitMain1)[choice]; + DAT_GameState::instance.playerDataArray[playerID].aiAttUnitMainChoiceIndex = choice + 1; + return unitType; + } + + return (UnitType)this->aics[aicIndex].AttUnitMain1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp b/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp new file mode 100644 index 000000000..1c29a67df --- /dev/null +++ b/src/OpenSHC/AI/AICState/getUnitTypeIndexForUnitID.cpp @@ -0,0 +1,60 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/Map/Units/UnitTypeInt.hpp" +#include "OpenSHC/Map/Units/UnitTypeShort.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::Map::Units::UnitTypeInt; + using OpenSHC::Map::Units::UnitTypeShort; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC390 + AIVUnitType AICState::getUnitTypeIndexForUnitID(int unitID, int param_2) + { + UnitTypeShort _unitType = DAT_UnitsState::instance.units[unitID].unitType; + int _playerID = DAT_UnitsState::instance.units[unitID].owner; + UnitTypeInt _unitType2; + if (_unitType == OpenSHC::Map::Units::UT_PEASANT) { + _unitType2 = DAT_UnitsState::instance.units[unitID].unitTypeToChangeInto; + } else { + _unitType2 = _unitType; + } + + // 6 is the Caliph's AIC index: he fields slaves rather than the ranged units listed below + if (DAT_GameState::instance.playerDataArray[_playerID].aiType - 1 != 6 && param_2 != 0 + && DAT_GameState::instance.playerDataArray[_playerID].aivUnitLocationSlotLocationCount[0xd] > 0) { + if (_unitType2 == OpenSHC::Map::Units::UT_E_ARCHER) { + return OpenSHC::AI::AIVUT_SLAVE; + } + if (_unitType2 == OpenSHC::Map::Units::UT_E_XBOW) { + return OpenSHC::AI::AIVUT_SLAVE; + } + if (_unitType2 == OpenSHC::Map::Units::UT_A_ARCHER) { + return OpenSHC::AI::AIVUT_SLAVE; + } + if (_unitType2 == OpenSHC::Map::Units::UT_A_SLINGER) { + return OpenSHC::AI::AIVUT_SLAVE; + } + if (_unitType2 == OpenSHC::Map::Units::UT_A_FIRETHROWER) { + return OpenSHC::AI::AIVUT_SLAVE; + } + } + + for (int someUnitTypeIndex = 0; someUnitTypeIndex < 0x14; someUnitTypeIndex++) { + if (DAT_SkirmishDefinedData::instance.SomeAIUnitTypeArray[someUnitTypeIndex] == _unitType2) { + return (AIVUnitType)someUnitTypeIndex; + } + } + return OpenSHC::AI::AIVUT_NONE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp b/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp new file mode 100644 index 000000000..00d41635a --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveMoveCommandToAttackTribes.cpp @@ -0,0 +1,50 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEB50 + void AICState::giveMoveCommandToAttackTribes(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + int destIndex = 0; + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + if (tribeType == 18 || tribeType == 190 || tribeType == 15) + continue; + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = (char)(SEC_RNG::instance.currentNumber2 & 1); + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .xOffset, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .yOffset, + 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + destIndex++; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp b/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp new file mode 100644 index 000000000..5423d3339 --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveMoveCommandToSortieUnits.cpp @@ -0,0 +1,71 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD8F0 + void AICState::giveMoveCommandToSortieUnits(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[167]; + int aicIndex = aiType - 1; + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[167]) + return; + + if (DAT_TribesState::instance.tribes[tribeID].size + < DAT_GameState::instance.playerDataArray[playerID].totalTroopsType7 - 1) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::syncBehaviourType7UnitTribes, this)(playerID); + + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + bool sendToKeep = true; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) { + if (DAT_TribesState::instance.tribes[tribeID].size < this->aics[aicIndex].SortieUnitMeleeMin) { + sendToKeep = false; + } else { + int unitID = DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitID; + if (unitID != 0 + && DAT_GameState::instance.playerDataArray[playerID].lastEncounteredTroopUnitUID + == DAT_UnitsState::instance.units[unitID].uid + && MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func:: + calculateCanPlayerUnitsNavigateToAreaFromArea, + DAT_PathFindingState::ptr)(playerID, + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[targetUnitID].tile], + (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_UnitsState::instance.units[unitID].tile], + 0) + != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, + DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_UnitsState::instance.units[unitID].x, + DAT_UnitsState::instance.units[unitID].y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + sendToKeep = false; + } + } + } + if (sendToKeep) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp b/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp new file mode 100644 index 000000000..c094f57f1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/giveSomeRaidCommand.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF920 + undefined4 AICState::giveSomeRaidCommand(int tribeIndex, int tribeType) + { + int owner = DAT_TribesState::instance.tribes[tribeIndex].owner; + int targetBuildingID = DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID; + if (tribeType == 186 || tribeType == 18 || tribeType == 14 || tribeType == 13) + return 0; + + if (targetBuildingID != 0 + && DAT_BuildingsState::instance.buildings[targetBuildingID].uid + == DAT_TribesState::instance.tribes[tribeIndex].targetBuildingUID + && DAT_BuildingsState::instance.buildings[targetBuildingID].fireDuration == 0) + return 1; + + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID = 0; + int buildingID + = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::chooseRandomBuildingIDFromPlayersArrayOfBuildings, this)( + DAT_GameState::instance.playerDataArray[owner].attackedPlayerID); + if (buildingID == 0) + return 0; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::queueDisbandAndAttackCommand4Params, + DAT_UnitsState::ptr)(tribeIndex, 9, buildingID, DAT_BuildingsState::instance.buildings[buildingID].uid); + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingUID + = DAT_BuildingsState::instance.buildings[buildingID].uid; + DAT_TribesState::instance.tribes[tribeIndex].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + DAT_TribesState::instance.tribes[tribeIndex].targetBuildingID = buildingID; + return 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp b/src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp new file mode 100644 index 000000000..b009cbc8a --- /dev/null +++ b/src/OpenSHC/AI/AICState/hasNoTroopsOrAllAreDiggers.cpp @@ -0,0 +1,43 @@ +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFFD0 + BOOLEnum AICState::hasNoTroopsOrAllAreDiggers(int playerID) + { + int totalTroops = DAT_GameState::instance.playerDataArray[playerID].totalAssassinTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnit2Troops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitPatrolTroops; + int halfOfDiggingTroops = 0; + + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState == 6) { + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker > 16) + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops / 8; + else + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops; + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + + DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops; + } else { + halfOfDiggingTroops = DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops / 2; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker > 12) + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops / 8; + else + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops; + totalTroops += DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + + DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + + DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops; + } + + if (totalTroops <= 1) + return TRUE; + if (totalTroops <= 4 && halfOfDiggingTroops > totalTroops * 2) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp b/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp new file mode 100644 index 000000000..96e8e3c36 --- /dev/null +++ b/src/OpenSHC/AI/AICState/hasNotEnoughSupplies.cpp @@ -0,0 +1,30 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D00A0 + BOOLEnum AICState::hasNotEnoughSupplies(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].marketplace.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] + < DAT_BuildingsState::instance.buildingCosts[Map::Buildings::BT_MARKETPLACE].requiredWood + && DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount < 1) + return TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].granary.id == 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] + < DAT_BuildingsState::instance.buildingCosts[Map::Buildings::BT_GRANARY].requiredWood + && DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount <= 1) + return TRUE; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] <= 0 + && DAT_GameState::instance.playerDataArray[playerID].popularity <= 25 + && DAT_GameState::instance.playerDataArray[playerID].totalFood <= 0) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp b/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp new file mode 100644 index 000000000..95b2d1aeb --- /dev/null +++ b/src/OpenSHC/AI/AICState/ifHasTribe0SendUnitsToKeep.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2830 + void AICState::ifHasTribe0SendUnitsToKeep(int playerID) + { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0]; + if (tribeID <= 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0]) + return; + + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp b/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp new file mode 100644 index 000000000..f7d69ad60 --- /dev/null +++ b/src/OpenSHC/AI/AICState/instructTribe166ToMove.cpp @@ -0,0 +1,60 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD7B0 + void AICState::instructTribe166ToMove(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) + return; + int aicIndex = aiType - 1; + + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 > 0) + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01--; + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 > 50) + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 = 50; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[166]; + BOOLEnum keepEnclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + if (tribeID == 0) + return; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[166]) + return; + + if (DAT_TribesState::instance.tribes[tribeID].size + < DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6 - 5) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addUnitsToTribe166, this)(playerID); + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && keepEnclosed == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } else if (DAT_TribesState::instance.tribes[tribeID].size >= this->aics[aicIndex].SortieUnitRangedMin) { + if (DAT_GameState::instance.playerDataArray[playerID].someXPosition <= 0 + || DAT_GameState::instance.playerDataArray[playerID].someYPosition <= 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } else { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, DAT_GameState::instance.playerDataArray[playerID].someXPosition, + DAT_GameState::instance.playerDataArray[playerID].someYPosition, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } + } + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp b/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp new file mode 100644 index 000000000..d4224026c --- /dev/null +++ b/src/OpenSHC/AI/AICState/isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0520 + BOOLEnum AICState::isResourceLargerOrEqualThanMinimumGoodsRequiredAfterTrade( + int playerID, ResourceType resourceType, int buffer) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return TRUE; + if (resourceType == Game::Resources::RT_GOLD) + return FALSE; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[resourceType] + < this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].minimumGoodsRequiredAfterTrade + + buffer) + return FALSE; + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp b/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp new file mode 100644 index 000000000..243d4828c --- /dev/null +++ b/src/OpenSHC/AI/AICState/makeOutpostTribesAttack.cpp @@ -0,0 +1,22 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3960 + void AICState::makeOutpostTribesAttack() + { + for (int i = 0; i < 1000; i++) { + int tribeID = this->tribeIDArray[i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid != this->tribeUIDArray[i]) + continue; + + MACRO_CALL_MEMBER(AICState_Func::decideOnTribeAttackLocation, this)(tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp b/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp new file mode 100644 index 000000000..d4f23d164 --- /dev/null +++ b/src/OpenSHC/AI/AICState/makeUnitsGoDefensiveAndBackToSomeLocation.cpp @@ -0,0 +1,47 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEA50 + void AICState::makeUnitsGoDefensiveAndBackToSomeLocation(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + if (DAT_GameState::instance.playerDataArray[playerID].someTile == 0) + return; + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + int x = DAT_GameState::instance.playerDataArray[playerID].someX; + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + int y = DAT_GameState::instance.playerDataArray[playerID].someY; + uint result = MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, x, y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + if (result == 0) + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::removeAllTribeUnits, DAT_TribesState::ptr)( + tribeID); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp b/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp new file mode 100644 index 000000000..41ac2d515 --- /dev/null +++ b/src/OpenSHC/AI/AICState/mapperIsWoodCuttersHutWhileNoWoodAvailable.cpp @@ -0,0 +1,19 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECAB0 + BOOLEnum AICState::mapperIsWoodCuttersHutWhileNoWoodAvailable( + undefined4 playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType == Commands::M_MAPPER_WOODSMAN + && DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) + return TRUE; + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp b/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp new file mode 100644 index 000000000..ee8e3ad57 --- /dev/null +++ b/src/OpenSHC/AI/AICState/moveAttackTribesToLocations.cpp @@ -0,0 +1,47 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE830 + void AICState::moveAttackTribesToLocations(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + int destIndex = 0; + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeType + j]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[j + tribeType]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .xOffset, + DAT_GameState::instance.mapAndTime.aiTribeMoveDestinationXYPairArray1[playerID * 5][destIndex] + .yOffset, + 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + destIndex++; + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp b/src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp new file mode 100644 index 000000000..04aa9c19d --- /dev/null +++ b/src/OpenSHC/AI/AICState/moveAttackingUnitsToNextRallyPoint.cpp @@ -0,0 +1,71 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2F80 + void AICState::moveAttackingUnitsToNextRallyPoint(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + + for (int i = 0; i < 2; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[190 + i]; + if (tribeID == 0) + continue; + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 190]) + continue; + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::allUnitsReachedTheirDestination, + DAT_TribesState::ptr)(tribeID) + == FALSE) + continue; + + DAT_TribesState::instance.tribes[tribeID].unknownAttackRelatedUpdateCounter = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addRallyPointForTribe, this)(tribeID); + + short rallyPointCount = DAT_TribesState::instance.tribes[tribeID].rallyPointCount; + int rallyPointIndex = DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex + 1; + if (rallyPointIndex >= rallyPointCount) + rallyPointIndex = 0; + + int x = DAT_TribesState::instance.tribes[tribeID].rallyPointArray[rallyPointIndex][0]; + int y = DAT_TribesState::instance.tribes[tribeID].rallyPointArray[rallyPointIndex][1]; + int targetUnitID = DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + DAT_TribesState::instance.tribes[tribeID].currentRallyPointIndex = rallyPointIndex; + + if (rallyPointCount <= 0) + continue; + + if (rallyPointCount <= 1) { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, x, y); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= 5) + continue; + } else { + if (DAT_UnitsState::instance.units[targetUnitID].closestEnemyMicroDistance <= 96) + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, x, y, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp b/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp new file mode 100644 index 000000000..a7af36538 --- /dev/null +++ b/src/OpenSHC/AI/AICState/percentageNonMovingTribesGTEAICSpecified.cpp @@ -0,0 +1,59 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CED90 + BOOLEnum AICState::percentageNonMovingTribesGTEAICSpecified(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return FALSE; + } + + int aicIndex = aiType - 1; + int tribeTotal = 0; + int notReallyMoving = 0; + + for (int i = 0; i < 11; i++) { + int tribeIndex = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex + j]) { + continue; + } + + tribeTotal++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) { + notReallyMoving++; + } + } + } + + if (tribeTotal <= 0) { + return FALSE; + } + + return notReallyMoving * 100 / tribeTotal >= this->aics[aicIndex].AttMovingTribesThreshold; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp new file mode 100644 index 000000000..ccf876a64 --- /dev/null +++ b/src/OpenSHC/AI/AICState/planToBuyWhenLowOnResourceAndSnoozeBuildings.cpp @@ -0,0 +1,226 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using namespace OpenSHC::Game::Resources; + using namespace OpenSHC::Map::Buildings; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CBA50 + void AICState::planToBuyWhenLowOnResourceAndSnoozeBuildings(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int allowIronMines = 1; + int allowArmourers = 1; + int allowFletchers = 1; + int allowPitchRigs = 1; + int allowWoodcutters = 1; + int allowPoleturners = 1; + int allowBlacksmiths = 1; + int allowTanners = 1; + int allowQuarries = 1; + int allowBakeries = 1; + int allowBreweries = 1; + int allowInns = 1; + int allowFarms = 1; + int allowWells = 1; + + int buildingBurning = MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::playerHasBurningBuilding, DAT_BuildingsState::ptr)(playerID); + int wood = DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_WOOD]; + + if (wood <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_IRON] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_FLOUR] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_HOPS] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_ALE] <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk++; + } else { + DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk = 0; + } + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter++; + + if (DAT_GameState::instance.playerDataArray[playerID].countFletchersPoleturners <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noWoodTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countArmorersAndBlacksmiths <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noIronTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countBakers <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noFlourTracker = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].countBrewers <= 0) { + DAT_GameState::instance.playerDataArray[playerID].noHopsTracker = 0; + } + + int population = DAT_GameState::instance.playerDataArray[playerID].currentPopulation; + if (population < 10 + && (DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers != 0 + || DAT_GameState::instance.playerDataArray[playerID].countWoodcutters > 2)) { + allowQuarries = 0; + } + + if (wood >= 20 && population < 10 && DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalFood < 8) { + allowWoodcutters = 0; + } + + if (buildingBurning == 0 + && DAT_GameState::instance.playerDataArray[playerID].farmsWithoutWorkers + >= DAT_GameState::instance.playerDataArray[playerID].countFarms + && DAT_GameState::instance.playerDataArray[playerID].currentResources[RT_GOLD] < 500) { + allowWells = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowFletchers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36) { + allowFletchers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowPoleturners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36) { + allowPoleturners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowBlacksmiths = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36) { + allowBlacksmiths = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowArmourers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36) { + allowArmourers = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].armory.id == 0) { + allowTanners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].someResourceCounter > 36) { + allowTanners = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noFlourTracker > 36) { + allowBakeries = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noHopsTracker > 72) { + allowBreweries = 0; + } + if (DAT_GameState::instance.playerDataArray[playerID].noBeerCounterUnk > 72) { + allowInns = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + allowFletchers = 1; + allowIronMines = 0; + allowQuarries = 0; + allowPitchRigs = 0; + allowFarms = 0; + allowWoodcutters = 0; + allowPoleturners = 1; + allowBlacksmiths = 1; + allowArmourers = 1; + allowTanners = 1; + allowBakeries = 0; + allowBreweries = 0; + allowInns = 0; + } + + if (allowWells) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WELL] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WATERPOT] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WELL] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WATERPOT] = true; + } + + if (allowQuarries) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_QUARRY] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_OXTETHER] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_QUARRY] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_OXTETHER] = true; + } + + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_PITCHRIG] = !allowPitchRigs; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_POLETURNER] = !allowPoleturners; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_FLETCHER] = !allowFletchers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_ARMOURER] = !allowArmourers; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BLACKSMITH] = !allowBlacksmiths; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_TANNER] = !allowTanners; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WOODCUTTERSHUT] = !allowWoodcutters; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BAKERY] = !allowBakeries; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_BREWERY] = !allowBreweries; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_INN] = !allowInns; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_IRONMINE] = !allowIronMines; + + if (allowFarms) { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WHEATFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HOPFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_APPLEFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_DAIRYFARM] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HUNTERSHUT] = false; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_MILL] = false; + } else { + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_WHEATFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HOPFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_APPLEFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_DAIRYFARM] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_HUNTERSHUT] = true; + DAT_GameState::instance.playerDataArray[playerID].snoozedBuildings[BT_MILL] = true; + } + + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0) { + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].noWoodTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_WOOD] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_WOOD] = 5; + } + if (DAT_GameState::instance.playerDataArray[playerID].noIronTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_IRON] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_IRON] = 2; + } + if (DAT_GameState::instance.playerDataArray[playerID].noFlourTracker > 36 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_FLOUR] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_FLOUR] = 2; + } + if (DAT_GameState::instance.playerDataArray[playerID].noHopsTracker > 72 + && DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_HOPS] == 0) { + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[RT_HOPS] = 2; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp new file mode 100644 index 000000000..c2f18af59 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playAnger1BikFromPlayer.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" +#include "OpenSHC/Globals/INT_ARRAY_00eb0af0.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0830 + void AICState::playAnger1BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + DWORD now = timeGetTime(); + if (INT_ARRAY_00eb0af0::instance[playerID] != 0 && now - INT_ARRAY_00eb0af0::instance[playerID] < 600000) { + return; + } + + INT_ARRAY_00eb0af0::instance[playerID] = now; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, 5); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..df0b308ad --- /dev/null +++ b/src/OpenSHC/AI/AICState/playAnger2BikFromPlayerToPlayer.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0C00 + void AICState::playAnger2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown3 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown3 = 96; + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 6); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp new file mode 100644 index 000000000..64249dc9c --- /dev/null +++ b/src/OpenSHC/AI/AICState/playFriendlyVictoryBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0FC0 + void AICState::playFriendlyVictoryBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 10); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp new file mode 100644 index 000000000..ca55ee528 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsNotSentBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0B70 + void AICState::playGoodsNotSentBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 28); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..acb064856 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsRequestBikFromPlayerToPlayer.cpp @@ -0,0 +1,32 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0A20 + void AICState::playGoodsRequestBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 14); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..7b807ddfc --- /dev/null +++ b/src/OpenSHC/AI/AICState/playGoodsSentBikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0BB0 + void AICState::playGoodsSentBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 29); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp b/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp new file mode 100644 index 000000000..639186e16 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playJokeBikFromAIToHuman.cpp @@ -0,0 +1,22 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0F90 + void AICState::playJokeBikFromAIToHuman(int playerID, int aiType) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aiType, 20); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp new file mode 100644 index 000000000..0bdd24ea6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNervous1BikFromPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0CF0 + void AICState::playNervous1BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown5 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown5 = 48; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 8); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp new file mode 100644 index 000000000..403a03302 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNervous2BikFromPlayer.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0B10 + void AICState::playNervous2BikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].nervousBikCountdown != 0) { + return; + } + + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + int currentPlayerID = DAT_GameSynchronyState::instance.currentPlayerSlotID; + DAT_GameState::instance.playerDataArray[playerID].nervousBikCountdown = 48; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[currentPlayerID]) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aiType - 1, 9); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp new file mode 100644 index 000000000..07b38053e --- /dev/null +++ b/src/OpenSHC/AI/AICState/playNoHelpBikFromPlayer.cpp @@ -0,0 +1,23 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1050 + void AICState::playNoHelpBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 31); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp b/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp new file mode 100644 index 000000000..a652216ca --- /dev/null +++ b/src/OpenSHC/AI/AICState/playPlayerKilledBiksFromKilledAndKillerPlayer.cpp @@ -0,0 +1,73 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D08B0 + void AICState::playPlayerKilledBiksFromKilledAndKillerPlayer(int killerPlayerID, int killedPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(killerPlayerID) + == 0) { + return; + } + + if (killedPlayerID == DAT_GameSynchronyState::instance.currentPlayerSlotID) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 12); + return; + } + + if (killerPlayerID == DAT_GameSynchronyState::instance.currentPlayerSlotID) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.playerDataArray[i].aiType == AITA_NULL) { + continue; + } + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + i, DAT_GameState::instance.playerDataArray[i].aiType - 1, 17); + } + return; + } + + if (DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID] + == DAT_GameState::instance.mapAndTime.playerTeams[killedPlayerID]) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 16); + return; + } + + if (DAT_GameState::instance.mapAndTime.playerTeams[killerPlayerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 18); + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killedPlayerID, DAT_GameState::instance.playerDataArray[killedPlayerID].aiType - 1, 7); + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + killerPlayerID, DAT_GameState::instance.playerDataArray[killerPlayerID].aiType - 1, 13); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp new file mode 100644 index 000000000..5f1cee729 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playRequestHelpBikFromPlayer.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0D40 + void AICState::playRequestHelpBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].someAiCountdown6 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].someAiCountdown6 = 48; + + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + != DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 19); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp new file mode 100644 index 000000000..f49839574 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playTauntToAttackedPlayerBikFromPlayer.cpp @@ -0,0 +1,41 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D07C0 + void AICState::playTauntToAttackedPlayerBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int bik = 0; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + == DAT_GameState::instance.mapAndTime.playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID]) { + bik = 23; + } + + if (DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + if (bik == 0) { + bik = (SEC_RNG::instance.currentNumber2 & 3) + 1; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, bik); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp b/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp new file mode 100644 index 000000000..429518496 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playTeamWinningBikFromPlayer.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1000 + void AICState::playTeamWinningBikFromPlayer(int playerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker != 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker != 0) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 30); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..823e435e9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playThanksBikFromPlayerToPlayer.cpp @@ -0,0 +1,43 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" +#include "OpenSHC/Globals/DWORD_00eb0b18.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0A80 + void AICState::playThanksBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + DAT_GameState::instance.playerDataArray[playerID].aiRequestDelay = this->aics[aicIndex].aiRequestDelay; + + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + if (DAT_GameState::instance.playerDataArray[DAT_GameSynchronyState::instance.currentPlayerSlotID] + .lordKilledByPlayerID + != 0) { + return; + } + + DWORD now = timeGetTime(); + if (DWORD_00eb0b18::instance != 0 && now - DWORD_00eb0b18::instance < 1200000) { + return; + } + + DWORD_00eb0b18::instance = now; + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, aicIndex, 15); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..0d11e93d4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playVictory2BikFromPlayerToPlayer.cpp @@ -0,0 +1,43 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0C70 + void AICState::playVictory2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (DAT_GameState::instance.playerDataArray[targetPlayerID].aiType == AITA_NULL) { + return; + } + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID] == -1) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(targetPlayerID) + == 0) { + return; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[targetPlayerID].someAiCountdown4 != 0) { + return; + } + + DAT_GameState::instance.playerDataArray[targetPlayerID].someAiCountdown4 = 48; + if (playerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + targetPlayerID, DAT_GameState::instance.playerDataArray[targetPlayerID].aiType - 1, 11); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playWillAttackBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillAttackBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..6898bcfdf --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillAttackBikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0F40 + void AICState::playWillAttackBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x21); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playWillHelpBikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillHelpBikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..1cca496c9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillHelpBikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0E50 + void AICState::playWillHelpBikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x20); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playWillNotAttack1BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillNotAttack1BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..a89b2faeb --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillNotAttack1BikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0EA0 + void AICState::playWillNotAttack1BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x18); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playWillNotAttack2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillNotAttack2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..7de2373fd --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillNotAttack2BikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0EF0 + void AICState::playWillNotAttack2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x19); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playWillNotHelp1BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillNotHelp1BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..5c7ab00f7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillNotHelp1BikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0DB0 + void AICState::playWillNotHelp1BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x1a); + } +} +} diff --git a/src/OpenSHC/AI/AICState/playWillNotHelp2BikFromPlayerToPlayer.cpp b/src/OpenSHC/AI/AICState/playWillNotHelp2BikFromPlayerToPlayer.cpp new file mode 100644 index 000000000..164826d3f --- /dev/null +++ b/src/OpenSHC/AI/AICState/playWillNotHelp2BikFromPlayerToPlayer.cpp @@ -0,0 +1,27 @@ +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/Rendering/Bink/AIMessageQueue.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" +#include "OpenSHC/Globals/DAT_VideoBikQueue.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0E00 + void AICState::playWillNotHelp2BikFromPlayerToPlayer(int playerID, int targetPlayerID) + { + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(playerID) == 0) { + return; + } + if (targetPlayerID != DAT_GameSynchronyState::instance.currentPlayerSlotID) { + return; + } + + MACRO_CALL_MEMBER(Rendering::Bink::AIMessageQueue_Func::playBikVideoFromPlayer, DAT_VideoBikQueue::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].aiType - 1, 0x1b); + } +} +} diff --git a/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp b/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp new file mode 100644 index 000000000..302c38a63 --- /dev/null +++ b/src/OpenSHC/AI/AICState/processAttForceRallyPercentage.cpp @@ -0,0 +1,68 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Tribes/AITribeIndex.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEEA0 + BOOLEnum AICState::processAttForceRallyPercentage(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return FALSE; + } + + int aicIndex = aiType - 1; + int tribeTotal = 0; + int notReallyMoving = 0; + + for (int i = 0; i < 11; i++) { + int tribeIndex = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + if (tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_BASIC_07_01 + || tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_10_01 + || tribeIndex == OpenSHC::AI::Tribes::AITI_BREACH_BASIC_05_01) { + continue; + } + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex + j]) { + continue; + } + + tribeTotal++; + if (DAT_TribesState::instance.tribes[tribeID].percentageMovingUnk < 20) { + notReallyMoving++; + } + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_STAND_GROUND; + } + } + + if (tribeTotal <= 0) { + return FALSE; + } + + return (BOOLEnum)(notReallyMoving * 100 / tribeTotal >= this->aics[aicIndex].AttForceRallyPercentage); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp b/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp new file mode 100644 index 000000000..4b0108c3d --- /dev/null +++ b/src/OpenSHC/AI/AICState/randomlySelectAttackUnitTypeToRecruit.cpp @@ -0,0 +1,138 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIUnitBehaviourType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC070 + AIUnitBehaviourType AICState::randomlySelectAttackUnitTypeToRecruit(int playerID) + { + AITypeInt aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + int aicIndex = aiType - 1; + int requiredCount = 0; + + // clear out flags + for (int i = 0; i < 11; i++) { + DAT_SkirmishDefinedData::instance.attackUnitRequired[i].required = 0; + } + + int engineersCount = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 4; + if (engineersCount > (int)this->aics[aicIndex].AttMaxEngineers) { + engineersCount = this->aics[aicIndex].AttMaxEngineers; + } + if (this->aics[aicIndex].AttMaxEngineers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops < engineersCount) { + requiredCount = 1; + DAT_SkirmishDefinedData::instance.attackUnitRequired[0].required = 1; + } + + if (this->aics[aicIndex].AttDiggingUnitMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalDiggingUnitTroops + < (int)this->aics[aicIndex].AttDiggingUnitMax + && DAT_GameState::instance.playerDataArray[attackedPlayerID].moatsOwned > 5) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[1].required = 1; + } + + if (this->aics[aicIndex].AttMaxAssassins != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalAssassinTroops + < (int)this->aics[aicIndex].AttMaxAssassins) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[2].required = 1; + } + + if (this->aics[aicIndex].AttUnit2Max != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnit2Troops + < (int)this->aics[aicIndex].AttUnit2Max) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[3].required = 1; + } + + if (this->aics[aicIndex].AttMaxLaddermen != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalLaddermenTroops + < (int)this->aics[aicIndex].AttMaxLaddermen) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[4].required = 1; + } + + if (this->aics[aicIndex].AttMaxTunnelers != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalTunnelerTroops + < (int)this->aics[aicIndex].AttMaxTunnelers) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[5].required = 1; + } + + if (this->aics[aicIndex].AttUnitPatrolMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitPatrolTroops + < (int)this->aics[aicIndex].AttUnitPatrolMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[6].required = 1; + } + + if (this->aics[aicIndex].AttUnitBackupMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitBackupTroops + < (int)this->aics[aicIndex].AttUnitBackupMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[7].required = 1; + } + + if (this->aics[aicIndex].AttUnitEngageMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitEngageTroops + < (int)this->aics[aicIndex].AttUnitEngageMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[8].required = 1; + } + + if (this->aics[aicIndex].AttUnitSiegeDefMax != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalUnitSiegeDefTroops + < (int)this->aics[aicIndex].AttUnitSiegeDefMax) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[9].required = 1; + } + + if (this->aics[aicIndex].AttMaxDefault != 0 + && DAT_GameState::instance.playerDataArray[playerID].totalMaxDefaultTroops + < (int)this->aics[aicIndex].AttMaxDefault) { + requiredCount++; + DAT_SkirmishDefinedData::instance.attackUnitRequired[10].required = 1; + } + + if (requiredCount <= 0) { + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + + int selection = (int)SEC_RNG::instance.currentNumber2 % requiredCount; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + for (int i = 0; i < 11; i++) { + if (DAT_SkirmishDefinedData::instance.attackUnitRequired[i].required == 0) { + continue; + } + if (selection == 0) { + return (AIUnitBehaviourType)DAT_SkirmishDefinedData::instance.attackUnitRequired[i].unitBehaviourType; + } + selection--; + } + + return OpenSHC::AI::AIUBT_ATTUNITMAIN; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp b/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp new file mode 100644 index 000000000..5fae78c3f --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAIPathCostToAttackedKeep.cpp @@ -0,0 +1,59 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE040 + void AICState::recomputeAIPathCostToAttackedKeep(int playerID, int useBorderDistance) + { + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + int keepArea = (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_GameState::instance.playerDataArray[playerID].campground.tileEntry]; + + int borderDistance; + if (DAT_GameState::instance.playerDataArray[attackedPlayerID].unknownPathBorderDistanceRelated > 120) { + borderDistance = 98; + } else { + borderDistance + = DAT_GameState::instance.playerDataArray[attackedPlayerID].unknownPathBorderDistanceRelated > 90 ? 70 + : 50; + } + if (useBorderDistance == 0) { + borderDistance = 0; + } + + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = attackedPlayerID; + + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, DAT_GameState::ptr)( + playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID) + != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 1, 1); + } else { + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, 0, 1); + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(110, borderDistance, keepArea, playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp b/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp new file mode 100644 index 000000000..2478afb94 --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAIZonerLayer.cpp @@ -0,0 +1,31 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE1A0 + void AICState::recomputeAIZonerLayer() + { + DAT_TroopValueState::instance.attackInfo.playerInfo[0].currentPlayerID_OR_attackedPlayerID_OR_one = 1; + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, DAT_PathFindingState::ptr)(1, 2, 1); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(252, 0, 1, 1); + DAT_GameState::instance.playerDataArray[1].unknownPathBorderDistanceRelated + = DAT_PathFindingState::instance.distance; + DAT_TroopValueState::instance.attackInfo.playerInfo[0].currentPlayerID_OR_attackedPlayerID_OR_one = 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp b/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp new file mode 100644 index 000000000..905549bec --- /dev/null +++ b/src/OpenSHC/AI/AICState/recomputeAttackAIZone.cpp @@ -0,0 +1,48 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/AttackInfoSubElement.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Map::AttackInfoSubElement; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE110 + void AICState::recomputeAttackAIZone() + { + for (int playerID = 1; playerID < 9; playerID++) { + int fullID = DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID]; + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = playerID; + if (fullID == -1 + && DAT_GameSynchronyState::instance.currentAIArray[playerID] == 0 + // fixme: this comparison looks dumb since fullID is already known to be the value, so a cmp eax eax + // is generated, but we need it for the 100% match. + && fullID == DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID]) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 80400, '\0', DAT_TileMapState::instance.AIZoneLayer); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::computeAIZoneLayer, + DAT_PathFindingState::ptr)(playerID, 2, 1); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::updateWalkLayerAndAIPathCostLayer, + DAT_PathFindingState::ptr)(252, 0, 1, playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownPathBorderDistanceRelated + = DAT_PathFindingState::instance.distance; + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].currentPlayerID_OR_attackedPlayerID_OR_one + = 1; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp b/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp new file mode 100644 index 000000000..ea0aff1d7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/recreateEngineersTribe.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4630 + void AICState::recreateEngineersTribe(int playerID) + { + if (0 >= DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops) { + return; + } + int _tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12]; + if (_tribeID != 0) { + if (DAT_TribesState::instance.tribes[_tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0x12]) { + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12] = 0; + } + } + MACRO_CALL_MEMBER(AICState_Func::addEngineersToTribe, this)(playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp new file mode 100644 index 000000000..d72a3a55f --- /dev/null +++ b/src/OpenSHC/AI/AICState/recruitHarrassingSiegeEngines.cpp @@ -0,0 +1,162 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIUnitBehaviourType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/Unit.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" +#include "OpenSHC/Map/Units/UnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Buildings::BuildingType; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::Unit; + using OpenSHC::Map::Units::UnitInstructionType; + using OpenSHC::Map::Units::UnitLogicState; + using OpenSHC::Map::Units::UnitType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD2A0 + void AICState::recruitHarrassingSiegeEngines(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + int engineersGuild = DAT_GameState::instance.playerDataArray[playerID].engineersGuild.id; + + if (aiType == 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] + < 500) { + return; + } + + int aicIndex = aiType - 1; + int harrassingSiegeEnginesMax = this->aics[aicIndex].HarassingSiegeEnginesMax; + if (harrassingSiegeEnginesMax <= 0) { + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout != 0) { + DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout -= 1; + return; + } + + if (DAT_GameState::instance.playerDataArray[playerID].harassingSiegeEnginesCountUnk + >= harrassingSiegeEnginesMax) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].unknownHarrassingSiegeRelated >= 20) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + || DAT_TribesState::instance.tribes[tribeID].size < 2) { + // Not enough engineers yet: recruit one at the engineers guild + if (DAT_GameState::instance.playerDataArray[playerID].canStartSpending == 0 + || DAT_GameState::instance.playerDataArray[playerID].currentResources[OpenSHC::Game::Resources::RT_GOLD] + < 30 + || engineersGuild <= 0 + || DAT_BuildingsState::instance.buildings[engineersGuild].buildingType + != OpenSHC::Map::Buildings::BT_ENGINEERSGUILD) { + return; + } + + uint unitID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::nonEuroRecruit, DAT_UnitsState::ptr)( + OpenSHC::Map::Units::UT_E_ENGINEER, engineersGuild, playerID, 0); + if (unitID == 0) { + return; + } + + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165]) { + tribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + } + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[165] = (short)tribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[165] + = DAT_TribesState::instance.tribes[tribeID].uid; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + unitID, tribeID); + DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType = OpenSHC::AI::AIUBT_HARASS_SIEGE; + } else { + // Enough engineers: pick the next siege engine from the AIC list and place its tent + if (DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex >= 8) { + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = 0; + } + if ((&this->aics[aicIndex].HarassingSiegeEngine1)[DAT_GameState::instance.playerDataArray[playerID] + .harrassingSiegeEnginesIndex] + == 0) { + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = 0; + } + + int engineIndex = DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex; + MappersEnum siegeEngineToBuild = (MappersEnum)(&this->aics[aicIndex].HarassingSiegeEngine1)[engineIndex]; + DAT_GameState::instance.playerDataArray[playerID].harrassingSiegeEnginesIndex = engineIndex + 1; + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AIVState_Func::findSpotNearEngineersGuild, DAT_AIVState::ptr)(playerID) + == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + + // Place the siege tent + MACRO_CALL_MEMBER(OpenSHC::Map::TileMapState_Func::placeBuilding, DAT_TileMapState::ptr)(playerID, + DAT_AIVState::instance.buildingApproriateGridXPosition * 5, + DAT_AIVState::instance.buildingAppropriateGridYPosition * 5, siegeEngineToBuild, 3, 15); + if (DAT_TileMapState::instance.buildingPlacementFail != FALSE) { + return; + } + + int buildingID = DAT_TileMapState::instance.placedBuildingID; + DAT_BuildingsState::instance.buildings[buildingID].attackWave = 0; + DAT_BuildingsState::instance.buildings[buildingID].unknownSiegeTentRelated01 = 2; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeAnInstruction, DAT_TribesState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, buildingID, + DAT_BuildingsState::instance.buildings[buildingID].uid, 0); + } + + // Point all harassing siege engines at the currently attacked player + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].logicalState != OpenSHC::Map::Units::ULS_INVISIBLE + && DAT_UnitsState::instance.units[unitID].dying == 0 + && (DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_CATAPULT + || DAT_UnitsState::instance.units[unitID].unitType == OpenSHC::Map::Units::UT_S_FBALLISTA) + && DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType == OpenSHC::AI::AIUBT_SIEGE_01) { + DAT_UnitsState::instance.units[unitID].siegeTargetPlayerID + = (short)DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + } + } + + DAT_GameState::instance.playerDataArray[playerID].harrasingEngineRecruitTimeout = 8; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp b/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp new file mode 100644 index 000000000..8d50e0976 --- /dev/null +++ b/src/OpenSHC/AI/AICState/removeOrganismsAndSetMoveDestinationPairs.cpp @@ -0,0 +1,49 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE430 + void AICState::removeOrganismsAndSetMoveDestinationPairs(int playerID) + { + // Clear a tree standing on the target tile + int organismID + = (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]; + if (organismID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)(organismID); + } + + // Hardcoded exception: the Sultan keeps a smaller distance than all other AIs + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + int extraDistance = aicIndex == OpenSHC::AI::AIT_SULTAN - 1 ? 8 : 20; + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, -1, -1, 10000, FALSE); + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::setMoveDestinationPairs, + DAT_PathFindingState::ptr)(playerID - 1, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, + DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].someMinimumDistance + extraDistance); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp new file mode 100644 index 000000000..7e3fe3be9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/renderDebugDataAiInfo.cpp @@ -0,0 +1,144 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" +#include "OpenSHC/Text/TextManager.func.hpp" +#include "OpenSHC/AI/AIC/AICSpecification.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AIStrengthType.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" +#include "OpenSHC/Text/TextAlignment.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BlendingDefinedData.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_RenderingDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TextManagerObject.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AICState; + using OpenSHC::AI::AIStrengthType; + using OpenSHC::AI::AIType; + using OpenSHC::AI::AIC::AICSpecification; + using OpenSHC::Game::Player::PlayerData; + using OpenSHC::Text::TextAlignment; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1320 + void AICState::renderDebugDataAiInfo(int x, int y, int width, int height) + { + int playerID = 1; + char text[1000]; + + // One row of 24 pixels per player, starting one row below y + y += 24; + for (; playerID <= 8; playerID++, y += 24) { + if (DAT_GameState::instance.playerDataArray[playerID].playerDeathRelated != 0) { + continue; + } + if (DAT_GameState::instance.playerDataArray[playerID].lordKilledByPlayerID != 0) { + continue; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL + && DAT_GameSynchronyState::instance.currentPlayerFullIDArray[playerID] == -1) { + continue; + } + + uint color = DAT_RenderingDefinedData::instance + .ColorArray[DAT_BlendingDefinedData::instance.PlayerSlotUnitColor[playerID]]; + + // Player number and name + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderNumberToScreen2, DAT_TextManagerObject::ptr)( + playerID, x + 6, y, OpenSHC::Text::TTA_LEFT, 0xc2f0eb, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderTextToScreen, DAT_TextManagerObject::ptr)( + "-", x + 16, y, OpenSHC::Text::TTA_LEFT, 0xc2f0eb, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + DAT_GameSynchronyState::instance.DAT_PlayerNames[playerID], x + 26, y + 1, OpenSHC::Text::TTA_LEFT, + color, 0, 18, FALSE, 0); + + // Strength feeling + if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling == OpenSHC::AI::AIST_WEAK) { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Weak", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling + == OpenSHC::AI::AIST_DEFAULT) { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Normal", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } else { + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)("Strong", x + 202, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + + // Current AIV pause + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + "P:", x + 262, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderNumber2, DAT_TextManagerObject::ptr)( + DAT_GameState::instance.playerDataArray[playerID].aivCurrentPause, x + 264, y, OpenSHC::Text::TTA_LEFT, + color, 0, 18, TRUE, 0); + + // Attack target and coordination level + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Atk:%d W:%d", + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID, + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 302, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // fixme: Castle (AIV); the castle ID is passed even for human players, whose format ignores it + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "AIV:n/a", + DAT_AIVState::instance.aivs[DAT_GameState::instance.playerDataArray[playerID].aivID].castleID + 1); + } else { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "AIV: %d", + DAT_AIVState::instance.aivs[DAT_GameState::instance.playerDataArray[playerID].aivID].castleID + 1); + } + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 382, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Engineers + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "E:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalTroopsType6, + DAT_GameState::instance.playerDataArray[playerID].unknownCounter_01 + + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].SortieUnitRangedMin); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 452, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Siege troops (attack troops without engineers): attack ticker in white when not attacking, + // otherwise the attack force target + int siegeTroops = DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + - DAT_GameState::instance.playerDataArray[playerID].totalAttackingEngineerTroops; + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState != 0) { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)( + text, "%d/%d", siegeTroops, DAT_GameState::instance.playerDataArray[playerID].attackTicker); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)(text, x + 512, y, OpenSHC::Text::TTA_LEFT, 0xffffff, 0, 18, FALSE, 0); + } else { + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Sg:%d/%d", siegeTroops, + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].AttForceBase); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, + DAT_TextManagerObject::ptr)(text, x + 512, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + + // Raiding/harassing troops + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Hr:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk, + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)( + (DAT_GameState::instance.playerDataArray[playerID].aiType - 1), playerID)); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 592, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + + // Defensive troops + MACRO_CALL(OpenSHC::OS_Func::_sprintf)(text, "Df:%d/%d", + DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk, + this->aics[(DAT_GameState::instance.playerDataArray[playerID].aiType - 1)].DefWalls); + MACRO_CALL_MEMBER(OpenSHC::Text::TextManager_Func::renderInGameTextWithShadow, DAT_TextManagerObject::ptr)( + text, x + 657, y, OpenSHC::Text::TTA_LEFT, color, 0, 18, FALSE, 0); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/requestGoods.cpp b/src/OpenSHC/AI/AICState/requestGoods.cpp new file mode 100644 index 000000000..2c10772ed --- /dev/null +++ b/src/OpenSHC/AI/AICState/requestGoods.cpp @@ -0,0 +1,70 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::Game::Resources::ResourceType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1F00 + void AICState::requestGoods(int playerID, ResourceType resourceType, int amount) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiRequestDelay != 0) { + return; + } + // Never request raw farm goods or ale + if (resourceType == OpenSHC::Game::Resources::RT_WHEAT || resourceType == OpenSHC::Game::Resources::RT_HOPS + || resourceType == OpenSHC::Game::Resources::RT_ALE) { + return; + } + // Only request food when almost out of it + if ((resourceType == OpenSHC::Game::Resources::RT_BREAD || resourceType == OpenSHC::Game::Resources::RT_CHEESE + || resourceType == OpenSHC::Game::Resources::RT_MEAT + || resourceType == OpenSHC::Game::Resources::RT_APPLE) + && DAT_GameState::instance.playerDataArray[playerID].totalFood >= 5) { + return; + } + // fixme: NOTE: the original performs the exact same food check a second time, it cannot be removed + if ((resourceType == OpenSHC::Game::Resources::RT_BREAD || resourceType == OpenSHC::Game::Resources::RT_CHEESE + || resourceType == OpenSHC::Game::Resources::RT_MEAT + || resourceType == OpenSHC::Game::Resources::RT_APPLE) + && DAT_GameState::instance.playerDataArray[playerID].totalFood >= 5) { + return; + } + + // Ask every human teammate that has no pending request from this player + for (int i = 1; i < 9; i++) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] == -1) { + continue; + } + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + != DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + if (DAT_GameState::instance.playerDataArray[i].requestedGoodsArray1Unk[playerID] != 0) { + continue; + } + + DAT_GameState::instance.playerDataArray[i].requestedGoodsArray1Unk[playerID] = resourceType; + DAT_GameState::instance.playerDataArray[i].requestedGoodsArray2Unk[playerID] = amount; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playGoodsRequestBikFromPlayerToPlayer, this)(playerID, i); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/selectAttackTarget.cpp b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp new file mode 100644 index 000000000..cacb4823f --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectAttackTarget.cpp @@ -0,0 +1,143 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AIC/AITargetChoiceInt.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIC::AITargetChoice; + using OpenSHC::AI::AIC::AITargetChoiceInt; + using OpenSHC::Game::Player::PlayerData; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4680 + void AICState::selectAttackTarget(int playerID) + { + int selectedPlayerID = 0; + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + int fromX = DAT_GameState::instance.playerDataArray[playerID].campground.xEntry; + int fromY = DAT_GameState::instance.playerDataArray[playerID].campground.yEntry; + int lowestDistance = 10000; + int highestGold = 0; + int lowestPoints = 1000000; + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeNervousness, this)(playerID); + + // Drop a pending request if the asking player's lord is dead + if ((DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + || DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk) + == 0) { + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } + + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2) { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = 0; + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 1) { + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk) + != 0) { + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID + = DAT_GameState::instance.playerDataArray[playerID].requestedAttackTargetUnk; + return; + } + DAT_GameState::instance.playerDataArray[playerID].requestStateUnk = 0; + } + + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(i) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[i].campground.xEntry, + DAT_GameState::instance.playerDataArray[i].campground.yEntry, fromX, fromY); + + int targetChoice = this->aics[aicIndex].TargetChoice; + if (targetChoice == OpenSHC::AI::AIC::AITC_PLAYER) { + // Closest human player + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_CLOSEST) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_GOLD) { + if (DAT_GameState::instance.playerDataArray[i].currentResources[0xf] >= highestGold) { + highestGold = DAT_GameState::instance.playerDataArray[i].currentResources[0xf]; + selectedPlayerID = i; + } + } else if (targetChoice == OpenSHC::AI::AIC::AITC_BALANCED) { + int points = DAT_GameState::instance.playerDataArray[i].currentPopulation * 5 + + DAT_GameState::instance.playerDataArray[i].totalTroopValue + + DAT_GameState::instance.playerDataArray[i].currentResources[0xf] / 100 + + DAT_DirectionAlgorithmState::instance.distanceHigh * 2; + if (points <= lowestPoints) { + lowestPoints = points; + selectedPlayerID = i; + } + } + } + + // Fall back to the closest enemy + if (selectedPlayerID == 0) { + for (int i = 1; i < 9; i++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID]) { + continue; + } + if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + i) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[i].campground.xEntry, + DAT_GameState::instance.playerDataArray[i].campground.yEntry, fromX, fromY); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= lowestDistance) { + lowestDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + selectedPlayerID = i; + } + } + + if (selectedPlayerID == 0) { + return; + } + } + + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk = selectedPlayerID; + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID = selectedPlayerID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp b/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp new file mode 100644 index 000000000..11008c453 --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectBuildingFromAListOfBuildingTypes.cpp @@ -0,0 +1,35 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDAB0 + int AICState::selectBuildingFromAListOfBuildingTypes(int playerID) + { + // Pick the targetable building whose type comes first in BuildingTargetPrioritySet1 + int selectedBuildingID = 0; + int bestPriority = 46; + for (int i = 0; i < DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; i++) { + int buildingID = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[i]; + if (buildingID == 0) { + continue; + } + for (int j = 0; j < bestPriority; j++) { + if ((short)DAT_BuildingsState::instance.buildings[buildingID].buildingType + == DAT_SkirmishDefinedData::instance.BuildingTargetPrioritySet1[j]) { + // Found a better match; lowering the bound also ends this loop + bestPriority = j; + selectedBuildingID = buildingID; + } + } + } + + return selectedBuildingID; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp b/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp new file mode 100644 index 000000000..c5845f0bb --- /dev/null +++ b/src/OpenSHC/AI/AICState/selectRandomBuildingIDFromListOfBuildingTypes.cpp @@ -0,0 +1,25 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDCA0 + int AICState::selectRandomBuildingIDFromListOfBuildingTypes(int playerID, undefined4 param_2) + { + int count = DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildingsTracker; + if (count <= 0) { + return 0; + } + + int index = SEC_RNG::instance.currentNumber2 % count; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + return DAT_GameState::instance.playerDataArray[playerID].top100TargetableBuildings[index]; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sellExcessGoods.cpp b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp new file mode 100644 index 000000000..038b44af9 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sellExcessGoods.cpp @@ -0,0 +1,113 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Trading/AIResourceTradeCategory.hpp" +#include "OpenSHC/AI/Trading/AIResourceTradeCategoryInt.hpp" +#include "OpenSHC/AI/Trading/ResourceAICResourceMappingEntry.hpp" +#include "OpenSHC/Game/Player/PlayerData.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIC::AICSpecification; + using OpenSHC::AI::Trading::AIResourceTradeCategory; + using OpenSHC::AI::Trading::AIResourceTradeCategoryInt; + using OpenSHC::AI::Trading::ResourceAICResourceMappingEntry; + using OpenSHC::Game::Player::PlayerData; + using OpenSHC::Game::Resources::ResourceType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1D60 + void AICState::sellExcessGoods(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + int playerResourceBase = playerID * (sizeof(PlayerData) / sizeof(int)); + for (int i = 0; i < 20; i++) { + ResourceType resourceType + = (ResourceType)DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].game; + int variance = this->aics[aicIndex].maxResourceVariance; + int resourceOffset = (playerResourceBase + resourceType) * sizeof(int); + + int amount; + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WOOD) { + amount = this->aics[aicIndex].maxWood; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_FOOD) { + amount = this->aics[aicIndex].maxFood; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_STONE) { + amount = this->aics[aicIndex].maxStone; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = this->aics[aicIndex].maxEquipment; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_ALE) { + amount = this->aics[aicIndex].maxBeer; + } else { + amount = this->aics[aicIndex].maxResourceOther; + } + amount += variance; + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf] < 500) { + // Nervous and low on gold: keep a quarter of the food and up to 10000 weapons, sell everything else + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_FOOD) { + amount = amount / 4; + } else if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = 10000; + } else { + amount = 0; + } + variance = 0; + } else if (DAT_GameState::instance.playerDataArray[playerID].popularity + < this->aics[aicIndex].criticalPopularity) { + // Critical popularity: keep all food, sell all weapons, keep a quarter of everything else + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + != OpenSHC::AI::Trading::AIRTC_FOOD) { + if (DAT_SkirmishDefinedData::instance.AIResourceTradeAICMapping[i].aic + == OpenSHC::AI::Trading::AIRTC_WEAPONS) { + amount = 0; + } else { + amount = amount / 4; + } + } + variance = 0; + } else { + amount += *( + int*)((char*)DAT_GameState::instance.playerDataArray[0].resourcesToAcquireArray + resourceOffset); + } + + // Resources the AIC always sells are kept at zero + for (int j = 0; j < 15; j++) { + if (resourceType == (&this->aics[aicIndex].sellResource01)[j]) { + amount = 0; + variance = 0; + break; + } + } + + if (*(int*)((char*)DAT_GameState::instance.playerDataArray[0].currentResources + resourceOffset) > amount) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sellGoods, this)(playerID, resourceType, + ((int*)DAT_GameState::instance.playerDataArray[0] + .currentResources)[playerResourceBase + resourceType] + - amount + variance); + return; + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/sellGoods.cpp b/src/OpenSHC/AI/AICState/sellGoods.cpp index 5ebd21d63..c830f93ea 100644 --- a/src/OpenSHC/AI/AICState/sellGoods.cpp +++ b/src/OpenSHC/AI/AICState/sellGoods.cpp @@ -23,6 +23,5 @@ namespace AI { MACRO_CALL_MEMBER(Game::GameStateStructures_Func::displayPlayerTradeVisualEffect, DAT_GameState::ptr)( playerID, 1, -amount, resourceType); } - } } diff --git a/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp b/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp new file mode 100644 index 000000000..ccea76429 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendAITribe_0xf_toAttackPatrol.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF4F0 + void AICState::sendAITribe_0xf_toAttackPatrol(int playerID) + { + int tribeIndex = 15; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + // Bug: only tribe 15 is sent, although tribe 16 can also exist if 'AIC: AttUnitPatrolGroupsCount' is 2 + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_DEFENSIVE; + MACRO_CALL_MEMBER(Map::Units::TroopValueState_Func::sendAttackingPatrolTribeToComputedDestination, + DAT_TroopValueState::ptr)(tribeID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp b/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp new file mode 100644 index 000000000..abe6c64a1 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed.cpp @@ -0,0 +1,64 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4220 + void AICState::sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed(int playerID) + { + BOOLEnum enclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + + for (int i = 0; i < 6; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].aivUnitType]; + for (int j = 0; j < count; j++) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].tribeArrayOffset + + j]; + if (tribeID != 0 + && DAT_TribesState::instance.tribes[tribeID].uid + == DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].tribeArrayOffset + j]) { + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + || enclosed != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForGroundUnits[i].aivUnitType, + j); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } + + // NOTE: the original sets the stance even when the tribe is invalid (tribeID may be 0) + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0 + || enclosed != FALSE) { + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } else { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp b/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp new file mode 100644 index 000000000..5fc7b54b3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendOilMenForOilPickupOrToAIVLocations.cpp @@ -0,0 +1,62 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D44E0 + void AICState::sendOilMenForOilPickupOrToAIVLocations(int playerID) + { + int oilSmelterID = DAT_GameState::instance.playerDataArray[playerID].oilSmelter.id; + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[1]; + if (count < 1) { + return; + } + if (count == 10) { + count = 9; + } + + // Oil men tribes start at offset 71 in the tribe ID array + for (int i = 1; i <= count; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[70 + i]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[70 + i]) { + continue; + } + + if (DAT_UnitsState::instance.units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .resourceToDeposit + != 0) { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::unsetRallyRelatedFlagOnUnits, DAT_TribesState::ptr)(tribeID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)( + tribeID, OpenSHC::AI::AIVUT_ENGINEER, i - 1); + continue; + } + + // Send for oil duty + if (oilSmelterID != 0) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::relayTribeInstruction, DAT_UnitsState::ptr)( + tribeID, OpenSHC::Map::Units::UIT_CONSTRUCT_SIEGE_EQUIPMENTOIL_DUTYENGINEERRELATED, oilSmelterID, + DAT_BuildingsState::instance.buildings[oilSmelterID].uid, 0); + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp b/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp new file mode 100644 index 000000000..0110e7fed --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendRangedUnitTribesToAIVSlotsOrKeepIfNervous.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4130 + void AICState::sendRangedUnitTribesToAIVSlotsOrKeepIfNervous(int playerID) + { + for (int i = 0; i < 5; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].aivUnitType]; + for (int j = 0; j < count; j++) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].tribeArrayOffset + + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].tribeArrayOffset + + j]) { + continue; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + if ((DAT_TileMapState::instance.LogicLayer[DAT_UnitsState::instance + .units[DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID] + .tile] + & 0x10000100U) + == 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID); + } + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForRangedUnits[i].aivUnitType, j); + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp b/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp new file mode 100644 index 000000000..272c3b113 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeAtOffset1ToCampfire.cpp @@ -0,0 +1,21 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2870 + void AICState::sendTribeAtOffset1ToCampfire(int playerID) + { + int _tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[1]; + if (_tribeID <= 0) + return; + if (DAT_TribesState::instance.tribes[_tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[1]) + return; + MACRO_CALL_MEMBER(AICState_Func::sendUnitsToCampfire, this)(_tribeID, playerID); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp b/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp new file mode 100644 index 000000000..a93f91fa0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeToAIVLocationSlot.cpp @@ -0,0 +1,48 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIVUnitType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D2E00 + void AICState::sendTribeToAIVLocationSlot(int tribeID, AIVUnitType aivUnitType, int slotIndex) + { + int owner = DAT_TribesState::instance.tribes[tribeID].owner; + + // Find the slotIndex-th non-empty slot + int tile = 0; + for (int i = 0; i < 10; i++) { + if (DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlots[aivUnitType][i] == 0) { + continue; + } + slotIndex--; + if (slotIndex < 0) { + tile = DAT_GameState::instance.playerDataArray[owner].aivUnitLocationSlots[aivUnitType][i]; + break; + } + } + + int y = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[tile]; + int x = tile - DAT_ViewportRenderState::instance.translationMatrix[y].addXgetTile; + + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::canNavigateUnitsFromTileToTargetTile, this)(tribeID, tile) + != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)( + tribeID, x, y, 0); + return; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, owner); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp b/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp new file mode 100644 index 000000000..7ceb0cfef --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendTribeToAttack.cpp @@ -0,0 +1,38 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF750 + void AICState::sendTribeToAttack(int playerID) + { + int tribeIndex = 0xbd; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3fb); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp b/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp new file mode 100644 index 000000000..026d0d65f --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToAttackBreachedCastle.cpp @@ -0,0 +1,110 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::Map::Units::Instructions::UnitMatchSpeedEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D30E0 + undefined4 AICState::sendUnitsToAttackBreachedCastle(int attackingPlayerIndex) + { + int vector = 0; + if (DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiType == OpenSHC::AI::AIT_NULL) { + return 1; + } + + BOOLEnum sentUnitsToAttackLord = FALSE; + int defendingLordID = MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, + DAT_UnitsState::ptr)(DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID); + + for (int i = 0; i < 11; i++) { + int tribeType = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxBreachTribes[i].tribeCount; + if (tribeType == 18 || tribeType == 14 || tribeType == 13) { + continue; + } + + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiTribeIDs[tribeType + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[attackingPlayerIndex].aiTribeUIDs[j + tribeType]) { + continue; + } + + int targetUnitID = (short)DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID; + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + + if (sentUnitsToAttackLord != FALSE || defendingLordID == 0 || tribeType == 186) { + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveSomeRaidCommand, this)(tribeID, tribeType) + != 0) { + continue; + } + + if (tribeType == 186) { + vector += 4; + } + DAT_TribesState::instance.tribes[tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_8; + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + int attackedPlayerID + = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID; + MACRO_CALL_MEMBER( + OpenSHC::Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_UnitsState::instance.units[targetUnitID].x, + DAT_UnitsState::instance.units[targetUnitID].y, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].x, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].y); + + if (DAT_DirectionAlgorithmState::instance.distanceHigh > 4) { + attackedPlayerID + = DAT_GameState::instance.playerDataArray[attackingPlayerIndex].attackedPlayerID; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, + DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].x, + DAT_GameState::instance.mapAndTime.attackVectors[attackedPlayerID][vector].y, 0, 0, + OpenSHC::Map::Units::Instructions::UMSE_0); + } + vector += 1; + } else { + // Send units to attack lord + int lordY = DAT_UnitsState::instance.units[defendingLordID].y; + int lordX = DAT_UnitsState::instance.units[defendingLordID].x; + if (MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, lordX, lordY, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0) + == 0) { + return 0; + } + sentUnitsToAttackLord = TRUE; + } + } + } + + return 1; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp b/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp new file mode 100644 index 000000000..c824f5d75 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToCampfire.cpp @@ -0,0 +1,24 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD110 + void AICState::sendUnitsToCampfire(int tribeID, int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].campground.id <= 0) { + return; + } + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)(tribeID, + DAT_GameState::instance.playerDataArray[playerID].campground.xEntry, + DAT_GameState::instance.playerDataArray[playerID].campground.yEntry, 0); + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp b/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp new file mode 100644 index 000000000..fcb585017 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToKeep.cpp @@ -0,0 +1,24 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CD0C0 + void AICState::sendUnitsToKeep(int tribeID, int playerID) + { + int keepID = DAT_GameState::instance.playerDataArray[playerID].keep.id; + if (keepID <= 0) { + return; + } + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::commandUnitsToLocation, DAT_TribesState::ptr)(tribeID, + (short)DAT_BuildingsState::instance.buildings[keepID].x + 3, + (short)DAT_BuildingsState::instance.buildings[keepID].y + 4, 0); + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp b/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp new file mode 100644 index 000000000..be3532ffb --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendUnitsToPlayerIDsKeepIfPossible.cpp @@ -0,0 +1,79 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/Instructions/UnitMatchSpeedEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::Map::Units::Instructions::UnitMatchSpeedEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE5F0 + void AICState::sendUnitsToPlayerIDsKeepIfPossible(int playerID1, int playerID2) + { + uint campfireX = DAT_GameState::instance.playerDataArray[playerID1].campground.xEntry; + uint campfireY = DAT_GameState::instance.playerDataArray[playerID1].campground.yEntry; + + uint gatehouseX; + uint gatehouseY; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, DAT_GameState::ptr)( + playerID1, playerID2) + == FALSE) { + uint gatehouseTile = MACRO_CALL_MEMBER(OpenSHC::Map::Navigation::PathFindingState_Func::findOwnedGatehouse, + DAT_PathFindingState::ptr)(playerID2, campfireX, campfireY); + if (gatehouseTile != 0) { + gatehouseY = DAT_ViewportRenderState::instance.tileTranslationMatrix_YComponent[gatehouseTile]; + gatehouseX + = gatehouseTile - DAT_ViewportRenderState::instance.translationMatrix[gatehouseY].addXgetTile; + } else { + DAT_GameState::instance.playerDataArray[playerID1].requestStateUnk = 0; + return; + } + } + + for (int i = 0; i < 11; i++) { + int tribeTypeStart = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeType; + int tribeCount = DAT_SkirmishDefinedData::instance.MaxAttackTribes1[i].tribeCount; + for (int j = 0; j < tribeCount; j++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID1].aiTribeIDs[tribeTypeStart + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID1].aiTribeUIDs[j + tribeTypeStart]) { + continue; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID1, playerID2) + != FALSE) { + if (tribeTypeStart == 0xbe) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID2); + } else if (MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::selectionContainsHorses, + DAT_TribesState::ptr)(tribeID) + != FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID2); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToKeep, this)(tribeID, playerID2); + } + } else { + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::giveTribeMoveInstruction, DAT_TribesState::ptr)( + tribeID, gatehouseX, gatehouseY, 0, 0, OpenSHC::Map::Units::Instructions::UMSE_0); + } + } + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp new file mode 100644 index 000000000..0296967f4 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sendWallPatrolUnitTribesToAIVLocations.cpp @@ -0,0 +1,94 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/AIVUnitType.hpp" +#include "OpenSHC/AI/AIVUnitTypeInt.hpp" +#include "OpenSHC/AI/Tribes/AIVUnitTypeMaxLocationPair.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::AIVUnitType; + using OpenSHC::AI::AIVUnitTypeInt; + using OpenSHC::AI::Tribes::AIVUnitTypeMaxLocationPair; + using OpenSHC::Map::Units::Behavior::UnitStanceEnum; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4340 + void AICState::sendWallPatrolUnitTribesToAIVLocations(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + int aicIndex = aiType - 1; + BOOLEnum enclosed = MACRO_CALL_MEMBER( + OpenSHC::Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID); + + for (int i = 0; i < 3; i++) { + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount + [DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType]; + int groups = count; + if (count > this->aics[aicIndex].defWallPatrolGroups) { + groups = this->aics[aicIndex].defWallPatrolGroups; + } + if (groups <= 0) { + continue; + } + + int stepSize = count / groups; + if (stepSize <= 0) { + stepSize = 1; + } + + for (int j = 0, patrolGroupIndex = 0; j < groups; j++, patrolGroupIndex += stepSize) { + int tribeID + = DAT_GameState::instance.playerDataArray[playerID] + .aiTribeIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset + + j]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID] + .aiTribeUIDs[DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].tribeArrayOffset + + j]) { + continue; + } + + int slot + = (DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + patrolGroupIndex) + % count; + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && enclosed == FALSE) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToCampfire, this)(tribeID, playerID); + } else { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAIVLocationSlot, this)(tribeID, + (AIVUnitType)DAT_SkirmishDefinedData::instance.MaxAIVLocationForPatrolUnits[i].aivUnitType, + slot); + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 + && enclosed == FALSE) { + DAT_TribesState::instance.tribes[tribeID].unitStance = OpenSHC::Map::Units::Behavior::USE_DEFENSIVE; + } else { + DAT_TribesState::instance.tribes[tribeID].unitStance + = OpenSHC::Map::Units::Behavior::USE_AGGRESSIVE; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_01.cpp b/src/OpenSHC/AI/AICState/setAICParameters_01.cpp new file mode 100644 index 000000000..6da83f2a3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_01.cpp @@ -0,0 +1,150 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C6D60 + void AICState::setAICParameters_01(int aicIndex) + { + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5500; + this->aics[aicIndex].highestPopularity = 7500; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].populationPerFarm = 6; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0x12; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0; + this->aics[aicIndex].maxWood = 0x1e; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 0x14; + this->aics[aicIndex].maxEquipment = 0x14; + this->aics[aicIndex].maxBeer = 0x14; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 8; + this->aics[aicIndex].populationPerQuarry = 0x12; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 6; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 6; + this->aics[aicIndex].buildInterval = 1; + this->aics[aicIndex].resourceRebuildDelay = 10; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 0x14; + this->aics[aicIndex].defWallPatrolGroups = 1; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 800; + this->aics[aicIndex].unknown073 = 0x50; + this->aics[aicIndex].recruitProbDefWeak = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x18; + this->aics[aicIndex].DefUnit2 = 0x18; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnit1 = 0x18; + this->aics[aicIndex].RaidUnit2 = 0x18; + this->aics[aicIndex].RaidUnit3 = 0x16; + this->aics[aicIndex].recruitProbDefDefault = 0; + this->aics[aicIndex].recruitProbDefStrong = 0; + this->aics[aicIndex].recruitProbRaidWeak = 0x32; + this->aics[aicIndex].recruitProbRaidDefault = 0x32; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x32; + this->aics[aicIndex].recruitProbAttackStrong = 100; + this->aics[aicIndex].SortieUnitRangedMin = 5; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefWalls = 0x14; + this->aics[aicIndex].DefTotal = 0x1e; + this->aics[aicIndex].OuterPatrolGroupsMove = 0; + this->aics[aicIndex].RaidUnitsRandom = 0xf; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0x14; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0xf; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x19; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].CowThrowInterval = 1000; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x16; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x16; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x10; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x18; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = AITC_PLAYER; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_02.cpp b/src/OpenSHC/AI/AICState/setAICParameters_02.cpp new file mode 100644 index 000000000..339becf6f --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_02.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7120 + void AICState::setAICParameters_02(int aicIndex) + { + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xf; + this->aics[aicIndex].maxFood = 0x96; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 0x14; + this->aics[aicIndex].minimumBread = 0xffffffff; + this->aics[aicIndex].minimumWheat = 0xffffffff; + this->aics[aicIndex].minimumHop = 0xffffffff; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].tradeAmountEquipment = 6; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x50; + this->aics[aicIndex].maxWood = 0x30; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 0xc; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 5; + this->aics[aicIndex].populationPerQuarry = 0xf; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0xc; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 500; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 400; + this->aics[aicIndex].unknown073 = 0x3c; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0x1e; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidStrong = 0x28; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x28; + this->aics[aicIndex].RecruitIntervalWeak = 0; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 2; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 8; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnitMax = 0x14; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolGroupsCount = 3; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x47; + this->aics[aicIndex].RaidUnit2 = 0x47; + this->aics[aicIndex].RaidUnit3 = 0x47; + this->aics[aicIndex].RaidUnit4 = 0x48; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x19; + this->aics[aicIndex].AttCoordinationPreference = 0x28; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].CowThrowInterval = 1000; + this->aics[aicIndex].AttMaxEngineers = 2; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x16; + this->aics[aicIndex].AttUnit2Max = 0x14; + this->aics[aicIndex].AttMaxLaddermen = 10; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x48; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x32; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x16; + this->aics[aicIndex].AttUnitSiegeDefMax = 0x1e; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = AITC_BALANCED; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_03.cpp b/src/OpenSHC/AI/AICState/setAICParameters_03.cpp new file mode 100644 index 000000000..d4429b581 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_03.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7520 + void AICState::setAICParameters_03(int aicIndex) + { + this->aics[aicIndex].flagType = 0xc; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 5000; + this->aics[aicIndex].lowestPopularity = 7000; + this->aics[aicIndex].highestPopularity = 0x2134; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 400; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].minimumBread = 10; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].tradeAmountEquipment = 4; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 100; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x3c; + this->aics[aicIndex].maxWood = 0x46; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 6; + this->aics[aicIndex].populationPerQuarry = 0x19; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0x14; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x32; + this->aics[aicIndex].blacksmithSetting = 0x15; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 10; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x28; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x3c; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackStrong = 0x46; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitRanged = 0x17; + this->aics[aicIndex].SortieUnitMeleeMin = 5; + this->aics[aicIndex].SortieUnitMelee = 0x1a; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 0x14; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].RaidUnit1 = 0x1a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 0x3c; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 5; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0xf; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].CowThrowInterval = 0x1e; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x3b; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x1a; + this->aics[aicIndex].AttDiggingUnitMax = 10; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 5; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1a; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x1a; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = AITC_GOLD; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_04.cpp b/src/OpenSHC/AI/AICState/setAICParameters_04.cpp new file mode 100644 index 000000000..2bcec1b88 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_04.cpp @@ -0,0 +1,149 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7910 + void AICState::setAICParameters_04(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x1f; + this->aics[aicIndex].farm4 = 0x20; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = 5; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 4; + this->aics[aicIndex].aiRequestDelay = 0x14; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x28; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x1e; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 0xf; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 4; + this->aics[aicIndex].populationPerQuarry = 9; + this->aics[aicIndex].populationPerIronmine = 0xd; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 6; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 2; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].defWallPatrolRallyTime = 4; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0xfa; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x5a; + this->aics[aicIndex].recruitProbDefDefault = 0x3c; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidDefault = 0x1e; + this->aics[aicIndex].recruitProbRaidStrong = 10; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 10; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 8; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].SortieUnitMelee = 0x19; + this->aics[aicIndex].DefDiggingUnitMax = 8; + this->aics[aicIndex].DefDiggingUnit = 0x19; + this->aics[aicIndex].DefWalls = 0x50; + this->aics[aicIndex].DefTotal = 100; + this->aics[aicIndex].OuterPatrolGroupsCount = 4; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 3; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].RaidUnitsRandom = 0x14; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 5; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].RaidUnit2 = 0x4a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 4; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 5; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 0xf; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0xc; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 8; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0xc; + this->aics[aicIndex].AttDiggingUnit = 0x19; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1b; + this->aics[aicIndex].AttUnitSiegeDefMax = 5; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x19; + this->aics[aicIndex].AttUnitMain3 = 0x19; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = AITC_CLOSEST; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_05.cpp b/src/OpenSHC/AI/AICState/setAICParameters_05.cpp new file mode 100644 index 000000000..b87465a08 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_05.cpp @@ -0,0 +1,148 @@ +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/AI/AICState.hpp" + +namespace OpenSHC { +namespace AI { + + using AIC::AITC_ANY; + using AIC::AITC_BALANCED; + using AIC::AITC_CLOSEST; + using AIC::AITC_GOLD; + using AIC::AITC_PLAYER; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C7D20 + void AICState::setAICParameters_05(int aicIndex) + { + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1f; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].farm4 = 0x20; + this->aics[aicIndex].populationPerFarm = 3; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 0x32; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = 5; + this->aics[aicIndex].minimumWheat = 5; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x28; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x14; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x14; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 2; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 6; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 0x14; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].maxWoodcutters = 0xe; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xf; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].defWallPatrolRallyTime = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0x14; + this->aics[aicIndex].defSiegeEngineBuildDelay = 8; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x22; + this->aics[aicIndex].recruitProbDefStrong = 0x19; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x21; + this->aics[aicIndex].recruitProbRaidStrong = 0x19; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x21; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 10; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 7; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x46; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x4b; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].DefUnit3 = 0x46; + this->aics[aicIndex].DefUnit4 = 0x46; + this->aics[aicIndex].DefUnit5 = 0x4c; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 8; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 4; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 1; + this->aics[aicIndex].AttCoordinationPreference = 0x3c; + this->aics[aicIndex].CowThrowInterval = 2000; + this->aics[aicIndex].SiegeEngine1 = 0x4d; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 5; + this->aics[aicIndex].AttUnit2 = 0x4a; + this->aics[aicIndex].AttUnit2Max = 0x14; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4a; + this->aics[aicIndex].AttUnitPatrolMax = 0x14; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 1; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x14; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0x4c; + this->aics[aicIndex].AttUnitSiegeDef = 0x4c; + this->aics[aicIndex].AttUnitSiegeDefMax = 6; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = AITC_GOLD; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_06.cpp b/src/OpenSHC/AI/AICState/setAICParameters_06.cpp new file mode 100644 index 000000000..f9b06cf09 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_06.cpp @@ -0,0 +1,150 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C8110 + void AICState::setAICParameters_06(int aicIndex) + { + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].farm3 = 0x20; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x32; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 0xf; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 7; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 10; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x3c; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].defWallPatrolRallyTime = 5; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 0x78; + this->aics[aicIndex].defSiegeEngineBuildDelay = 8; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefStrong = 0x28; + this->aics[aicIndex].recruitProbRaidWeak = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x1e; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 0; + this->aics[aicIndex].RecruitIntervalStrong = 0; + this->aics[aicIndex].SortieUnitRangedMin = 7; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 5; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x3c; + this->aics[aicIndex].DefTotal = 0x3c; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x4c; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 0x1e; + this->aics[aicIndex].DefUnit3 = 0x46; + this->aics[aicIndex].DefUnit4 = 0x46; + this->aics[aicIndex].RaidUnit1 = 0x47; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; + this->aics[aicIndex].HarassingSiegeEngine2 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 0xf; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x46; + this->aics[aicIndex].AttForceRallyPercentage = 100; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 2; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 10; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 7; + this->aics[aicIndex].SiegeEngine1 = 0x4d; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0x4b; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4a; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x28; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x46; + this->aics[aicIndex].AttUnitSiegeDefMax = 10; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 5; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_07.cpp b/src/OpenSHC/AI/AICState/setAICParameters_07.cpp new file mode 100644 index 000000000..101537122 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_07.cpp @@ -0,0 +1,147 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C8530 + void AICState::setAICParameters_07(int aicIndex) + { + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 0; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 5000; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 1; + this->aics[aicIndex].taxesMax = 8; + this->aics[aicIndex].farm1 = 0; + this->aics[aicIndex].populationPerFarm = 100; + this->aics[aicIndex].unknown011 = 0x14; + this->aics[aicIndex].maxFood = 500; + this->aics[aicIndex].minimumApples = 0x14; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x1e; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0; + this->aics[aicIndex].maxWood = 0x1e; + this->aics[aicIndex].maxStone = 0x14; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 5; + this->aics[aicIndex].maxBeer = 0x14; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 2; + this->aics[aicIndex].populationPerQuarry = 5; + this->aics[aicIndex].populationPerIronmine = 0; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 2; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 0xf; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 6; + this->aics[aicIndex].buildInterval = 5; + this->aics[aicIndex].resourceRebuildDelay = 0x50; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 10; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 0x14; + this->aics[aicIndex].unknown072 = 800; + this->aics[aicIndex].unknown073 = 0x50; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].DefDiggingUnitMax = 2; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x28; + this->aics[aicIndex].RecruitIntervalWeak = 8; + this->aics[aicIndex].RecruitInterval = 8; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 0; + this->aics[aicIndex].SortieUnitRanged = 0x48; + this->aics[aicIndex].SortieUnitMeleeMin = 3; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsMove = 0; + this->aics[aicIndex].DefUnit1 = 0x48; + this->aics[aicIndex].DefUnit2 = 0x48; + this->aics[aicIndex].DefUnit3 = 0x4b; + this->aics[aicIndex].RaidUnitsBase = 0x14; + this->aics[aicIndex].RaidUnit1 = 0x48; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 3; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 5; + this->aics[aicIndex].AttForceRallyPercentage = 0x1e; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 0x28; + this->aics[aicIndex].AttCoordinationPreference = 0x14; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0; + this->aics[aicIndex].AttDiggingUnitMax = 0; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x46; + this->aics[aicIndex].AttUnitBackupMax = 0x78; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 1; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_08.cpp b/src/OpenSHC/AI/AICState/setAICParameters_08.cpp new file mode 100644 index 000000000..28cef0fd5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_08.cpp @@ -0,0 +1,149 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C8920 + void AICState::setAICParameters_08(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 0; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x20; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 4; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xf; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 4; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x5a; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 10; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0; + this->aics[aicIndex].recruitProbRaidStrong = 0; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x46; + this->aics[aicIndex].recruitProbAttackStrong = 0x5a; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 6; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 6; + this->aics[aicIndex].SortieUnitMelee = 0x19; + this->aics[aicIndex].DefDiggingUnitMax = 0; + this->aics[aicIndex].DefDiggingUnit = 0x19; + this->aics[aicIndex].DefWalls = 0x14; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 5; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].DefUnit3 = 0x19; + this->aics[aicIndex].RaidUnit1 = 0x16; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 5; + this->aics[aicIndex].AttForceBase = 0x14; + this->aics[aicIndex].AttForceRandom = 5; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 10; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0x14; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 2; + this->aics[aicIndex].AttCoordinationPreference = 10; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x3b; + this->aics[aicIndex].SiegeEngine5 = 0x28; + this->aics[aicIndex].SiegeEngine6 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0x10; + this->aics[aicIndex].AttDiggingUnit = 0x19; + this->aics[aicIndex].AttDiggingUnitMax = 6; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 3; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x14; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1b; + this->aics[aicIndex].AttUnitSiegeDefMax = 4; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 2; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x19; + this->aics[aicIndex].AttUnitMain3 = 0x19; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_09.cpp b/src/OpenSHC/AI/AICState/setAICParameters_09.cpp new file mode 100644 index 000000000..082d66a3a --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_09.cpp @@ -0,0 +1,146 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C8D20 + void AICState::setAICParameters_09(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 0x157c; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x1f; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 0xc; + this->aics[aicIndex].maxFood = 200; + this->aics[aicIndex].minimumApples = 5; + this->aics[aicIndex].minimumCheese = 5; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 0x14; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0xc; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 5; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x1e; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = -999; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 2; + this->aics[aicIndex].unknown072 = 100; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 0xc; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 4; + this->aics[aicIndex].SortieUnitMelee = 0x1b; + this->aics[aicIndex].DefDiggingUnitMax = 10; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolGroupsCount = 2; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 10; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 4; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].RaidUnitsRandom = 0; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEnginesMax = 2; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 6; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRandom = 4; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0x14; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].SiegeEngine3 = 0x28; + this->aics[aicIndex].SiegeEngine4 = 0x28; + this->aics[aicIndex].SiegeEngine5 = 0x28; + this->aics[aicIndex].SiegeEngine6 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 0x12; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x17; + this->aics[aicIndex].AttUnitPatrolMax = 0x14; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 1; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 2; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x17; + this->aics[aicIndex].AttUnitSiegeDefMax = 10; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttUnitMain2 = 0x1b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 4; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_GOLD; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_10.cpp b/src/OpenSHC/AI/AICState/setAICParameters_10.cpp new file mode 100644 index 000000000..99dc276f8 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_10.cpp @@ -0,0 +1,147 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C9120 + void AICState::setAICParameters_10(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].minimumBread = 10; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 0xf; + this->aics[aicIndex].maxEquipment = 6; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0; + this->aics[aicIndex].populationPerIronmine = 7; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 8; + this->aics[aicIndex].buildInterval = 4; + this->aics[aicIndex].resourceRebuildDelay = 0x3c; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0x14; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidStrong = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].RecruitIntervalWeak = 4; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 2; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x18; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x18; + this->aics[aicIndex].DefWalls = 4; + this->aics[aicIndex].DefTotal = 0x14; + this->aics[aicIndex].OuterPatrolGroupsCount = 4; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x1c; + this->aics[aicIndex].DefUnit2 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 4; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].DefUnit3 = 0x16; + this->aics[aicIndex].DefUnit4 = 0x16; + this->aics[aicIndex].DefUnit5 = 0x16; + this->aics[aicIndex].DefUnit6 = 0x16; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x28; + this->aics[aicIndex].AttForceRallyPercentage = 0x28; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x46; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x18; + this->aics[aicIndex].AttDiggingUnitMax = 0x3c; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0x18; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0; + this->aics[aicIndex].AttUnitBackupMax = 0; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x18; + this->aics[aicIndex].AttUnitMain2 = 0x18; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_11.cpp b/src/OpenSHC/AI/AICState/setAICParameters_11.cpp new file mode 100644 index 000000000..57e5e6c3b --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_11.cpp @@ -0,0 +1,145 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C94F0 + void AICState::setAICParameters_11(int aicIndex) + { + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = -1; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = -1; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0x16; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x1e; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x32; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 100; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 6; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 1; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 4; + this->aics[aicIndex].maxFarms = 8; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x32; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource02 = 3; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource06 = 0xe; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x46; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x14; + this->aics[aicIndex].recruitProbAttackWeak = 0x1e; + this->aics[aicIndex].recruitProbAttackDefault = 0x32; + this->aics[aicIndex].recruitProbAttackStrong = 0x3c; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 6; + this->aics[aicIndex].SortieUnitRanged = 0x4a; + this->aics[aicIndex].SortieUnitMeleeMin = 10; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x46; + this->aics[aicIndex].DefUnit2 = 0x4c; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x4a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 4; + this->aics[aicIndex].HarassingSiegeEngine1 = 0x166; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 5; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x1e; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 4; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x5a; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x4d; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x4d; + this->aics[aicIndex].AttMaxEngineers = 9; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0xf; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x50; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 10; + this->aics[aicIndex].AttUnitEngage = 0x4c; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_12.cpp b/src/OpenSHC/AI/AICState/setAICParameters_12.cpp new file mode 100644 index 000000000..887cf7c84 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_12.cpp @@ -0,0 +1,146 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004C9900 + void AICState::setAICParameters_12(int aicIndex) + { + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].farm2 = 0x20; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].farm4 = 0x1f; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 0x50; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x3c; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0x10; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 0; + this->aics[aicIndex].RecruitIntervalStrong = 0; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x4b; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].DefWalls = 0x32; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].DefUnit2 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x4b; + this->aics[aicIndex].HarassingSiegeEnginesMax = 3; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].HarassingSiegeEngine2 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].HarassingSiegeEngine3 = 0x166; + this->aics[aicIndex].AttForceBase = 0x1e; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x14; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x50; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x3d; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0xf; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0x50; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x4b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_GOLD; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_13.cpp b/src/OpenSHC/AI/AICState/setAICParameters_13.cpp new file mode 100644 index 000000000..afea2e4b0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_13.cpp @@ -0,0 +1,142 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004C9CF0 + void AICState::setAICParameters_13(int aicIndex) + { + this->aics[aicIndex].flagType = 10; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1e; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].farm4 = 0x1f; + this->aics[aicIndex].populationPerFarm = 3; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 5; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x1e; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x1e; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 100; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 0x14; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 2; + this->aics[aicIndex].maxWoodcutters = 0xc; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 0x10; + this->aics[aicIndex].buildInterval = 1; + this->aics[aicIndex].resourceRebuildDelay = 0x14; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 0x14; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 8; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRangedMin = 5; + this->aics[aicIndex].SortieUnitRanged = 0x46; + this->aics[aicIndex].SortieUnitMeleeMin = 0xf; + this->aics[aicIndex].SortieUnitMelee = 0x46; + this->aics[aicIndex].DefDiggingUnitMax = 0x3c; + this->aics[aicIndex].DefDiggingUnit = 0x47; + this->aics[aicIndex].DefWalls = 0x50; + this->aics[aicIndex].DefTotal = 0x50; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x46; + this->aics[aicIndex].RaidUnitsBase = 5; + this->aics[aicIndex].RaidUnitsRandom = 5; + this->aics[aicIndex].RaidUnit1 = 0x49; + this->aics[aicIndex].HarassingSiegeEnginesMax = 0; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 0x28; + this->aics[aicIndex].AttForceRandom = 0; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 0; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0x28; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x4a; + this->aics[aicIndex].AttUnitBackupMax = 0; + this->aics[aicIndex].AttUnitBackupGroupsCount = 0; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x49; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 4; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_14.cpp b/src/OpenSHC/AI/AICState/setAICParameters_14.cpp new file mode 100644 index 000000000..64c2099b7 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_14.cpp @@ -0,0 +1,150 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CA0C0 + void AICState::setAICParameters_14(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 6000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 3; + this->aics[aicIndex].taxesMax = 0xb; + this->aics[aicIndex].farm1 = 0x20; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].farm3 = 0x21; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 300; + this->aics[aicIndex].minimumApples = 0x14; + this->aics[aicIndex].minimumCheese = 0x14; + this->aics[aicIndex].minimumBread = 0xffffffff; + this->aics[aicIndex].minimumWheat = 0xffffffff; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 10; + this->aics[aicIndex].tradeAmountEquipment = 2; + this->aics[aicIndex].aiRequestDelay = 0x28; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0x28; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x32; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 10; + this->aics[aicIndex].maxResourceVariance = 5; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 10; + this->aics[aicIndex].populationPerIronmine = 0xc; + this->aics[aicIndex].populationPerPitchrig = 0xf; + this->aics[aicIndex].maxQuarries = 3; + this->aics[aicIndex].maxIronmines = 6; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 4; + this->aics[aicIndex].maxFarms = 10; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 8; + this->aics[aicIndex].blacksmithSetting = 0x15; + this->aics[aicIndex].fletcherSetting = 0x12; + this->aics[aicIndex].poleturnerSetting = 0x14; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource08 = 0x11; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 8; + this->aics[aicIndex].defWallPatrolGroups = 2; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 200; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x1e; + this->aics[aicIndex].recruitProbDefWeak = 100; + this->aics[aicIndex].recruitProbDefDefault = 0x32; + this->aics[aicIndex].recruitProbDefStrong = 0x1e; + this->aics[aicIndex].recruitProbRaidWeak = 0; + this->aics[aicIndex].recruitProbRaidDefault = 0x1e; + this->aics[aicIndex].recruitProbRaidStrong = 0x32; + this->aics[aicIndex].recruitProbAttackWeak = 0; + this->aics[aicIndex].recruitProbAttackDefault = 0x14; + this->aics[aicIndex].recruitProbAttackStrong = 0x14; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 4; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 10; + this->aics[aicIndex].SortieUnitRanged = 0x17; + this->aics[aicIndex].SortieUnitMeleeMin = 10; + this->aics[aicIndex].SortieUnitMelee = 0x1a; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x1a; + this->aics[aicIndex].DefWalls = 0x28; + this->aics[aicIndex].DefTotal = 0x32; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 5; + this->aics[aicIndex].DefUnit1 = 0x17; + this->aics[aicIndex].DefUnit2 = 0x17; + this->aics[aicIndex].RaidUnitsBase = 10; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].DefUnit3 = 0x17; + this->aics[aicIndex].DefUnit4 = 0x4c; + this->aics[aicIndex].DefUnit5 = 0x49; + this->aics[aicIndex].RaidUnit1 = 0x1a; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].HarassingSiegeEngine2 = 0x166; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x32; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 5; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 8; + this->aics[aicIndex].AttCoordinationPreference = 0x32; + this->aics[aicIndex].CowThrowInterval = 6; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x4d; + this->aics[aicIndex].SiegeEngine4 = 0x4d; + this->aics[aicIndex].SiegeEngine5 = 0x4d; + this->aics[aicIndex].AttMaxEngineers = 10; + this->aics[aicIndex].AttDiggingUnit = 0x47; + this->aics[aicIndex].AttDiggingUnitMax = 0x50; + this->aics[aicIndex].AttMaxAssassins = 10; + this->aics[aicIndex].AttUnit2 = 0x1a; + this->aics[aicIndex].AttUnit2Max = 10; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0x4c; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x17; + this->aics[aicIndex].AttUnitBackupMax = 0x32; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0x1a; + this->aics[aicIndex].AttUnitSiegeDefMax = 6; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 1; + this->aics[aicIndex].AttUnitMain1 = 0x1a; + this->aics[aicIndex].AttUnitMain2 = 0x1a; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 3; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_CLOSEST; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_15.cpp b/src/OpenSHC/AI/AICState/setAICParameters_15.cpp new file mode 100644 index 000000000..856ba4069 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_15.cpp @@ -0,0 +1,142 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + // FUNCTION: STRONGHOLDCRUSADER 0x004CA4D0 + void AICState::setAICParameters_15(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 4000; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 8000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 10; + this->aics[aicIndex].farm1 = 0x21; + this->aics[aicIndex].farm2 = 0x1f; + this->aics[aicIndex].farm3 = 0x21; + this->aics[aicIndex].farm4 = 0x21; + this->aics[aicIndex].populationPerFarm = 5; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = 10; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = -1; + this->aics[aicIndex].minimumHop = 5; + this->aics[aicIndex].tradeAmountFood = 0x14; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 0; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x50; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x32; + this->aics[aicIndex].maxResourceOther = 0xc; + this->aics[aicIndex].maxEquipment = 10; + this->aics[aicIndex].maxBeer = 6; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 200; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 7; + this->aics[aicIndex].populationPerIronmine = 7; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 1; + this->aics[aicIndex].maxIronmines = 3; + this->aics[aicIndex].maxWoodcutters = 8; + this->aics[aicIndex].maxPitchrigs = 1; + this->aics[aicIndex].maxFarms = 9; + this->aics[aicIndex].buildInterval = 3; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource04 = 7; + this->aics[aicIndex].sellResource05 = 9; + this->aics[aicIndex].sellResource07 = 0x10; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x32; + this->aics[aicIndex].recruitProbDefDefault = 0x1e; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 0x28; + this->aics[aicIndex].recruitProbRaidDefault = 0x28; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x1e; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 4; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].SortieUnitMeleeMin = 4; + this->aics[aicIndex].SortieUnitMelee = 0x1c; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].DefTotal = 0x28; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].RaidUnitsBase = 0x10; + this->aics[aicIndex].RaidUnitsRandom = 4; + this->aics[aicIndex].RaidUnit1 = 0x1c; + this->aics[aicIndex].HarassingSiegeEnginesMax = 1; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].AttForceBase = 10; + this->aics[aicIndex].AttForceRandom = 0x14; + this->aics[aicIndex].AttForceSupportAllyThreshold = 10; + this->aics[aicIndex].AttForceRallyPercentage = 0x32; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x46; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x28; + this->aics[aicIndex].SiegeEngine2 = 0x28; + this->aics[aicIndex].AttMaxEngineers = 6; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 0x14; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x1e; + this->aics[aicIndex].AttUnitBackupGroupsCount = 3; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x1b; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAICParameters_16.cpp b/src/OpenSHC/AI/AICState/setAICParameters_16.cpp new file mode 100644 index 000000000..2ce895b50 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAICParameters_16.cpp @@ -0,0 +1,144 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIC::AITargetChoice; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CA8A0 + void AICState::setAICParameters_16(int aicIndex) + { + this->aics[aicIndex].flagType = 0xd; + this->aics[aicIndex].unknown002 = 0; + this->aics[aicIndex].unknown001 = 0; + this->aics[aicIndex].unknown003 = 0; + this->aics[aicIndex].unknown004 = 1; + this->aics[aicIndex].unknown005 = 1; + this->aics[aicIndex].criticalPopularity = 0x1194; + this->aics[aicIndex].lowestPopularity = 5000; + this->aics[aicIndex].highestPopularity = 9000; + this->aics[aicIndex].taxesMin = 2; + this->aics[aicIndex].taxesMax = 0xc; + this->aics[aicIndex].farm1 = 0x1f; + this->aics[aicIndex].farm2 = 0x1e; + this->aics[aicIndex].farm3 = 0x1e; + this->aics[aicIndex].populationPerFarm = 4; + this->aics[aicIndex].unknown011 = 10; + this->aics[aicIndex].maxFood = 100; + this->aics[aicIndex].minimumApples = 10; + this->aics[aicIndex].minimumCheese = -1; + this->aics[aicIndex].minimumBread = -1; + this->aics[aicIndex].minimumWheat = 2; + this->aics[aicIndex].minimumHop = 2; + this->aics[aicIndex].tradeAmountFood = 5; + this->aics[aicIndex].tradeAmountEquipment = 0; + this->aics[aicIndex].aiRequestDelay = 0xc; + this->aics[aicIndex].minimumGoodsRequiredAfterTrade = 10; + this->aics[aicIndex].doubleRationsFoodThreshold = 0x28; + this->aics[aicIndex].maxWood = 0x3c; + this->aics[aicIndex].maxStone = 0x28; + this->aics[aicIndex].maxResourceOther = 10; + this->aics[aicIndex].maxEquipment = 4; + this->aics[aicIndex].maxBeer = 5; + this->aics[aicIndex].maxResourceVariance = 4; + this->aics[aicIndex].recruitGoldThreshold = 0x32; + this->aics[aicIndex].populationPerWoodcutter = 7; + this->aics[aicIndex].populationPerQuarry = 6; + this->aics[aicIndex].populationPerIronmine = 10; + this->aics[aicIndex].populationPerPitchrig = 0; + this->aics[aicIndex].maxQuarries = 4; + this->aics[aicIndex].maxIronmines = 2; + this->aics[aicIndex].maxWoodcutters = 10; + this->aics[aicIndex].maxPitchrigs = 0; + this->aics[aicIndex].maxFarms = 0xc; + this->aics[aicIndex].buildInterval = 2; + this->aics[aicIndex].resourceRebuildDelay = 0x28; + this->aics[aicIndex].blacksmithSetting = 0x16; + this->aics[aicIndex].fletcherSetting = 0x11; + this->aics[aicIndex].poleturnerSetting = 0x13; + this->aics[aicIndex].sellResource03 = 6; + this->aics[aicIndex].sellResource09 = 0x12; + this->aics[aicIndex].sellResource10 = 0x13; + this->aics[aicIndex].sellResource11 = 0x14; + this->aics[aicIndex].sellResource12 = 0x15; + this->aics[aicIndex].sellResource13 = 0x16; + this->aics[aicIndex].sellResource14 = 0x17; + this->aics[aicIndex].sellResource15 = 0x18; + this->aics[aicIndex].defWallPatrolRallyTime = 2; + this->aics[aicIndex].defWallPatrolGroups = 3; + this->aics[aicIndex].defSiegeEngineGoldThreshold = 100; + this->aics[aicIndex].defSiegeEngineBuildDelay = 4; + this->aics[aicIndex].unknown072 = 200; + this->aics[aicIndex].unknown073 = 0x14; + this->aics[aicIndex].recruitProbDefWeak = 0x50; + this->aics[aicIndex].recruitProbDefDefault = 0x28; + this->aics[aicIndex].recruitProbDefStrong = 0x14; + this->aics[aicIndex].recruitProbRaidWeak = 10; + this->aics[aicIndex].RecruitIntervalWeak = 1; + this->aics[aicIndex].RecruitInterval = 1; + this->aics[aicIndex].RecruitIntervalStrong = 1; + this->aics[aicIndex].SortieUnitRanged = 0x16; + this->aics[aicIndex].DefDiggingUnit = 0x16; + this->aics[aicIndex].OuterPatrolGroupsCount = 1; + this->aics[aicIndex].OuterPatrolGroupsMove = 1; + this->aics[aicIndex].OuterPatrolRallyDelay = 2; + this->aics[aicIndex].DefUnit1 = 0x16; + this->aics[aicIndex].recruitProbRaidDefault = 0x14; + this->aics[aicIndex].recruitProbRaidStrong = 0x1e; + this->aics[aicIndex].recruitProbAttackWeak = 10; + this->aics[aicIndex].recruitProbAttackDefault = 0x28; + this->aics[aicIndex].recruitProbAttackStrong = 0x32; + this->aics[aicIndex].SortieUnitRangedMin = 4; + this->aics[aicIndex].SortieUnitMeleeMin = 0x14; + this->aics[aicIndex].SortieUnitMelee = 0x25; + this->aics[aicIndex].DefDiggingUnitMax = 4; + this->aics[aicIndex].DefWalls = 0x1e; + this->aics[aicIndex].DefTotal = 0x1e; + this->aics[aicIndex].RaidUnitsBase = 0x1e; + this->aics[aicIndex].RaidUnitsRandom = 10; + this->aics[aicIndex].RaidUnit1 = 0x25; + this->aics[aicIndex].HarassingSiegeEnginesMax = 5; + this->aics[aicIndex].RaidRetargetDelay = 4; + this->aics[aicIndex].HarassingSiegeEngine1 = 0xbe; + this->aics[aicIndex].AttForceBase = 0x32; + this->aics[aicIndex].AttForceRandom = 10; + this->aics[aicIndex].AttForceSupportAllyThreshold = 0x28; + this->aics[aicIndex].AttForceRallyPercentage = 0x50; + this->aics[aicIndex].AttMovingTribesThreshold = 100; + this->aics[aicIndex].AttAssaultDelay = 0; + this->aics[aicIndex].AttUnitPatrolRecommandDelay = 5; + this->aics[aicIndex].AttCoordinationPreference = 0x50; + this->aics[aicIndex].CowThrowInterval = 10000; + this->aics[aicIndex].SiegeEngine1 = 0x27; + this->aics[aicIndex].SiegeEngine2 = 0x27; + this->aics[aicIndex].SiegeEngine3 = 0x27; + this->aics[aicIndex].SiegeEngine4 = 0x27; + this->aics[aicIndex].AttMaxEngineers = 8; + this->aics[aicIndex].AttDiggingUnit = 0x16; + this->aics[aicIndex].AttDiggingUnitMax = 10; + this->aics[aicIndex].AttMaxAssassins = 0; + this->aics[aicIndex].AttUnit2 = 0; + this->aics[aicIndex].AttUnit2Max = 0; + this->aics[aicIndex].AttMaxLaddermen = 0; + this->aics[aicIndex].AttMaxTunnelers = 0; + this->aics[aicIndex].AttUnitPatrol = 0; + this->aics[aicIndex].AttUnitPatrolMax = 0; + this->aics[aicIndex].AttUnitPatrolGroupsCount = 0; + this->aics[aicIndex].AttUnitBackup = 0x16; + this->aics[aicIndex].AttUnitBackupMax = 0x28; + this->aics[aicIndex].AttUnitBackupGroupsCount = 1; + this->aics[aicIndex].AttUnitEngageMax = 0; + this->aics[aicIndex].AttUnitEngage = 0; + this->aics[aicIndex].AttUnitSiegeDef = 0; + this->aics[aicIndex].AttUnitSiegeDefMax = 0; + this->aics[aicIndex].AttUnitSiegeDefGroupsCount = 0; + this->aics[aicIndex].AttUnitMain1 = 0x25; + this->aics[aicIndex].AttMaxDefault = 200; + this->aics[aicIndex].AttMainGroupsCount = 2; + this->aics[aicIndex].TargetChoice = OpenSHC::AI::AIC::AITC_BALANCED; + return; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp b/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp new file mode 100644 index 000000000..b6a98e489 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAttackCoordinationLevel.cpp @@ -0,0 +1,33 @@ +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0110 + void AICState::setAttackCoordinationLevel(int playerID) + { + int const _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == AITA_NULL) { + return; + } + int _willingnessPercentage = this->aics[_aiType - 1].AttCoordinationPreference + + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 4; + if (90 < _willingnessPercentage) { + _willingnessPercentage = 90; + } + int _rngMod100 = SEC_RNG::instance.currentNumber2 % 100; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + if (_rngMod100 < _willingnessPercentage / 2) { + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel = 2; + return; + } + DAT_GameState::instance.playerDataArray[playerID].aiAttackCoordinationLevel + = _rngMod100 < _willingnessPercentage; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp b/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp new file mode 100644 index 000000000..db0febac5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setAttackPrimeInfo.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDFD0 + void AICState::setAttackPrimeInfo(int playerID, int attackedPlayerID) + { + int tile = DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile; + DAT_TroopValueState::instance.attackInfo.startZone = (char)DAT_TileMapState::instance.AIZoneLayer[tile]; + DAT_TroopValueState::instance.attackInfo.startCon = (short)DAT_TileMapState::instance.PathConnectionLayer[tile]; + DAT_TroopValueState::instance.attackInfo.zoneSize + = DAT_PathFindingState::instance.zoneSizesArray[DAT_TroopValueState::instance.attackInfo.startCon]; + DAT_TroopValueState::instance.attackInfo.unknownOne_0x20f90 = 1; + DAT_TroopValueState::instance.attackInfo.keepCon + = (short)DAT_TileMapState::instance + .PathConnectionLayer[DAT_GameState::instance.playerDataArray[attackedPlayerID].campground.tileEntry]; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp b/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp new file mode 100644 index 000000000..ef07f7cf0 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setCurrentAttackRaidParameter.cpp @@ -0,0 +1,40 @@ +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDF10 + void AICState::setCurrentAttackRaidParameter(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return; + int aicIndex = aiType - 1; + + int randomPercent = SEC_RNG::instance.currentNumber2 % 100; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + int attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID_2Unk; + int raidUnits = this->aics[aicIndex].RaidUnitsRandom; + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] < 1000) { + raidUnits = 0; + } else if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + >= 5000) { + raidUnits *= 2; + } + + if (DAT_GameState::instance.playerDataArray[attackedPlayerID].currentResources[Game::Resources::RT_GOLD] + < 500) { + raidUnits = -(this->aics[aicIndex].RaidUnitsBase / 2); + } + + DAT_GameState::instance.playerDataArray[playerID].currentAttackRaidParameter + = (raidUnits * randomPercent) / 100; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp b/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp new file mode 100644 index 000000000..879635162 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setCurrentAttackStrength.cpp @@ -0,0 +1,37 @@ +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CDE60 + void AICState::setCurrentAttackStrength(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) + return; + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + int randomPercent = SEC_RNG::instance.currentNumber2 % 100; + MACRO_CALL_MEMBER(Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + = (this->aics[aicIndex].AttForceRandom * randomPercent) / 100; + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave++; + + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] > 10000) { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + += DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 7; + } else { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength + += DAT_GameState::instance.playerDataArray[playerID].currentAttackWave * 5; + } + + if (DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength > 200) { + DAT_GameState::instance.playerDataArray[playerID].currentWaveRandomAttackingStrength = 200; + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp new file mode 100644 index 000000000..bf1b2a003 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setEightTribe0xc0TribesToAggressiveAndAttack.cpp @@ -0,0 +1,38 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF5E0 + void AICState::setEightTribe0xc0TribesToAggressiveAndAttack(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + for (int i = 0; i < 8; i++) { + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[i + 0xc0]; + if (tribeID == 0) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[i + 0xc0]) { + continue; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + continue; + } + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp b/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp index d556d1026..780b9a3d4 100644 --- a/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp +++ b/src/OpenSHC/AI/AICState/setFoodBuyPlan.cpp @@ -9,14 +9,16 @@ namespace OpenSHC { namespace AI { + using OpenSHC::AI::AITypeA; + using OpenSHC::Game::Resources::ResourceType; + // FUNCTION: STRONGHOLDCRUSADER 0x004CB060 void AICState::setFoodBuyPlan(int playerID) { - int const aiType = DAT_GameState::ptr->playerDataArray[playerID].aiType; // Early return if not an AI player (human player has aiType == 0) - if (aiType == AITA_NULL) { + if (aiType == AITypeA::AITA_NULL) { return; } @@ -28,36 +30,36 @@ namespace AI { // Apples: special handling with >= 0 check (allows -1 to disable) int const minimumApples = this->aics[aiConfigIndex].minimumApples; int* currentResources = DAT_GameState::ptr->playerDataArray[playerID].currentResources; - if (minimumApples >= 0 && currentResources[Game::Resources::RT_APPLE] < minimumApples) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_APPLE] + if (minimumApples >= 0 && currentResources[ResourceType::RT_APPLE] < minimumApples) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_APPLE] = this->aics[aiConfigIndex].tradeAmountFood; } // Cheese: standard check with > 0 int const minimumCheese = this->aics[aiConfigIndex].minimumCheese; - if (minimumCheese > 0 && currentResources[Game::Resources::RT_CHEESE] < minimumCheese) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_CHEESE] + if (minimumCheese > 0 && currentResources[ResourceType::RT_CHEESE] < minimumCheese) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_CHEESE] = this->aics[aiConfigIndex].tradeAmountFood; } // Bread: standard check with > 0 int const minimumBread = this->aics[aiConfigIndex].minimumBread; - if (minimumBread > 0 && currentResources[Game::Resources::RT_BREAD] < minimumBread) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_BREAD] + if (minimumBread > 0 && currentResources[ResourceType::RT_BREAD] < minimumBread) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_BREAD] = this->aics[aiConfigIndex].tradeAmountFood; } // Wheat: standard check with > 0 int const minimumWheat = this->aics[aiConfigIndex].minimumWheat; - if (minimumWheat > 0 && currentResources[Game::Resources::RT_WHEAT] < minimumWheat) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_WHEAT] + if (minimumWheat > 0 && currentResources[ResourceType::RT_WHEAT] < minimumWheat) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_WHEAT] = this->aics[aiConfigIndex].tradeAmountFood; } // Hops: standard check with > 0 int const minimumHop = this->aics[aiConfigIndex].minimumHop; - if (minimumHop > 0 && currentResources[Game::Resources::RT_HOPS] < minimumHop) { - DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[Game::Resources::RT_HOPS] + if (minimumHop > 0 && currentResources[ResourceType::RT_HOPS] < minimumHop) { + DAT_GameState::ptr->playerDataArray[playerID].resourcesToAcquireArray[ResourceType::RT_HOPS] = this->aics[aiConfigIndex].tradeAmountFood; } } diff --git a/src/OpenSHC/AI/AICState/setInitialAIC.cpp b/src/OpenSHC/AI/AICState/setInitialAIC.cpp new file mode 100644 index 000000000..9ab0b2c85 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setInitialAIC.cpp @@ -0,0 +1,45 @@ +#include "../AICState.func.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D18B0 + void AICState::setInitialAIC() + { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::wipeAICMemory, this)(); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_01, this)(1); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_02, this)(2); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_03, this)(3); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_04, this)(4); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_05, this)(5); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_06, this)(6); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_07, this)(7); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_08, this)(8); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_09, this)(9); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_10, this)(10); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_11, this)(0xb); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_12, this)(0xc); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_13, this)(0xd); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_14, this)(0xe); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_15, this)(0xf); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAICParameters_16, this)(0x10); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp b/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp new file mode 100644 index 000000000..01b79f53f --- /dev/null +++ b/src/OpenSHC/AI/AICState/setNextFarmTypeToBuild.cpp @@ -0,0 +1,25 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB1B0 + int AICState::setNextFarmTypeToBuild(PlayerID playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType == AITA_NULL) + return 0; + int aicIndex = aiType - 1; + + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex++; + if (DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex >= 8) + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex = 0; + if ((&this->aics[aicIndex].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex] == 0) + DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex = 0; + return (&this->aics[aicIndex].farm1)[DAT_GameState::instance.playerDataArray[playerID].aiFarmIndex]; + } +} +} diff --git a/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp b/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp new file mode 100644 index 000000000..56eda5317 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setNextMoveLocationForUnits.cpp @@ -0,0 +1,38 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/LandscapeState.func.hpp" +#include "OpenSHC/Map/Navigation/PathFindingState.func.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_LandscapeState.hpp" +#include "OpenSHC/Globals/DAT_PathFindingState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" +#include "OpenSHC/Globals/DAT_ViewportRenderState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CE3B0 + void AICState::setNextMoveLocationForUnits(int playerID) + { + int organismID + = (short)DAT_TileMapState::instance.OrganismLayer + [DAT_ViewportRenderState::instance + .translationMatrix[DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY] + .addXgetTile + + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX]; + if (organismID != 0) { + MACRO_CALL_MEMBER(Map::LandscapeState_Func::removeTree, DAT_LandscapeState::ptr)(organismID); + } + + MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::findLinkageBasedPathOrWalkRadius, + DAT_PathFindingState::ptr)(DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, -1, -1, 4000, FALSE); + MACRO_CALL_MEMBER(Map::Navigation::PathFindingState_Func::setDestinationPairsBasedOnPreviousSearch, + DAT_PathFindingState::ptr)( + playerID - 1, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp b/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp new file mode 100644 index 000000000..f9d369e1e --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTargetForTribeBasedOnTargetChoiceLogic.cpp @@ -0,0 +1,106 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Navigation/DirectionAlgorithmState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIC/AITargetChoice.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_DirectionAlgorithmState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CACB0 + void AICState::setTargetForTribeBasedOnTargetChoiceLogic(int owner, int tribe) + { + if (DAT_GameState::instance.playerDataArray[owner].aiType == 0) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[owner].aiType - 1; + + int targetPlayer = 0; + int minDistance = 10000; + int maxGold = 0; + int minPoints = 1000000; + int ownerKeepX = DAT_GameState::instance.playerDataArray[owner].campground.xEntry; + int ownerKeepY = DAT_GameState::instance.playerDataArray[owner].campground.yEntry; + for (int player = 1; player < 9; player++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[player] + == DAT_GameState::instance.mapAndTime.playerTeams[owner]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(player) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[player].campground.xEntry, + DAT_GameState::instance.playerDataArray[player].campground.yEntry, ownerKeepX, ownerKeepY); + + if (this->aics[aicIndex].TargetChoice == AIC::AITC_PLAYER) { + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[player] != -1 + && DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_CLOSEST) { + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_GOLD) { + if (DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD] + >= maxGold) { + maxGold + = DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD]; + targetPlayer = player; + } + } else if (this->aics[aicIndex].TargetChoice == AIC::AITC_BALANCED) { + int points = DAT_GameState::instance.playerDataArray[player].currentPopulation * 5 + + DAT_GameState::instance.playerDataArray[player].totalTroopValue + + DAT_GameState::instance.playerDataArray[player].currentResources[Game::Resources::RT_GOLD] / 100 + + DAT_DirectionAlgorithmState::instance.distanceHigh * 2; + if (points <= minPoints) { + minPoints = points; + targetPlayer = player; + } + } + } + + if (targetPlayer == 0) { + // Fallback: pick the closest enemy with a living lord + for (int player = 1; player < 9; player++) { + if (DAT_GameState::instance.mapAndTime.playerTeams[player] + == DAT_GameState::instance.mapAndTime.playerTeams[owner]) { + continue; + } + if (MACRO_CALL_MEMBER(Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)(player) + == 0) { + continue; + } + + MACRO_CALL_MEMBER(Map::Navigation::DirectionAlgorithmState_Func::setAxisBasedDistanceResult, + DAT_DirectionAlgorithmState::ptr)(DAT_GameState::instance.playerDataArray[player].campground.xEntry, + DAT_GameState::instance.playerDataArray[player].campground.yEntry, ownerKeepX, ownerKeepY); + if (DAT_DirectionAlgorithmState::instance.distanceHigh <= minDistance) { + minDistance = DAT_DirectionAlgorithmState::instance.distanceHigh; + targetPlayer = player; + } + } + + if (targetPlayer == 0) { + return; + } + } + + DAT_TribesState::instance.tribes[tribe].targetPlayer_outpost = (short)targetPlayer; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp b/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp new file mode 100644 index 000000000..ceaf2f197 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0x11ToAggressiveAndAttackIfConditionMet.cpp @@ -0,0 +1,42 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF560 + void AICState::setTribe0x11ToAggressiveAndAttackIfConditionMet(int playerID) + { + int tribeIndex = 17; + if (DAT_TroopValueState::instance.attackInfo.playerInfo[playerID - 1].moat2 <= 0) { + return; + } + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f7); + } +} +} diff --git a/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp new file mode 100644 index 000000000..5f96df880 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0xCtoAggressiveAndAttack.cpp @@ -0,0 +1,38 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF400 + void AICState::setTribe0xCtoAggressiveAndAttack(int playerID) + { + int tribeIndex = 12; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } +} +} diff --git a/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp b/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp new file mode 100644 index 000000000..ec4cde70e --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTribe0xbToAggressiveAndAttack.cpp @@ -0,0 +1,38 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Map/Units/Behavior/UnitStanceEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF6E0 + void AICState::setTribe0xbToAggressiveAndAttack(int playerID) + { + int tribeIndex = 11; + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[tribeIndex]; + if (tribeID == 0) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[tribeIndex]) { + return; + } + if (DAT_TribesState::instance.tribes[tribeID].selectionTargetUnitID == 0) { + return; + } + + DAT_TribesState::instance.tribes[tribeID].unitStance = Map::Units::Behavior::USE_AGGRESSIVE; + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::assignAttackTargetsForTribe, DAT_TribesState::ptr)( + tribeID, Map::Units::STBT_0x3f2); + } +} +} diff --git a/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp b/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp new file mode 100644 index 000000000..45c33ff67 --- /dev/null +++ b/src/OpenSHC/AI/AICState/setTriggerForAITauntResponse.cpp @@ -0,0 +1,17 @@ +#include "OpenSHC/Game/GameCore.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D1090 + void AICState::setTriggerForAITauntResponse() + { + if (MACRO_CALL_MEMBER(Game::GameCore_Func::getAreWeInAInGameMenu, DAT_GameCore::ptr)() != FALSE) { + this->aiTauntResponseTimeWindow = timeGetTime(); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp b/src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp new file mode 100644 index 000000000..94036fdec --- /dev/null +++ b/src/OpenSHC/AI/AICState/setupWorkshopProductionType.cpp @@ -0,0 +1,85 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Game/Resources/ResourceTypeShort.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Game::Resources::ResourceTypeShort; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CBE30 + void AICState::setupWorkshopProductionType(int playerID, int buildingID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AIT_NULL) { + return; + } + + int aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_BLACKSMITH) { + int setting = this->aics[aicIndex].blacksmithSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_BLACKSMITH); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_SWORD) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_SWORD; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_SWORD; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_MACE; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_MACE; + return; + } + + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_POLETURNER) { + int setting = this->aics[aicIndex].poleturnerSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_POLETURNER); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_SPEAR) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_SPEAR; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_SPEAR; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_PIKE; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_PIKE; + return; + } + + if (DAT_BuildingsState::instance.buildings[buildingID].buildingType == Map::Buildings::BT_FLETCHER) { + int setting = this->aics[aicIndex].fletcherSetting; + if (setting == -999) { + int itemType = MACRO_CALL_MEMBER( + Map::Buildings::BuildingsState_Func::chooseProductionItemType, DAT_BuildingsState::ptr)( + DAT_BuildingsState::instance.buildings[buildingID].owner, Map::Buildings::BT_FLETCHER); + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = (ResourceTypeShort)itemType; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = (ResourceTypeShort)itemType; + return; + } + if (setting == Game::Resources::RT_BOW) { + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_BOW; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_BOW; + return; + } + DAT_BuildingsState::instance.buildings[buildingID].producedItemType = Game::Resources::RT_CROSSBOW; + DAT_BuildingsState::instance.buildings[buildingID].producedItemTypeNext = Game::Resources::RT_CROSSBOW; + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp b/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp new file mode 100644 index 000000000..da204a269 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shareGoldAmongTeamMembers.cpp @@ -0,0 +1,77 @@ +// disable deprecation warnings for strcpy +#pragma warning(disable : 4996) + +#include "../AICState.func.hpp" + +#include "OpenSHC/OS.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/Text/TextManager.func.hpp" +#include "OpenSHC/DE/SHCDE/eTextSections.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_TextManagerObject.hpp" + +namespace OpenSHC { +namespace AI { + + // FIXME: The original function never sets a return value (eax is left over from the last computation), + // so it was most likely a void function. The header declares int, which forces the "return 0"s below + // and costs an extra "xor eax, eax" in the assembly. Change the return type to void in the header. + + // FUNCTION: STRONGHOLDCRUSADER 0x004D05D0 + void AICState::shareGoldAmongTeamMembers(int playerID, int gold) + { + int candidateCount = 0; + int candidates[9]; + for (int i = 1; i < 9; i++) { + if ((DAT_GameSynchronyState::instance.currentAIArray[i] != 0 + || DAT_GameSynchronyState::instance.currentPlayerFullIDArray[i] != -1) + && i != playerID + && DAT_GameState::instance.mapAndTime.playerTeams[i] + == DAT_GameState::instance.mapAndTime.playerTeams[playerID] + && DAT_GameState::instance.playerDataArray[i].lordKilledByPlayerID == 0) { + candidates[candidateCount] = i; + candidateCount++; + } + } + + if (candidateCount == 0) { + return; + } + + int goldPerPlayer = gold / candidateCount; + if (goldPerPlayer == 0) { + return; + } + + for (int j = 0; j < candidateCount; j++) { + int targetPlayer = candidates[j]; + if (DAT_GameSynchronyState::instance.currentPlayerFullIDArray[targetPlayer] != -1) { + // "Goods received from ally" + strcpy(DAT_GameSynchronyState::instance.receivedChatMessage, + MACRO_CALL_MEMBER(Text::TextManager_Func::getTextStringInGroupAtOffset, DAT_TextManagerObject::ptr)( + DE::SHCDE::TEXT_ALLIES2, 1)); + MACRO_CALL_MEMBER(Synchrony::GameSynchronyState_Func::addChatMessageToDisplayList, + DAT_GameSynchronyState::ptr)(targetPlayer, 0); + + // "Gold" + MACRO_CALL(OS_Func::_sprintf)(DAT_GameSynchronyState::instance.receivedChatMessage, "%d %s - ", + goldPerPlayer, + MACRO_CALL_MEMBER(Text::TextManager_Func::getTextStringInGroupAtOffset, DAT_TextManagerObject::ptr)( + DE::SHCDE::TEXT_GOODS, Game::Resources::RT_GOLD)); + MACRO_CALL_MEMBER(Synchrony::GameSynchronyState_Func::addChatMessageToDisplayList, + DAT_GameSynchronyState::ptr)(targetPlayer, playerID); + } + + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] + -= goldPerPlayer; + DAT_GameState::instance.playerDataArray[targetPlayer].currentResources[Game::Resources::RT_GOLD] + += goldPerPlayer; + } + return; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp b/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp new file mode 100644 index 000000000..ea63343e2 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldDestroyWoodCutterUnk.cpp @@ -0,0 +1,52 @@ +#include "OpenSHC/AI/AIVState.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" +#include "OpenSHC/Game/Resources/ResourceType.hpp" +#include "OpenSHC/Map/Buildings/BuildingType.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB890 + BOOLEnum AICState::shouldDestroyWoodCutterUnk(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return FALSE; + } + + int buildingID; + if (DAT_AIVState::instance.mapExtraInfo.totalWoodAvailable <= 0) { + buildingID = MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::findFirstBuildingIDForPlayerAndType, + DAT_BuildingsState::ptr)(playerID, Map::Buildings::BT_WOODCUTTERSHUT); + } else { + if (DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_WOOD] <= 20) { + return FALSE; + } + if (DAT_GameState::instance.playerDataArray[playerID].noLabourerBuildingCount < 2) { + return FALSE; + } + if (DAT_GameState::instance.playerDataArray[playerID].someCount10 < 25) { + return FALSE; + } + buildingID = DAT_GameState::instance.playerDataArray[playerID].someCount11; + } + + if (buildingID != 0) { + MACRO_CALL_MEMBER(AIVState_Func::resetCountdownInHeatMap, DAT_AIVState::ptr)( + (short)DAT_BuildingsState::instance.buildings[buildingID].x, + (short)DAT_BuildingsState::instance.buildings[buildingID].y); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::giveBackResourceForDestroyedBuilding, + DAT_BuildingsState::ptr)(buildingID, playerID, 50); + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::destroyBuilding, DAT_BuildingsState::ptr)( + buildingID); + return TRUE; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp new file mode 100644 index 000000000..51ca93934 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingArmoury.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA60 + BOOLEnum AICState::shouldNotBeBuildingArmoury(PlayerID playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType != Commands::M_MAPPER_ARMOURY) { + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].isArmouryAlmostFilledUp == 0; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp new file mode 100644 index 000000000..5ef58f276 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingGranary.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA30 + BOOLEnum AICState::shouldNotBeBuildingGranary(int playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType != Commands::M_MAPPER_GRANARY) { + return FALSE; + } + return DAT_GameState::instance.playerDataArray[playerID].granaryIsAlmostFilledUp == FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp b/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp new file mode 100644 index 000000000..b674070c3 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBeBuildingHovel.cpp @@ -0,0 +1,36 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB5B0 + BOOLEnum AICState::shouldNotBeBuildingHovel(PlayerID playerID, Commands::MappersEnum param_2) + { + if (param_2 != Commands::M_MAPPER_HOVEL) { + return FALSE; + } + + int const populationCap = DAT_GameState::instance.playerDataArray[playerID].populationCap; + if (populationCap <= 12) { + return FALSE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].currentPopulation < populationCap) { + return TRUE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].previousAvailablePeasants >= 5) { + return TRUE; + } + + if (DAT_GameState::instance.playerDataArray[playerID].averagePopulationGrowthUnk >= 5) { + return TRUE; + } + + return DAT_GameState::instance.playerDataArray[playerID].popularity < 5000; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp b/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp new file mode 100644 index 000000000..d44c311ef --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBuildKillingPits.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_AIVState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004ECA90 + BOOLEnum AICState::shouldNotBuildKillingPits(int playerID, Commands::MappersEnum commandBuildingType) + { + if (commandBuildingType == Commands::M_MAPPER_KILLING_PIT) { + return DAT_AIVState::instance.mapExtraInfo.playerTotalKillingPits[playerID] >= 100; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp b/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp new file mode 100644 index 000000000..8d1ab8554 --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldNotBuildMarketArmoryGranaryBarracksMercenarypost.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB9F0 + BOOLEnum AICState::shouldNotBuildMarketArmoryGranaryBarracksMercenarypost( + int playerID, Commands::MappersEnum param_2) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + // nervous: reject market/mercenarypost/barracks/armory types + if (param_2 != Commands::M_MAPPER_TRADEPOST && param_2 != Commands::M_MAPPER_BARRACKS_EURO + && param_2 != Commands::M_MAPPER_BARRACKS_ARAB && param_2 != Commands::M_MAPPER_ARMOURY) { + return param_2 != Commands::M_MAPPER_GRANARY; + } + } else { + // not nervous: only reject if destroy tracker > 0 and not marketplace + if (DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker > 0 + && param_2 != Commands::M_MAPPER_TRADEPOST) { + return param_2 != Commands::M_MAPPER_GRANARY; + } + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp new file mode 100644 index 000000000..83caadc1e --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingAPositiveFearFactor.cpp @@ -0,0 +1,50 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB700 + BOOLEnum AICState::shouldWaitWithBuildingAPositiveFearFactor(int playerID, Commands::MappersEnum param_2) + { + switch (param_2) { + case Commands::M_MAPPER_GARDEN1: + case Commands::M_MAPPER_GARDEN2: + case Commands::M_MAPPER_GARDEN3: + case Commands::M_MAPPER_GARDEN4: + case Commands::M_MAPPER_GARDEN5: + case Commands::M_MAPPER_GARDEN6: + case Commands::M_MAPPER_GARDEN7: + case Commands::M_MAPPER_GARDEN8: + case Commands::M_MAPPER_GARDEN9: + case Commands::M_MAPPER_GARDEN10: + case Commands::M_MAPPER_GARDEN11: + case Commands::M_MAPPER_GARDEN12: + case Commands::M_MAPPER_MAYPOLE: + case Commands::M_MAPPER_STATUE1: + case Commands::M_MAPPER_STATUE2: + case Commands::M_MAPPER_STATUE3: + case Commands::M_MAPPER_STATUE4: + case Commands::M_MAPPER_STATUE5: + case Commands::M_MAPPER_SHRINE1: + case Commands::M_MAPPER_SHRINE2: + case Commands::M_MAPPER_SHRINE3: + case Commands::M_MAPPER_SHRINE4: + case Commands::M_MAPPER_SHRINE5: + case Commands::M_MAPPER_DANCING_BEAR: + case Commands::M_MAPPER_POND1: + case Commands::M_MAPPER_POND2_SMALL: + case Commands::M_MAPPER_POND3_LARGE1: + case Commands::M_MAPPER_POND4_LARGE2: + DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay += 1; + if (DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay < 200) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivPositiveFearFactorDelay = 0; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp new file mode 100644 index 000000000..768c6f8de --- /dev/null +++ b/src/OpenSHC/AI/AICState/shouldWaitWithBuildingNegativeFearFactor.cpp @@ -0,0 +1,39 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CB610 + BOOLEnum AICState::shouldWaitWithBuildingNegativeFearFactor(int playerID, Commands::MappersEnum mapper) + { + switch (mapper) { + case Commands::M_MAPPER_GALLOWS: + case Commands::M_MAPPER_STOCKS: + case Commands::M_MAPPER_CESS_PIT1: + case Commands::M_MAPPER_CESS_PIT2: + case Commands::M_MAPPER_CESS_PIT3: + case Commands::M_MAPPER_CESS_PIT4: + case Commands::M_MAPPER_BURNING_STAKE: + case Commands::M_MAPPER_GIBBET: + case Commands::M_MAPPER_DUNGEON: + case Commands::M_MAPPER_RACK_STRETCHING: + case Commands::M_MAPPER_RACK_FLOGGING: + case Commands::M_MAPPER_CHOPPING_BLOCK: + case Commands::M_MAPPER_DUNKING_STOOL: + if (DAT_GameState::instance.playerDataArray[playerID].popularity < 5000) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay++; + if (DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay < 100) { + return TRUE; + } + DAT_GameState::instance.playerDataArray[playerID].aivNegativeFearFactorDelay = 0; + return FALSE; + } + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp new file mode 100644 index 000000000..ce194bb2d --- /dev/null +++ b/src/OpenSHC/AI/AICState/smallestTribeOfUnitType.cpp @@ -0,0 +1,59 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC990 + int AICState::smallestTribeOfUnitType(int playerID, AIVUnitType unitType) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == AITA_NULL) { + return 0; + } + + int maxGroups = this->aics[DAT_GameState::instance.playerDataArray[playerID].aiType - 1].defWallPatrolGroups; + int count = DAT_GameState::instance.playerDataArray[playerID].aivUnitLocationSlotLocationCount[unitType]; + int smallestTribeID = 0; + int smallestIndex = 0; + int smallestSize = 1000; + if (count > maxGroups && (unitType == 8 || unitType == 10 || unitType == 17)) { + count = maxGroups; + } + + int i = 0; + int offset = DAT_SkirmishDefinedData::instance.AITribeIDOffsetForAIVUnitType[unitType]; + for (;; i++) { + if (i >= count) { + if (smallestTribeID == 0) + return 0; + break; + } + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + i]; + if (tribeID == 0 + || DAT_TribesState::instance.tribes[tribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + i]) { + smallestTribeID = MACRO_CALL_MEMBER( + Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + smallestIndex = i; + break; + } + if (DAT_TribesState::instance.tribes[tribeID].size < smallestSize) { + smallestSize = DAT_TribesState::instance.tribes[tribeID].size; + smallestTribeID = tribeID; + smallestIndex = i; + } + } + + DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[offset + smallestIndex] = (short)smallestTribeID; + DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[offset + smallestIndex] + = DAT_TribesState::instance.tribes[smallestTribeID].uid; + return smallestTribeID; + } +} +} diff --git a/src/OpenSHC/AI/AICState/someChatMessageSelection.cpp b/src/OpenSHC/AI/AICState/someChatMessageSelection.cpp new file mode 100644 index 000000000..f6194c79a --- /dev/null +++ b/src/OpenSHC/AI/AICState/someChatMessageSelection.cpp @@ -0,0 +1,127 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameCore.func.hpp" +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/Synchrony/GameSynchronyState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/Commands/GameCommandType.hpp" +#include "OpenSHC/Game/GameMode.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_GameSynchronyState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::Commands::GameCommandType; + using OpenSHC::Game::GameMode; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D10B0 + void AICState::someChatMessageSelection() + { + int candidates[9]; + int candidateCount; + int total; + int i; + int playerID; + + if ((MACRO_CALL_MEMBER(OpenSHC::Game::GameCore_Func::getAreWeInAInGameMenu, DAT_GameCore::ptr)() != FALSE) + && (DAT_GameSynchronyState::instance.currentGameMode == OpenSHC::Game::GM_SKIRMISH_SINGLE_PLAYER)) { + + if (this->aiTauntResponseTimeWindow == 0) { + return; + } + + if (timeGetTime() - this->aiTauntResponseTimeWindow <= 2000) { + return; + } + + candidateCount = 0; + // Players 1..8, manually unrolled by 4 in the original. + for (playerID = 1; playerID < 9; playerID += 4) { + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 1] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 1] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 1; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 2] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 2] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 2; + candidateCount++; + } + if ((DAT_GameSynchronyState::instance.currentAIArray[playerID + 3] != 0) + && (DAT_GameState::instance.mapAndTime.playerTeams[playerID + 3] + != DAT_GameState::instance.mapAndTime + .playerTeams[DAT_GameSynchronyState::instance.currentPlayerSlotID])) { + candidates[candidateCount] = playerID + 3; + candidateCount++; + } + } + + if (candidateCount > 0) { + total = 0; + // NOTE: 'i = total' keeps the (redundant) loop entry check of the original. + for (i = total; i < candidateCount; i++) { + total += DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[candidates[i]].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeType; + } + + if (total != 0) { + int roll = (int)SEC_RNG::instance.currentNumber1; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber1, SEC_RNG::ptr)(); + roll = roll % total; + + for (i = 0; i < candidateCount; i++) { + playerID = candidates[i]; + roll -= DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[playerID].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeType; + if (roll < 0) { + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[1] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[2] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[3] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[4] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[5] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[6] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[7] = 1; + DAT_GameSynchronyState::instance.DAT_ChatMessageReceiverArray[8] = 1; + DAT_GameSynchronyState::instance.DAT_ChatTauntOrMessage = -2 + - DAT_SkirmishDefinedData::instance + .MaxBreachTribes[DAT_GameState::instance.playerDataArray[playerID].aiType + + OpenSHC::AI::AIT_FREDERICK] + .tribeCount; + DAT_GameSynchronyState::instance.DAT_GameCommandParam0 = playerID; + MACRO_CALL_MEMBER(OpenSHC::Synchrony::GameSynchronyState_Func::queueCommand, + DAT_GameSynchronyState::ptr)(OpenSHC::Commands::GCT_TAUNT_OR_CHAT); + break; + } + } + } + } + } + + this->aiTauntResponseTimeWindow = 0; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp b/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp new file mode 100644 index 000000000..0a37073ba --- /dev/null +++ b/src/OpenSHC/AI/AICState/syncBehaviourType7UnitTribes.cpp @@ -0,0 +1,35 @@ +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/Map/Units/UnitLogicState.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC7B0 + void AICState::syncBehaviourType7UnitTribes(int playerID) + { + for (int unitID = 1; unitID < (int)DAT_UnitsState::instance.maxUnitCount; unitID++) { + if (DAT_UnitsState::instance.units[unitID].owner != playerID) + continue; + if (DAT_UnitsState::instance.units[unitID].logicalState != Map::Units::ULS_NORMAL) + continue; + if (DAT_UnitsState::instance.units[unitID].dying != 0) + continue; + if (DAT_UnitsState::instance.units[unitID].aiUnitBehaviourType != 7) + continue; + + int tribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[167]; + if (DAT_UnitsState::instance.units[unitID].tribeID == tribeID) + continue; + + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::removeUnitFromTribe, DAT_TribesState::ptr)( + unitID, DAT_UnitsState::instance.units[unitID].tribeID); + MACRO_CALL_MEMBER(Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)(unitID, tribeID); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp b/src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp new file mode 100644 index 000000000..480816f06 --- /dev/null +++ b/src/OpenSHC/AI/AICState/teamIsWeakRelativeToEnemy.cpp @@ -0,0 +1,52 @@ +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004D0190 + BOOLEnum AICState::teamIsWeakRelativeToEnemy(int playerID, BOOLEnum param_2) + { + // strength of the own team in percent of the enemy strength + int _relativeStrength; + if (DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue == 0) { + _relativeStrength = 100; + } else { + _relativeStrength = DAT_GameState::instance.playerDataArray[playerID].totalTeamTroopValue * 100 + / DAT_GameState::instance.playerDataArray[playerID].totalEnemyTroopValue; + } + DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy = _relativeStrength; + + // the team counts as weak below a threshold that is lower with an enclosed keep + // and much higher during the first 12000 ticks + if (param_2) { + if (MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 70) + return FALSE; + } else if ((int)DAT_GameCore::instance.mapTimeInTicks < 12000) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 300) + return FALSE; + } else if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 100) { + return FALSE; + } + } else { + // without enemy ranged troops there is no reason to become nervous + if (DAT_GameState::instance.playerDataArray[playerID].totalEnemyRangedTroopValue < 100) + return FALSE; + if (MACRO_CALL_MEMBER(Game::GameStateStructures_Func::checkKeepEnclosed, DAT_GameState::ptr)(playerID)) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 50) + return FALSE; + } else if ((int)DAT_GameCore::instance.mapTimeInTicks < 12000) { + if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 250) + return FALSE; + } else if (DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy >= 75) { + return FALSE; + } + } + return TRUE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp b/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp new file mode 100644 index 000000000..393f1a259 --- /dev/null +++ b/src/OpenSHC/AI/AICState/unitIDIsRangedOrArmored.cpp @@ -0,0 +1,27 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/Skirmish/SkirmishDefinedData.hpp" + +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::Game::Skirmish::SkirmishDefinedData; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CC8D0 + BOOLEnum AICState::unitIDIsRangedOrArmored(int unitID) + { + int _unitType = DAT_UnitsState::instance.units[unitID].unitType; + + for (int i = 0; i < 7; i++) { + if (DAT_SkirmishDefinedData::instance.RangedAndArmoredUnits[i] == _unitType) { + return TRUE; + } + } + + return FALSE; + } +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp b/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp new file mode 100644 index 000000000..eef4ddd57 --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIBehaviour.cpp @@ -0,0 +1,94 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/TileMapState.func.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TileMapState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIRecruitUnitChoice; + using OpenSHC::AI::AIType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D53E0 + void AICState::updateAIBehaviour() + { + if (DAT_GameState::instance.mapAndTime.dayTicks == 10) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeOutpostTribesAttack, this)(); + + // every AI gets its own tick of the day, week and month, so the work is spread out + for (int _playerID = 1; _playerID < 9; _playerID++) { + if (DAT_GameState::instance.playerDataArray[_playerID].aiType == OpenSHC::AI::AIT_NULL) + continue; + if (DAT_GameState::instance.playerDataArray[_playerID].playerDeathRelated != 0) + continue; + if (DAT_GameState::instance.playerDataArray[_playerID].lordKilledByPlayerID != 0) + continue; + int _aicIndex = DAT_GameState::instance.playerDataArray[_playerID].aiType - 1; + + if (DAT_GameState::instance.mapAndTime.dayTicks == _playerID) { + if (DAT_GameState::instance.mapAndTime.skirmishNoRushTicks == 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitSortieRangedUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitSortieMeleeUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recruitHarrassingSiegeEngines, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiAssignMoatDiggers, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCreateSiegeUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitEngineers, this)(_playerID); + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiBuyAndSellGoods, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiGiveRaidInstructions, this)(_playerID); + } + + if (DAT_GameState::instance.mapAndTime.weekTicks == _playerID) { + if (DAT_GameState::instance.mapAndTime.skirmishNoRushTicks == 0) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRecruitEngineerForOilDuty, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateTribeLocations, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIPlayerState, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiUpdateTaxesAndRations, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setFoodBuyPlan, this)(_playerID); + + // on the month tick the strength update happens below, with the nervousness handling enabled + if (DAT_GameState::instance.mapAndTime.monthTicks != _playerID) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIStrengthState, this)(_playerID, 0); + + if (DAT_GameState::instance.playerDataArray[_playerID].aiRequestDelay != 0) + DAT_GameState::instance.playerDataArray[_playerID].aiRequestDelay--; + if (DAT_GameState::instance.playerDataArray[_playerID].nervousBikCountdown != 0) + DAT_GameState::instance.playerDataArray[_playerID].nervousBikCountdown--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown3 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown3--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown4 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown4--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown5 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown5--; + if (DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown6 != 0) + DAT_GameState::instance.playerDataArray[_playerID].someAiCountdown6--; + } + + if (DAT_GameState::instance.mapAndTime.monthTicks == _playerID) { + MACRO_CALL_MEMBER( + OpenSHC::Map::TileMapState_Func::countMoatsOwnedByEachPlayer, DAT_TileMapState::ptr)(); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::instructTribe166ToMove, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveMoveCommandToSortieUnits, this)(_playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::updateAIStrengthState, this)(_playerID, 1); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::selectAttackTarget, this)(_playerID); + + if (DAT_GameState::instance.playerDataArray[_playerID].aiRecruitUnitChoiceState + == OpenSHC::AI::AIRUC_RAIDING) { + DAT_GameState::instance.playerDataArray[_playerID].canStartSpending = 1; + } else { + // only spend on recruiting while gold is above the AIC's threshold + DAT_GameState::instance.playerDataArray[_playerID].canStartSpending + = DAT_GameState::instance.playerDataArray[_playerID].currentResources[0xf] + > this->aics[_aicIndex].recruitGoldThreshold; + } + } + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp new file mode 100644 index 000000000..5050ed89e --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIPlayerState.cpp @@ -0,0 +1,283 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Audio/SFX/SFXState.func.hpp" +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.hpp" +#include "OpenSHC/Map/Units/UnitsState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_BuildingsState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SFXState.hpp" +#include "OpenSHC/Globals/DAT_UnitsState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D49E0 + void AICState::updateAIPlayerState(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int _aicIndex = DAT_GameState::instance.playerDataArray[playerID].aiType - 1; + + // an ally asked for help and is still alive: drop everything and go to their keep + if (DAT_GameState::instance.playerDataArray[playerID].requestStateUnk != 1 + && DAT_GameState::instance.playerDataArray[playerID].requestStateUnk == 2 + && MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearRallyFlagOnAllAIAttackTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToPlayerIDsKeepIfPossible, this)( + playerID, DAT_GameState::instance.playerDataArray[playerID].playerID_askerUnk); + return; + } + + int _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; + if (_aiPlayerState == 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiShouldAttackOrWaitForTeamCoordination, this)( + playerID)) { + // send own units back to keep or campfire + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiRetreatUnits, this)(playerID); + return; + } + + // proceed with attack + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 1; + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue = 0; + DAT_GameState::instance.playerDataArray[playerID].aiCoordinatedAttackPatience = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setAttackCoordinationLevel, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackStrength, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setCurrentAttackRaidParameter, this)(playerID); + // fixme: struct layout. This looks like an int[10][8] attack tracker starting at + // attackedByPlayerArrayUnk (0x2BDC) and ending where aivUnitLocationSlots begins (0x2D1C). + (&DAT_GameState::instance + .playerDataArray[DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID] + .attackedByPlayerArrayUnk)[playerID * 8]++; + } else if ((MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID) + && _aiPlayerState != 9) + || (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0 && _aiPlayerState <= 5) + || DAT_GameState::instance.playerDataArray[playerID].attackTicker >= 200) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 8; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearRallyFlagOnAllAIAttackTribes, this)(playerID); + + if (!MACRO_CALL_MEMBER(OpenSHC::Map::Units::UnitsState_Func::getAliveLordForPlayer, DAT_UnitsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) { + // attacked player is dead + if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState <= 3) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER( + OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, DAT_BuildingsState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)( + playerID); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiPlayerState < 7) { + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 7; + } + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::clearOutdatedAITribes, this)(playerID); + + _aiPlayerState = DAT_GameState::instance.playerDataArray[playerID].aiPlayerState; + + if (_aiPlayerState == 1) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recreateEngineersTribe, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 2; + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::initializeGatePathfindingForOwner, + DAT_BuildingsState::ptr)(playerID); + // computes the first siege location for units + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeEnemyKeepApproachTile, this)(playerID); + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile > 0) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackTribesToLocations, this)(playerID); + return; + } + // no approach tile found: back to idle + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + return; + } + + if (_aiPlayerState == 2) { + // gathering: wait until enough of the attack force has rallied + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiChoiceAttForceRallyPercentage, this)(playerID)) + return; + // send the attacking units to move closer + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 3; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 1); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setNextMoveLocationForUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::giveMoveCommandToAttackTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::useAITribe_0xe_toPlaceTunnels, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::removeOrganismsAndSetMoveDestinationPairs, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::commandFourTribesToMove, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playTauntToAttackedPlayerBikFromPlayer, this)(playerID); + return; + } + + if (_aiPlayerState == 3) { + // send the attacking units closer to the castle + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackingUnitsToNextRallyPoint, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::processAttForceRallyPercentage, this)(playerID)) + return; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 4; + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe12AggressiveApproach, this)(playerID); + return; + } + + if (_aiPlayerState == 4) { + // siege: wait AttAssaultDelay (skipped without siege engines) before the assault + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::moveAttackingUnitsToNextRallyPoint, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAttack, this)(playerID); + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::countSiegeEngineUnits, this)(playerID) <= 0) + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue = 100; + DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue++; + if (DAT_GameState::instance.playerDataArray[playerID].attackAssaultDelayRelatedValue + <= this->aics[_aicIndex].AttAssaultDelay) + return; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Audio::SFX::SFXState_Func::playSFXAtLocationFullVolume, DAT_SFXState::ptr)( + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceX, + DAT_GameState::instance.playerDataArray[playerID].shortestDistanceY, 0x14); + if (!MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) { + // castle is closed: attack the walls + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 5; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe12AggressiveApproach, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe11StandGroundToGate, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandTribe13DefensiveWave, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + return; + } + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 6; + return; + } + + if (_aiPlayerState == 5) { + // attack walls + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile <= 0) + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01++; + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter = 0; + if (DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 >= 4) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandWallAssaultTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiCommandSiegeEngineTribes, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0x11ToAggressiveAndAttackIfConditionMet, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setEightTribe0xc0TribesToAggressiveAndAttack, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeToAttack, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::removeOrganismsAndSetMoveDestinationPairs, this)( + playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::commandFourTribesToMove, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0xCtoAggressiveAndAttack, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::setTribe0xbToAggressiveAndAttack, this)(playerID); + } + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendAITribe_0xf_toAttackPatrol, this)(playerID); + if (!MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID) + && !MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canUnitsNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 6; + return; + } + + if (_aiPlayerState == 6) { + // enemy castle breached + if (DAT_GameState::instance.playerDataArray[playerID].shortestDistanceTile <= 0) + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + int _attackedPlayerID = DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID; + DAT_GameState::instance.playerDataArray[playerID].someResourceCounter = 0; + MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::calculateAttackVectorsToCampFireOfPlayer, + DAT_GameState::ptr)(_attackedPlayerID); + if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendUnitsToAttackBreachedCastle, this)(playerID)) + return; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + if (MACRO_CALL_MEMBER(OpenSHC::Game::GameStateStructures_Func::canUnitsNavigateFromKeepToKeep, + DAT_GameState::ptr)(playerID, DAT_GameState::instance.playerDataArray[playerID].attackedPlayerID)) + return; + // the breach got closed again: back to attacking the walls + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 5; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::recomputeAIPathCostToAttackedKeep, this)(playerID, 0); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::computeAttackPossibilities, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].unknownCounter01 = 0; + return; + } + + if (_aiPlayerState == 7) { + // target died mid-attack: linger for AttUnitPatrolRecommandDelay, then retreat + DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue++; + if (DAT_GameState::instance.playerDataArray[playerID].unitPatrolRecommandRelatedDelayValue + <= this->aics[_aicIndex].AttUnitPatrolRecommandDelay) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, + DAT_BuildingsState::ptr)(DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)(playerID); + return; + } + + if (_aiPlayerState == 8) { + // retreat from attack + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 9; + DAT_GameState::instance.playerDataArray[playerID].attackTicker = 0; + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::disbandAIPlayerSiegeUnits, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::Map::Buildings::BuildingsState_Func::removeSiegeBuildings, + DAT_BuildingsState::ptr)(DAT_GameState::instance.playerDataArray[playerID].currentAttackWave, playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiReassignTunnelersToTribe, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::findAttackTribePositionAndPathToDefenses, this)(playerID); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::makeUnitsGoDefensiveAndBackToSomeLocation, this)(playerID); + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 0) + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playAnger1BikFromPlayer, this)(playerID); + return; + } + + if (_aiPlayerState == 9) { + // retreating: back to idle once the units are home (or after 20 ticks) + DAT_GameState::instance.playerDataArray[playerID].attackTicker++; + if (DAT_GameState::instance.playerDataArray[playerID].attackTicker < 20 + && !MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::percentageNonMovingTribesGTEAICSpecified, this)( + playerID) + && !MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNoTroopsOrAllAreDiggers, this)(playerID)) + return; + DAT_GameState::instance.playerDataArray[playerID].aiPlayerState = 0; + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp new file mode 100644 index 000000000..f302283a6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateAIStrengthState.cpp @@ -0,0 +1,214 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Random/RNG.func.hpp" +#include "OpenSHC/AI/AINervousDestroyBuildingCategory.hpp" +#include "OpenSHC/AI/AINervousDestroyBuildingCategoryInt.hpp" +#include "OpenSHC/AI/AIRecruitUnitChoice.hpp" +#include "OpenSHC/AI/AIStrengthType.hpp" +#include "OpenSHC/Game/GameMode2.hpp" +#include "OpenSHC/Game/TrailType.hpp" +#include "OpenSHC/WindowsHelper/Enums/BOOLEnum.hpp" + +#include "OpenSHC/Globals/DAT_GameCore.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/SEC_RNG.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AINervousDestroyBuildingCategory; + using OpenSHC::AI::AINervousDestroyBuildingCategoryInt; + using OpenSHC::AI::AIRecruitUnitChoice; + using OpenSHC::AI::AIStrengthType; + using OpenSHC::Game::GameMode2; + using OpenSHC::Game::TrailType; + using OpenSHC::WindowsHelper::Enums::BOOLEnum; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D3320 + void AICState::updateAIStrengthState(int playerID, int aiRecruitUnitChoicePreference) + { + int _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == 0) + return; + + if ((char)aiRecruitUnitChoicePreference == 0) { + if (DAT_GameCore::instance.gameMode_2 != OpenSHC::Game::GM_SKIRMISH_AND_MULTIPLAYER) + return; + if (DAT_GameCore::instance.isSkirmishTrail != TRUE) + return; + if (DAT_GameCore::instance.currentTrailType != OpenSHC::Game::TT_EXTREME) + return; + } + + int _aicIndex = _aiType - 1; + + int _currentGold = DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf]; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 0; + if (_currentGold < 200) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 0; + else if (_currentGold < 2000) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState = 2; + + int _currentPopulation = DAT_GameState::instance.playerDataArray[playerID].currentPopulation; + if (_currentPopulation >= 15) { + if (_currentPopulation < 40) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + } + + int _totalFood = DAT_GameState::instance.playerDataArray[playerID].totalFood; + if (_totalFood >= 20) { + if (_totalFood < 200) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 1; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + } + + int _totalTroopCount = DAT_GameState::instance.playerDataArray[playerID].totalAttackTroops + + DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk + + DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk; + if (_totalTroopCount >= 8) { + if (_totalTroopCount < 40) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 2; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthState += 4; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthState <= 2) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + else if (DAT_GameState::instance.playerDataArray[playerID].aiStrengthState <= 7) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_DEFAULT; + else + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_STRONG; + + // while the first AIV pause is still running, the AI always feels weak + if (DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex == 1 + && DAT_GameState::instance.playerDataArray[playerID] + .aivPauses[DAT_GameState::instance.playerDataArray[playerID].aivCurrentPauseIndex] + > 0) + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + + // the three recruitProb* triples are laid out as Default, Weak, Strong, matching AIStrengthType + int _recruitProbDefensive = (&this->aics[_aicIndex] + .recruitProbDefDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int _recruitProbAttack = (&this->aics[_aicIndex] + .recruitProbAttackDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int _recruitProbRaid = (&this->aics[_aicIndex] + .recruitProbRaidDefault)[DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling]; + int _totalRecruitProb = _recruitProbAttack + _recruitProbRaid + _recruitProbDefensive; + + int _aiRecruitUnitChoice; + if (_totalRecruitProb != 0) { + int _random = SEC_RNG::instance.currentNumber2 % _totalRecruitProb; + MACRO_CALL_MEMBER(OpenSHC::Random::RNG_Func::nextRandomNumber2, SEC_RNG::ptr)(); + + if ((int)DAT_GameCore::instance.mapTimeInTicks < 4800) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_DEFENSIVE; + else if (_random < _recruitProbDefensive) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_DEFENSIVE; + else if (_random < _recruitProbRaid + _recruitProbDefensive) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_RAIDING; + else + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } else { + _aiRecruitUnitChoice = aiRecruitUnitChoicePreference; + } + + if (_aiRecruitUnitChoice == OpenSHC::AI::AIRUC_RAIDING) { + if (DAT_GameState::instance.playerDataArray[playerID].totalRaidingTroopsUnk >= MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::getCurrentDesiredAttackRaidUnitCount, this)(_aicIndex, playerID)) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } else if (_aiRecruitUnitChoice == OpenSHC::AI::AIRUC_DEFENSIVE) { + if (DAT_GameState::instance.playerDataArray[playerID].totalDefensiveTroopsUnk + >= this->aics[_aicIndex].DefTotal) + _aiRecruitUnitChoice = OpenSHC::AI::AIRUC_ATTACKING; + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState != _aiRecruitUnitChoice) + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = _aiRecruitUnitChoice; + + if ((char)aiRecruitUnitChoicePreference == 0) + return; + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::teamIsWeakRelativeToEnemy, this)(playerID, TRUE)) { + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker = 0; + return; + } + + int _gold = DAT_GameState::instance.playerDataArray[playerID].currentResources[0xf]; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = OpenSHC::AI::AIRUC_DEFENSIVE; + + if (_gold > 500) { + if (_gold > 2500) { + DAT_GameState::instance.playerDataArray[playerID].aiStateCounter++; + if ((DAT_GameState::instance.playerDataArray[playerID].aiStateCounter & 1) + && DAT_GameState::instance.playerDataArray[playerID].relativeStrengthOfTeamComparedToEnemy + <= 20) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::shareGoldAmongTeamMembers, this)( + playerID, _gold / 10); + } + } + } else if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 7) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FEAR_FACTOR_BUILDINGS); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS); + } else if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker <= 5) { + // unreachable in the original as well: anything <= 5 is already handled by the <= 7 branch + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_RESOURCE_BUILDINGS); + } + + if (DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker > 1) + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker--; + return; + } + + int _destroyChoice = DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker; + if (_destroyChoice > 0) { + if (!MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNotEnoughSupplies, this)(playerID)) { + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker = 0; + return; + } + + DAT_GameState::instance.playerDataArray[playerID].resourcesToAcquireArray[2] = 20; + DAT_GameState::instance.playerDataArray[playerID].aiStrengthFeeling = OpenSHC::AI::AIST_WEAK; + DAT_GameState::instance.playerDataArray[playerID].aiRecruitUnitChoiceState = OpenSHC::AI::AIRUC_DEFENSIVE; + + if (_destroyChoice == 8) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FEAR_FACTOR_BUILDINGS); + } else if (_destroyChoice == 7) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WORKSHOPS_AND_RELIGIOUS_BUILDINGS); + } else if (_destroyChoice == 6) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_GATEHOUSES_TOWERS_GUILDS); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_BARRACKS_MERCPOST_STABLES); + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_WATER_BUILDINGS); + } else if (_destroyChoice == 5) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_RESOURCE_BUILDINGS); + } else if (_destroyChoice == 4) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::destroyAnyBuildingInCategory, this)( + playerID, OpenSHC::AI::AINDBC_FARMS); + } + + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker--; + } else if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::teamIsWeakRelativeToEnemy, this)(playerID, FALSE)) { + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::playNervous2BikFromPlayer, this)(playerID); + DAT_GameState::instance.playerDataArray[playerID].aiNervousActionsTracker = 8; + DAT_GameState::instance.playerDataArray[playerID].aiStateCounter = 0; + } else if (MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::hasNotEnoughSupplies, this)(playerID)) { + DAT_GameState::instance.playerDataArray[playerID].aiBuildingDestroyChoiceTracker = 8; + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/updateTribeLocations.cpp b/src/OpenSHC/AI/AICState/updateTribeLocations.cpp new file mode 100644 index 000000000..83f75e037 --- /dev/null +++ b/src/OpenSHC/AI/AICState/updateTribeLocations.cpp @@ -0,0 +1,58 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" + +#include "OpenSHC/Globals/DAT_EntityState.hpp" +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + + // FUNCTION: STRONGHOLDCRUSADER 0x004D4940 + void AICState::updateTribeLocations(int playerID) + { + AITypeInt _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + + if (_aiType == OpenSHC::AI::AIT_NULL) { + return; + } + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + = DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + 1; + + if (DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter + >= this->aics[_aiType - 1].defWallPatrolRallyTime) { + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + = DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeHits + 1; + + DAT_GameState::instance.playerDataArray[playerID].defWallPatrolRallyTimeWaitCounter = 0; + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::ifHasTribe0SendUnitsToKeep, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendTribeAtOffset1ToCampfire, this)(playerID); + + if (DAT_GameState::instance.mapAndTime.monthTicks == playerID) { + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::digNearestMoat, this)(playerID); + } + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendRangedUnitTribesToAIVSlotsOrKeepIfNervous, this)(playerID); + + MACRO_CALL_MEMBER( + OpenSHC::AI::AICState_Func::sendCertainUnitTribesToAIVSlotsOrKeepIfNervousAndNotEnclosed, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendWallPatrolUnitTribesToAIVLocations, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::sendOilMenForOilPickupOrToAIVLocations, this)(playerID); + + MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::aiGiveOuterPatrolCommand, this)(playerID); + } + +} +} diff --git a/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp b/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp new file mode 100644 index 000000000..01599292d --- /dev/null +++ b/src/OpenSHC/AI/AICState/useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers.cpp @@ -0,0 +1,105 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/AITypeInt.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_SkirmishDefinedData.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::AITypeInt; + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CEFD0 + void AICState::useAITribe_0x12_toPlaceSiegeTentsAndAssignEngineers(int playerID) + { + int _aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (_aiType == OpenSHC::AI::AIT_NULL) + return; + int _aicIndex = _aiType - 1; + int _aiTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0x12]; + if (_aiTribeID == 0) + return; + if (DAT_TribesState::instance.tribes[_aiTribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0x12]) + return; + + int _availableEngineers = DAT_TribesState::instance.tribes[_aiTribeID].size; + int _requiredEngineers; + int _totalRequiredEngineers = 0; + + for (int i = 0; i < 8; i++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] <= 0) + break; + for (int j = 0; j < 6; j++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] + == DAT_SkirmishDefinedData::instance.SiegeEngineUnitType[j]) { + _requiredEngineers = DAT_SkirmishDefinedData::instance.SiegeEngineTypeRequiredEngineers[j]; + break; + } + } + if (_totalRequiredEngineers + _requiredEngineers > _availableEngineers) + break; + _totalRequiredEngineers += _requiredEngineers; + } + + if (_totalRequiredEngineers <= 0) + return; + + for (; DAT_TribesState::instance.tribes[_aiTribeID].size != 0; _totalRequiredEngineers--) { + if (_totalRequiredEngineers <= 0) + break; + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::popUnitFromTribe, DAT_TribesState::ptr)( + _aiTribeID); + } + + MappersEnum _siegeEngineType; + for (int i = 0; i < 8; i++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] <= 0) + return; + for (int j = 0; j < 6; j++) { + if ((&this->aics[_aicIndex].SiegeEngine1)[i] + == DAT_SkirmishDefinedData::instance.SiegeEngineUnitType[j]) { + _requiredEngineers = DAT_SkirmishDefinedData::instance.SiegeEngineTypeRequiredEngineers[j]; + _siegeEngineType + = (MappersEnum)DAT_SkirmishDefinedData::instance.SiegeEngineCommandBuildingTypes[j]; + break; + } + } + if (_requiredEngineers > _availableEngineers) + return; + _availableEngineers -= _requiredEngineers; + + int _tribeID = MACRO_CALL_MEMBER(OpenSHC::AI::AICState_Func::addEngineersToSelection, this)( + playerID, _requiredEngineers); + if (_tribeID == 0) + return; + + DAT_TribesState::instance.tribes[_tribeID].tribeType = OpenSHC::AI::Tribes::AITT_ENGINEERS; + DAT_TribesState::instance.tribes[_tribeID].tribeBehaviorType + = OpenSHC::Map::Units::STBT_0x410_SIEGE_EQUIPMENT_CONSTRUCTION; + DAT_TribesState::instance.tribes[_tribeID].attackWave + = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave; + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TroopValueState_Func::placeSiegeTentOrTunnelAtSuitableLocationAndAssignEngineers, + DAT_TroopValueState::ptr)(_tribeID, _siegeEngineType, 60, (UnitInstructionType)15); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp b/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp new file mode 100644 index 000000000..f9a3597ee --- /dev/null +++ b/src/OpenSHC/AI/AICState/useAITribe_0xe_toPlaceTunnels.cpp @@ -0,0 +1,67 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Map/Units/TribesState.func.hpp" +#include "OpenSHC/Map/Units/TroopValueState.func.hpp" +#include "OpenSHC/AI/AIType.hpp" +#include "OpenSHC/AI/Tribes/AITribeType.hpp" +#include "OpenSHC/Commands/MappersEnum.hpp" +#include "OpenSHC/Map/Units/SomeTribeBehaviorType.hpp" +#include "OpenSHC/Map/Units/UnitInstructionType.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" +#include "OpenSHC/Globals/DAT_TribesState.hpp" +#include "OpenSHC/Globals/DAT_TroopValueState.hpp" + +namespace OpenSHC { +namespace AI { + + using OpenSHC::AI::AIType; + using OpenSHC::AI::Tribes::AITribeType; + using OpenSHC::Commands::MappersEnum; + using OpenSHC::Map::Units::SomeTribeBehaviorType; + using OpenSHC::Map::Units::UnitInstructionType; + + // FUNCTION: STRONGHOLDCRUSADER 0x004CF180 + void AICState::useAITribe_0xe_toPlaceTunnels(int playerID) + { + if (DAT_GameState::instance.playerDataArray[playerID].aiType == OpenSHC::AI::AIT_NULL) + return; + int _aiTribeID = DAT_GameState::instance.playerDataArray[playerID].aiTribeIDs[0xe]; + if (_aiTribeID == 0) + return; + if (DAT_TribesState::instance.tribes[_aiTribeID].uid + != DAT_GameState::instance.playerDataArray[playerID].aiTribeUIDs[0xe]) + return; + int _countdown = DAT_TribesState::instance.tribes[_aiTribeID].size; + + while (DAT_TribesState::instance.tribes[_aiTribeID].size != 0) { + if (_countdown <= 0) + break; + + int _unitID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::popUnitFromTribe, DAT_TribesState::ptr)(_aiTribeID); + // Yes this line has to be right here for the code to match. + _countdown--; + + int _tribeID = MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TribesState_Func::createTribeForPlayer, DAT_TribesState::ptr)(playerID); + + DAT_TribesState::instance.tribes[_tribeID].attackWave + = DAT_GameState::instance.playerDataArray[playerID].currentAttackWave; + + DAT_TribesState::instance.tribes[_tribeID].tribeType = OpenSHC::AI::Tribes::AITT_TUNNELERS; + + DAT_TribesState::instance.tribes[_tribeID].tribeBehaviorType = OpenSHC::Map::Units::STBT_0x415; + + MACRO_CALL_MEMBER(OpenSHC::Map::Units::TribesState_Func::addUnitToTribe, DAT_TribesState::ptr)( + _unitID, _tribeID); + + MACRO_CALL_MEMBER( + OpenSHC::Map::Units::TroopValueState_Func::placeSiegeTentOrTunnelAtSuitableLocationAndAssignEngineers, + DAT_TroopValueState::ptr)( + _tribeID, OpenSHC::Commands::M_MAPPER_TUNNEL_CONSTRUCTION, 50, (UnitInstructionType)21); + } + } + +} +} diff --git a/src/OpenSHC/AI/AICState/wipeAICMemory.cpp b/src/OpenSHC/AI/AICState/wipeAICMemory.cpp new file mode 100644 index 000000000..ed4a02035 --- /dev/null +++ b/src/OpenSHC/AI/AICState/wipeAICMemory.cpp @@ -0,0 +1,18 @@ +#include "OpenSHC/IO/LowLevelMemory.func.hpp" +#include "OpenSHC/AI/AICState.hpp" + +#include "OpenSHC/Globals/DAT_LowLevelMemory.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004C6D30 + void AICState::wipeAICMemory() + { + for (int i = 0; i < 20; i++) { + MACRO_CALL_MEMBER(IO::LowLevelMemory_Func::fillMemory_ByteValue, DAT_LowLevelMemory::ptr)( + 676, '\0', &this->aics[i]); + } + } +} +} diff --git a/src/OpenSHC/AI/AICState/yieldCowThrow.cpp b/src/OpenSHC/AI/AICState/yieldCowThrow.cpp new file mode 100644 index 000000000..c381e7b3e --- /dev/null +++ b/src/OpenSHC/AI/AICState/yieldCowThrow.cpp @@ -0,0 +1,29 @@ +#include "OpenSHC/AI/AICState.hpp" +#include "OpenSHC/AI/AITypeA.hpp" + +#include "OpenSHC/Globals/DAT_GameState.hpp" + +namespace OpenSHC { +namespace AI { + + // FUNCTION: STRONGHOLDCRUSADER 0x004CFF60 + BOOLEnum AICState::yieldCowThrow(int playerID) + { + int aiType = DAT_GameState::instance.playerDataArray[playerID].aiType; + if (aiType != AITA_NULL) { + int aicIndex = aiType - 1; + if (DAT_GameState::instance.mapAndTime.skirmishNoCowThrowing == 0 + && this->aics[aicIndex].CowThrowInterval != 0 + && DAT_GameState::instance.playerDataArray[playerID].counter > 0) { + DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter++; + if (DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter + > this->aics[aicIndex].CowThrowInterval) { + DAT_GameState::instance.playerDataArray[playerID].aiCowThrowCounter = 0; + return TRUE; + } + } + } + return FALSE; + } +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index f6e47278b..19c7e8b9e 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -34157,73 +34157,73 @@ SHC_3BB0A8C1_0x004C6CD0 | 0.0% | Pending SHC_3BB0A8C1_0x004C6D30 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C6D60 | 0.0% | Pending +SHC_3BB0A8C1_0x004C6D60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7120 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7520 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7910 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7910 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C7D20 | 0.0% | Pending +SHC_3BB0A8C1_0x004C7D20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8110 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8530 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8530 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8920 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8920 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C8D20 | 0.0% | Pending +SHC_3BB0A8C1_0x004C8D20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9120 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C94F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004C94F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9900 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9900 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004C9CF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004C9CF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA0C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA0C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA4D0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CA8A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CA8A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CAC70 | 0.0% | Pending +SHC_3BB0A8C1_0x004CAC70 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CACB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CACB0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CAD10 | 0.0% | Pending SHC_3BB0A8C1_0x004CAE20 | 0.0% | Pending -SHC_3BB0A8C1_0x004CAEA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CAEA0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB060 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB120 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB150 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB150 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB180 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB180 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB1B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB1B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB220 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB220 | 97.8% | Reimplemented -SHC_3BB0A8C1_0x004CB290 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB290 | 98.1% | Reimplemented -SHC_3BB0A8C1_0x004CB330 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB330 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB3A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB3A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB4D0 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB540 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB540 | 97.5% | Reimplemented -SHC_3BB0A8C1_0x004CB5B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB5B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB610 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB610 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB66C | 0.0% | Pending @@ -34501,7 +34501,7 @@ SHC_3BB0A8C1_0x004CB6FA | 0.0% | Pending SHC_3BB0A8C1_0x004CB6FB | 0.0% | Pending -SHC_3BB0A8C1_0x004CB700 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB700 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CB754 | 0.0% | Pending @@ -34845,55 +34845,55 @@ SHC_3BB0A8C1_0x004CB803 | 0.0% | Pending SHC_3BB0A8C1_0x004CB804 | 0.0% | Pending -SHC_3BB0A8C1_0x004CB810 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB810 | 92.3% | Reimplemented -SHC_3BB0A8C1_0x004CB890 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB890 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB940 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB940 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CB9F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CB9F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CBA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CBA50 | 90.7% | flag slot order differs -SHC_3BB0A8C1_0x004CBE30 | 0.0% | Pending +SHC_3BB0A8C1_0x004CBE30 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CBFA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC000 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004CC000 | 92.9% | Reimplemented -SHC_3BB0A8C1_0x004CC070 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC070 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC250 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC250 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC390 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC390 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC420 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC420 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC520 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC5A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC5A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC680 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC680 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC6B0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CC720 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC720 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CC7B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC7B0 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CC840 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC840 | 94.3% | Reimplemented -SHC_3BB0A8C1_0x004CC8D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC8D0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC910 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC910 | 97.1% | Reimplemented -SHC_3BB0A8C1_0x004CC990 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC990 | 56.6% | regalloc/locals differ -SHC_3BB0A8C1_0x004CCAF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCAF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CCBE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCBE0 | 99.0% | Reimplemented -SHC_3BB0A8C1_0x004CCD20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCD20 | 66.0% | frame and regalloc differ SHC_3BB0A8C1_0x004CCEDC | 0.0% | Pending @@ -35261,289 +35261,289 @@ SHC_3BB0A8C1_0x004CCFA0 | 0.0% | Pending SHC_3BB0A8C1_0x004CCFA1 | 0.0% | Pending -SHC_3BB0A8C1_0x004CCFB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CCFB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD070 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD070 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD0C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD0C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD110 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD160 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD160 | 91.7% | Register allocation differs -SHC_3BB0A8C1_0x004CD250 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD250 | 94.7% | Reimplemented -SHC_3BB0A8C1_0x004CD2A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD2A0 | 59.60% | Reimplemented functionally, code block ordering problem -SHC_3BB0A8C1_0x004CD560 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD560 | 95.2% | Reimplemented -SHC_3BB0A8C1_0x004CD690 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD690 | 94.9% | Reimplemented -SHC_3BB0A8C1_0x004CD7B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD7B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CD8F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CD8F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDA50 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDAB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDAB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDB20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDB20 | 44.4% | regalloc/spill differs -SHC_3BB0A8C1_0x004CDBC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDBC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDCA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDCF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDCF0 | 77.2% | regalloc/counters differ -SHC_3BB0A8C1_0x004CDE60 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDE60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CDF10 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDF10 | 98.2% | Reimplemented -SHC_3BB0A8C1_0x004CDFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CDFD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE040 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE040 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE110 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE1A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE1A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE200 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE200 | 64.5% | spilled local differs SHC_3BB0A8C1_0x004CE270 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE3B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE3B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE430 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE430 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CE4D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE4D0 | 93.8% | Reimplemented SHC_3BB0A8C1_0x004CE530 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE5F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE5F0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE660 | 0.0% | Pending SHC_3BB0A8C1_0x004CE6B0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE790 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE790 | 98.0% | Reimplemented -SHC_3BB0A8C1_0x004CE830 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE830 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CE860 | 0.0% | Pending SHC_3BB0A8C1_0x004CE890 | 0.0% | Pending -SHC_3BB0A8C1_0x004CE930 | 0.0% | Pending +SHC_3BB0A8C1_0x004CE930 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEA50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEA50 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEB50 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEB50 | 98.8% | Reimplemented SHC_3BB0A8C1_0x004CEB80 | 0.0% | Pending -SHC_3BB0A8C1_0x004CEC80 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEC80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CED90 | 0.0% | Pending +SHC_3BB0A8C1_0x004CED90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEEA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEEA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CEFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CEFD0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CF060 | 0.0% | Pending -SHC_3BB0A8C1_0x004CF180 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF180 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF270 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF270 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF2E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF2E0 | 94.7% | esi/edi regalloc differs -SHC_3BB0A8C1_0x004CF400 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF400 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF470 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF470 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF4F0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF4F0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF560 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF560 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF5E0 | 100.0% | Reimplemented without for-loop +SHC_3BB0A8C1_0x004CF5E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF660 | 100.0% | Reimplemented without for-loop +SHC_3BB0A8C1_0x004CF660 | 97.2% | Reimplemented -SHC_3BB0A8C1_0x004CF6E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF6E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF750 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF750 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF7C0 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004CF7C0 | 98.2% | Reimplemented SHC_3BB0A8C1_0x004CF7F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004CF870 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004CF870 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CF920 | 0.0% | Pending +SHC_3BB0A8C1_0x004CF920 | 96.9% | Reimplemented -SHC_3BB0A8C1_0x004CFA10 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFA10 | 96.9% | Reimplemented -SHC_3BB0A8C1_0x004CFB20 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFB20 | 97.8% | Reimplemented -SHC_3BB0A8C1_0x004CFBC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFBC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFCD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFCD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFF60 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFF60 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CFFD0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CFFD0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D00A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D00A0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0110 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0190 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0190 | 100.0% | Reimplemented, compiler didn't optimize one tail -SHC_3BB0A8C1_0x004D0280 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0280 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0520 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0520 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0580 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0580 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D05D0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D05D0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0720 | 0.0% | Pending -SHC_3BB0A8C1_0x004D07C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D07C0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0830 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0830 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D08B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D08B0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D0910 | 0.0% | Pending -SHC_3BB0A8C1_0x004D0A20 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0A20 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0A80 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0A80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0B10 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0B10 | 68.0% | Load scheduling differs -SHC_3BB0A8C1_0x004D0B70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0B70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0BB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0BB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0C00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0C00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0C70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0C70 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0CF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0CF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0D40 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0D40 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0DB0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0DB0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0E00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0E50 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0E50 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0EA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0EA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0EF0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0EF0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0F40 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0F40 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0F90 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0F90 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D0FC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D0FC0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1000 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1000 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1050 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1050 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1090 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1090 | 90.0% | Reimplemented -SHC_3BB0A8C1_0x004D10B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D10B0 | 100.0% | Reimplemented, one reordered push edi SHC_3BB0A8C1_0x004D11C0 | 0.0% | Pending SHC_3BB0A8C1_0x004D12A0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1320 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1320 | 72.3% | Style pass, asm differs SHC_3BB0A8C1_0x004D1700 | 0.0% | Pending SHC_3BB0A8C1_0x004D1710 | 0.0% | Pending -SHC_3BB0A8C1_0x004D18B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D18B0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D1950 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1950 | 98.0% | Reimplemented -SHC_3BB0A8C1_0x004D1A00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1A00 | 58.0% | Register allocation differs -SHC_3BB0A8C1_0x004D1D60 | 0.0% | Pending +SHC_3BB0A8C1_0x004D1D60 | 62.4% | register permutation -SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented, for-loop didn't work +SHC_3BB0A8C1_0x004D1F00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2000 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2000 | 41.9% | regalloc/frame differ SHC_3BB0A8C1_0x004D2080 | 0.0% | Pending SHC_3BB0A8C1_0x004D20C0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D2500 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2500 | 94.6% | Reimplemented -SHC_3BB0A8C1_0x004D2660 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2660 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2730 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2730 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2790 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D2790 | 91.3% | Reimplemented -SHC_3BB0A8C1_0x004D27E0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D27E0 | 92.0% | Reimplemented SHC_3BB0A8C1_0x004D2830 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2870 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2870 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D28B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D28B0 | 86.3% | select call block layout diffe -SHC_3BB0A8C1_0x004D2A70 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2A70 | 54.3% | regalloc/spills differ -SHC_3BB0A8C1_0x004D2E00 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2E00 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D2EC0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2EC0 | 74.1% | Call diffs, load order differs -SHC_3BB0A8C1_0x004D2F80 | 0.0% | Pending +SHC_3BB0A8C1_0x004D2F80 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D30E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D30E0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D3320 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3320 | 97.2% | ebp/ebx regalloc differs -SHC_3BB0A8C1_0x004D3780 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3780 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D3850 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3850 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D38C0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D38C0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3940 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D3960 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D39B0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D39B0 | 96.6% | Reimplemented SHC_3BB0A8C1_0x004D39F0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D3AE0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3AE0 | 43.4% | regalloc/spills differ -SHC_3BB0A8C1_0x004D3F20 | 0.0% | Pending +SHC_3BB0A8C1_0x004D3F20 | 97.4% | Reimplemented -SHC_3BB0A8C1_0x004D4040 | 100.0% | Reimplemented, for loop didn't work +SHC_3BB0A8C1_0x004D4040 | 97.0% | Reimplemented -SHC_3BB0A8C1_0x004D40B0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D40B0 | 94.9% | Reimplemented SHC_3BB0A8C1_0x004D4130 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4220 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4220 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4340 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4340 | 54.63% | Reimplemented, different register usage SHC_3BB0A8C1_0x004D4400 | 0.0% | Pending -SHC_3BB0A8C1_0x004D44E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D44E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4530 | 0.0% | Pending @@ -35551,15 +35551,15 @@ SHC_3BB0A8C1_0x004D45E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D4630 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D4680 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4680 | 64.80% | Stuck, register usage SHC_3BB0A8C1_0x004D4870 | 0.0% | Pending -SHC_3BB0A8C1_0x004D48F0 | 100.0% | Reimplemented +SHC_3BB0A8C1_0x004D48F0 | 86.7% | Register allocation differs -SHC_3BB0A8C1_0x004D4940 | 0.0% | Pending +SHC_3BB0A8C1_0x004D4940 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004D49E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D49E0 | 52.84% | Register allocation differences, logic checks out SHC_3BB0A8C1_0x004D5010 | 100.0% | Reimplemented @@ -35571,7 +35571,7 @@ SHC_3BB0A8C1_0x004D5370 | 0.0% | Pending SHC_3BB0A8C1_0x004D53A0 | 0.0% | Pending -SHC_3BB0A8C1_0x004D53E0 | 0.0% | Pending +SHC_3BB0A8C1_0x004D53E0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004D55D0 | 0.0% | Pending diff --git a/tools/reimplementation-helper/batch/README.md b/tools/reimplementation-helper/batch/README.md index a5de287a6..08b5d17c6 100644 --- a/tools/reimplementation-helper/batch/README.md +++ b/tools/reimplementation-helper/batch/README.md @@ -10,56 +10,56 @@ output goes to `build-RelWithDebInfo/batch/` (git-ignored). A recent `clang-form (`pip install clang-format`, or set `CLANG_FORMAT`) is needed for the scripts that rewrite sources, because the Visual Studio bundled one is too old for `.clang-format`. -| Script | Purpose | -|---|---| -| `build_quiet.py [--keep-going]` | `build.bat RelWithDebInfo OpenSHC.dll`, prints only errors and `BUILD_OK`/`BUILD_FAIL` (build.bat exits 0 even on errors). Retries once on a PDB server error, see the note below. | -| `syntax_check.py [substr...]` | `cl /Zs` every (matching) list file serially; finds all compile errors in one pass without linking. | -| `reccmp_report.py [--run] pct\|save\|cmp\|diff` | Match % and *normalized* % (call/tail-jump targets and resolver addresses ignored) per function, snapshots, before/after comparison, compact asm diffs. | -| `orig_asm.py NAME [--both\|--stats]` | The **original** instruction stream of one function (not the interleaved diff), in address order; `--stats` reports the tells that decide how to reimplement it: `mov reg, 0` (handwritten asm), frame pointer (built /Od), frame size, jump tables. | -| `show_functions.py PREFIX [--skip-100]` | Print function bodies under `src/OpenSHC/PREFIX` with their percentages, in the block format `splice_functions.py` reads. | -| `splice_functions.py BLOCKS.txt` | Replace many function bodies at once (keeps the `// FUNCTION:` line, can add includes/usings), then clang-format. | -| `commit_progress_batch.py PREFIX... [Name=remark]` | One `reimplement:` commit per changed function with its status line, like `commit_progress`. | -| `fix_func_refs.py [--drop-include H]` | After a header refactor: rewrite `X_Func::name` references whose resolver moved to another namespace. | -| `move_to_resolver_namespace.py FILE...` | After a header refactor: re-wrap a definition in its resolver's new namespace and `git mv` it to the matching folder. | - -Analysis helpers, for deciding *what* to change when a function is stuck below 100%: - -| Script | Purpose | -|---|---| -| `diff_triage.py [--run] [filter]` | One line per function: match %, raw and register/slot-normalized difflib ratios, `sub esp` frame size per side, byte-sized `[esp]` accesses per side, `ret` placement. Flags `FRAME` / `BYTE` / `RET` / `alloc-only`. | -| `diff_slots.py NAME` | Every `[esp + N]` slot with its access count on each side, and the slots only we use. Run after `diff_triage.py` reports `FRAME`. | -| `diff_jcc.py [NAME]` | Comparisons whose operator is one strictness step off the original (`jl`/`jle`, `jg`/`jge`, `ja`/`jae`) or whose `cmp` operands are the other way round, with context. | -| `diff_types.py [--run] [NAME]` | The non-comparison counterpart of `diff_jcc.py`: `movsx` against `movzx` for the same field (its signedness is wrong in the generated header), `add` against `sub` with the same magnitude (a sign error in a formula - how `2400/n + 40` was caught being `- 40`). It pairs only equal-length runs inside one reccmp block, so it stays quiet once the streams drift: it reports nothing across the 101 functions of `Game::GameStateStructures` and fires on both bugs when they are reintroduced. | -| `quick_diff.py FILE.cpp... [-q]` | Compile one file with `/FA` and diff it against the original function disassembled straight from `_original/` - about 6 seconds, no link and no reccmp, so a dozen source variants can be tried in the time of one build. Shows the whole original function, which reccmp's diff does not when ours is shorter. A triage loop: confirm a 1.0 with `reccmp_report.py --run`, and do not trust a *rise* of its ratio below about 0.6 - on a badly matching function difflib rewards a different alignment, and one such "gain" was a 10-point loss in reccmp. | -| `push_pattern.py FILE.cpp...` | Per call, the argument-push run as `pushes/interleaved` for both sides. A neat run (`0` interleaved) means every argument was already in a register or slot, i.e. the source had a **local**; a mixed run is what writing the expression inline at the call produces. A starred row is a call where the two sides disagree - add the local, or drop it, accordingly. Counts only calls whose set-up it can see, so a differing call *total* means our source has an extra call (a duplicated block), not that the tool failed. | -| `reorder_search.py FILE NAME [--pairs] [--dry]` | Hill-climb the match % by reordering independent statements: permute runs of >=3 by default, or swap every adjacent independent pair with `--pairs`. Builds once per move and reverts anything that does not improve. | +| Script | Purpose | +| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `build_quiet.py [--keep-going]` | `build.bat RelWithDebInfo OpenSHC.dll`, prints only errors and `BUILD_OK`/`BUILD_FAIL` (build.bat exits 0 even on errors). Retries once on a PDB server error, see the note below. | +| `syntax_check.py [substr...]` | `cl /Zs` every (matching) list file serially; finds all compile errors in one pass without linking. | +| `reccmp_report.py [--run] pct\|save\|cmp\|diff` | Match % and _normalized_ % (call/tail-jump targets and resolver addresses ignored) per function, snapshots, before/after comparison, compact asm diffs. | +| `orig_asm.py NAME [--both\|--stats]` | The **original** instruction stream of one function (not the interleaved diff), in address order; `--stats` reports the tells that decide how to reimplement it: `mov reg, 0` (handwritten asm), frame pointer (built /Od), frame size, jump tables. | +| `show_functions.py PREFIX [--skip-100]` | Print function bodies under `src/OpenSHC/PREFIX` with their percentages, in the block format `splice_functions.py` reads. | +| `splice_functions.py BLOCKS.txt` | Replace many function bodies at once (keeps the `// FUNCTION:` line, can add includes/usings), then clang-format. | +| `commit_progress_batch.py PREFIX... [Name=remark]` | One `reimplement:` commit per changed function with its status line, like `commit_progress`. | +| `fix_func_refs.py [--drop-include H]` | After a header refactor: rewrite `X_Func::name` references whose resolver moved to another namespace. | +| `move_to_resolver_namespace.py FILE...` | After a header refactor: re-wrap a definition in its resolver's new namespace and `git mv` it to the matching folder. | + +Analysis helpers, for deciding _what_ to change when a function is stuck below 100%: + +| Script | Purpose | +| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `diff_triage.py [--run] [filter]` | One line per function: match %, raw and register/slot-normalized difflib ratios, `sub esp` frame size per side, byte-sized `[esp]` accesses per side, `ret` placement. Flags `FRAME` / `BYTE` / `RET` / `alloc-only`. | +| `diff_slots.py NAME` | Every `[esp + N]` slot with its access count on each side, and the slots only we use. Run after `diff_triage.py` reports `FRAME`. | +| `diff_jcc.py [NAME]` | Comparisons whose operator is one strictness step off the original (`jl`/`jle`, `jg`/`jge`, `ja`/`jae`) or whose `cmp` operands are the other way round, with context. | +| `diff_types.py [--run] [NAME]` | The non-comparison counterpart of `diff_jcc.py`: `movsx` against `movzx` for the same field (its signedness is wrong in the generated header), `add` against `sub` with the same magnitude (a sign error in a formula - how `2400/n + 40` was caught being `- 40`). It pairs only equal-length runs inside one reccmp block, so it stays quiet once the streams drift: it reports nothing across the 101 functions of `Game::GameStateStructures` and fires on both bugs when they are reintroduced. | +| `quick_diff.py FILE.cpp... [-q]` | Compile one file with `/FA` and diff it against the original function disassembled straight from `_original/` - about 6 seconds, no link and no reccmp, so a dozen source variants can be tried in the time of one build. Shows the whole original function, which reccmp's diff does not when ours is shorter. A triage loop: confirm a 1.0 with `reccmp_report.py --run`, and do not trust a _rise_ of its ratio below about 0.6 - on a badly matching function difflib rewards a different alignment, and one such "gain" was a 10-point loss in reccmp. | +| `push_pattern.py FILE.cpp...` | Per call, the argument-push run as `pushes/interleaved` for both sides. A neat run (`0` interleaved) means every argument was already in a register or slot, i.e. the source had a **local**; a mixed run is what writing the expression inline at the call produces. A starred row is a call where the two sides disagree - add the local, or drop it, accordingly. Counts only calls whose set-up it can see, so a differing call _total_ means our source has an extra call (a duplicated block), not that the tool failed. | +| `reorder_search.py FILE NAME [--pairs] [--dry]` | Hill-climb the match % by reordering independent statements: permute runs of >=3 by default, or swap every adjacent independent pair with `--pairs`. Builds once per move and reverts anything that does not improve. | ### Choosing what to work on -| Script | Purpose | -|---|---| -| `rank_functions.py [filter] [--top N]` | Rank by source lines x (1 - match), so the big badly-matching functions come first instead of the small ones with a low percentage. | -| `scan_dispatch.py [filter]` | Functions whose dispatch form disagrees with the original (jump table on one side, if/else-if chain on the other), and which way to fix each. | -| `scan_chains.py [filter] [--min N]` | `if/else if` chains comparing one expression against several constants: the candidates to convert when `scan_dispatch.py` says the original has a table we do not. | -| `try_styles.py FUNC [style...]` | Compile and measure several hand-written variants of one function, restore the winner, print the table. Variants live in `build-RelWithDebInfo/batch/variants/FUNC/*.cpp`. | +| Script | Purpose | +| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `rank_functions.py [filter] [--top N]` | Rank by source lines x (1 - match), so the big badly-matching functions come first instead of the small ones with a low percentage. | +| `scan_dispatch.py [filter]` | Functions whose dispatch form disagrees with the original (jump table on one side, if/else-if chain on the other), and which way to fix each. | +| `scan_chains.py [filter] [--min N]` | `if/else if` chains comparing one expression against several constants: the candidates to convert when `scan_dispatch.py` says the original has a table we do not. | +| `try_styles.py FUNC [style...]` | Compile and measure several hand-written variants of one function, restore the winner, print the table. Variants live in `build-RelWithDebInfo/batch/variants/FUNC/*.cpp`. | ### Readability passes These undo decompiler artefacts. Each measured as code-generation neutral or better where it was applied, but none is guaranteed to be -- re-measure after running one. -| Script | Purpose | -|---|---| -| `declare_at_use.py [filter]` | Move the leading declaration block down to each variable's first use, and drop declarations that are never used. Only moves one when the first assignment provably dominates every later use. | -| `decast.py [filter]` | Collapse doubled casts, `(int)((int)(x))` -> `(int)(x)`. Skips `(int)((int)X + Y)`, where the inner cast covers one operand and dropping it would move the conversion onto the sum. | -| `deparen.py [filter] [--apply]` | Drop redundant brackets from `if`/`while` conditions using C precedence. Keeps the load-bearing ones (`(a \|\| b) && c`, `(x & 7) == 0`, assignments and comma operators inside conditions). Dry run unless `--apply`. | -| `test_deparen.py` | Precedence tests for `deparen.py`. Run after touching its table: an earlier version silently rewrote `(a != 0 \|\| b != 0) && (x & 0x100) == 0` into a different expression that still compiled. | -| `undiv.py [filter] [--apply]` | Turn MSVC's biased shift for a signed power-of-two division back into a division: `(x + (x >> 0x1f & 7U)) >> 3` -> `x / 8`. Dry run unless `--apply`. Usually a large gain rather than merely neutral: over one namespace it was worth up to 22% on a single function. | -| `test_undiv.py` | Cases for `undiv.py`, including the near-misses it must leave alone: a shift disagreeing with the mask, a mask that is not 2^k - 1, and a bias taken from another variable. | -| `unmod.py [filter] [--apply]` | The same for a signed power-of-two remainder: `v = x & 0x8000000f;` plus the negative-case repair is `v = x % 16`. Forces a signed dividend, because the repair only exists for a signed operand and an unsigned `%` compiles to a bare `and`. | -| `test_unmod.py` | Cases for `unmod.py`: a repair constant that does not complement the mask, a repair applied to another variable, and a bare mask with no repair. | -| `unptr.py [filter] [--apply]` | Replace the decompiler's field-walking pointers (`piVar1 = &x.f; *piVar1 = *piVar1 + 1;`) with the field itself. Only the two-adjacent-line shape, since a pointer freezes the address while the field form re-evaluates the index. Reports which pointers are still in use. | -| `test_unptr.py` | Cases for `unptr.py`, including a pointer declared on the spot (`char* pcVar3 = &...`, whose type prefix must go with it) and the set-up-then-use-later shape it must leave alone. | +| Script | Purpose | +| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `declare_at_use.py [filter]` | Move the leading declaration block down to each variable's first use, and drop declarations that are never used. Only moves one when the first assignment provably dominates every later use. | +| `decast.py [filter]` | Collapse doubled casts, `(int)((int)(x))` -> `(int)(x)`. Skips `(int)((int)X + Y)`, where the inner cast covers one operand and dropping it would move the conversion onto the sum. | +| `deparen.py [filter] [--apply]` | Drop redundant brackets from `if`/`while` conditions using C precedence. Keeps the load-bearing ones (`(a \|\| b) && c`, `(x & 7) == 0`, assignments and comma operators inside conditions). Dry run unless `--apply`. | +| `test_deparen.py` | Precedence tests for `deparen.py`. Run after touching its table: an earlier version silently rewrote `(a != 0 \|\| b != 0) && (x & 0x100) == 0` into a different expression that still compiled. | +| `undiv.py [filter] [--apply]` | Turn MSVC's biased shift for a signed power-of-two division back into a division: `(x + (x >> 0x1f & 7U)) >> 3` -> `x / 8`. Dry run unless `--apply`. Usually a large gain rather than merely neutral: over one namespace it was worth up to 22% on a single function. | +| `test_undiv.py` | Cases for `undiv.py`, including the near-misses it must leave alone: a shift disagreeing with the mask, a mask that is not 2^k - 1, and a bias taken from another variable. | +| `unmod.py [filter] [--apply]` | The same for a signed power-of-two remainder: `v = x & 0x8000000f;` plus the negative-case repair is `v = x % 16`. Forces a signed dividend, because the repair only exists for a signed operand and an unsigned `%` compiles to a bare `and`. | +| `test_unmod.py` | Cases for `unmod.py`: a repair constant that does not complement the mask, a repair applied to another variable, and a bare mask with no repair. | +| `unptr.py [filter] [--apply]` | Replace the decompiler's field-walking pointers (`piVar1 = &x.f; *piVar1 = *piVar1 + 1;`) with the field itself. Only the two-adjacent-line shape, since a pointer freezes the address while the field form re-evaluates the index. Reports which pointers are still in use. | +| `test_unptr.py` | Cases for `unptr.py`, including a pointer declared on the spot (`char* pcVar3 = &...`, whose type prefix must go with it) and the set-up-then-use-later shape it must leave alone. | ## Typical loop @@ -73,11 +73,25 @@ python reccmp_report.py diff someFunction # inspe python commit_progress_batch.py Map/Units "someFunction=LTCG ecx reuse" ``` +## Which `norm` is which + +The two tools print a column called `norm` and they do **not** mean the same thing: + +- `reccmp_report.py`'s `norm` ignores call/tail-jump targets and resolver addresses only. It does **not** normalize + registers, so a function that matches the original instruction-for-instruction except that two registers are + swapped still reports `norm` equal to its raw percentage. Do not read a low `norm` here as "real structure differs". +- `diff_triage.py`'s `norm` does normalize registers and stack slots, and is the one that tells you whether source + work can still pay. + +`diff_triage.py` also silently **skips** functions whose blocks it cannot pair - 12 of 68 in one `AI::AICState` run, +and they were exactly the lowest-scoring ones. If a function you care about is missing from its table, fall back to +reading `reccmp_report.py diff NAME` and judging by eye whether the two streams differ only in register names. + ## Reading `diff_triage.py` The two ratios are the quickest way to tell whether more source work can pay: -- `norm` much higher than `raw` - only register and stack-slot *naming* differs. +- `norm` much higher than `raw` - only register and stack-slot _naming_ differs. The allocator is not reachable from the source; record the reason and move on. - `norm` still low - real structure or ordering differs; keep looking. - `FRAME` - one local too many or too few. Chase it with `diff_slots.py`; this has @@ -109,7 +123,7 @@ bugs that Ghidra had decompiled wrongly. - `build_quiet.py` and `syntax_check.py` both retry such a failure twice, because it has two causes. A wedged server survives the kill `build.bat` does up front, so the first retry stops this worktree's `mspdbsrv.exe` (`common.kill_pdb_server`, never other worktrees'). A wedged - *endpoint* outlives every `mspdbsrv.exe` using it, so the second retry moves to a fresh name + _endpoint_ outlives every `mspdbsrv.exe` using it, so the second retry moves to a fresh name (`common.fresh_pdb_endpoint`) rather than retrying into one that will keep failing. - In `syntax_check.py` a PDB failure is not just a lost run: the fatal line counts as an error against whichever file hit it and the rest of the batch still gets checked, so without the diff --git a/tools/reimplementation-helper/batch/commit_progress_batch.py b/tools/reimplementation-helper/batch/commit_progress_batch.py index 241123951..c17a43e53 100644 --- a/tools/reimplementation-helper/batch/commit_progress_batch.py +++ b/tools/reimplementation-helper/batch/commit_progress_batch.py @@ -58,7 +58,10 @@ def commit(cpp, address, percent, remark): return False, "address %s not in %s (wrong FUNCTION address?)" % (address, STATUS_FILE) STATUS_FILE.write_text("".join(lines), encoding="utf-8", newline="") git("add", cpp, str(STATUS_FILE)) - r = git("commit", "-m", "reimplement: %s %.2f%% %s" % (function_id, percent, remark)) + # commit only these two paths: anything else already in the index (a `git checkout other-branch --` + # stages what it writes) would otherwise be swept into the first function's commit + r = git("commit", "-m", "reimplement: %s %.2f%% %s" % (function_id, percent, remark), + "--only", "--", cpp, str(STATUS_FILE)) return r.returncode == 0, r.stdout[-200:] + r.stderr[-300:] diff --git a/tools/reimplementation-helper/batch/quick_diff.py b/tools/reimplementation-helper/batch/quick_diff.py index ab36a2bdd..13c759456 100644 --- a/tools/reimplementation-helper/batch/quick_diff.py +++ b/tools/reimplementation-helper/batch/quick_diff.py @@ -27,9 +27,14 @@ import common quiet = '-q' in sys.argv +<<<<<<< HEAD keep_slots = '-s' in sys.argv files = [os.path.abspath(f) for f in sys.argv[1:] if f not in ('-q', '-s')] if not files and __name__ == "__main__": +======= +files = [os.path.abspath(f) for f in sys.argv[1:] if f != '-q'] +if not files: +>>>>>>> 45472f1b (tools/batch: quick_diff.py, and the source patterns it found in AI::AICState) sys.exit(__doc__) ROOT = str(common.ROOT).replace(chr(92), '/') os.chdir(ROOT) @@ -111,6 +116,7 @@ def ours(f, cls, name): print('no code found for %s::%s' % (cls, name)) return None res = [] +<<<<<<< HEAD # `_x$ = -16` lines define each local's offset; `_x$[esp+44H]` then means [esp + 0x44 - 16] pre = txt[txt.rfind('_TEXT SEGMENT', 0, m.start()):m.start()] slots = {k: int(v) for k, v in re.findall(r'^(\w+\$\w*|tv\d+) = (-?\d+)', pre, re.M)} @@ -129,6 +135,13 @@ def slot(mm): continue l = l.replace('\t', ' ') l = re.sub(r'(\w+\$\w*|tv\d+)\[esp(?:([+-][0-9a-fA-F]+H|[+-]\d+))?\]', slot, l) +======= + for l in m.group(2).splitlines(): + l = l.split(';')[0].strip() + if not l or l.endswith(':') or re.match(r'^\w+\$ = ', l) or l.startswith('npad') or l.startswith('$') or l.startswith('DD') or l.startswith('DB') or l.startswith('_'): + continue + l = l.replace('\t', ' ') +>>>>>>> 45472f1b (tools/batch: quick_diff.py, and the source patterns it found in AI::AICState) op = l.split(' ')[0] if op.startswith('j') and 'DWORD PTR' not in l: l = op + (' L' if '$' in l else ' F') @@ -157,8 +170,12 @@ def slot(mm): def canon(s): # esp-relative slots and globals compare loosely +<<<<<<< HEAD if not keep_slots: s = re.sub(r'\[esp( [+-] 0x[0-9a-f]+)?\]', '[esp+N]', s) +======= + s = re.sub(r'\[esp( [+-] 0x[0-9a-f]+)?\]', '[esp+N]', s) +>>>>>>> 45472f1b (tools/batch: quick_diff.py, and the source patterns it found in AI::AICState) s = re.sub(r'tv\d+\[', '[', s) s = re.sub(r'_\w+\$\w*\[', '[', s) s = s.replace('mov eax, G', 'mov eax, 0x51eb851f') if s == 'mov eax, G' else s @@ -166,6 +183,7 @@ def canon(s): return s +<<<<<<< HEAD def streams(f): """(original, ours) normalized instruction lists for one source file.""" src = open(f, encoding='utf-8').read() @@ -190,3 +208,20 @@ def main(): if __name__ == '__main__': main() +======= +for f in files: + src = open(f, encoding='utf-8').read() + va = int(re.search(r'FUNCTION: STRONGHOLDCRUSADER (0x[0-9A-Fa-f]+)', src).group(1), 16) + a = [canon(x) for x in orig(va)] + cls, name = re.search(r'FUNCTION: STRONGHOLDCRUSADER[^\n]*\n[^\n(]*?(\w+)::(\w+)\(', src).groups() + b = ours(f, cls, name) + if b is None: + continue + b = [canon(x) for x in b] + ratio = difflib.SequenceMatcher(None, a, b, autojunk=False).ratio() + print('##### %s ratio %.4f orig %d ours %d' % (os.path.basename(f), ratio, len(a), len(b))) + if not quiet: + for l in difflib.unified_diff(a, b, lineterm='', n=2): + if not l.startswith(('---', '+++')): + print(l) +>>>>>>> 45472f1b (tools/batch: quick_diff.py, and the source patterns it found in AI::AICState)