8#include "Systems/NetworkSyncSystem.hpp"
34 : _network(network), _registry(registry), _builder(builder) {}
46 const auto now = std::chrono::steady_clock::now();
47 const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
61 std::strncpy(payload.
username, username.c_str(),
sizeof(payload.
username) - 1);
62 std::strncpy(payload.
password, password.c_str(),
sizeof(payload.
password) - 1);
74 std::strncpy(payload.
username, username.c_str(),
sizeof(payload.
username) - 1);
75 std::strncpy(payload.
password, password.c_str(),
sizeof(payload.
password) - 1);
85 log::info(
"Requesting list of available rooms from server...");
92 void NetworkSyncSystem::tryCreateRoom(
const std::string& roomName, uint32_t maxPlayers,
float difficulty,
float speed, uint32_t duration, uint32_t seed, uint32_t levelId, uint8_t roomType) {
93 log::info(
"Attempting to create room '{}' on server...", roomName);
97 std::strncpy(payload.
roomName, roomName.c_str(),
sizeof(payload.
roomName) - 1);
100 payload.
speed = speed;
116 if (r.roomId == roomId) {
124 payload.isSpectator =
static_cast<uint8_t
>(asSpectator ? 1 : 0);
141 packet << static_cast<uint8_t>(
isReady ? 1 : 0);
155 std::strncpy(payload.message, message.c_str(),
sizeof(payload.message) - 1);
156 payload.message[
sizeof(payload.message) - 1] =
'\0';
164 log::info(
"Sending selected weapon {} to server",
static_cast<int>(weaponKind));
166 packet << weaponKind;
334 log::info(
"Received StartGame notification from server.");
371 event.packet >> payload;
377 event.packet >> payload;
384 event.packet >> payload;
386 summary.
bestPlayer = std::string(payload.bestPlayer);
430 log::info(
"Registration successful for user '{}'", std::string(payload.
username));
446 uint32_t roomCount = 0;
449 log::info(
"Received {} available rooms from server.", roomCount);
451 if (roomCount <= 0) {
452 log::info(
"No available rooms received from server.");
456 for (uint32_t i = 0; i < roomCount; ++i) {
462 "Received Room: ID={} Name='{}' Players={}/{} InGame={} Difficulty={} Speed={} Duration={} Seed={} LevelID={}",
464 std::string(roomInfo.roomName),
465 roomInfo.currentPlayers,
467 (
int)roomInfo.inGame,
484 log::info(
"Successfully joined the room.");
514 log::info(
"Successfully left the room.");
528 log::info(
"Spawning entity from server: NetID={}, Type={}, Position=({}, {})",
559 t.
tag =
"boomerang_bullet";
566 t.
tag =
"boomerang_bullet";
568 if (payload.
sizeX > 0.0f) {
570 if (payload.
sizeX <= 6.0f) {
572 }
else if (payload.
sizeX < 12.0f) {
574 }
else if (payload.
sizeX < 16.0f) {
579 t.
scale = {scale, scale};
593 log::debug(
"Creating PowerupHeal template at ({}, {})", pos.x, pos.y);
600 log::debug(
"Creating PowerupDoubleFire template at ({}, {})", pos.x, pos.y);
604 log::debug(
"Creating PowerupShield template at ({}, {})", pos.x, pos.y);
608 log::debug(
"Creating BossShield template at ({}, {})", pos.x, pos.y);
613 log::warning(
"Unknown entity type {}, fallback template", (
int)payload.
type);
618 log::debug(
"About to spawn entity template, tag='{}'", t.
tag);
621 log::error(
"Failed to spawn entity from template: {}", res.error().message());
624 log::debug(
"Successfully spawned entity from template");
626 auto e = res.value();
665 auto &weapons = weaponsOpt.value().get();
666 if (weapons.has(e)) {
676 auto &ammos = ammoOpt.value().get();
679 ammos[e].max =
static_cast<uint16_t
>(wcfg.
maxAmmo);
681 ammos[e].current =
static_cast<uint16_t
>(wcfg.
ammo);
684 uint16_t max = (wcfg.
maxAmmo >= 0) ?
static_cast<uint16_t
>(wcfg.
maxAmmo) : 100;
685 uint16_t cur = (wcfg.
ammo >= 0) ?
static_cast<uint16_t
>( (wcfg.
ammo > 0) ? wcfg.
ammo : max ) : max;
689 uint16_t max = (wcfg.
maxAmmo >= 0) ?
static_cast<uint16_t
>(wcfg.
maxAmmo) : 100;
690 uint16_t cur = (wcfg.
ammo >= 0) ?
static_cast<uint16_t
>( (wcfg.
ammo > 0) ? wcfg.
ammo : max ) : max;
696 if (payload.
sizeX > 0.0f && payload.
sizeY > 0.0f) {
710 auto &transforms = transformsOpt.value().get();
711 if (transforms.has(e)) {
712 transforms[e].rotation = 180.0f;
717 auto &transforms = transformsOpt.value().get();
718 if (transforms.has(e)) {
719 transforms[e].rotation = 0.0f;
741 if (entityTypesOpt) {
742 auto& entityTypes = entityTypesOpt.value().get();
743 if (entityTypes.has(entity)) {
744 const auto entityType = entityTypes[entity].
type;
745 log::debug(
"EntityDeath: netId={}, entityType={}", payload.netId,
static_cast<int>(entityType));
748 log::info(
"🛡️ PowerupShield collected!");
752 if (playerTypesOpt && shieldVisualsOpt) {
753 auto& playerTypes = playerTypesOpt.value().get();
754 auto& shieldVisuals = shieldVisualsOpt.value().get();
757 for (
auto playerEntity : playerTypes.entities()) {
758 if (!playerTypes.has(playerEntity))
continue;
763 if (!shieldVisuals.has(playerEntity)) {
767 shieldVisuals[playerEntity].alpha = 255.0f;
771 log::warning(
"Could not get playerTypes or shieldVisuals!");
779 if (playerTypesOpt && shieldVisualsOpt && transformsOpt) {
780 auto& playerTypes = playerTypesOpt.value().get();
781 auto& shieldVisuals = shieldVisualsOpt.value().get();
782 auto& transforms = transformsOpt.value().get();
784 for (
auto playerEntity : shieldVisuals.entities()) {
785 if (!shieldVisuals.has(playerEntity))
continue;
786 if (!playerTypes.has(playerEntity))
continue;
789 if (transforms.has(playerEntity) && transforms.has(entity)) {
790 const auto& playerPos = transforms[playerEntity].position;
791 const auto& bulletPos = transforms[entity].position;
793 const float dx = bulletPos.x - playerPos.x;
794 const float dy = bulletPos.y - playerPos.y;
795 const float distance = std::sqrt(dx * dx + dy * dy);
797 if (distance < 150.0f) {
816 std::vector<net::EntitySnapshotPayload> snapshots;
818 packet >> header >> snapshots;
820 for (
const auto& snap : snapshots) {
832 auto &transforms = transformsOpt.value().get();
833 if (!transforms.has(e))
836 transforms[e].
position.
x = snap.position.x;
837 transforms[e].position.y = snap.position.y;
838 transforms[e].rotation = snap.rotation;
841 auto &vec = beamIt->second;
843 auto &transforms2 = tOpt2.value().get();
844 const float spriteW = 81.0f;
847 float frontOffset = 20.0f;
850 auto &sprites = sOpt.value().get();
851 if (sprites.has(ownerEntity)) {
852 const float playerSpriteW =
static_cast<float>(sprites[ownerEntity].rectWidth);
854 auto &transforms3 = t3Opt.value().get();
855 if (transforms3.has(ownerEntity)) {
856 const float playerScaleX = std::abs(transforms3[ownerEntity].scale.x);
857 frontOffset = (playerSpriteW * playerScaleX) * 0.5f + 4.0f;
864 for (
size_t i = 0; i < vec.size(); ++i) {
866 float offsetY = vec[i].second;
868 if (lenIt !=
_beamLengths.end() && i < lenIt->second.size()) length = lenIt->second[i];
870 if (!transforms2.has(beamEntity))
continue;
871 float scaleMag = (length > 0.0f) ? (length / spriteW) : std::abs(transforms2[beamEntity].scale.x);
872 transforms2[beamEntity].rotation = 0.0f;
873 transforms2[beamEntity].scale.x = -std::abs(scaleMag);
874 const float scaledWidth = spriteW * std::abs(transforms2[beamEntity].scale.x);
875 transforms2[beamEntity].position.x = snap.position.x + frontOffset + (scaledWidth * 0.5f);
876 transforms2[beamEntity].position.y = snap.position.y + offsetY;
888 std::string username(payload.username);
889 std::string message(payload.message);
890 const std::string line = username.empty()
892 : (username +
": " + message);
933 if (payload.active) {
934 Vec2f pos{0.0f, 0.0f};
936 auto &transforms = tOpt.value().get();
937 if (transforms.has(ownerEntity)) {
938 pos = transforms[ownerEntity].position;
943 const float spriteW = 81.0f;
945 float frontOffset = 20.0f;
947 auto &sprites = sOpt.value().get();
948 if (sprites.has(ownerEntity)) {
949 const float playerSpriteW =
static_cast<float>(sprites[ownerEntity].rectWidth);
951 auto &transforms3 = t3Opt.value().get();
952 if (transforms3.has(ownerEntity)) {
953 const float playerScaleX = std::abs(transforms3[ownerEntity].scale.x);
954 frontOffset = (playerSpriteW * playerScaleX) * 0.5f + 4.0f;
960 float scaleMag = (payload.length > 0.0f) ? (payload.length / spriteW) : 1.0f;
962 t.
scale.
x = -std::abs(scaleMag);
964 t.
tag =
"beam_visual";
965 const float scaledWidth = spriteW * std::abs(t.
scale.
x);
966 t.
position.
x = pos.x + frontOffset + (scaledWidth * 0.5f);
971 log::error(
"Failed to spawn beam visual: {}", res.error().message());
974 auto beamEntity = res.value();
975 _beamEntities[payload.ownerNetId].push_back({beamEntity, payload.offsetY});
976 _beamLengths[payload.ownerNetId].push_back(payload.length);
980 auto &vec = it2->second;
981 for (
size_t i = 0; i < vec.size(); ++i) {
982 if (std::fabs(vec[i].second - payload.offsetY) < 0.1f) {
985 vec.erase(vec.begin() + i);
987 if (lenIt !=
_beamLengths.end() && i < lenIt->second.size()) lenIt->second.erase(lenIt->second.begin() + i);
1004 const auto now = std::chrono::steady_clock::now();
1005 const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
1006 if (ms >= payload.clientTimeMs) {
1007 _pingMs =
static_cast<uint32_t
>(ms - payload.clientTimeMs);
Logger declaration with support for multiple log levels.
Network packet implementation for R-Type protocol.
void sendPacket(const net::Packet &packet, net::NetworkMode mode)
Send a packet to the server.
bool isUdpReady(void) const
Check if UDP socket is ready.
std::optional< net::NetworkEvent > pollEvent(void) override
Poll for a network event.
void kill(ecs::Entity entity)
auto spawn(const EntityTemplate &t) -> std::expected< ecs::Entity, Error >
bool _isLoggedIn
Flag indicating if the client is logged in.
void onSpawnEntityFromServer(net::Packet &packet)
std::deque< std::string > _chatHistory
Recent chat messages.
State getState(void) const
Get the current state of the client.
void tryRegister(const std::string &username, const std::string &password) const
Send a request to server attempting to register with provided credentials.
uint16_t getAmmoCurrent(void) const
bool isInGame(void) const
Check if the client is in game.
void onEntityDeath(net::Packet &packet)
void trySetReady(bool isReady)
Send a request to set the client's ready status in the current room.
void onLeaveRoomResponse(net::Packet &packet)
void onRoomResponse(net::Packet &packet)
bool isInRoom(void) const
Check if the client is in a room.
ecs::Registry & _registry
Reference to the entity registry.
std::unordered_map< uint32_t, ecs::Entity > _netIdToEntity
Map of network IDs to entities.
int _healthMax
Latest max health.
bool isReloading(void) const
void onDebugModeUpdate(net::Packet &packet)
void setGameOverSummary(const GameOverSummary &summary)
void pushChatMessage(const std::string &message)
NetworkSyncSystem(ClientNetwork &network, ecs::Registry ®istry, EntityBuilder builder)
Constructor for NetworkSyncSystem.
float _ammoReloadRemaining
Reload time remaining.
bool _isReady
Flag indicating if the client is ready.
bool isReady(void) const
Check if the client is ready.
uint32_t getPingMs(void) const
void onBeamState(net::Packet &packet)
std::list< net::RoomInfo > _availableRooms
List of available rooms from the server.
uint32_t _currentLevelId
Current level id used by client visuals.
int getHealthCurrent(void) const
bool consumeGameOver(void)
float _pingTimer
Ping timer accumulator.
void onPong(net::Packet &packet)
void onRegisterResponse(net::Packet &packet)
void tryLogin(const std::string &username, const std::string &password, uint8_t weaponKind) const
Send a request to server attempting to log in with provided credentials.
bool isLoggedIn(void) const
Check if the client is logged in.
float _pingInterval
Ping interval in seconds.
void tryJoinRoom(uint32_t roomId, bool asSpectator=false)
Send a request to join an existing room on the server.
void tryLeaveRoom(void)
Send a request to leave the current room on the server.
void onAmmoUpdate(net::Packet &packet)
uint16_t _ammoMax
Max ammo.
void tryStartSolo(void)
Start a solo game by creating a private room and auto-joining/readying.
std::unordered_map< uint32_t, std::vector< std::pair< ecs::Entity, float > > > _beamEntities
bool _ammoReloading
Reload state.
std::unordered_map< uint32_t, std::vector< float > > _beamLengths
bool _gameOverPending
Game over pending flag.
uint16_t getAmmoMax(void) const
uint16_t _ammoCurrent
Current ammo.
float getReloadCooldownRemaining(void) const
void onJoinRoomResponse(net::Packet &packet)
EntityBuilder _builder
Entity builder for spawning entities.
void handleEvent(net::NetworkEvent &event)
Handle a network event.
std::size_t _chatHistoryLimit
Max chat messages to keep.
void onCreateRoomResponse(net::Packet &packet)
uint32_t getCurrentLevelId(void) const
Get the current level ID for client-side visuals (parallax)
int _score
Latest server score.
bool isUdpReady(void) const
Check if UDP is ready.
void onRoomChatReceived(net::Packet &packet)
int getHealthMax(void) const
void tryCreateRoom(const std::string &roomName, uint32_t maxPlayers, float difficulty, float speed, uint32_t duration, uint32_t seed, uint32_t levelId, uint8_t roomType)
Send a request to create a new room on the server.
void trySendMessage(const std::string &message) const
Send a chat message to the server.
int _healthCurrent
Latest health.
void requestListRooms(void)
Send a request to have the list of available rooms from the server.
std::string _username
The username of the client.
void update(float dt) override
Update system logic for one frame.
ClientNetwork & _network
Reference to the client network manager.
bool _isStartingSolo
Check if the client is starting a solo game.
GameOverSummary _gameOverSummary
Cached game over summary.
std::string getLastChatMessage(void) const
Get the last received room chat message.
std::string getUsername(void) const
Get the username of the client.
void onLoginResponse(net::Packet &packet)
std::string _lastChatMessage
Last received chat message.
const std::deque< std::string > & getChatHistory(void) const
Get chat history buffer (most recent last)
void onRoomUpdate(net::Packet &packet)
void sendSelectedWeapon(uint8_t weaponKind) const
Send currently selected weapon to server to apply immediately.
GameOverSummary getGameOverSummary(void) const
State _currentState
Current state of the client.
std::list< net::RoomInfo > getAvailableRooms(void) const
Get a list of available rooms from the server.
uint32_t _pingMs
Latest ping in ms.
Represents an entity in the ECS (Entity-Component-System) architecture.
auto add(Entity entity, Args &&...args) -> std::expected< std::reference_wrapper< T >, rtp::Error >
auto get(this const Self &self) -> std::expected< std::reference_wrapper< ConstLike< Self, SparseArray< T > > >, rtp::Error >
void remove(Entity entity) noexcept
Network packet with header and serializable body.
Header header
Packet header.
const rtp::ecs::components::SimpleWeapon & getWeaponDef(rtp::ecs::components::WeaponKind kind)
WeaponKind
Types of player weapons.
@ Tracker
Weak auto-homing shots.
@ Boomerang
Single projectile that returns.
@ Classic
Default spam/charge laser.
@ Beam
Continuous beam 5s active, 5s cooldown.
void error(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an error message.
void warning(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a warning message.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.
void debug(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log a debug message.
EntityType
Types of entities in the game.
@ DebugModeUpdate
Debug mode toggle.
@ SetReady
Set player readiness status.
@ LoginRequest
Client login request.
@ ListRooms
Request for room list.
@ BeamState
Beam start/stop notification.
@ RoomChatReceived
Chat message received in room.
@ LeaveRoom
Request to leave a room.
@ RegisterResponse
Incorrect password notification.
@ LoginResponse
Successful connection notification.
@ UpdateSelectedWeapon
Client selected weapon changed.
@ JoinRoom
Request to join a room.
@ RoomList
Response with room list.
@ RegisterRequest
Server login response.
@ AmmoUpdate
Ammo update notification.
@ RoomChatSended
Chat message in room.
@ StartGame
Notification to start the game.
@ GameOver
Game over summary.
@ CreateRoom
Request to create a room.
@ HealthUpdate
Player health update.
@ RoomUpdate
Notification of room update.
@ EntitySpawn
Entity spawn notification.
@ ScoreUpdate
Player score update.
@ Kicked
Player kicked notification.
@ EntityDeath
Entity death notification.
static EntityTemplate createBoss2Bullet(const Vec2f &initialPos)
static EntityTemplate createBoss2Kraken(const Vec2f &initialPos)
Vec2f position
Spawn position.
static EntityTemplate player_ship(const Vec2f &initialPos)
static EntityTemplate createBossShip(const Vec2f &initialPos)
std::string tag
Template tag for entity differentiation.
static EntityTemplate createBossShield(const Vec2f &initialPos)
static EntityTemplate effect_4(const Vec2f &initialPos)
static EntityTemplate effect_1(const Vec2f &initialPos)
static EntityTemplate createBulletEnemy(const Vec2f &initialPos)
float rotation
Spawn rotation.
static EntityTemplate createPowerUpDoubleFire(const Vec2f &initialPos)
static EntityTemplate createBulletPlayer(const Vec2f &initialPos)
static EntityTemplate shot_5(const Vec2f &initialPos)
static EntityTemplate enemy_2(const Vec2f &initialPos)
static EntityTemplate enemy_1(const Vec2f &initialPos)
static EntityTemplate createPowerUpShield(const Vec2f &initialPos)
static EntityTemplate createPowerUpHeal(const Vec2f &initialPos)
static EntityTemplate shot_1(const Vec2f &initialPos)
Ammo tracking for weapons.
rtp::net::EntityType type
Component representing a network identifier for an entity.
Component for visual shield effect displayed around the player.
float animationTime
Time accumulator for pulse animation.
Component representing a weapon configuration.
float beamCooldown
Beam cooldown time (Beam)
int maxAmmo
Max ammo (-1 = infinite)
bool isBoomerang
Projectile returns (Boomerang)
int ammo
Current ammo (-1 = infinite)
float beamDuration
Beam active time (Beam)
float fireRate
Shots per second (or cooldown)
int damage
Damage per shot.
bool homing
Shots home to enemies (Tracker)
WeaponKind kind
Weapon type.
Component representing a sprite.
Ammo update notification data Server OpCode.
Notify clients that an entity's beam started or stopped.
Data for creating a new room Client OpCode.
uint32_t seed
Seed for random generation.
uint32_t duration
Duration of the game session.
float difficulty
Difficulty level.
uint32_t maxPlayers
Maximum number of players.
uint8_t roomType
Room type (public/private)
char roomName[64]
Desired room name.
float speed
Speed multiplier.
uint32_t levelId
Level identifier.
Debug mode toggle data Server OpCode.
Entity death notification data.
Entity spawn notification data.
uint8_t type
Entity type from EntityType.
uint32_t netId
Network entity identifier.
uint8_t weaponKind
Optional weapon kind for player entities.
float posX
Spawn X position.
float posY
Spawn Y position.
float sizeY
Optional height for static entities.
float sizeX
Optional width for static entities.
Player health update data.
int32_t current
Current health.
Data for joining an existing room Client OpCode.
uint32_t roomId
Room identifier to join.
char password[32]
Player password.
uint8_t weaponKind
Selected weapon kind.
char username[32]
Player username.
Login response data sent by server for LoginResponse Server OpCode.
uint8_t success
Login success flag.
char username[32]
Player username.
Represents a network event containing session ID and packet data.
Ping request/response payload Client/Server /Pong OpCodes.
Player registration data sended by client Client OpCode.
char password[32]
Player password.
char username[32]
Player username.
Registration response data sent by server for RegisterResponse Server OpCode.
uint8_t success
Registration success flag.
char username[32]
Player username.
Data for sending chat messages in a room Client OpCode.
Data for receiving chat messages in a room Server OpCode.
Information about a game room Server OpCode.
Player score update notification data.
int32_t score
Player score.