Browse Source

Sync monster spawn in multiplayer

pull/6494/head^2
obligaron 2 years ago committed by Anders Jenbo
parent
commit
51ab0106ab
  1. 24
      Source/lua/modules/dev/monsters.cpp
  2. 70
      Source/monster.cpp
  3. 13
      Source/monster.h
  4. 94
      Source/msg.cpp
  5. 15
      Source/msg.h

24
Source/lua/modules/dev/monsters.cpp

@ -114,6 +114,7 @@ std::string DebugCmdSpawnMonster(std::string name, std::optional<unsigned> count
}
if (mtype == -1) return "Monster not found";
if (!MyPlayer->isLevelOwnedByLocalClient()) return "You are not the level owner.";
size_t id = MaxLvlMTypes - 1;
bool found = false;
@ -137,28 +138,27 @@ std::string DebugCmdSpawnMonster(std::string name, std::optional<unsigned> count
Player &myPlayer = *MyPlayer;
unsigned spawnedMonster = 0;
size_t monstersToSpawn = std::min<size_t>(MaxMonsters - ActiveMonsterCount, count);
if (monstersToSpawn == 0)
return "Can't spawn any monsters";
auto ret = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional<std::string> {
size_t spawnedMonster = 0;
Crawl(0, MaxCrawlRadius, [&](Displacement displacement) {
Point pos = myPlayer.position.tile + displacement;
if (dPlayer[pos.x][pos.y] != 0 || dMonster[pos.x][pos.y] != 0)
return {};
return false;
if (!IsTileWalkable(pos))
return {};
return false;
if (AddMonster(pos, myPlayer._pdir, id, true) == nullptr)
return StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)");
SpawnMonster(pos, myPlayer._pdir, id);
spawnedMonster += 1;
if (spawnedMonster >= count)
return StrCat("Spawned ", spawnedMonster, " monsters.");
return {};
return spawnedMonster == monstersToSpawn;
});
if (!ret.has_value())
if (monstersToSpawn != count)
return StrCat("Spawned ", spawnedMonster, " monsters. (Unable to spawn more)");
return *ret;
return StrCat("Spawned ", spawnedMonster, " monsters.");
}
} // namespace

70
Source/monster.cpp

@ -3135,6 +3135,21 @@ MonsterSpritesData LoadMonsterSpritesData(const MonsterData &monsterData)
return result;
}
void EnsureMonsterIndexIsActive(size_t monsterId)
{
assert(monsterId < MaxMonsters);
for (size_t index = 0; index < MaxMonsters; index++) {
if (ActiveMonsters[index] != monsterId)
continue;
if (index < ActiveMonsterCount)
return; // monster is already active
int oldId = ActiveMonsters[ActiveMonsterCount];
ActiveMonsters[ActiveMonsterCount] = static_cast<int>(monsterId);
ActiveMonsters[index] = oldId;
ActiveMonsterCount += 1;
}
}
} // namespace
size_t AddMonsterType(_monster_id type, placeflag placeflag)
@ -3645,9 +3660,58 @@ Monster *AddMonster(Point position, Direction dir, size_t typeIndex, bool inMap)
void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpecialStand /*= false*/)
{
Monster *monster = AddMonster(position, dir, typeIndex, true);
if (startSpecialStand && monster != nullptr)
StartSpecialStand(*monster, dir);
if (ActiveMonsterCount >= MaxMonsters)
return;
// The command is only executed for the level owner, to prevent desyncs in multiplayer.
if (!MyPlayer->isLevelOwnedByLocalClient())
return;
size_t monsterIndex = ActiveMonsters[ActiveMonsterCount];
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);
}
void LoadDeltaSpawnedMonster(size_t typeIndex, size_t monsterId, uint32_t seed)
{
SetRndSeed(seed);
EnsureMonsterIndexIsActive(monsterId);
WorldTilePosition position = GolemHoldingCell;
Monster &monster = Monsters[monsterId];
M_ClearSquares(monster);
InitMonster(monster, Direction::South, typeIndex, position);
}
void InitializeSpawnedMonster(Point position, Direction dir, size_t typeIndex, size_t monsterId, uint32_t seed)
{
SetRndSeed(seed);
EnsureMonsterIndexIsActive(monsterId);
Monster &monster = Monsters[monsterId];
M_ClearSquares(monster);
// When we receive a network message, the position we got for the new monster may already be occupied.
// That's why we check for the next free tile for the monster.
auto freePosition = Crawl(0, MaxCrawlRadius, [&](Displacement displacement) -> std::optional<Point> {
Point posToCheck = position + displacement;
if (IsTileAvailable(posToCheck))
return posToCheck;
return {};
});
assert(freePosition);
assert(!MyPlayer->isLevelOwnedByLocalClient() || (freePosition && position == *freePosition));
position = freePosition.value_or(position);
monster.occupyTile(position, false);
InitMonster(monster, dir, typeIndex, position);
if (IsSkel(monster.type().type))
StartSpecialStand(monster, dir);
else
M_StartStand(monster, dir);
}
void AddDoppelganger(Monster &monster)

13
Source/monster.h

@ -495,7 +495,20 @@ void InitGolems();
void InitMonsters();
void SetMapMonsters(const uint16_t *dunData, Point startPosition);
Monster *AddMonster(Point position, Direction dir, size_t mtype, bool inMap);
/**
* @brief Spawns a new monsters (dynamically/not on level load).
* The command is only executed for the level owner, to prevent desyncs in multiplayer.
* The level owner sends a CMD_SPAWNMONSTER-message to the other players.
*/
void SpawnMonster(Point position, Direction dir, size_t typeIndex, bool startSpecialStand = false);
/**
* @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);
/**
* @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 AddDoppelganger(Monster &monster);
void ApplyMonsterDamage(DamageType damageType, Monster &monster, int damage);
bool M_Talker(const Monster &monster);

94
Source/msg.cpp

@ -177,6 +177,7 @@ std::string_view CmdIdString(_cmd_id cmd)
case CMD_NAKRUL: return "CMD_NAKRUL";
case CMD_OPENHIVE: return "CMD_OPENHIVE";
case CMD_OPENGRAVE: return "CMD_OPENGRAVE";
case CMD_SPAWNMONSTER: return "CMD_SPAWNMONSTER";
case FAKE_CMD_SETID: return "FAKE_CMD_SETID";
case FAKE_CMD_DROPID: return "FAKE_CMD_DROPID";
case CMD_INVALID: return "CMD_INVALID";
@ -210,9 +211,15 @@ struct DObjectStr {
_cmd_id bCmd;
};
struct DSpawnedMonster {
size_t typeIndex;
uint32_t seed;
};
struct DLevel {
TCmdPItem item[MAXITEMS];
std::unordered_map<WorldTilePosition, DObjectStr> object;
std::unordered_map<size_t, DSpawnedMonster> spawnedMonsters;
DMonsterStr monster[MaxMonsters];
};
@ -539,7 +546,7 @@ std::byte *DeltaExportMonster(std::byte *dst, const DMonsterStr *src)
return dst;
}
void DeltaImportMonster(const std::byte *src, DMonsterStr *dst)
size_t DeltaImportMonster(const std::byte *src, DMonsterStr *dst)
{
size_t size = 0;
for (size_t i = 0; i < MaxMonsters; i++, dst++) {
@ -551,6 +558,43 @@ void DeltaImportMonster(const std::byte *src, DMonsterStr *dst)
size += sizeof(DMonsterStr);
}
}
return size;
}
std::byte *DeltaExportSpawnedMonsters(std::byte *dst, const std::unordered_map<size_t, DSpawnedMonster> &spawnedMonsters)
{
auto &size = *reinterpret_cast<uint16_t *>(dst);
size = static_cast<uint16_t>(spawnedMonsters.size());
dst += sizeof(uint16_t);
for (const auto &deltaSpawnedMonster : spawnedMonsters) {
auto &monsterId = *reinterpret_cast<uint16_t *>(dst);
monsterId = static_cast<uint16_t>(deltaSpawnedMonster.first);
dst += sizeof(uint16_t);
memcpy(dst, &deltaSpawnedMonster.second, sizeof(DSpawnedMonster));
dst += sizeof(DSpawnedMonster);
}
return dst;
}
const std::byte *DeltaImportSpawnedMonsters(const std::byte *src, std::unordered_map<size_t, DSpawnedMonster> &spawnedMonsters)
{
uint16_t size = *reinterpret_cast<const uint16_t *>(src);
src += sizeof(uint16_t);
for (size_t i = 0; i < size; i++) {
uint16_t monsterId = *reinterpret_cast<const uint16_t *>(src);
src += sizeof(uint16_t);
DSpawnedMonster spawnedMonster;
memcpy(&spawnedMonster, src, sizeof(DSpawnedMonster));
src += sizeof(DSpawnedMonster);
spawnedMonsters.emplace(monsterId, spawnedMonster);
}
return src;
}
std::byte *DeltaExportJunk(std::byte *dst)
@ -636,7 +680,8 @@ void DeltaImportData(_cmd_id cmd, uint32_t recvOffset)
DLevel &deltaLevel = GetDeltaLevel(i);
src += DeltaImportItem(src, deltaLevel.item);
src = DeltaImportObjects(src, deltaLevel.object);
DeltaImportMonster(src, deltaLevel.monster);
src += DeltaImportMonster(src, deltaLevel.monster);
src = DeltaImportSpawnedMonsters(src, deltaLevel.spawnedMonsters);
} else {
app_fatal(StrCat("Unkown network message type: ", cmd));
}
@ -2328,6 +2373,26 @@ size_t OnOpenGrave(const TCmd *pCmd)
return sizeof(*pCmd);
}
size_t OnSpawnMonster(const TCmd *pCmd, const Player &player)
{
const auto &message = *reinterpret_cast<const TCmdSpawnMonster *>(pCmd);
if (gbBufferMsgs == 1)
return sizeof(message);
const Point position { message.x, message.y };
size_t typeIndex = static_cast<size_t>(SDL_SwapLE16(message.typeIndex));
size_t monsterId = static_cast<size_t>(SDL_SwapLE16(message.monsterId));
DLevel &deltaLevel = GetDeltaLevel(player);
deltaLevel.spawnedMonsters[monsterId] = { typeIndex, message.seed };
if (player.isOnActiveLevel() && &player != MyPlayer)
InitializeSpawnedMonster(position, message.dir, typeIndex, monsterId, message.seed);
return sizeof(message);
}
} // namespace
void PrepareItemForNetwork(const Item &item, TItem &messageItem)
@ -2434,7 +2499,9 @@ void DeltaExportData(uint8_t pnum)
+ sizeof(deltaLevel.item) /* items spawned during dungeon generation which have been picked up, and items dropped by a player during a game */
+ sizeof(uint8_t) /* count of object interactions which caused a state change since dungeon generation */
+ (sizeof(WorldTilePosition) + sizeof(DObjectStr)) * deltaLevel.object.size() /* location/action pairs for the object interactions */
+ sizeof(deltaLevel.monster); /* latest monster state */
+ sizeof(deltaLevel.monster) /* latest monster state */
+ sizeof(uint16_t) /* spanwned monster count */
+ (sizeof(uint16_t) + sizeof(DSpawnedMonster)) * MaxMonsters; /* spanwned monsters */
std::unique_ptr<std::byte[]> dst { new std::byte[bufferSize] };
std::byte *dstEnd = &dst.get()[1];
@ -2443,6 +2510,7 @@ void DeltaExportData(uint8_t pnum)
dstEnd = DeltaExportItem(dstEnd, deltaLevel.item);
dstEnd = DeltaExportObject(dstEnd, deltaLevel.object);
dstEnd = DeltaExportMonster(dstEnd, deltaLevel.monster);
dstEnd = DeltaExportSpawnedMonsters(dstEnd, deltaLevel.spawnedMonsters);
uint32_t size = CompressData(dst.get(), dstEnd);
multi_send_zero_packet(pnum, CMD_DLEVEL, dst.get(), size);
}
@ -2616,6 +2684,10 @@ void DeltaLoadLevel()
uint8_t localLevel = GetLevelForMultiplayer(*MyPlayer);
DLevel &deltaLevel = GetDeltaLevel(localLevel);
if (leveltype != DTYPE_TOWN) {
for (auto &deltaSpawnedMonster : deltaLevel.spawnedMonsters) {
LoadDeltaSpawnedMonster(deltaSpawnedMonster.second.typeIndex, deltaSpawnedMonster.first, deltaSpawnedMonster.second.seed);
assert(deltaLevel.monster[deltaSpawnedMonster.first].position.x != 0xFF);
}
for (size_t i = 0; i < MaxMonsters; i++) {
if (deltaLevel.monster[i].position.x == 0xFF)
continue;
@ -2756,6 +2828,20 @@ void NetSendCmdGolem(uint8_t mx, uint8_t my, Direction dir, uint8_t menemy, int
NetSendLoPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd));
}
void NetSendCmdSpawnMonster(Point position, Direction dir, uint16_t typeIndex, uint16_t monsterId, uint32_t seed)
{
TCmdSpawnMonster cmd;
cmd.bCmd = CMD_SPAWNMONSTER;
cmd.x = position.x;
cmd.y = position.y;
cmd.dir = dir;
cmd.typeIndex = SDL_SwapLE16(typeIndex);
cmd.monsterId = SDL_SwapLE16(monsterId);
cmd.seed = SDL_SwapLE32(seed);
NetSendHiPri(MyPlayerId, (std::byte *)&cmd, sizeof(cmd));
}
void NetSendCmdLoc(uint8_t playerId, bool bHiPri, _cmd_id bCmd, Point position)
{
if (playerId == MyPlayerId && WasPlayerCmdAlreadyRequested(bCmd, position))
@ -3264,6 +3350,8 @@ size_t ParseCmd(uint8_t pnum, const TCmd *pCmd)
return OnOpenHive(pCmd, player);
case CMD_OPENGRAVE:
return OnOpenGrave(pCmd);
case CMD_SPAWNMONSTER:
return OnSpawnMonster(pCmd, player);
default:
break;
}

15
Source/msg.h

@ -416,6 +416,10 @@ enum _cmd_id : uint8_t {
CMD_NAKRUL,
CMD_OPENHIVE,
CMD_OPENGRAVE,
// Spawn a monster at target location.
//
// body (TCmdSpawnMonster)
CMD_SPAWNMONSTER,
// Fake command; set current player for succeeding mega pkt buffer messages.
//
// body (TFakeCmdPlr)
@ -514,6 +518,16 @@ struct TCmdGolem {
uint8_t _currlevel;
};
struct TCmdSpawnMonster {
_cmd_id bCmd;
uint8_t x;
uint8_t y;
Direction dir;
uint16_t typeIndex;
uint16_t monsterId;
uint32_t seed;
};
struct TCmdQuest {
_cmd_id bCmd;
int8_t q;
@ -738,6 +752,7 @@ void DeltaLoadLevel();
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 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);

Loading…
Cancel
Save