-
AlexSpl
имя: Александр
- Эксперт
-
- Сообщения: 5587
- Зарегистрирован: 17 сен 2010, 12:58
- Пол:
- Награды: 14
-
-
- Поблагодарили: 2185 раз.
|
AlexSpl » 30 апр 2021, 11:49
Эсперимент #1. AI нанимает отряды существ в порядке убывания Fight Value 1-го существа из отряда. Пример. В оригинале AI предпочёл бы нанять 3 Чудищ вместо 1 Древнего Чудища + 2 Чудища. С плагином AI выберет второй вариант. - Код: Выделить всё
#define _CRT_SECURE_NO_WARNINGS #include "stdafx.h" #include "..\..\HotA\homm3.h"
#define DEBUGL1 //#define DEBUGL2
Patcher* _P; PatcherInstance* _PI;
short hiredIdx = ID_NONE;
// For debugging purposes void showDebugInfo(const char* format, ...) { va_list args; va_start(args, format);
vsprintf(o_TextBuffer, format, args); b_MsgBox(o_TextBuffer, MBX_OK);
va_end(args); }
void showInfo(const _Army_* army, const _Hero_* hero, const int action, const bool upgradeDone = false) { char txtBuffer[1024]; char infoStr[1024];
if ( upgradeDone ) sprintf(txtBuffer, "{%s}", "UPGRADE"); else sprintf(txtBuffer, "Action: {%d}", action);
sprintf(infoStr, "Player: {%d} | Hero: {%s} | %s\n\n", o_ActivePlayer->id, hero ? hero->name : "N/A", txtBuffer); for (int i = 0; i < 7; ++i) { if ( army->type[i] != ID_NONE ) sprintf(txtBuffer, "{%s}: %d\n", o_pCreatureInfo[army->type[i]].name_plural, army->count[i]); else sprintf(txtBuffer, "%s\n", "[Empty]"); strcat(infoStr, txtBuffer); } showDebugInfo("%s\nWood: {%d} | Ore: {%d}\nMercury: {%d} | Sulfur: {%d} | Crystal: {%d} | Gems: {%d}\nGold: {%d}", infoStr, o_ActivePlayer->resourses.wood, o_ActivePlayer->resourses.ore, o_ActivePlayer->resourses.mercury, o_ActivePlayer->resourses.sulfur, o_ActivePlayer->resourses.crystal, o_ActivePlayer->resourses.jems, o_ActivePlayer->resourses.gold); }
int getCreatureBasicType(const int id) { int basicType = id; if ( id % 2 && (id < CID_AIR_ELEMENTAL || id == CID_SPRITE || id == CID_MAGIC_ELEMENTAL || id == CID_PHOENIX) ) basicType = id - 1; else switch ( id ) { case CID_STORM_ELEMENTAL: basicType = CID_AIR_ELEMENTAL; break; case CID_MAGMA_ELEMENTAL: basicType = CID_EARTH_ELEMENTAL; break; case CID_ENERGY_ELEMENTAL: basicType = CID_FIRE_ELEMENTAL; break; case CID_ICE_ELEMENTAL: basicType = CID_WATER_ELEMENTAL; break; }
return basicType; }
int getCreatureUpgradeType(const int id) { return CALL_1(int, __fastcall, 0x47AAD0, id); }
_Dwelling_* GetDwelling(const int id) { return (_Dwelling_ *)(o_GameMgr->Field<int>(0x4E39C) + 92 * id); }
int __stdcall initDwelling(LoHook* h, HookContext* c) { _Dwelling_* dwelling = (_Dwelling_*)c->esi; int upgMonId = getCreatureUpgradeType(dwelling->creature_types[0]); if ( dwelling->type == 0x11 && upgMonId != ID_NONE ) { dwelling->creature_types[1] = dwelling->creature_types[0]; dwelling->creature_counts[1] = dwelling->creature_counts[0]; dwelling->creature_types[0] = upgMonId; if ( dwelling->defenders.type[0] != ID_NONE ) dwelling->defenders.type[0] = upgMonId; }
return EXEC_DEFAULT; }
void __stdcall fixDwellingDefenders(HiHook* h, _Dwelling_* dwelling, bool unused) { CALL_2(void, __thiscall, h->GetDefaultFunc(), dwelling, unused);
if ( dwelling->type == 0x11 ) { dwelling->defenders.type[1] = ID_NONE; dwelling->defenders.count[1] = 0; } }
short getObjectType(const _Hero_* hero) { short objType = ID_NONE;
if ( o_ActivePlayer->selected_hero_id != -1 && hero ) { _MapItem_* item = o_GameMgr->Map.GetItem(hero->x, hero->y, hero->z); if ( item ) objType = item->object_type; }
return objType; }
_Dwelling_* getDwelling(const _Hero_* hero) { _Dwelling_* dwelling = 0; if ( hero ) { _MapItem_* item = o_GameMgr->Map.GetItem(hero->x, hero->y, hero->z); if ( item && (item->object_type == 0x11 || item->object_type == 0x14) ) dwelling = GetDwelling(item->setup); }
return dwelling; }
int __stdcall afterHiring(LoHook* h, HookContext* c) { _Dwelling_* dwelling = 0; if ( o_ActivePlayer->selected_hero_id != ID_NONE ) dwelling = getDwelling(&o_GameMgr->hero[o_ActivePlayer->selected_hero_id]); if ( dwelling && dwelling->type == 0x11 ) { dwelling->creature_counts[0] = min(dwelling->creature_counts[0], dwelling->creature_counts[1]); dwelling->creature_counts[1] = dwelling->creature_counts[0];
if ( h->GetAddress() == 0x5510B1 ) { c->return_address = 0x55121B; return NO_EXEC_DEFAULT; } }
return EXEC_DEFAULT; }
struct AIStackToRecruit { int creatureType; short* sourceArmyCounts; short creaturesAvailable; char r1; char r2; void updateAvailableCreatures(); } *AIStacksToRecruit = new AIStackToRecruit[16];
struct _ResourcesEx_ : public _Resources_ { friend const _ResourcesEx_ operator + (const _ResourcesEx_& a, const _ResourcesEx_& b); friend const _ResourcesEx_ operator - (const _ResourcesEx_& a, const _ResourcesEx_& b); friend _ResourcesEx_ operator += (_ResourcesEx_& a, const _ResourcesEx_& b); friend _ResourcesEx_ operator -= (_ResourcesEx_& a, const _ResourcesEx_& b); friend const _ResourcesEx_ operator * (const int n, const _ResourcesEx_& r); friend const _ResourcesEx_ operator * (const _ResourcesEx_& r, const int n); friend bool operator >= (const _ResourcesEx_& a, const _ResourcesEx_& b); };
const _ResourcesEx_ operator + (const _ResourcesEx_& a, const _ResourcesEx_& b) { _ResourcesEx_ result;
result.wood = a.wood + b.wood; result.ore = a.ore + b.ore; result.mercury = a.mercury + b.mercury; result.sulfur = a.sulfur + b.sulfur; result.crystal = a.crystal + b.crystal; result.jems = a.jems + b.jems; result.gold = a.gold + b.gold;
return result; }
const _ResourcesEx_ operator - (const _ResourcesEx_& a, const _ResourcesEx_& b) { _ResourcesEx_ result;
result.wood = a.wood - b.wood; result.ore = a.ore - b.ore; result.mercury = a.mercury - b.mercury; result.sulfur = a.sulfur - b.sulfur; result.crystal = a.crystal - b.crystal; result.jems = a.jems - b.jems; result.gold = a.gold - b.gold;
return result; }
_ResourcesEx_ operator += (_ResourcesEx_& a, const _ResourcesEx_& b) { a.wood += b.wood; a.ore += b.ore; a.mercury += b.mercury; a.sulfur += b.sulfur; a.crystal += b.crystal; a.jems += b.jems; a.gold += b.gold;
return a; }
_ResourcesEx_ operator -= (_ResourcesEx_& a, const _ResourcesEx_& b) { a.wood -= b.wood; a.ore -= b.ore; a.mercury -= b.mercury; a.sulfur -= b.sulfur; a.crystal -= b.crystal; a.jems -= b.jems; a.gold -= b.gold;
return a; }
const _ResourcesEx_ operator * (const int n, const _ResourcesEx_& r) { _ResourcesEx_ result;
result.wood = n * r.wood; result.ore = n * r.ore; result.mercury = n * r.mercury; result.sulfur = n * r.sulfur; result.crystal = n * r.crystal; result.jems = n * r.jems; result.gold = n * r.gold;
return result; }
const _ResourcesEx_ operator * (const _ResourcesEx_& r, const int n) { _ResourcesEx_ result;
result.wood = n * r.wood; result.ore = n * r.ore; result.mercury = n * r.mercury; result.sulfur = n * r.sulfur; result.crystal = n * r.crystal; result.jems = n * r.jems; result.gold = n * r.gold;
return result; }
bool operator >= (const _ResourcesEx_& a, const _ResourcesEx_& b) { return a.gold >= b.gold && a.mercury >= b.mercury && a.sulfur >= b.sulfur && a.crystal >= b.crystal && a.jems >= b.jems && a.wood >= b.wood && a.ore >= b.ore; }
void AIStackToRecruit::updateAvailableCreatures() { *this->sourceArmyCounts = this->creaturesAvailable; // Если грейд, а справа - его базовое существо, уравниваем кол-во существ, доступных для найма // Пользуемся тем, что после грейда всегда идёт его базовое существо if ( this[1].creatureType == getCreatureBasicType(this->creatureType) ) { *this[1].sourceArmyCounts = this->creaturesAvailable; this[1].creaturesAvailable = this->creaturesAvailable; }
// Если базовое существо, а слева - его грейд, уравниваем кол-во существ, доступных для найма if ( hiredIdx && this->creatureType == getCreatureBasicType(this[-1].creatureType) ) { *this[-1].sourceArmyCounts = this->creaturesAvailable; this[-1].creaturesAvailable = this->creaturesAvailable; } }
int __stdcall AI_TryingToHire(LoHook* h, HookContext* c) { AIStackToRecruit* stacksBegin = *(AIStackToRecruit**)(c->edi + 0x30); AIStackToRecruit* stacksEnd = *(AIStackToRecruit**)(c->edi + 0x34);
// Сохраняем оригинальные указатели AIStackToRecruit* stacksBeginSaved = stacksBegin; AIStackToRecruit* stacksEndSaved = stacksEnd;
_Army_* army = *(_Army_**)(c->ebp + 8); _Hero_* hero = army ? (_Hero_*)((int)army - 0x91) : 0; _Army_* garrisonArmy = *(_Army_**)(c->ebp + 0x10); _Town_* town = garrisonArmy ? (_Town_*)((int)garrisonArmy - 0xE0) : 0; _ResourcesEx_* playerResources = *(_ResourcesEx_**)(c->ebp + 0x14); int action = 0; if ( stacksBegin ) { int stackPos = 0;
for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack) { AIStacksToRecruit[stackPos] = *iStack;
int basicType = getCreatureBasicType(AIStacksToRecruit[stackPos].creatureType); int objType = getObjectType(hero); if ( AIStacksToRecruit[stackPos].creatureType != basicType && objType != 0x11 && objType != 0x14 && objType != 0x4E ) { AIStacksToRecruit[stackPos + 1] = AIStacksToRecruit[stackPos]; AIStacksToRecruit[stackPos + 1].creatureType = basicType; ++stackPos; } ++stackPos; }
stacksBegin = &AIStacksToRecruit[0]; stacksEnd = &AIStacksToRecruit[stackPos];
// Подменяем оригинальный вектор существ для найма *(AIStackToRecruit**)(c->edi + 0x30) = stacksBegin; *(AIStackToRecruit**)(c->edi + 0x34) = stacksEnd; #ifdef DEBUGL1 int stacksNumber = stacksEnd - stacksBegin; char txtBuffer[1024] = ""; char infoStr[1024]; sprintf(infoStr, "Trying to hire {%d} stack%s:\n\n", stacksNumber, stacksNumber > 1 ? "s" : "");
for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack) { sprintf(txtBuffer, "{%s}\n", o_pCreatureInfo[iStack->creatureType].name_plural); strcat(infoStr, txtBuffer); }
showDebugInfo(infoStr); // Показываем армию и ресурсы до найма if ( army ) showInfo(army, hero, action++); #endif }
int value; do { // Пытаемся нанять отряды value = CALL_2(int, __thiscall, 0x42D420, c->edi, c->esi); if ( value > 0 && hiredIdx != ID_NONE ) { stacksBegin[hiredIdx].updateAvailableCreatures(); hiredIdx = ID_NONE;
#ifdef DEBUGL1 if ( army ) showInfo(army, hero, action++); #endif } } while (value > 0); // Восстанавливаем оригинальный вектор существ для найма, // чтобы игра могла корректно освободить выделенную память *(AIStackToRecruit**)(c->edi + 0x30) = stacksBeginSaved; *(AIStackToRecruit**)(c->edi + 0x34) = stacksEndSaved; c->return_address = 0x42D768; return NO_EXEC_DEFAULT; }
int __stdcall AI_ThinkingOfHiring(LoHook* h, HookContext* c) { AIStackToRecruit* stacksBegin = *(AIStackToRecruit**)(c->esi + 0x30); AIStackToRecruit* stacksEnd = *(AIStackToRecruit**)(c->esi + 0x34); #ifdef DEBUGL2 if ( stacksBegin ) { int stacksNumber = stacksEnd - stacksBegin; char txtBuffer[1024] = ""; char infoStr[1024]; sprintf(infoStr, "Thinking of hiring {%d} stack%s:\n\n", stacksNumber, stacksNumber > 1 ? "s" : "");
for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack) { sprintf(txtBuffer, "{%s}\n", o_pCreatureInfo[iStack->creatureType].name_plural); strcat(infoStr, txtBuffer); }
showDebugInfo(infoStr); } #endif
c->edx = *(int*)(c->ebp + 8); int value; do { value = CALL_2(int, __thiscall, 0x42D420, c->esi, 0); c->edx += value; } while (value > 0); c->return_address = 0x42D864; return NO_EXEC_DEFAULT; }
int __stdcall AI_HiringBestCreaturesFirst(LoHook* h, HookContext* c) { AIStackToRecruit* stacksBegin = *(AIStackToRecruit**)(c->esi + 0x30); int maxValue = *(int*)(c->ebp - 0xC); int stackIdx = *(int*)(c->ebp - 0x18); int stackType = *(int*)(c->ebp - 0x20); int fightValue = o_pCreatureInfo[stackType].fight_value; c->edi = fightValue; return EXEC_DEFAULT; }
int __stdcall AI_SaveHiredCreatureIdx(LoHook* h, HookContext* c) { hiredIdx = *(short*)(c->ebp - 0x18);
return EXEC_DEFAULT; }
int __stdcall skipMessages(LoHook* h, HookContext* c) { if ( _P->VarGetValue("HD.UI.AdvMgr.SkipMapMsgs", 1) ) { c->return_address = 0x4A1917; return NO_EXEC_DEFAULT; }
return EXEC_DEFAULT; }
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { static bool plugin_On = false;
if ( DLL_PROCESS_ATTACH == ul_reason_for_call ) { if ( !plugin_On ) { plugin_On = true; _P = GetPatcher(); _PI = _P->CreateInstance((char*)"HD.Plugin.NewDwellings");
_PI->WriteLoHook(0x4A17BD, skipMessages); _PI->WriteLoHook(0x4B85EE, initDwelling); _PI->WriteLoHook(0x5510B1, afterHiring); _PI->WriteHexPatch(0x4A197B, "90 90"); _PI->WriteHexPatch(0x4AB812, "90 90"); _PI->WriteHiHook(0x4B8760, SPLICE_, EXTENDED_, THISCALL_, fixDwellingDefenders); // AI _PI->WriteLoHook(0x42D71C, AI_TryingToHire); _PI->WriteLoHook(0x42D84F, AI_ThinkingOfHiring); _PI->WriteLoHook(0x42D67B, AI_SaveHiredCreatureIdx); _PI->WriteLoHook(0x42D768, afterHiring); _PI->WriteHexPatch(0x42CC06, "90 90 90");
// AI Experiments _PI->WriteLoHook(0x42D57D, AI_HiringBestCreaturesFirst); } }
return TRUE; }
В хуке AI_HiringBestCreaturesFirst() можно определить собственное правило, которому будет следовать AI при найме отрядов, заменив условие if ( fightValue > maxValue ). Например, будем нанимать первыми отряды с наибольшей скоростью: ( if o_pCreatureInfo[stacksBegin[stackIdx].creatureType].speed > maxValue ), где stacksBegin[stackIdx] - это текущий отряд, рассматриваемый AI. Критерий (в примере это скорость) следует возвращать в регистре edi, как это сделано в коде выше. Можно запретить покупку существ, вернув отрицательное значение (например, -1) в edi. Таким образом, можно добиться того, чтобы AI не брал в армию существ со скоростью ниже 5, например. А чтобы он сумел защитить свои замки, можно разрешить AI скупать таких существ при приближении вражеского героя к замку. Так, одного отрицательного edi недостаточно: AI всё равно купит такой отряд в самом конце. Но запретить реально. Может, понадобится отдельный хук для запрета найма. Я придумал, как можно 100% запретить: выкинуть его из вектора найма * * * UPD: Исправил ошибки в AI_HiringBestCreaturesFirst() (для первого стека в maxValue попадал оригинальный критерий, что приводило к неправильному порядку найма) и getObjectType().
Последний раз редактировалось AlexSpl 02 май 2021, 18:55, всего редактировалось 2 раз(а).
|