Browse Source

Decouple Golems from Players and use SpawnMonster for Golem

pull/7952/head
obligaron 1 year ago committed by Stephen C. Wills
parent
commit
3a1eb6bed7
  1. 16
      Source/missiles.cpp
  2. 94
      Source/monster.cpp
  3. 6
      Source/monster.h
  4. 76
      Source/msg.cpp
  5. 21
      Source/msg.h
  6. 12
      Source/player.cpp

16
Source/missiles.cpp

@ -2346,8 +2346,7 @@ void AddGolem(Missile &missile, AddMissileParameter &parameter)
// Is Golem alive? // Is Golem alive?
if (golem != nullptr) { if (golem != nullptr) {
if (&player == MyPlayer) KillGolem(*golem);
KillGolem(*golem);
return; return;
} }
@ -2360,7 +2359,18 @@ void AddGolem(Missile &missile, AddMissileParameter &parameter)
if (!spawnPosition) if (!spawnPosition)
return; return;
SpawnGolem(player, *golem, *spawnPosition, missile); if (&player != MyPlayer)
return;
uint8_t spellLevel = static_cast<uint8_t>(missile._mispllvl);
// The command is only executed for the level owner, to prevent desyncs in multiplayer.
if (!MyPlayer->isLevelOwnedByLocalClient()) {
// If we are not the level owner, request the level owner to spawn the golem for us
NetSendCmdLocParam1(true, CMD_REQUESTSPAWNGOLEM, *spawnPosition, spellLevel);
return;
}
SpawnGolem(player, *spawnPosition, spellLevel);
} }
void AddApocalypseBoom(Missile &missile, AddMissileParameter &parameter) void AddApocalypseBoom(Missile &missile, AddMissileParameter &parameter)

94
Source/monster.cpp

@ -177,6 +177,7 @@ void InitMonster(Monster &monster, Direction rd, size_t typeIndex, Point positio
monster.goalVar2 = 0; monster.goalVar2 = 0;
monster.goalVar3 = 0; monster.goalVar3 = 0;
monster.pathCount = 0; monster.pathCount = 0;
monster.enemy = 0;
monster.isInvalid = false; monster.isInvalid = false;
monster.uniqueType = UniqueMonsterType::None; monster.uniqueType = UniqueMonsterType::None;
monster.activeForTicks = 0; monster.activeForTicks = 0;
@ -3133,6 +3134,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<int8_t>(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 } // namespace
tl::expected<size_t, std::string> AddMonsterType(_monster_id type, placeflag placeflag) tl::expected<size_t, std::string> AddMonsterType(_monster_id type, placeflag placeflag)
@ -3641,11 +3656,11 @@ void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpe
ActiveMonsterCount += 1; ActiveMonsterCount += 1;
uint32_t seed = GetLCGEngineState(); uint32_t seed = GetLCGEngineState();
// Update local state immediately to increase ActiveMonsterCount instantly (this allows multiple monsters to be spawned in one game tick) // 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); InitializeSpawnedMonster(position, dir, typeIndex, monsterIndex, seed, 0, 0);
NetSendCmdSpawnMonster(position, dir, static_cast<uint16_t>(typeIndex), static_cast<uint16_t>(monsterIndex), seed); NetSendCmdSpawnMonster(position, dir, static_cast<uint16_t>(typeIndex), static_cast<uint16_t>(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); SetRndSeed(seed);
EnsureMonsterIndexIsActive(monsterId); EnsureMonsterIndexIsActive(monsterId);
@ -3653,9 +3668,12 @@ void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed)
Monster &monster = Monsters[monsterId]; Monster &monster = Monsters[monsterId];
M_ClearSquares(monster); M_ClearSquares(monster);
InitMonster(monster, Direction::South, typeIndex, position); 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); SetRndSeed(seed);
EnsureMonsterIndexIsActive(monsterId); EnsureMonsterIndexIsActive(monsterId);
@ -3678,10 +3696,14 @@ void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, s
monster.occupyTile(position, false); monster.occupyTile(position, false);
InitMonster(monster, dir, typeIndex, position); InitMonster(monster, dir, typeIndex, position);
if (IsSkel(monster.type().type)) if (monster.type().type == MT_GOLEM) {
InitGolem(monster, golemOwnerPlayerId, golemSpellLevel);
StartSpecialStand(monster, dir); StartSpecialStand(monster, dir);
else } else if (IsSkel(monster.type().type)) {
StartSpecialStand(monster, dir);
} else {
M_StartStand(monster, dir); M_StartStand(monster, dir);
}
} }
void AddDoppelganger(Monster &monster) void AddDoppelganger(Monster &monster)
@ -4657,32 +4679,44 @@ void TalktoMonster(Player &player, Monster &monster)
} }
} }
void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile) void SpawnGolem(const Player &player, Point position, uint8_t spellLevel)
{ {
golem.occupyTile(position, false); // Search monster index to use for the new golem
golem.position.tile = position; Monster *golem = nullptr;
golem.position.future = position; // 1. Prefer MonsterIndex = PlayerIndex for vanilla compatibility
golem.position.old = position; if (player.getId() < ReservedMonsterSlotsForGolems) {
golem.pathCount = 0; Monster &reservedGolem = Monsters[player.getId()];
golem.maxHitPoints = 2 * (320 * missile._mispllvl + player._pMaxMana / 3); if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == 0)
golem.hitPoints = golem.maxHitPoints; golem = &reservedGolem;
golem.armorClass = 25; }
golem.golemToHit = 5 * (missile._mispllvl + 8) + 2 * player.getCharacterLevel(); // 2. Use reserved slots, so additional Monsters can spawn
golem.minDamage = 2 * (missile._mispllvl + 4); if (golem == nullptr) {
golem.maxDamage = 2 * (missile._mispllvl + 8); for (int i = 0; i < ReservedMonsterSlotsForGolems; i++) {
golem.flags |= MFLAG_GOLEM; Monster &reservedGolem = Monsters[i];
golem.goalVar3 = player.getId(); if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == 0) {
StartSpecialStand(golem, Direction::South); golem = &reservedGolem;
UpdateEnemy(golem); break;
if (&player == MyPlayer) { }
NetSendCmdGolem( }
golem.position.tile.x, }
golem.position.tile.y, // 3. Use normal monster slot
golem.direction, if (golem == nullptr) {
golem.enemy, if (ActiveMonsterCount >= MaxMonsters)
golem.hitPoints, return;
GetLevelForMultiplayer(player)); 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(), spellLevel);
NetSendCmdSpawnMonster(position, Direction::South, 0, static_cast<uint16_t>(monsterIndex), seed, player.getId(), spellLevel);
} }
bool CanTalkToMonst(const Monster &monster) bool CanTalkToMonst(const Monster &monster)

6
Source/monster.h

@ -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. * @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). * @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 AddDoppelganger(Monster &monster);
void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage); void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage);
bool M_Talker(const Monster &monster); bool M_Talker(const Monster &monster);
@ -569,7 +569,7 @@ bool IsGoat(_monster_id mt);
void ActivateSkeleton(Monster &monster, Point position); void ActivateSkeleton(Monster &monster, Point position);
Monster *PreSpawnSkeleton(); Monster *PreSpawnSkeleton();
void TalktoMonster(Player &player, Monster &monster); void TalktoMonster(Player &player, Monster &monster);
void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile); void SpawnGolem(const Player &player, Point position, uint8_t spellLevel);
bool CanTalkToMonst(const Monster &monster); bool CanTalkToMonst(const Monster &monster);
uint8_t encode_enemy(Monster &monster); uint8_t encode_enemy(Monster &monster);
void decode_enemy(Monster &monster, uint8_t enemyId); void decode_enemy(Monster &monster, uint8_t enemyId);

76
Source/msg.cpp

@ -169,7 +169,7 @@ std::string_view CmdIdString(_cmd_id cmd)
case CMD_ITEMEXTRA: return "CMD_ITEMEXTRA"; case CMD_ITEMEXTRA: return "CMD_ITEMEXTRA";
case CMD_SYNCPUTITEM: return "CMD_SYNCPUTITEM"; case CMD_SYNCPUTITEM: return "CMD_SYNCPUTITEM";
case CMD_SYNCQUEST: return "CMD_SYNCQUEST"; case CMD_SYNCQUEST: return "CMD_SYNCQUEST";
case CMD_AWAKEGOLEM: return "CMD_AWAKEGOLEM"; case CMD_REQUESTSPAWNGOLEM: return "CMD_REQUESTSPAWNGOLEM";
case CMD_SETSHIELD: return "CMD_SETSHIELD"; case CMD_SETSHIELD: return "CMD_SETSHIELD";
case CMD_REMSHIELD: return "CMD_REMSHIELD"; case CMD_REMSHIELD: return "CMD_REMSHIELD";
case CMD_SETREFLECT: return "CMD_SETREFLECT"; case CMD_SETREFLECT: return "CMD_SETREFLECT";
@ -213,6 +213,8 @@ struct DObjectStr {
struct DSpawnedMonster { struct DSpawnedMonster {
size_t typeIndex; size_t typeIndex;
uint32_t seed; uint32_t seed;
uint8_t golemOwnerPlayerId;
int16_t golemSpellLevel;
}; };
struct DLevel { struct DLevel {
@ -731,19 +733,6 @@ size_t OnLevelData(uint8_t pnum, const TCmd *pCmd)
return wBytes + sizeof(message); 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) void DeltaLeaveSync(uint8_t bLevel)
{ {
if (!gbIsMultiplayer) if (!gbIsMultiplayer)
@ -1768,27 +1757,16 @@ size_t OnMonstDeath(const TCmd *pCmd, Player &player)
return sizeof(message); return sizeof(message);
} }
size_t OnAwakeGolem(const TCmd *pCmd, Player &player) size_t OnRequestSpawnGolem(const TCmd *pCmd, const Player &player)
{ {
const auto &message = *reinterpret_cast<const TCmdGolem *>(pCmd); const auto &message = *reinterpret_cast<const TCmdLocParam1 *>(pCmd);
const Point position { message._mx, message._my }; if (gbBufferMsgs == 1)
return sizeof(message);
if (gbBufferMsgs == 1) { const WorldTilePosition position { message.x, message.y };
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); if (player.isLevelOwnedByLocalClient() && InDungeonBounds(position))
} SpawnGolem(player, position, static_cast<uint8_t>(message.wParam1));
}
return sizeof(message); return sizeof(message);
} }
@ -2368,10 +2346,15 @@ size_t OnSpawnMonster(const TCmd *pCmd, const Player &player)
size_t typeIndex = static_cast<size_t>(SDL_SwapLE16(message.typeIndex)); size_t typeIndex = static_cast<size_t>(SDL_SwapLE16(message.typeIndex));
size_t monsterId = static_cast<size_t>(SDL_SwapLE16(message.monsterId)); size_t monsterId = static_cast<size_t>(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<uint8_t>(MaxSpellLevel + Players[golemOwnerPlayerId]._pISplLvlAdd));
DLevel &deltaLevel = GetDeltaLevel(player); DLevel &deltaLevel = GetDeltaLevel(player);
deltaLevel.spawnedMonsters[monsterId] = { typeIndex, message.seed }; deltaLevel.spawnedMonsters[monsterId] = { typeIndex, message.seed, golemOwnerPlayerId, golemSpellLevel };
// Override old monster delta information // Override old monster delta information
auto &deltaMonster = deltaLevel.monster[monsterId]; auto &deltaMonster = deltaLevel.monster[monsterId];
deltaMonster.position = position; deltaMonster.position = position;
@ -2380,7 +2363,7 @@ size_t OnSpawnMonster(const TCmd *pCmd, const Player &player)
deltaMonster._mactive = 0; deltaMonster._mactive = 0;
if (player.isOnActiveLevel() && &player != MyPlayer) 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); return sizeof(message);
} }
@ -2675,7 +2658,8 @@ void DeltaLoadLevel()
DLevel &deltaLevel = GetDeltaLevel(localLevel); DLevel &deltaLevel = GetDeltaLevel(localLevel);
if (leveltype != DTYPE_TOWN) { if (leveltype != DTYPE_TOWN) {
for (auto &deltaSpawnedMonster : deltaLevel.spawnedMonsters) { 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); assert(deltaLevel.monster[deltaSpawnedMonster.first].position.x != 0xFF);
} }
for (size_t i = 0; i < MaxMonsters; i++) { for (size_t i = 0; i < MaxMonsters; i++) {
@ -2804,21 +2788,7 @@ void NetSendCmd(bool bHiPri, _cmd_id bCmd)
NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); 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) void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, uint8_t golemSpellLevel)
{
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)
{ {
TCmdSpawnMonster cmd; TCmdSpawnMonster cmd;
@ -2829,6 +2799,8 @@ void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, u
cmd.typeIndex = SDL_SwapLE16(typeIndex); cmd.typeIndex = SDL_SwapLE16(typeIndex);
cmd.monsterId = SDL_SwapLE16(monsterId); cmd.monsterId = SDL_SwapLE16(monsterId);
cmd.seed = SDL_SwapLE32(seed); cmd.seed = SDL_SwapLE32(seed);
cmd.golemOwnerPlayerId = golemOwnerPlayerId;
cmd.golemSpellLevel = golemSpellLevel;
NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd)); NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd));
} }
@ -3243,8 +3215,8 @@ size_t ParseCmd(uint8_t pnum, const TCmd *pCmd)
return OnWarp(pCmd, player); return OnWarp(pCmd, player);
case CMD_MONSTDEATH: case CMD_MONSTDEATH:
return OnMonstDeath(pCmd, player); return OnMonstDeath(pCmd, player);
case CMD_AWAKEGOLEM: case CMD_REQUESTSPAWNGOLEM:
return OnAwakeGolem(pCmd, player); return OnRequestSpawnGolem(pCmd, player);
case CMD_MONSTDAMAGE: case CMD_MONSTDAMAGE:
return OnMonstDamage(pCmd, player); return OnMonstDamage(pCmd, player);
case CMD_PLRDEAD: case CMD_PLRDEAD:

21
Source/msg.h

@ -393,10 +393,10 @@ enum _cmd_id : uint8_t {
// //
// body (TCmdQuest) // body (TCmdQuest)
CMD_SYNCQUEST, CMD_SYNCQUEST,
// Spawn golem at target location. // Request to spawn a golem at target location.
// //
// body (TCmdGolem) // body (TCmdLocParam1)
CMD_AWAKEGOLEM, CMD_REQUESTSPAWNGOLEM,
// Enable mana shield of player (render). // Enable mana shield of player (render).
// //
// body (TCmd) // body (TCmd)
@ -500,16 +500,6 @@ struct TCmdParam4 {
uint16_t wParam4; 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 { struct TCmdSpawnMonster {
_cmd_id bCmd; _cmd_id bCmd;
uint8_t x; uint8_t x;
@ -518,6 +508,8 @@ struct TCmdSpawnMonster {
uint16_t typeIndex; uint16_t typeIndex;
uint16_t monsterId; uint16_t monsterId;
uint32_t seed; uint32_t seed;
uint8_t golemOwnerPlayerId;
uint8_t golemSpellLevel;
}; };
struct TCmdQuest { struct TCmdQuest {
@ -743,8 +735,7 @@ void DeltaLoadLevel();
/** @brief Clears last sent player command for the local player. This is used when a game tick changes. */ /** @brief Clears last sent player command for the local player. This is used when a game tick changes. */
void ClearLastSentPlayerCmd(); void ClearLastSentPlayerCmd();
void NetSendCmd(bool bHiPri, _cmd_id bCmd); 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, uint8_t golemOwnerPlayerId, uint8_t golemSpellLevel);
void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed);
void NetSendCmdLoc(uint8_t playerId, bool bHiPri, _cmd_id bCmd, Point position); 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 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); void NetSendCmdLocParam2(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wParam1, uint16_t wParam2);

12
Source/player.cpp

@ -2811,16 +2811,10 @@ void SyncPlrKill(Player &player, DeathReason deathReason)
void RemovePlrMissiles(const Player &player) void RemovePlrMissiles(const Player &player)
{ {
if (leveltype != DTYPE_TOWN && &player == MyPlayer) { if (leveltype != DTYPE_TOWN) {
Monster *golem = FindGolemForPlayer(player); Monster *golem;
if (golem != nullptr) { while ((golem = FindGolemForPlayer(player)) != nullptr) {
KillGolem(*golem); KillGolem(*golem);
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();
} }
} }

Loading…
Cancel
Save