diff --git a/docs/wiki.rst b/docs/wiki.rst index ce0e938e..131e770a 100644 --- a/docs/wiki.rst +++ b/docs/wiki.rst @@ -30,11 +30,14 @@ The Game itself ------------------ - :doc:`Load balancing of the core game engine ` - Game Mechanics -- AI Behavior - Graphics and Sound Systems - Modding Support - Multiplayer Architecture (Coming Soon!) +AI Behavior +~~~~~~~~~~~ +- :doc:`AI market pricing and goods trading ` + .. include:: wiki/approach.rst diff --git a/docs/wiki/market-trading.md b/docs/wiki/market-trading.md new file mode 100644 index 00000000..e3e5ecb4 --- /dev/null +++ b/docs/wiki/market-trading.md @@ -0,0 +1,30 @@ +# AI market pricing and goods trading + +This article describes the AI's market behavior. The AI buys and sells resources +using the game's shared price table. Its trade routines handle storage and gold +accounting; the decision to trade is made by their AI callers. + +| Function | Crusader 1.41 address | Behavior | +| --- | --- | --- | +| `GameStateStructures::getBuyPrice` | `0x004588A0` | Returns `(buyPrice / 5) * amount`. | +| `GameStateStructures::getSellPrice` | `0x00458910` | Returns `(salesPrice / 5) * amount`. | +| `AICState::buyGoods` | `0x004CC000` | Attempts resource storage, then deducts gold and displays the trade on success. | +| `AICState::sellGoods` | `0x004CBFA0` | Credits gold and final-result statistics, removes resources, then displays the trade. | + +## Prices + +Prices are stored for lots of five goods. The game divides that price by five +using integer division, then multiplies by the amount traded. For example, a +table price of 14 and a trade of five goods produce a price of 10. The player +argument does not affect the price calculation. + +## AI buying and selling + +An AI purchase first attempts to store the goods. If storage fails, it returns +without deducting gold or displaying a trade. The routine itself does not check +whether the player can afford the purchase. Successful purchases reduce both +the player's gold and their net market-gold total. + +An AI sale increases those two totals and the final-results gold statistic, then +removes the sold goods. Both successful purchases and sales display the trade +amount to the player. diff --git a/src/OpenSHC/AI/AICState/buyGoods.cpp b/src/OpenSHC/AI/AICState/buyGoods.cpp new file mode 100644 index 00000000..7075afd5 --- /dev/null +++ b/src/OpenSHC/AI/AICState/buyGoods.cpp @@ -0,0 +1,29 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.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 0x004CC000 + BOOLEnum AICState::buyGoods(int playerID, ResourceType resourceType, int amount) + { + int const 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)) { + 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)( + playerID, 0, amount, resourceType); + return TRUE; + } + return FALSE; + } + +} +} diff --git a/src/OpenSHC/AI/AICState/sellGoods.cpp b/src/OpenSHC/AI/AICState/sellGoods.cpp new file mode 100644 index 00000000..5ebd21d6 --- /dev/null +++ b/src/OpenSHC/AI/AICState/sellGoods.cpp @@ -0,0 +1,28 @@ +#include "../AICState.func.hpp" + +#include "OpenSHC/Game/GameStateStructures.func.hpp" +#include "OpenSHC/Map/Buildings/BuildingsState.func.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 0x004CBFA0 + void AICState::sellGoods(int playerID, ResourceType resourceType, int amount) + { + int const reward = MACRO_CALL_MEMBER(Game::GameStateStructures_Func::getSellPrice, DAT_GameState::ptr)( + playerID, resourceType, amount); + DAT_GameSynchronyState::instance.finalResults.finalGold[playerID] += reward; + DAT_GameState::instance.playerDataArray[playerID].currentResources[Game::Resources::RT_GOLD] += reward; + DAT_GameState::instance.playerDataArray[playerID].marketGold += reward; + MACRO_CALL_MEMBER(Map::Buildings::BuildingsState_Func::processResourceLoss, DAT_BuildingsState::ptr)( + playerID, resourceType, amount, 0); + MACRO_CALL_MEMBER(Game::GameStateStructures_Func::displayPlayerTradeVisualEffect, DAT_GameState::ptr)( + playerID, 1, -amount, resourceType); + } + +} +} diff --git a/src/OpenSHC/Game/GameStateStructures/getBuyPrice.cpp b/src/OpenSHC/Game/GameStateStructures/getBuyPrice.cpp new file mode 100644 index 00000000..4fc13374 --- /dev/null +++ b/src/OpenSHC/Game/GameStateStructures/getBuyPrice.cpp @@ -0,0 +1,13 @@ +#include "../GameStateStructures.func.hpp" + +namespace OpenSHC { +namespace Game { + + // FUNCTION: STRONGHOLDCRUSADER 0x004588A0 + int GameStateStructures::getBuyPrice(undefined4 playerID, int resourceType, int amount) + { + return (this->mapAndTime.buyAndSalesPriceArray[resourceType].buyPrice / 5) * amount; + } + +} +} diff --git a/src/OpenSHC/Game/GameStateStructures/getSellPrice.cpp b/src/OpenSHC/Game/GameStateStructures/getSellPrice.cpp new file mode 100644 index 00000000..c823f1ed --- /dev/null +++ b/src/OpenSHC/Game/GameStateStructures/getSellPrice.cpp @@ -0,0 +1,13 @@ +#include "../GameStateStructures.func.hpp" + +namespace OpenSHC { +namespace Game { + + // FUNCTION: STRONGHOLDCRUSADER 0x00458910 + int GameStateStructures::getSellPrice(int playerID, int resourceType, int amount) + { + return (this->mapAndTime.buyAndSalesPriceArray[resourceType].salesPrice / 5) * amount; + } + +} +} diff --git a/status/addresses-SHC-3BB0A8C1.txt b/status/addresses-SHC-3BB0A8C1.txt index 10a0c2b0..78c1a605 100644 --- a/status/addresses-SHC-3BB0A8C1.txt +++ b/status/addresses-SHC-3BB0A8C1.txt @@ -17715,13 +17715,13 @@ SHC_3BB0A8C1_0x00458840 | 0.0% | Pending SHC_3BB0A8C1_0x00458890 | 0.0% | Pending -SHC_3BB0A8C1_0x004588A0 | 0.0% | Pending +SHC_3BB0A8C1_0x004588A0 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004588D0 | 0.0% | Pending SHC_3BB0A8C1_0x004588F0 | 0.0% | Pending -SHC_3BB0A8C1_0x00458910 | 0.0% | Pending +SHC_3BB0A8C1_0x00458910 | 100.0% | Reimplemented SHC_3BB0A8C1_0x00458940 | 0.0% | Pending @@ -34857,9 +34857,9 @@ SHC_3BB0A8C1_0x004CBA50 | 0.0% | Pending SHC_3BB0A8C1_0x004CBE30 | 0.0% | Pending -SHC_3BB0A8C1_0x004CBFA0 | 0.0% | Pending +SHC_3BB0A8C1_0x004CBFA0 | 100.0% | Reimplemented -SHC_3BB0A8C1_0x004CC000 | 0.0% | Pending +SHC_3BB0A8C1_0x004CC000 | 100.0% | Reimplemented SHC_3BB0A8C1_0x004CC070 | 0.0% | Pending