/*================================================================= File created by Yohann NICOLAS. Add support 1.13d by L'Autour. Add support 1.14d by haxifix. Uber Quest Management. =================================================================*/ #include #include "uberQuest.h" #include "common.h" #define OBJECT_PERMANENT_PORTAL 0x3C #define LEVEL_ACT5TOWN 109 #define LEVEL_ACT5_PANDEMONIUM1 133 #define LEVEL_ACT5_PANDEMONIUM2 134 #define LEVEL_ACT5_PANDEMONIUM3 135 #define LEVEL_ACT5_PANDEMONIUM_FINAL 136 #define UBER_IZUAL_ID 706 #define UBER_ANDARIEL_ID 707 #define UBER_DURIEL_ID 708 #define UBER_MEPHISTO_ID 704 #define UBER_DIABLO_ID 705 #define UBER_BAAL_ID 709 #define UBER_MEPHISTO_SQUELETON 725 #define UBER_MEPHISTO_ARCHER 726 #define UBER_MEPHISTO_FIRE 727 #define UBER_MEPHISTO_LIGHTNING 728 #define UBER_MEPHISTO_COLD 729 #define UBER_MEPHISTO_POISON 730 #define UBER_BAAL_DARK_LORD 731 #define UBER_BAAL_SPECTER 732 #define UBER_DIABLO_PIT_LORD 711 bool active_UberQuest = false; DWORD UberMephistoX = 25130; DWORD UberMephistoY = 5143; DWORD UberDiabloX = 25139; DWORD UberDiabloY = 5139; DWORD UberBaalX = 25139; DWORD UberBaalY = 5135; bool active_UberMinions = true; DWORD UberMephistoNbMinions = 6; DWORD UberMephistoMinions[] = {UBER_MEPHISTO_SQUELETON, UBER_MEPHISTO_ARCHER, UBER_MEPHISTO_FIRE, UBER_MEPHISTO_LIGHTNING, UBER_MEPHISTO_COLD, UBER_MEPHISTO_POISON}; int UberMephistoSpawnPercent = 80; DWORD UberMephistoSpawnRadius = 30; DWORD UberBaalNbMinions = 2; DWORD UberBaalMinions[] = {UBER_BAAL_DARK_LORD, UBER_BAAL_SPECTER}; int UberBaalSpawnPercent = 30; DWORD UberBaalSpawnRadius = 30; DWORD UberDiabloNbMinions = 1; DWORD UberDiabloMinions[] = {UBER_DIABLO_PIT_LORD}; int UberDiabloSpawnPercent = 30; DWORD UberDiabloSpawnRadius = 30; bool active_UberDiabloRushTweekAI = false; bool active_UberBaalTeleportTweekAI = false; bool active_UberBaalChillingArmorTweekAI = false; int uberBaalChillingArmorTimer = 6000; int uberBaalChillingArmorLastFrame = 0; static struct { union{ DWORD all; struct{ DWORD portal1Opened:1; DWORD portal2Opened:1; DWORD portal3Opened:1; DWORD portal4Opened:1; DWORD uber1Spawned:1; DWORD uber2Spawned:1; DWORD uber3Spawned:1; DWORD uber4Spawned:1; DWORD uber5Spawned:1; DWORD uber6Spawned:1; }; }; int count1; int count2; int count3; } questState; int GetNbMonsters(Level* level, Position position, int radius); int GetNbMonsters(Level* level); bool IsInRoom(Room* ptRoom, DWORD x, DWORD y) { DWORD startX = version_D2Game > V112 ? ptRoom->startX : *((DWORD*)ptRoom + 6); DWORD startY = version_D2Game > V112 ? ptRoom->startY : *((DWORD*)ptRoom + 7); DWORD sizeX = version_D2Game > V112 ? ptRoom->sizeX : *((DWORD*)ptRoom + 8); DWORD sizeY = version_D2Game > V112 ? ptRoom->sizeY : *((DWORD*)ptRoom + 9); return x >= startX && x <= startX + sizeX && y >= startY && y <= startY + sizeY; } double Distance(Position p1, Position p2) { return sqrt((double)(p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)); } void resetQuestState() { ZeroMemory(&questState,sizeof(questState)); questState.count1 = 10 + RANDOM(10); questState.count2 = 20 + RANDOM(40); questState.count3 = 10 + RANDOM(20); uberBaalChillingArmorLastFrame = 0; log_msg("Uber Quest State : %d %d %d\n",questState.count1, questState.count2, questState.count3); } DWORD openPortalOnLevel(Game* ptGame, Unit* ptChar, DWORD levelID) { log_msg("Open portal to level %d\n", levelID); Room* ptRoom = D2GetRoom(ptChar); if ( D2GetLevelID(ptRoom) != LEVEL_ACT5TOWN ) return 0; //Get position in the room Position pos; D2GetPosition(ptChar, &pos); log_msg("ptChar pos : %d %d\n", pos.x, pos.y); if (!D2GetDropRoom(ptRoom, &pos, &pos, 3, 0x3E01, 0xC01, 0)) return 0; ptRoom = D2TestPositionInRoom(ptRoom, pos.x, pos.y); if (!ptRoom) return 0; // Create Portal Unit* ptPortal = D2CreateUnit(UNIT_OBJECT, OBJECT_PERMANENT_PORTAL, pos.x, pos.y, ptGame, ptRoom, 1, 1, 0); ptPortal->ptObjectData->levelID = (BYTE)levelID; D2Common10572(ptPortal, 1); D2LinkPortal(ptGame, ptPortal, levelID, LEVEL_ACT5TOWN); D2SetObjectFlags(ptPortal, D2GetObjectFlags(ptPortal) | 3); D2Common11084(ptRoom, 0); D2Common11084(D2GetRoom(ptPortal), 0); return 1; } DWORD FASTCALL openPandPortal(Game* ptGame, Unit* ptChar) { log_msg("openPandPortal\n"); if (ptGame->difficultyLevel != D2DM_HELL) return 0; int available[3]; int nbAvailable=0; if (!questState.portal1Opened) available[nbAvailable++]=LEVEL_ACT5_PANDEMONIUM1; if (!questState.portal2Opened) available[nbAvailable++]=LEVEL_ACT5_PANDEMONIUM2; if (!questState.portal3Opened) available[nbAvailable++]=LEVEL_ACT5_PANDEMONIUM3; int selectedTargetLevel; if (nbAvailable) selectedTargetLevel = available[RANDOM(nbAvailable)]; else selectedTargetLevel = LEVEL_ACT5_PANDEMONIUM1 + RANDOM(3); int ret = openPortalOnLevel(ptGame, ptChar, selectedTargetLevel); if (ret) { if (selectedTargetLevel == LEVEL_ACT5_PANDEMONIUM1) questState.portal1Opened = 1; else if (selectedTargetLevel == LEVEL_ACT5_PANDEMONIUM2) questState.portal2Opened = 1; else if (selectedTargetLevel == LEVEL_ACT5_PANDEMONIUM3) questState.portal3Opened = 1; } return ret; } DWORD FASTCALL openPandFinalPortal(Game* ptGame, Unit* ptChar) { log_msg("openPandFinalPortal\n"); if (ptGame->difficultyLevel != D2DM_HELL) return 0; return openPortalOnLevel(ptGame, ptChar, LEVEL_ACT5_PANDEMONIUM_FINAL); } void STDCALL spawnUber(Game* ptGame, Room* ptRoom) { log_msg("Uber Quest State : %d %d %d\n",questState.count1, questState.count2, questState.count3); log_msg("Uber Quest questState spawn : %d %d %d %d %d %d\n",questState.uber1Spawned, questState.uber2Spawned, questState.uber3Spawned, questState.uber4Spawned, questState.uber5Spawned, questState.uber6Spawned); switch(D2GetLevelID(ptRoom)) { case LEVEL_ACT5_PANDEMONIUM1: if (!questState.uber1Spawned) { if (questState.count1) questState.count1--; else { if (D2SpawnSuperUnique(ptGame, ptRoom, 0, 0, 0, -1, UBER_ANDARIEL_ID, 0)) questState.uber1Spawned = 1; } } break; case LEVEL_ACT5_PANDEMONIUM2: if (!questState.uber2Spawned) { if (questState.count2) questState.count2--; else { if (D2SpawnSuperUnique(ptGame, ptRoom, 0, 0, 0, -1, UBER_DURIEL_ID, 0)) questState.uber2Spawned = 1; } } break; case LEVEL_ACT5_PANDEMONIUM3: if (!questState.uber3Spawned) { if (questState.count3) questState.count3--; else { if (D2SpawnSuperUnique(ptGame, ptRoom, 0, 0, 0, -1, UBER_IZUAL_ID, 0)) questState.uber3Spawned = 1; } } break; case LEVEL_ACT5_PANDEMONIUM_FINAL: if (!questState.uber4Spawned) { log_msg("Spawn Mephisto ptRoom: startX:%d - startY:%d / x:%d y:%d\n", ptRoom->startX, ptRoom->startY, ptRoom->sizeX, ptRoom->sizeY); if (IsInRoom(ptRoom, UberMephistoX, UberMephistoY)) { log_msg("Spawn Mephisto ptRoom: startX:%d - startY:%d / x:%d y:%d\n", ptRoom->startX, ptRoom->startY, ptRoom->sizeX, ptRoom->sizeY); if (D2SpawnSuperUnique(ptGame, ptRoom, 0, UberMephistoX, UberMephistoY, -1, UBER_MEPHISTO_ID, 0)) questState.uber4Spawned = 1; } } if (!questState.uber5Spawned) { if (IsInRoom(ptRoom, UberDiabloX, UberDiabloY)) { log_msg("Spawn Diablo ptRoom: startX:%d - startY:%d / x:%d y:%d\n", ptRoom->startX, ptRoom->startY, ptRoom->sizeX, ptRoom->sizeY); if (D2SpawnSuperUnique(ptGame, ptRoom, 0, UberDiabloX, UberDiabloY, -1, UBER_DIABLO_ID, 0)) questState.uber4Spawned = 1; } } if (!questState.uber6Spawned) { if (IsInRoom(ptRoom, UberBaalX, UberBaalY)) { log_msg("Spawn Baal ptRoom: startX:%d - startY:%d / x:%d y:%d\n", ptRoom->startX, ptRoom->startY, ptRoom->sizeX, ptRoom->sizeY); if (D2SpawnSuperUnique(ptGame, ptRoom, 0, UberBaalX, UberBaalY, -1, UBER_BAAL_ID, 0)) questState.uber4Spawned = 1; } } } D2Game235C0(ptGame, ptRoom); } ////////////////////////////////////////////////////////////////////////////// int GetNbMonsters(Level* level) { int nbMonsters = 0; for (RoomEx* roomEx = level->ptFirstRoomEx; roomEx; roomEx = roomEx->ptNextRoomEx) { if (roomEx->ptRoom) { for (Unit* unit = roomEx->ptRoom->ptFirstUnit; unit; unit = unit->ptNextUnitInRoom) { if (unit->nUnitType == UNIT_MONSTER && unit->mode != UNIT_MODE_KICK && unit->nTxtFileNo != 711) { //log_msg("GetNbMonsters ++ / type:%d - id:%d - mode:%d\n", unit->nUnitType, unit->nTxtFileNo, unit->mode); nbMonsters++; } //else log_msg("GetNbMonsters no / type:%d - id:%d - mode:%d\n", unit->nUnitType, unit->nTxtFileNo, unit->mode); } } } return nbMonsters; } int GetNbMonsters(Level* level, Position position, int radius) { int nbMonsters = 0; for (RoomEx* roomEx = level->ptFirstRoomEx; roomEx; roomEx = roomEx->ptNextRoomEx) { if (roomEx->ptRoom) { for (Unit* unit = roomEx->ptRoom->ptFirstUnit; unit; unit = unit->ptNextUnitInRoom) { if (unit->nUnitType == UNIT_MONSTER && unit->mode != UNIT_MODE_KICK && unit->nTxtFileNo != 711) { Position p; D2GetPosition(unit, &p); if (Distance(position, p) <= radius) nbMonsters++; } } } } return nbMonsters; } bool CastSummonMonster(Game* ptGame, Unit* ptMonster, DWORD x, DWORD y, DWORD* minions, int minionsSize, int mode) { DWORD minionId = minions[RANDOM(minionsSize)]; log_msg("CastSummonMonster: id:%d x:%d y:%d mode:%d\n", minionId, x, y, mode); Room* ptRoom = D2GetRoom(ptMonster); // Check nb of monsters Level* ptLevel = ptRoom->ptRoomEx->ptLevel; int monstersInLevel = GetNbMonsters(ptLevel); Position position; D2GetPosition(ptMonster, &position); int monstersNearby = GetNbMonsters(ptLevel, position, 25); log_msg("CastSummonMonster nbMonsters: %d - %d\n", monstersNearby, monstersInLevel); if (monstersNearby > 16 || monstersInLevel > 100) return false; // Spawn Monster log_msg("CastSummonMonster D2SpawnMonster(%d, %d, %08X, %08X, %d, %d, %d, %d)\n", minionId, mode, ptGame, ptRoom, x, y, 1, 0); Unit* monster = D2SpawnMonster(minionId, mode, ptGame, ptRoom, x, y, 1, 0); log_msg("CastSummonMonster D2SpawnMonster end\n"); if (monster) return true; // Try in near rooms log_msg("CastSummonMonster Try in near rooms: %d - %d\n", ptRoom->startX, ptRoom->startY); for(int i = 0; i < ptRoom->nbNearRooms; i++) { Room* ptRoom2 = ptRoom->ptNearRooms[i]; if (IsInRoom(ptRoom2, x, y)) { log_msg("CastSummonMonster D2SpawnMonster(%d, %d, %08X, %08X, %d, %d, %d, %d)\n", minionId, mode, ptGame, ptRoom2, x, y, 1, 0); monster = D2SpawnMonster(minionId, mode, ptGame, ptRoom2, x, y, 1, 0); log_msg("CastSummonMonster D2SpawnMonster end\n"); if (monster) return true; } } log_msg("CastSummonMonster end: %d - %d\n", ptRoom->startX, ptRoom->startY); return false; } void RandInCircle(Position* position, DWORD radius) { double theta = RANDOMF() * 6.283185; double r = sqrt(RANDOMF()) * radius; position->x += (DWORD)(r * cos(theta)); position->y += (DWORD)(r * sin(theta)); } void FASTCALL uberMephAI(Game* ptGame, Unit* ptMonster, AIParam* ptAIArgs) { log_msg("UberMephAI called.\n"); // Spawn Minions if (active_UberMinions && RANDOM(100) < UberMephistoSpawnPercent && ptAIArgs->distanceToTarget < UberMephistoSpawnRadius) { Position pos; D2GetPosition(ptAIArgs->target, &pos); CastSummonMonster(ptGame, ptMonster, pos.x, pos.y, UberMephistoMinions, UberMephistoNbMinions, 8); } log_msg("Uber Mephisto AI activated.\n"); D2MephAI(ptGame, ptMonster, ptAIArgs); } void FASTCALL uberDiabloAI(Game* ptGame, Unit* ptMonster, AIParam* ptAIArgs) { log_msg("UberDiabloAI called.\n"); // Spawn Minions if (active_UberMinions && RANDOM(100) < UberDiabloSpawnPercent && ptAIArgs->distanceToTarget < UberDiabloSpawnRadius) { Position pos; D2GetPosition(ptAIArgs->target, &pos); RandInCircle(&pos, 3); CastSummonMonster(ptGame, ptMonster, pos.x, pos.y, UberDiabloMinions, UberDiabloNbMinions, 1); } // Tweek Diablo AI Rush if (active_UberDiabloRushTweekAI && RANDOM(100) < (int)ptAIArgs->distanceToTarget - 10) { if (RANDOM(3) > 0)//67% to Rush { log_msg("Uber Diablo Run activated.\n"); int diabloRunSkillId = 4; D2MonsterUseSkill(ptGame, ptMonster, ptAIArgs->ptMonStatsBIN->skillArg[diabloRunSkillId], ptAIArgs->ptMonStatsBIN->skill[diabloRunSkillId], ptAIArgs->target, 0, 0); } else { log_msg("Uber Diablo Move activated.\n"); Position pos; D2GetPosition(ptAIArgs->target, &pos); D2MonsterMove(ptGame, ptMonster, NULL, 2, pos.x, pos.y, 1, 0); } return; } log_msg("Uber Diablo AI activated.\n"); D2DiabloAI(ptGame, ptMonster, ptAIArgs); } void FASTCALL uberBaalAI(Game* ptGame, Unit* ptMonster, AIParam* ptAIArgs) { log_msg("UberBaalAI called.\n"); if (active_UberMinions && RANDOM(100) < UberBaalSpawnPercent && ptAIArgs->distanceToTarget < UberBaalSpawnRadius) { Position pos; D2GetPosition(ptAIArgs->target, &pos); RandInCircle(&pos, 3); CastSummonMonster(ptGame, ptMonster, pos.x, pos.y, UberBaalMinions, UberBaalNbMinions, 1); } // Tweek Baal AI Chilling Armor if (active_UberBaalChillingArmorTweekAI) { if (uberBaalChillingArmorLastFrame == 0 || ((int)ptGame->gameFrame - uberBaalChillingArmorLastFrame > uberBaalChillingArmorTimer)) { log_msg("Uber Baal Chilling Armor activated.\n"); uberBaalChillingArmorLastFrame = ptGame->gameFrame; const int baalChillingArmorSkillId = 7; D2MonsterUseSkill(ptGame, ptMonster, ptAIArgs->ptMonStatsBIN->skillArg[baalChillingArmorSkillId], ptAIArgs->ptMonStatsBIN->skill[baalChillingArmorSkillId], NULL, 0, 0); return; } } // Tweek Baal AI Teleport if (active_UberBaalTeleportTweekAI && RANDOM(100) < (int)ptAIArgs->distanceToTarget - 10) { if (RANDOM(3) > 0)//67% to teleport { log_msg("Uber Baal Teleport activated.\n"); const int baalTeleportSkillId = 4; Position pos; D2GetPosition(ptAIArgs->target, &pos); D2MonsterUseSkill(ptGame, ptMonster, ptAIArgs->ptMonStatsBIN->skillArg[baalTeleportSkillId], ptAIArgs->ptMonStatsBIN->skill[baalTeleportSkillId], NULL/*ptAIArgs->target*/, pos.x, pos.y); } else { log_msg("Uber Baal Move activated.\n"); Position pos; D2GetPosition(ptAIArgs->target, &pos); D2MonsterMove(ptGame, ptMonster, NULL, 2, pos.x, pos.y, 1, 0); } return; } log_msg("Uber Baal AI activated.\n"); D2BaalAI(ptGame, ptMonster, ptAIArgs); } FCT_ASM ( caller_spawnUber ) PUSH EDX PUSH ECX CALL spawnUber RETN }} void Install_UberQuest() { static int isInstalled = false; if (isInstalled) return; if ( version_D2Game <= V110 ) return; log_msg("Patch D2Game for active Uber Quest. (UberQuest)\n"); DWORD oldProtection; // open Red Portal VirtualProtect((LPVOID)R8(D2Game,0,0,0,FA480,FA7B8, FA228, FA5F0, FA2C4, 2E11D0), 8, PAGE_EXECUTE_READWRITE, &oldProtection); mem_seek R8(D2Game, 0000, 0000, 0000, FA480, FA7B8, FA228, FA5F0, FA2C4, 2E11D0); MEMT_DWORD( D2OpenPandPortal , openPandPortal); MEMT_DWORD( D2OpenPandFinalPortal , openPandFinalPortal); //0201E357 |. FFD0 |CALL EAX //01FA77E7 |. FFD0 |CALL EAX //6FCF3CC7 |. FFD0 |CALL EAX //6FC92437 |. FFD0 |CALL EAX //6FCB7127 |. FFD0 |CALL EAX //00565D45 . FFD0 CALL EAX VirtualProtect((LPVOID)R8(D2Game,0,0,0,FA480,FA7B8, FA228, FA5F0, FA2C4, 2E11D0), 8, oldProtection, &oldProtection); // manage uberIA (fct table at 0209E7E8) VirtualProtect((LPVOID)(R8(D2Game,0,0,0,10E7E8,10ECD0,10EF58,10E788, 10ED00, 33CA18) + 145*0x10), 0x30, PAGE_EXECUTE_READWRITE, &oldProtection); mem_seek R8(D2Game, 0000, 0000, 0000, 10F100, 10F5E8, 10F870, 10F0A0, 10F618, 33D330); MEMT_DWORD( D2UberBaalAI , uberBaalAI); mem_seek R8(D2Game, 0000, 0000, 0000, 10F110, 10F5F8, 10F880, 10F0B0, 10F628, 33D340); MEMT_DWORD( D2UberMephAI , uberMephAI); mem_seek R8(D2Game, 0000, 0000, 0000, 10F120, 10F608, 10F890, 10F0C0, 10F638, 33D350); MEMT_DWORD( D2UberDiabloAI , uberDiabloAI); VirtualProtect((LPVOID)(R8(D2Game,0,0,0,10E7E8,10ECD0,10EF58,10E788, 10ED00, 33CA18) + 145*0x10), 0x30, oldProtection, &oldProtection); //0202ADA7 |> B8 E8E70902 MOV EAX,D2Game.0209E7E8 //01FD2BE7 |> B8 D0EC0702 MOV EAX,D2Game.0207ECD0 //6FC3B597 |> B8 58EFD26F MOV EAX,D2Game.6FD2EF58 //6FCBD157 |> B8 88E7D26F MOV EAX,D2Game.6FD2E788 //6FC5C617 |> B8 00EDD26F MOV EAX,D2Game.6FD2ED00 //005B163D |. B8 18CA7300 MOV EAX,Game.0073CA18 // spawn Uber // mem_seek R7(D2Game, 0000, 0000, 0000, 98DAD, 0000, 0000, 0000); // MEMJ_REF4( D2WarpPlayer , spawnUber); //02028DAC |. E8 491CF7FF CALL mem_seek R8(D2Game, 0000, 0000, 0000, E26E2, E6B52, A850B, 2CCAB, BE9AB, 12D1DC); MEMC_REF4( V2Game235C0 , version_D2Game >= V114d ? (DWORD)caller_spawnUber : (DWORD)spawnUber); //020726E1 |. E8 2A46FFFF ||CALL D2Game.02066D10 //02056B51 |. E8 6ACAF3FF ||CALL D2Game.01F935C0 //6FCC850A |. E8 014FF6FF ||CALL D2Game.6FC2D410 //6FC4CCAA |. E8 3134FFFF ||CALL D2Game.6FC400E0 //6FCDE9AA |. E8 D1AFF9FF ||CALL D2Game.6FC79980 //0052D1DB |. E8 60590100 ||CALL Game.00542B40 log_msg("\n"); isInstalled = true; } /*================================= END OF FILE =================================*/