Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple Golems from Players and use SpawnMonster for Golem #7665

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions Source/missiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2339,22 +2339,24 @@ void AddGolem(Missile &missile, AddMissileParameter &parameter)

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<Point> spawnPosition = FindClosestValidPosition(
[start = missile.position.start](Point target) {
return !IsTileOccupied(target) && LineClearMissile(start, target);
},
parameter.dst, 0, 5);
std::optional<Point> 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 &parameter)
Expand Down
147 changes: 103 additions & 44 deletions Source/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<size_t>(attacker.goalVar3);
const Player &player = Players[playerId];
target.tag(player);
}
Expand Down Expand Up @@ -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<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

tl::expected<size_t, std::string> AddMonsterType(_monster_id type, placeflag placeflag)
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -3587,7 +3605,7 @@ tl::expected<void, std::string> 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);
Expand Down Expand Up @@ -3637,21 +3655,24 @@ 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<uint16_t>(typeIndex), static_cast<uint16_t>(monsterIndex), seed);
InitializeSpawnedMonster(position, dir, typeIndex, monsterIndex, seed, 0, 0);
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);
EnsureMonsterIndexIsActive(monsterId);
WorldTilePosition position = GolemHoldingCell;
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);
Expand All @@ -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)
Expand Down Expand Up @@ -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<uint16_t>(golem.getId()));
M_StartKill(golem, *MyPlayer);
}

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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<int>(ActiveMonsters[i])) // Unselect monster if player highlighted it
pcursmonst = -1;
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -4636,55 +4678,72 @@ 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<uint16_t>(monsterIndex), seed, player.getId(), static_cast<uint8_t>(missile._mispllvl));
}

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;
}
Expand Down
15 changes: 8 additions & 7 deletions Source/monster.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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
Loading
Loading