Skip to content

Commit 7062fc5

Browse files
committed
Decouple Golems from Players and use SpawnMonster for Golem
1 parent 9dca30f commit 7062fc5

File tree

6 files changed

+93
-119
lines changed

6 files changed

+93
-119
lines changed

Source/missiles.cpp

+2-3
Original file line numberDiff line numberDiff line change
@@ -2343,8 +2343,7 @@ void AddGolem(Missile &missile, AddMissileParameter &parameter)
23432343

23442344
// Is Golem alive?
23452345
if (golem != nullptr) {
2346-
if (&player == MyPlayer)
2347-
KillGolem(*golem);
2346+
KillGolem(*golem);
23482347
return;
23492348
}
23502349

@@ -2357,7 +2356,7 @@ void AddGolem(Missile &missile, AddMissileParameter &parameter)
23572356
if (!spawnPosition)
23582357
return;
23592358

2360-
SpawnGolem(player, *golem, *spawnPosition, missile);
2359+
SpawnGolem(player, *spawnPosition, missile);
23612360
}
23622361

23632362
void AddApocalypseBoom(Missile &missile, AddMissileParameter &parameter)

Source/monster.cpp

+68-30
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ void InitMonster(Monster &monster, Direction rd, size_t typeIndex, Point positio
176176
monster.goalVar2 = 0;
177177
monster.goalVar3 = 0;
178178
monster.pathCount = 0;
179+
monster.enemy = 0;
179180
monster.isInvalid = false;
180181
monster.uniqueType = UniqueMonsterType::None;
181182
monster.activeForTicks = 0;
@@ -3132,6 +3133,20 @@ void EnsureMonsterIndexIsActive(size_t monsterId)
31323133
}
31333134
}
31343135

3136+
void InitGolem(devilution::Monster &monster, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel)
3137+
{
3138+
monster.flags |= MFLAG_GOLEM;
3139+
monster.goalVar3 = static_cast<int8_t>(golemOwnerPlayerId);
3140+
const Player &player = Players[golemOwnerPlayerId];
3141+
monster.maxHitPoints = 2 * (320 * golemSpellLevel + player._pMaxMana / 3);
3142+
monster.hitPoints = monster.maxHitPoints;
3143+
monster.armorClass = 25;
3144+
monster.golemToHit = 5 * (golemSpellLevel + 8) + 2 * player.getCharacterLevel();
3145+
monster.minDamage = 2 * (golemSpellLevel + 4);
3146+
monster.maxDamage = 2 * (golemSpellLevel + 8);
3147+
UpdateEnemy(monster);
3148+
}
3149+
31353150
} // namespace
31363151

31373152
tl::expected<size_t, std::string> AddMonsterType(_monster_id type, placeflag placeflag)
@@ -3640,21 +3655,24 @@ void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpe
36403655
ActiveMonsterCount += 1;
36413656
uint32_t seed = GetLCGEngineState();
36423657
// Update local state immediately to increase ActiveMonsterCount instantly (this allows multiple monsters to be spawned in one game tick)
3643-
InitializeSpawnedMonster(position, dir, typeIndex, monsterIndex, seed);
3644-
NetSendCmdSpawnMonster(position, dir, static_cast<uint16_t>(typeIndex), static_cast<uint16_t>(monsterIndex), seed);
3658+
InitializeSpawnedMonster(position, dir, typeIndex, monsterIndex, seed, 0, 0);
3659+
NetSendCmdSpawnMonster(position, dir, static_cast<uint16_t>(typeIndex), static_cast<uint16_t>(monsterIndex), seed, 0, 0);
36453660
}
36463661

3647-
void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed)
3662+
void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel)
36483663
{
36493664
SetRndSeed(seed);
36503665
EnsureMonsterIndexIsActive(monsterId);
36513666
WorldTilePosition position = GolemHoldingCell;
36523667
Monster &monster = Monsters[monsterId];
36533668
M_ClearSquares(monster);
36543669
InitMonster(monster, Direction::South, typeIndex, position);
3670+
if (monster.type().type == MT_GOLEM) {
3671+
InitGolem(monster, golemOwnerPlayerId, golemSpellLevel);
3672+
}
36553673
}
36563674

3657-
void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed)
3675+
void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel)
36583676
{
36593677
SetRndSeed(seed);
36603678
EnsureMonsterIndexIsActive(monsterId);
@@ -3677,10 +3695,14 @@ void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, s
36773695
monster.occupyTile(position, false);
36783696
InitMonster(monster, dir, typeIndex, position);
36793697

3680-
if (IsSkel(monster.type().type))
3698+
if (monster.type().type == MT_GOLEM) {
3699+
InitGolem(monster, golemOwnerPlayerId, golemSpellLevel);
36813700
StartSpecialStand(monster, dir);
3682-
else
3701+
} else if (IsSkel(monster.type().type)) {
3702+
StartSpecialStand(monster, dir);
3703+
} else {
36833704
M_StartStand(monster, dir);
3705+
}
36843706
}
36853707

36863708
void AddDoppelganger(Monster &monster)
@@ -4656,32 +4678,48 @@ void TalktoMonster(Player &player, Monster &monster)
46564678
}
46574679
}
46584680

4659-
void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile)
4681+
void SpawnGolem(Player &player, Point position, Missile &missile)
46604682
{
4661-
golem.occupyTile(position, false);
4662-
golem.position.tile = position;
4663-
golem.position.future = position;
4664-
golem.position.old = position;
4665-
golem.pathCount = 0;
4666-
golem.maxHitPoints = 2 * (320 * missile._mispllvl + player._pMaxMana / 3);
4667-
golem.hitPoints = golem.maxHitPoints;
4668-
golem.armorClass = 25;
4669-
golem.golemToHit = 5 * (missile._mispllvl + 8) + 2 * player.getCharacterLevel();
4670-
golem.minDamage = 2 * (missile._mispllvl + 4);
4671-
golem.maxDamage = 2 * (missile._mispllvl + 8);
4672-
golem.flags |= MFLAG_GOLEM;
4673-
golem.goalVar3 = player.getId();
4674-
StartSpecialStand(golem, Direction::South);
4675-
UpdateEnemy(golem);
4676-
if (&player == MyPlayer) {
4677-
NetSendCmdGolem(
4678-
golem.position.tile.x,
4679-
golem.position.tile.y,
4680-
golem.direction,
4681-
golem.enemy,
4682-
golem.hitPoints,
4683-
GetLevelForMultiplayer(player));
4683+
// The command is only executed for the level owner, to prevent desyncs in multiplayer.
4684+
if (!MyPlayer->isLevelOwnedByLocalClient())
4685+
return;
4686+
4687+
// Search monster index to use for the new golem
4688+
Monster *golem = nullptr;
4689+
// 1. Prefer MonsterIndex = PlayerIndex for vanilla compatibility
4690+
if (player.getId() < ReservedMonsterSlotsForGolems) {
4691+
Monster &reservedGolem = Monsters[player.getId()];
4692+
if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == 0)
4693+
golem = &reservedGolem;
4694+
}
4695+
// 2. Use reserved slots, so additional Monsters can spawn
4696+
if (golem == nullptr) {
4697+
for (int i = 0; i < ReservedMonsterSlotsForGolems; i++) {
4698+
Monster &reservedGolem = Monsters[player.getId()];
4699+
if (reservedGolem.position.tile == GolemHoldingCell || reservedGolem.hitPoints == 0) {
4700+
golem = &reservedGolem;
4701+
break;
4702+
}
4703+
}
4704+
}
4705+
// 3. Use normal monster slot
4706+
if (golem == nullptr) {
4707+
if (ActiveMonsterCount >= MaxMonsters)
4708+
return;
4709+
size_t monsterIndex = ActiveMonsters[ActiveMonsterCount];
4710+
ActiveMonsterCount += 1;
4711+
golem = &Monsters[monsterIndex];
46844712
}
4713+
4714+
if (golem == nullptr)
4715+
return;
4716+
4717+
size_t monsterIndex = golem->getId();
4718+
uint32_t seed = GetLCGEngineState();
4719+
4720+
// Update local state immediately to increase ActiveMonsterCount instantly (this allows multiple monsters to be spawned in one game tick)
4721+
InitializeSpawnedMonster(position, Direction::South, 0, monsterIndex, seed, player.getId(), missile._mispllvl);
4722+
NetSendCmdSpawnMonster(position, Direction::South, 0, static_cast<uint16_t>(monsterIndex), seed, player.getId(), static_cast<uint8_t>(missile._mispllvl));
46854723
}
46864724

46874725
bool CanTalkToMonst(const Monster &monster)

Source/monster.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -514,11 +514,11 @@ void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpe
514514
/**
515515
* @brief Loads data for a dynamically spawned monster when entering a level in multiplayer.
516516
*/
517-
void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed);
517+
void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel);
518518
/**
519519
* @brief Initialize a spanwed monster (from a network message or from SpawnMonster-function).
520520
*/
521-
void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed);
521+
void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, int16_t golemSpellLevel);
522522
void AddDoppelganger(Monster &monster);
523523
void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage);
524524
bool M_Talker(const Monster &monster);
@@ -569,7 +569,7 @@ bool IsGoat(_monster_id mt);
569569
void ActivateSkeleton(Monster &monster, Point position);
570570
Monster *PreSpawnSkeleton();
571571
void TalktoMonster(Player &player, Monster &monster);
572-
void SpawnGolem(Player &player, Monster &golem, Point position, Missile &missile);
572+
void SpawnGolem(Player &player, Point position, Missile &missile);
573573
bool CanTalkToMonst(const Monster &monster);
574574
uint8_t encode_enemy(Monster &monster);
575575
void decode_enemy(Monster &monster, uint8_t enemyId);

Source/msg.cpp

+14-58
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ struct DObjectStr {
215215
struct DSpawnedMonster {
216216
size_t typeIndex;
217217
uint32_t seed;
218+
uint8_t golemOwnerPlayerId;
219+
int16_t golemSpellLevel;
218220
};
219221

220222
struct DLevel {
@@ -733,19 +735,6 @@ size_t OnLevelData(uint8_t pnum, const TCmd *pCmd)
733735
return wBytes + sizeof(message);
734736
}
735737

736-
void DeltaSyncGolem(const TCmdGolem &message, const Player &player, uint8_t level)
737-
{
738-
if (!gbIsMultiplayer)
739-
return;
740-
741-
DMonsterStr &monster = GetDeltaLevel(level).monster[player.getId()];
742-
monster.position.x = message._mx;
743-
monster.position.y = message._my;
744-
monster._mactive = UINT8_MAX;
745-
monster._menemy = message._menemy;
746-
monster.hitPoints = SDL_SwapLE32(message._mhitpoints);
747-
}
748-
749738
void DeltaLeaveSync(uint8_t bLevel)
750739
{
751740
if (!gbIsMultiplayer)
@@ -1770,31 +1759,6 @@ size_t OnMonstDeath(const TCmd *pCmd, Player &player)
17701759
return sizeof(message);
17711760
}
17721761

1773-
size_t OnAwakeGolem(const TCmd *pCmd, Player &player)
1774-
{
1775-
const auto &message = *reinterpret_cast<const TCmdGolem *>(pCmd);
1776-
const Point position { message._mx, message._my };
1777-
1778-
if (gbBufferMsgs == 1) {
1779-
SendPacket(player, &message, sizeof(message));
1780-
} else if (InDungeonBounds(position)) {
1781-
if (!player.isOnActiveLevel()) {
1782-
DeltaSyncGolem(message, player, message._currlevel);
1783-
} else if (&player != MyPlayer) {
1784-
// Check if this player already has an active golem
1785-
for (auto &missile : Missiles) {
1786-
if (missile._mitype == MissileID::Golem && &Players[missile._misource] == &player) {
1787-
return sizeof(message);
1788-
}
1789-
}
1790-
1791-
AddMissile(player.position.tile, position, message._mdir, MissileID::Golem, TARGET_MONSTERS, player, 0, 1);
1792-
}
1793-
}
1794-
1795-
return sizeof(message);
1796-
}
1797-
17981762
size_t OnMonstDamage(const TCmd *pCmd, Player &player)
17991763
{
18001764
const auto &message = *reinterpret_cast<const TCmdMonDamage *>(pCmd);
@@ -2370,10 +2334,15 @@ size_t OnSpawnMonster(const TCmd *pCmd, const Player &player)
23702334

23712335
size_t typeIndex = static_cast<size_t>(SDL_SwapLE16(message.typeIndex));
23722336
size_t monsterId = static_cast<size_t>(SDL_SwapLE16(message.monsterId));
2337+
uint8_t golemOwnerPlayerId = message.golemOwnerPlayerId;
2338+
if (golemOwnerPlayerId >= Players.size()) {
2339+
return sizeof(message);
2340+
}
2341+
uint8_t golemSpellLevel = std::min(message.golemSpellLevel, static_cast<uint8_t>(MaxSpellLevel + Players[golemOwnerPlayerId]._pISplLvlAdd));
23732342

23742343
DLevel &deltaLevel = GetDeltaLevel(player);
23752344

2376-
deltaLevel.spawnedMonsters[monsterId] = { typeIndex, message.seed };
2345+
deltaLevel.spawnedMonsters[monsterId] = { typeIndex, message.seed, golemOwnerPlayerId, golemSpellLevel };
23772346
// Override old monster delta information
23782347
auto &deltaMonster = deltaLevel.monster[monsterId];
23792348
deltaMonster.position = position;
@@ -2382,7 +2351,7 @@ size_t OnSpawnMonster(const TCmd *pCmd, const Player &player)
23822351
deltaMonster._mactive = 0;
23832352

23842353
if (player.isOnActiveLevel() && &player != MyPlayer)
2385-
InitializeSpawnedMonster(position, message.dir, typeIndex, monsterId, message.seed);
2354+
InitializeSpawnedMonster(position, message.dir, typeIndex, monsterId, message.seed, golemOwnerPlayerId, golemSpellLevel);
23862355
return sizeof(message);
23872356
}
23882357

@@ -2678,7 +2647,8 @@ void DeltaLoadLevel()
26782647
DLevel &deltaLevel = GetDeltaLevel(localLevel);
26792648
if (leveltype != DTYPE_TOWN) {
26802649
for (auto &deltaSpawnedMonster : deltaLevel.spawnedMonsters) {
2681-
LoadDeltaSpawnedMonster(deltaSpawnedMonster.second.typeIndex, deltaSpawnedMonster.first, deltaSpawnedMonster.second.seed);
2650+
auto &monsterData = deltaSpawnedMonster.second;
2651+
LoadDeltaSpawnedMonster(deltaSpawnedMonster.second.typeIndex, deltaSpawnedMonster.first, monsterData.seed, monsterData.golemOwnerPlayerId, monsterData.golemSpellLevel);
26822652
assert(deltaLevel.monster[deltaSpawnedMonster.first].position.x != 0xFF);
26832653
}
26842654
for (size_t i = 0; i < MaxMonsters; i++) {
@@ -2807,21 +2777,7 @@ void NetSendCmd(bool bHiPri, _cmd_id bCmd)
28072777
NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd));
28082778
}
28092779

2810-
void NetSendCmdGolem(uint8_t mx, uint8_t my, Direction dir, uint8_t menemy, int hp, uint8_t cl)
2811-
{
2812-
TCmdGolem cmd;
2813-
2814-
cmd.bCmd = CMD_AWAKEGOLEM;
2815-
cmd._mx = mx;
2816-
cmd._my = my;
2817-
cmd._mdir = dir;
2818-
cmd._menemy = menemy;
2819-
cmd._mhitpoints = hp;
2820-
cmd._currlevel = cl;
2821-
NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd));
2822-
}
2823-
2824-
void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed)
2780+
void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, uint8_t golemSpellLevel)
28252781
{
28262782
TCmdSpawnMonster cmd;
28272783

@@ -2832,6 +2788,8 @@ void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, u
28322788
cmd.typeIndex = SDL_SwapLE16(typeIndex);
28332789
cmd.monsterId = SDL_SwapLE16(monsterId);
28342790
cmd.seed = SDL_SwapLE32(seed);
2791+
cmd.golemOwnerPlayerId = golemOwnerPlayerId;
2792+
cmd.golemSpellLevel = golemSpellLevel;
28352793
NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd));
28362794
}
28372795

@@ -3246,8 +3204,6 @@ size_t ParseCmd(uint8_t pnum, const TCmd *pCmd)
32463204
return OnWarp(pCmd, player);
32473205
case CMD_MONSTDEATH:
32483206
return OnMonstDeath(pCmd, player);
3249-
case CMD_AWAKEGOLEM:
3250-
return OnAwakeGolem(pCmd, player);
32513207
case CMD_MONSTDAMAGE:
32523208
return OnMonstDamage(pCmd, player);
32533209
case CMD_PLRDEAD:

Source/msg.h

+3-16
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,6 @@ enum _cmd_id : uint8_t {
393393
//
394394
// body (TCmdQuest)
395395
CMD_SYNCQUEST,
396-
// Spawn golem at target location.
397-
//
398-
// body (TCmdGolem)
399-
CMD_AWAKEGOLEM,
400396
// Enable mana shield of player (render).
401397
//
402398
// body (TCmd)
@@ -500,16 +496,6 @@ struct TCmdParam4 {
500496
uint16_t wParam4;
501497
};
502498

503-
struct TCmdGolem {
504-
_cmd_id bCmd;
505-
uint8_t _mx;
506-
uint8_t _my;
507-
Direction _mdir;
508-
int8_t _menemy;
509-
int32_t _mhitpoints;
510-
uint8_t _currlevel;
511-
};
512-
513499
struct TCmdSpawnMonster {
514500
_cmd_id bCmd;
515501
uint8_t x;
@@ -518,6 +504,8 @@ struct TCmdSpawnMonster {
518504
uint16_t typeIndex;
519505
uint16_t monsterId;
520506
uint32_t seed;
507+
uint8_t golemOwnerPlayerId;
508+
uint8_t golemSpellLevel;
521509
};
522510

523511
struct TCmdQuest {
@@ -743,8 +731,7 @@ void DeltaLoadLevel();
743731
/** @brief Clears last sent player command for the local player. This is used when a game tick changes. */
744732
void ClearLastSentPlayerCmd();
745733
void NetSendCmd(bool bHiPri, _cmd_id bCmd);
746-
void NetSendCmdGolem(uint8_t mx, uint8_t my, Direction dir, uint8_t menemy, int hp, uint8_t cl);
747-
void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed);
734+
void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed, uint8_t golemOwnerPlayerId, uint8_t golemSpellLevel);
748735
void NetSendCmdLoc(uint8_t playerId, bool bHiPri, _cmd_id bCmd, Point position);
749736
void NetSendCmdLocParam1(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wParam1);
750737
void NetSendCmdLocParam2(bool bHiPri, _cmd_id bCmd, Point position, uint16_t wParam1, uint16_t wParam2);

Source/player.cpp

+3-9
Original file line numberDiff line numberDiff line change
@@ -2809,16 +2809,10 @@ void SyncPlrKill(Player &player, DeathReason deathReason)
28092809

28102810
void RemovePlrMissiles(const Player &player)
28112811
{
2812-
if (leveltype != DTYPE_TOWN && &player == MyPlayer) {
2813-
Monster *golem = FindGolemForPlayer(player);
2814-
if (golem != nullptr) {
2812+
if (leveltype != DTYPE_TOWN) {
2813+
Monster *golem;
2814+
while ((golem = FindGolemForPlayer(player)) != nullptr) {
28152815
KillGolem(*golem);
2816-
AddCorpse(golem->position.tile, golem->type().corpseId, golem->direction);
2817-
int mx = golem->position.tile.x;
2818-
int my = golem->position.tile.y;
2819-
dMonster[mx][my] = 0;
2820-
golem->isInvalid = true;
2821-
DeleteMonsterList();
28222816
}
28232817
}
28242818

0 commit comments

Comments
 (0)