-
AlexSpl
имя: Александр
- Эксперт
-
- Сообщения: 5587
- Зарегистрирован: 17 сен 2010, 12:58
- Пол:
- Награды: 14
-
-
- Поблагодарили: 2185 раз.
|
AlexSpl » 28 апр 2021, 18:46
Код почти готов. Реализованы новые жилища (выбор между грейдом и не грейдом, 1-й уровень платный) и найм AI негрейженых существ в городах при наличии ул. постройки. Осталось побороть баг оригинальной игры с откатом ресурсов. Вы можете самостоятельно убедиться в его наличии (в плагине включён дебаг-мод; закомментируйте строчку для того, чтобы отключить). Баг не критичный, вылетов не должно быть (максимум неправильный откат ресурсов). Попробую найти причину. Суть в том, что при покупке нового героя игра может нанять существ, а потом их вернуть обратно, а потраченные деньги - 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 int action) { char txtBuffer[1024]; char infoStr[1024];
sprintf(infoStr, "Player: {%d} | Hero: {%s} | Action: {%d}\n\n", o_ActivePlayer->id, o_ActivePlayer->selected_hero_id != ID_NONE ? o_GameMgr->hero[o_ActivePlayer->selected_hero_id].name : "N/A", action); 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; }
_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 = CALL_1(int, __fastcall, 0x47AAD0, 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);
dwelling->defenders.type[1] = ID_NONE; dwelling->defenders.count[1] = 0; }
_Dwelling_* getDwelling(_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->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];
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); int action = 0; if ( stacksBegin ) { int stackPos = 0;
_Dwelling_* dwelling = 0; bool isDwelling0x11 = false; bool isDwelling0x14 = false; if ( o_ActivePlayer->selected_hero_id != ID_NONE ) { _Dwelling_* dwelling = getDwelling(&o_GameMgr->hero[o_ActivePlayer->selected_hero_id]); if ( dwelling ) { isDwelling0x11 = dwelling->type == 0x11; isDwelling0x14 = dwelling->type == 0x14; } }
for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack) { AIStacksToRecruit[stackPos] = *iStack;
int basicType = getCreatureBasicType(AIStacksToRecruit[stackPos].creatureType); if ( AIStacksToRecruit[stackPos].creatureType != basicType && !isDwelling0x11 && !isDwelling0x14 ) { 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); #endif #ifdef DEBUGL1 // Показываем армию и ресурсы до найма if ( army ) showInfo(army, 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 army = *(_Army_**)(c->ebp + 8); if ( army ) showInfo(army, 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);
if ( stacksBegin ) { int stackPos = 0;
for (AIStackToRecruit* iStack = stacksBegin; iStack < stacksEnd; ++iStack) { AIStacksToRecruit[stackPos] = *iStack;
int basicType = getCreatureBasicType(AIStacksToRecruit[stackPos].creatureType); if ( AIStacksToRecruit[stackPos].creatureType != basicType ) { AIStacksToRecruit[stackPos + 1] = AIStacksToRecruit[stackPos]; AIStacksToRecruit[stackPos + 1].creatureType = basicType; ++stackPos; } ++stackPos; }
stacksBegin = &AIStacksToRecruit[0]; stacksEnd = &AIStacksToRecruit[stackPos]; #ifdef DEBUGL2 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_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"); } }
return TRUE; } UPD: В вектор существ для двеллинга 0x14 не попадает неулучшенный отряд Iron Golems. Нужно обновить код для Summoning Portal и Mercenary Camp.
Последний раз редактировалось AlexSpl 29 апр 2021, 04:46, всего редактировалось 4 раз(а).
|