diff --git a/Source/missiles.cpp b/Source/missiles.cpp index d693e7363c4..1d1a3d8df62 100644 --- a/Source/missiles.cpp +++ b/Source/missiles.cpp @@ -2339,22 +2339,24 @@ void AddGolem(Missile &missile, AddMissileParameter ¶meter) int playerId = missile._misource; Player &player = Players[playerId]; - Monster &golem = Monsters[playerId]; + Monster *golem = FindGolemForPlayer(player); - if (golem.position.tile != GolemHoldingCell && &player == MyPlayer) - KillMyGolem(); + // Is Golem alive? + if (golem != nullptr) { + KillGolem(*golem); + return; + } - if (golem.position.tile == GolemHoldingCell) { - std::optional spawnPosition = FindClosestValidPosition( - [start = missile.position.start](Point target) { - return !IsTileOccupied(target) && LineClearMissile(start, target); - }, - parameter.dst, 0, 5); + std::optional spawnPosition = FindClosestValidPosition( + [start = missile.position.start](Point target) { + return !IsTileOccupied(target) && LineClearMissile(start, target); + }, + parameter.dst, 0, 5); - if (spawnPosition) { - SpawnGolem(player, golem, *spawnPosition, missile); - } - } + if (!spawnPosition) + return; + + SpawnGolem(player, *spawnPosition, missile); } void AddApocalypseBoom(Missile &missile, AddMissileParameter ¶meter) diff --git a/Source/monster.cpp b/Source/monster.cpp index 78a6890eb1d..13564efdaac 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -79,6 +79,9 @@ constexpr int HellToHitBonus = 120; constexpr int NightmareAcBonus = 50; constexpr int HellAcBonus = 80; +/** @brief Reserved some entries in @Monster for golems. For vanilla compatibility, this must remain 4. */ +constexpr int ReservedMonsterSlotsForGolems = 4; + /** Tracks which missile files are already loaded */ size_t totalmonsters; int monstimgtot; @@ -173,6 +176,7 @@ void InitMonster(Monster &monster, Direction rd, size_t typeIndex, Point positio monster.goalVar2 = 0; monster.goalVar3 = 0; monster.pathCount = 0; + monster.enemy = 0; monster.isInvalid = false; monster.uniqueType = UniqueMonsterType::None; monster.activeForTicks = 0; @@ -1064,7 +1068,7 @@ void MonsterAttackMonster(Monster &attacker, Monster &target, int hper, int mind ApplyMonsterDamage(DamageType::Physical, target, dam); if (attacker.isPlayerMinion()) { - size_t playerId = attacker.getId(); + size_t playerId = static_cast(attacker.goalVar3); const Player &player = Players[playerId]; target.tag(player); } @@ -3129,6 +3133,20 @@ void EnsureMonsterIndexIsActive(size_t monsterId) } } +void InitGolem(devilution::Monster &monster, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel) +{ + monster.flags |= MFLAG_GOLEM; + monster.goalVar3 = static_cast(golemOwnerPlayerId); + const Player &player = Players[golemOwnerPlayerId]; + monster.maxHitPoints = 2 * (320 * golemSpellLevel + player._pMaxMana / 3); + monster.hitPoints = monster.maxHitPoints; + monster.armorClass = 25; + monster.golemToHit = 5 * (golemSpellLevel + 8) + 2 * player.getCharacterLevel(); + monster.minDamage = 2 * (golemSpellLevel + 4); + monster.maxDamage = 2 * (golemSpellLevel + 8); + UpdateEnemy(monster); +} + } // namespace tl::expected AddMonsterType(_monster_id type, placeflag placeflag) @@ -3517,7 +3535,7 @@ void WeakenNaKrul() void InitGolems() { if (!setlevel) { - for (int i = 0; i < MAX_PLRS; i++) + for (int i = 0; i < ReservedMonsterSlotsForGolems; i++) AddMonster(GolemHoldingCell, Direction::South, 0, false); } } @@ -3587,7 +3605,7 @@ tl::expected SetMapMonsters(const uint16_t *dunData, Point st { RETURN_IF_ERROR(AddMonsterType(MT_GOLEM, PLACE_SPECIAL)); if (setlevel) - for (int i = 0; i < MAX_PLRS; i++) + for (int i = 0; i < ReservedMonsterSlotsForGolems; i++) AddMonster(GolemHoldingCell, Direction::South, 0, false); WorldTileSize size = GetDunSize(dunData); @@ -3637,11 +3655,11 @@ void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpe ActiveMonsterCount += 1; uint32_t seed = GetLCGEngineState(); // Update local state immediately to increase ActiveMonsterCount instantly (this allows multiple monsters to be spawned in one game tick) - InitializeSpawnedMonster(position, dir, typeIndex, monsterIndex, seed); - NetSendCmdSpawnMonster(position, dir, static_cast(typeIndex), static_cast(monsterIndex), seed); + InitializeSpawnedMonster(position, dir, typeIndex, monsterIndex, seed, 0, 0); + NetSendCmdSpawnMonster(position, dir, static_cast(typeIndex), static_cast(monsterIndex), seed, 0, 0); } -void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed) +void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel) { SetRndSeed(seed); EnsureMonsterIndexIsActive(monsterId); @@ -3649,9 +3667,12 @@ void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed) Monster &monster = Monsters[monsterId]; M_ClearSquares(monster); InitMonster(monster, Direction::South, typeIndex, position); + if (monster.type().type == MT_GOLEM) { + InitGolem(monster, golemOwnerPlayerId, golemSpellLevel); + } } -void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed) +void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel) { SetRndSeed(seed); EnsureMonsterIndexIsActive(monsterId); @@ -3674,10 +3695,14 @@ void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, s monster.occupyTile(position, false); InitMonster(monster, dir, typeIndex, position); - if (IsSkel(monster.type().type)) + if (monster.type().type == MT_GOLEM) { + InitGolem(monster, golemOwnerPlayerId, golemSpellLevel); StartSpecialStand(monster, dir); - else + } else if (IsSkel(monster.type().type)) { + StartSpecialStand(monster, dir); + } else { M_StartStand(monster, dir); + } } void AddDoppelganger(Monster &monster) @@ -3829,11 +3854,10 @@ void StartMonsterDeath(Monster &monster, const Player &player, bool sendmsg) MonsterDeath(monster, md, sendmsg); } -void KillMyGolem() +void KillGolem(Monster &golem) { - Monster &golem = Monsters[MyPlayerId]; delta_kill_monster(golem, golem.position.tile, *MyPlayer); - NetSendCmdLoc(MyPlayerId, false, CMD_KILLGOLEM, golem.position.tile); + NetSendCmdLocParam1(false, CMD_MONSTDEATH, golem.position.tile, static_cast(golem.getId())); M_StartKill(golem, *MyPlayer); } @@ -3997,7 +4021,7 @@ void GolumAi(Monster &golem) if (golem.pathCount > 8) golem.pathCount = 5; - if (RandomWalk(golem, Players[golem.getId()]._pdir)) + if (RandomWalk(golem, Players[golem.goalVar3]._pdir)) return; Direction md = Left(golem.direction); @@ -4011,7 +4035,7 @@ void GolumAi(Monster &golem) void DeleteMonsterList() { - for (int i = 0; i < MAX_PLRS; i++) { + for (int i = 0; i < ReservedMonsterSlotsForGolems; i++) { Monster &golem = Monsters[i]; if (!golem.isInvalid) continue; @@ -4022,7 +4046,7 @@ void DeleteMonsterList() golem.isInvalid = false; } - for (size_t i = MAX_PLRS; i < ActiveMonsterCount;) { + for (size_t i = ReservedMonsterSlotsForGolems; i < ActiveMonsterCount;) { if (Monsters[ActiveMonsters[i]].isInvalid) { if (pcursmonst == static_cast(ActiveMonsters[i])) // Unselect monster if player highlighted it pcursmonst = -1; @@ -4512,6 +4536,24 @@ Monster *FindUniqueMonster(UniqueMonsterType monsterType) return nullptr; } +Monster *FindGolemForPlayer(const Player &player) +{ + for (size_t i = 0; i < ActiveMonsterCount; i++) { + int monsterId = ActiveMonsters[i]; + Monster &monster = Monsters[monsterId]; + if (monster.type().type != MT_GOLEM) + continue; + if (monster.position.tile == GolemHoldingCell) + continue; + if (monster.goalVar3 != player.getId()) + continue; + if (monster.hitPoints == 0) + continue; + return &monster; + } + return nullptr; +} + bool IsTileAvailable(const Monster &monster, Point position) { if (!IsTileAvailable(position)) @@ -4636,31 +4678,48 @@ void TalktoMonster(Player &player, Monster &monster) } } -void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile) +void SpawnGolem(Player &player, Point position, Missile &missile) { - golem.occupyTile(position, false); - golem.position.tile = position; - golem.position.future = position; - golem.position.old = position; - golem.pathCount = 0; - golem.maxHitPoints = 2 * (320 * missile._mispllvl + player._pMaxMana / 3); - golem.hitPoints = golem.maxHitPoints; - golem.armorClass = 25; - golem.golemToHit = 5 * (missile._mispllvl + 8) + 2 * player.getCharacterLevel(); - golem.minDamage = 2 * (missile._mispllvl + 4); - golem.maxDamage = 2 * (missile._mispllvl + 8); - golem.flags |= MFLAG_GOLEM; - StartSpecialStand(golem, Direction::South); - UpdateEnemy(golem); - if (&player == MyPlayer) { - NetSendCmdGolem( - golem.position.tile.x, - golem.position.tile.y, - golem.direction, - golem.enemy, - golem.hitPoints, - GetLevelForMultiplayer(player)); + // The command is only executed for the level owner, to prevent desyncs in multiplayer. + if (!MyPlayer->isLevelOwnedByLocalClient()) + return; + + // Search monster index to use for the new golem + Monster *golem = nullptr; + // 1. Prefer MonsterIndex = PlayerIndex for vanilla compatibility + if (player.getId() < ReservedMonsterSlotsForGolems) { + Monster &reservedGolem = Monsters[player.getId()]; + if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == 0) + golem = &reservedGolem; + } + // 2. Use reserved slots, so additional Monsters can spawn + if (golem == nullptr) { + for (int i = 0; i < ReservedMonsterSlotsForGolems; i++) { + Monster &reservedGolem = Monsters[player.getId()]; + if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == 0) { + golem = &reservedGolem; + break; + } + } } + // 3. Use normal monster slot + if (golem == nullptr) { + if (ActiveMonsterCount >= MaxMonsters) + return; + size_t monsterIndex = ActiveMonsters[ActiveMonsterCount]; + ActiveMonsterCount += 1; + golem = &Monsters[monsterIndex]; + } + + if (golem == nullptr) + return; + + size_t monsterIndex = golem->getId(); + uint32_t seed = GetLCGEngineState(); + + // Update local state immediately to increase ActiveMonsterCount instantly (this allows multiple monsters to be spawned in one game tick) + InitializeSpawnedMonster(position, Direction::South, 0, monsterIndex, seed, player.getId(), missile._mispllvl); + NetSendCmdSpawnMonster(position, Direction::South, 0, static_cast(monsterIndex), seed, player.getId(), static_cast(missile._mispllvl)); } bool CanTalkToMonst(const Monster &monster) @@ -4668,23 +4727,23 @@ bool CanTalkToMonst(const Monster &monster) return IsAnyOf(monster.goal, MonsterGoal::Inquiring, MonsterGoal::Talking); } -int encode_enemy(Monster &monster) +uint8_t encode_enemy(Monster &monster) { if ((monster.flags & MFLAG_TARGETS_MONSTER) != 0) - return monster.enemy + MAX_PLRS; + return monster.enemy; - return monster.enemy; + return monster.enemy + MaxMonsters; } -void decode_enemy(Monster &monster, int enemyId) +void decode_enemy(Monster &monster, uint8_t enemyId) { - if (enemyId < MAX_PLRS) { + if (enemyId >= MaxMonsters) { + enemyId -= MaxMonsters; monster.flags &= ~MFLAG_TARGETS_MONSTER; monster.enemy = enemyId; monster.enemyPosition = Players[enemyId].position.future; } else { monster.flags |= MFLAG_TARGETS_MONSTER; - enemyId -= MAX_PLRS; monster.enemy = enemyId; monster.enemyPosition = Monsters[enemyId].position.future; } diff --git a/Source/monster.h b/Source/monster.h index 93221b1a68c..71efef2ecdf 100644 --- a/Source/monster.h +++ b/Source/monster.h @@ -237,7 +237,7 @@ struct Monster { // note: missing field _mAFNum /** * @brief Controls monster's behaviour regarding special actions. - * Used only by @p ScavengerAi and @p MegaAi. + * Used only by @p ScavengerAi, @p MegaAi and @p GolemAi. */ int8_t goalVar3; @@ -514,11 +514,11 @@ void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpe /** * @brief Loads data for a dynamically spawned monster when entering a level in multiplayer. */ -void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed); +void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel); /** * @brief Initialize a spanwed monster (from a network message or from SpawnMonster-function). */ -void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed); +void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel); void AddDoppelganger(Monster &monster); void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage); bool M_Talker(const Monster &monster); @@ -529,7 +529,7 @@ void M_StartHit(Monster &monster, int dam); void M_StartHit(Monster &monster, const Player &player, int dam); void StartMonsterDeath(Monster &monster, const Player &player, bool sendmsg); void MonsterDeath(Monster &monster, Direction md, bool sendmsg); -void KillMyGolem(); +void KillGolem(Monster &golem); void M_StartKill(Monster &monster, const Player &player); void M_SyncStartKill(Monster &monster, Point position, const Player &player); void M_UpdateRelations(const Monster &monster); @@ -553,6 +553,7 @@ void MissToMonst(Missile &missile, Point position); Monster *FindMonsterAtPosition(Point position, bool ignoreMovingMonsters = false); Monster *FindUniqueMonster(UniqueMonsterType monsterType); +Monster *FindGolemForPlayer(const Player &player); /** * @brief Check that the given tile is available to the monster @@ -568,9 +569,9 @@ bool IsGoat(_monster_id mt); void ActivateSkeleton(Monster &monster, Point position); Monster *PreSpawnSkeleton(); void TalktoMonster(Player &player, Monster &monster); -void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile); +void SpawnGolem(Player &player, Point position, Missile &missile); bool CanTalkToMonst(const Monster &monster); -int encode_enemy(Monster &monster); -void decode_enemy(Monster &monster, int enemyId); +uint8_t encode_enemy(Monster &monster); +void decode_enemy(Monster &monster, uint8_t enemyId); } // namespace devilution diff --git a/Source/msg.cpp b/Source/msg.cpp index 571a03d9f9e..4c798aab1fb 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -215,6 +215,8 @@ struct DObjectStr { struct DSpawnedMonster { size_t typeIndex; uint32_t seed; + uint8_t golemOwnerPlayerId; + int16_t golemSpellLevel; }; struct DLevel { @@ -733,19 +735,6 @@ size_t OnLevelData(uint8_t pnum, const TCmd *pCmd) return wBytes + sizeof(message); } -void DeltaSyncGolem(const TCmdGolem &message, const Player &player, uint8_t level) -{ - if (!gbIsMultiplayer) - return; - - DMonsterStr &monster = GetDeltaLevel(level).monster[player.getId()]; - monster.position.x = message._mx; - monster.position.y = message._my; - monster._mactive = UINT8_MAX; - monster._menemy = message._menemy; - monster.hitPoints = SDL_SwapLE32(message._mhitpoints); -} - void DeltaLeaveSync(uint8_t bLevel) { if (!gbIsMultiplayer) @@ -1770,50 +1759,6 @@ size_t OnMonstDeath(const TCmd *pCmd, Player &player) return sizeof(message); } -size_t OnKillGolem(const TCmd *pCmd, Player &player) -{ - const auto &message = *reinterpret_cast(pCmd); - const Point position { message.x, message.y }; - - if (gbBufferMsgs != 1) { - if (&player != MyPlayer && InDungeonBounds(position)) { - Monster &monster = Monsters[player.getId()]; - if (player.isOnActiveLevel()) - M_SyncStartKill(monster, position, player); - delta_kill_monster(monster, position, player); // BUGFIX: should be p->wParam1, plrlevel will be incorrect if golem is killed because player changed levels - } - } else { - SendPacket(player, &message, sizeof(message)); - } - - return sizeof(message); -} - -size_t OnAwakeGolem(const TCmd *pCmd, Player &player) -{ - const auto &message = *reinterpret_cast(pCmd); - const Point position { message._mx, message._my }; - - if (gbBufferMsgs == 1) { - SendPacket(player, &message, sizeof(message)); - } else if (InDungeonBounds(position)) { - if (!player.isOnActiveLevel()) { - DeltaSyncGolem(message, player, message._currlevel); - } else if (&player != MyPlayer) { - // Check if this player already has an active golem - for (auto &missile : Missiles) { - if (missile._mitype == MissileID::Golem && &Players[missile._misource] == &player) { - return sizeof(message); - } - } - - AddMissile(player.position.tile, position, message._mdir, MissileID::Golem, TARGET_MONSTERS, player, 0, 1); - } - } - - return sizeof(message); -} - size_t OnMonstDamage(const TCmd *pCmd, Player &player) { const auto &message = *reinterpret_cast(pCmd); @@ -2389,10 +2334,15 @@ size_t OnSpawnMonster(const TCmd *pCmd, const Player &player) size_t typeIndex = static_cast(SDL_SwapLE16(message.typeIndex)); size_t monsterId = static_cast(SDL_SwapLE16(message.monsterId)); + uint8_t golemOwnerPlayerId = message.golemOwnerPlayerId; + if (golemOwnerPlayerId >= Players.size()) { + return sizeof(message); + } + uint8_t golemSpellLevel = std::min(message.golemSpellLevel, static_cast(MaxSpellLevel + Players[golemOwnerPlayerId]._pISplLvlAdd)); DLevel &deltaLevel = GetDeltaLevel(player); - deltaLevel.spawnedMonsters[monsterId] = { typeIndex, message.seed }; + deltaLevel.spawnedMonsters[monsterId] = { typeIndex, message.seed, golemOwnerPlayerId, golemSpellLevel }; // Override old monster delta information auto &deltaMonster = deltaLevel.monster[monsterId]; deltaMonster.position = position; @@ -2401,7 +2351,7 @@ size_t OnSpawnMonster(const TCmd *pCmd, const Player &player) deltaMonster._mactive = 0; if (player.isOnActiveLevel() && &player != MyPlayer) - InitializeSpawnedMonster(position, message.dir, typeIndex, monsterId, message.seed); + InitializeSpawnedMonster(position, message.dir, typeIndex, monsterId, message.seed, golemOwnerPlayerId, golemSpellLevel); return sizeof(message); } @@ -2697,7 +2647,8 @@ void DeltaLoadLevel() DLevel &deltaLevel = GetDeltaLevel(localLevel); if (leveltype != DTYPE_TOWN) { for (auto &deltaSpawnedMonster : deltaLevel.spawnedMonsters) { - LoadDeltaSpawnedMonster(deltaSpawnedMonster.second.typeIndex, deltaSpawnedMonster.first, deltaSpawnedMonster.second.seed); + auto &monsterData = deltaSpawnedMonster.second; + LoadDeltaSpawnedMonster(deltaSpawnedMonster.second.typeIndex, deltaSpawnedMonster.first, monsterData.seed, monsterData.golemOwnerPlayerId, monsterData.golemSpellLevel); assert(deltaLevel.monster[deltaSpawnedMonster.first].position.x != 0xFF); } for (size_t i = 0; i < MaxMonsters; i++) { @@ -2826,21 +2777,7 @@ void NetSendCmd(bool bHiPri, _cmd_id bCmd) NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } -void NetSendCmdGolem(uint8_t mx, uint8_t my, Direction dir, uint8_t menemy, int hp, uint8_t cl) -{ - TCmdGolem cmd; - - cmd.bCmd = CMD_AWAKEGOLEM; - cmd._mx = mx; - cmd._my = my; - cmd._mdir = dir; - cmd._menemy = menemy; - cmd._mhitpoints = hp; - cmd._currlevel = cl; - NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); -} - -void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed) +void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, uint8_t golemSpellLevel) { TCmdSpawnMonster cmd; @@ -2851,6 +2788,8 @@ void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, u cmd.typeIndex = SDL_SwapLE16(typeIndex); cmd.monsterId = SDL_SwapLE16(monsterId); cmd.seed = SDL_SwapLE32(seed); + cmd.golemOwnerPlayerId = golemOwnerPlayerId; + cmd.golemSpellLevel = golemSpellLevel; NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); } @@ -3265,10 +3204,6 @@ size_t ParseCmd(uint8_t pnum, const TCmd *pCmd) return OnWarp(pCmd, player); case CMD_MONSTDEATH: return OnMonstDeath(pCmd, player); - case CMD_KILLGOLEM: - return OnKillGolem(pCmd, player); - case CMD_AWAKEGOLEM: - return OnAwakeGolem(pCmd, player); case CMD_MONSTDAMAGE: return OnMonstDamage(pCmd, player); case CMD_PLRDEAD: diff --git a/Source/msg.h b/Source/msg.h index 2287824c4fb..a5b5549f894 100644 --- a/Source/msg.h +++ b/Source/msg.h @@ -389,21 +389,10 @@ enum _cmd_id : uint8_t { // // body (TCmdPItem) CMD_SYNCPUTITEM, - // Golem death at location. - // - // body (TCmdLocParam1): - // int8_t x - // int8_t y - // int16_t dlvl - CMD_KILLGOLEM, // Synchronize quest state. // // body (TCmdQuest) CMD_SYNCQUEST, - // Spawn golem at target location. - // - // body (TCmdGolem) - CMD_AWAKEGOLEM, // Enable mana shield of player (render). // // body (TCmd) @@ -507,16 +496,6 @@ struct TCmdParam4 { uint16_t wParam4; }; -struct TCmdGolem { - _cmd_id bCmd; - uint8_t _mx; - uint8_t _my; - Direction _mdir; - int8_t _menemy; - int32_t _mhitpoints; - uint8_t _currlevel; -}; - struct TCmdSpawnMonster { _cmd_id bCmd; uint8_t x; @@ -525,6 +504,8 @@ struct TCmdSpawnMonster { uint16_t typeIndex; uint16_t monsterId; uint32_t seed; + uint8_t golemOwnerPlayerId; + uint8_t golemSpellLevel; }; struct TCmdQuest { @@ -750,8 +731,7 @@ void DeltaLoadLevel(); /** @brief Clears last sent player command for the local player. This is used when a game tick changes. */ void ClearLastSentPlayerCmd(); void NetSendCmd(bool bHiPri, _cmd_id bCmd); -void NetSendCmdGolem(uint8_t mx, uint8_t my, Direction dir, uint8_t menemy, int hp, uint8_t cl); -void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed); +void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, uint8_t golemSpellLevel); void NetSendCmdLoc(uint8_t playerId, bool bHiPri, _cmd_id bCmd, Point position); void NetSendCmdLocParam1(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wParam1); void NetSendCmdLocParam2(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wParam1, uint16_t wParam2); diff --git a/Source/player.cpp b/Source/player.cpp index 871a3218127..2f4aa1b57c9 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -2809,16 +2809,10 @@ void SyncPlrKill(Player &player, DeathReason deathReason) void RemovePlrMissiles(const Player &player) { - if (leveltype != DTYPE_TOWN && &player == MyPlayer) { - Monster &golem = Monsters[MyPlayerId]; - if (golem.position.tile.x != 1 || golem.position.tile.y != 0) { - KillMyGolem(); - AddCorpse(golem.position.tile, golem.type().corpseId, golem.direction); - int mx = golem.position.tile.x; - int my = golem.position.tile.y; - dMonster[mx][my] = 0; - golem.isInvalid = true; - DeleteMonsterList(); + if (leveltype != DTYPE_TOWN) { + Monster *golem; + while ((golem = FindGolemForPlayer(player)) != nullptr) { + KillGolem(*golem); } } diff --git a/Source/sync.cpp b/Source/sync.cpp index abd510ffceb..199ed651ebe 100644 --- a/Source/sync.cpp +++ b/Source/sync.cpp @@ -160,7 +160,7 @@ void SyncMonster(bool isOwner, const TSyncMonster &monsterSync) } const Point position { monsterSync._mx, monsterSync._my }; - const int enemyId = monsterSync._menemy; + const uint8_t enemyId = monsterSync._menemy; if (monster.activeForTicks != 0) { uint32_t delta = MyPlayer->position.tile.ManhattanDistance(monster.position.tile); @@ -205,21 +205,15 @@ void SyncMonster(bool isOwner, const TSyncMonster &monsterSync) monster.whoHit |= monsterSync.mWhoHit; } -bool IsEnemyIdValid(const Monster &monster, int enemyId) +bool IsEnemyIdValid(const Monster &monster, uint8_t enemyId) { - if (enemyId < 0) { - return false; - } - - if (enemyId < MAX_PLRS) { + if (enemyId > MaxMonsters) { + enemyId -= MaxMonsters; + if (enemyId >= Players.size()) + return false; return Players[enemyId].plractive; } - enemyId -= MAX_PLRS; - if (static_cast(enemyId) >= MaxMonsters) { - return false; - } - const Monster &enemy = Monsters[enemyId]; if (&enemy == &monster) {