Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
be8dd10
hotfix(server/systems): Fixed collision detection on ApplyMovement sy…
nl1x Jan 18, 2026
7b96f12
feat(server/systems): Added BroadcastDeadEntities system implementation
nl1x Jan 18, 2026
b01434f
fix(server/cmake): Added src/systems/broadcast_dead_entities.cpp in s…
nl1x Jan 18, 2026
fb7d609
feat(server/handlers/user-inputs): Added the component State to Bullets
nl1x Jan 18, 2026
3e7e296
feat(server/level-director/parse-ennemies): Added a state to the enemies
nl1x Jan 18, 2026
bcaa5b6
feat(server/lobby): Added a method 'Lobby::killEntity'
nl1x Jan 18, 2026
e5d8d66
refactor(server/systems): Improved the way of moving entities
nl1x Jan 18, 2026
45a2551
refactor(server/enums/player_state.hpp): Renamed the namespace player…
nl1x Jan 18, 2026
bf3b7fd
feat(lib/rtecs/ecs): Added a method 'getEntityComponent<T>(types::Ent…
nl1x Jan 18, 2026
55c43b1
fix(server/lobby): Replaced player::state to entity::state
nl1x Jan 18, 2026
3b9ba5e
fix(server/lobby): Uncomment the registration of BroadcastDeadEntities
nl1x Jan 18, 2026
8571f03
docs(server/systems): Added string doc of the ApplyMovement system
nl1x Jan 18, 2026
62e90ea
fix(server>lobby>level-director): Fixed merge conflit induced error
PierreMarguerie Jan 18, 2026
e649767
feat(server>lobby>level-director): Entities now spawn based on player…
PierreMarguerie Jan 18, 2026
66f7db6
feat(server/level-director/parse-ennemies): Added a state to the enemies
nl1x Jan 18, 2026
4fd6c97
fix(server/level-director): Fix compilation error
nl1x Jan 18, 2026
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
1 change: 1 addition & 0 deletions Server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ add_executable(${PROJECT_NAME}
src/systems/apply_movement.cpp
src/systems/apply_enemy_movement.cpp
src/systems/broadcast_updated_movements.cpp
src/systems/broadcast_dead_entities.cpp
)

target_include_directories(${PROJECT_NAME} PRIVATE
Expand Down
14 changes: 11 additions & 3 deletions Server/src/handlers/handle_user_input.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "components/hitbox.hpp"
#include "components/position.hpp"
#include "enums/input.hpp"
#include "enums/player_state.hpp"
#include "handlers.hpp"
#include "lobby/lobby.hpp"
#include "packets/client/user_input.hpp"
Expand All @@ -14,8 +15,14 @@ static void spawnBullet(const rtecs::types::EntityID& id,
{
if (packet.input_mask & static_cast<uint8_t>(game::Input::kShoot)) {
using namespace components;
lobby.spawnEntity<Type, Position, Owner, Velocity, Hitbox, Collision>(
{entity::Type::kBullet}, {pos.x, pos.y}, {id}, {20, 0}, {true, 75, 35}, {});
lobby.spawnEntity<Type, Position, Owner, Velocity, Hitbox, State, Collision>(
{entity::Type::kBullet},
{pos.x, pos.y},
{id},
{20, 0},
{true, 75, 35},
{entity::state::EntityAlive},
{});
}
}

Expand All @@ -34,7 +41,8 @@ void handleUserInput(const SessionPtr& session,
if (!velocity) {
LOG_WARN(
"This should not happen, check for component velocity on this entity or for this "
"entity presence.");
"entity presence. (entity id: {})",
lobby.getPlayerId(session).value());
return;
}
auto& [vx, vy, max_vx, max_vy] = velocity.value().get();
Expand Down
3 changes: 2 additions & 1 deletion Server/src/level_director/level_director.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,14 @@ void Director::update(const float dt,
x,
y);
using namespace components;
lobby.spawnEntity<Position, Type, Hitbox, Velocity, Collision, Value, MoveSet>(
lobby.spawnEntity<Position, Type, Hitbox, Velocity, Collision, Value, State, MoveSet>(
{x, y},
{group.type},
typeToHitbox(group.type),
typeToVelocity(group.type),
{},
typeToValue(group.type),
{entity::state::EntityAlive},
{nameToMoveSet(group.patternName)});

wave.spawnedInGroup++;
Expand Down
23 changes: 20 additions & 3 deletions Server/src/lobby/lobby.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "logger/Thread.h"
#include "systems/apply_enemy_movement.hpp"
#include "systems/apply_movement.hpp"
#include "systems/broadcast_dead_entities.hpp"
#include "systems/broadcast_updated_movements.hpp"

Lobby::Lobby(const lobby::Id id,
Expand All @@ -34,6 +35,7 @@ void Lobby::registerAllSystems()
_engine.registerSystem(std::make_shared<server::systems::ApplyMovement>());
_engine.registerSystem(std::make_shared<server::systems::ApplyEnemyMovement>());
_engine.registerSystem(std::make_shared<server::systems::BroadcastUpdatedMovements>(*this));
_engine.registerSystem(std::make_shared<server::systems::BroadcastDeadEntities>(*this));
}

lobby::Id Lobby::getRoomId() const { return _roomId; }
Expand Down Expand Up @@ -68,6 +70,21 @@ void Lobby::broadcast(const packet::server::Variant& packet) const
_outGoing.push({getAllSessions(), packet});
}

rtecs::types::EntityID Lobby::killEntity(const rtecs::types::EntityID id,
const packet::server::SessionPtr& session)
{
std::cout << "Killing entity " << id << std::endl;
_engine.destroyEntity(id);
if (session) {
_players.erase(session);
}
packet::Destroy p{};
p.id = id;
p.earned_points = 0;
broadcast(p);
return id;
}

rteng::GameEngine& Lobby::getEngine() { return _engine; }

void Lobby::changeGameState(const uint64_t& gameState)
Expand Down Expand Up @@ -95,7 +112,7 @@ bool Lobby::hasPlayerAlive()
}
return std::ranges::any_of(_players | std::views::values, [&](const auto& playerId) {
const auto& stateOpt = _engine.getEntityFromGroup<components::State>(playerId);
return stateOpt && stateOpt->get().state == player::state::PlayerAlive;
return stateOpt && stateOpt->get().state == entity::state::EntityAlive;
});
}

Expand All @@ -104,7 +121,7 @@ void Lobby::restart()
_levelDirector.restart();
for (const auto& playerId : _players | std::views::values) {
_engine.getEntityFromGroup<components::State>(playerId).value().get().state =
player::state::PlayerAlive;
entity::state::EntityAlive;
}
}

Expand All @@ -130,7 +147,7 @@ void Lobby::join(const packet::server::SessionPtr& session)
{true, 150, 75},
{},
{},
{player::state::PlayerWaiting},
{entity::state::PlayerWaiting},
session);
packet::JoinAck j = {_players.at(session), _roomId, _engine.getGameState(), true};
send(session, j);
Expand Down
8 changes: 8 additions & 0 deletions Server/src/lobby/lobby.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ class Lobby
return id;
}

/**
* @brief Remove an entity and broadcasts it's deletion.
* @param id The entity's ID.
* @param session The session triggering the deletion (nullptr if none).
*/
rtecs::types::EntityID killEntity(rtecs::types::EntityID id,
const packet::server::SessionPtr& session = nullptr);

/**
* @brief Getter for the gameEngine.
* @return A reference to the used gameEngine.
Expand Down
157 changes: 107 additions & 50 deletions Server/src/systems/apply_movement.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include "apply_movement.hpp"

#include "components/collision.hpp"
#include "components/factory.hpp"
#include "components/position.hpp"
#include "components/velocity.hpp"
#include "enums/player_state.hpp"
#include "rtecs/ECS.hpp"

using namespace server::systems;
Expand All @@ -14,78 +14,135 @@ ApplyMovement::ApplyMovement()
{
}

void ApplyMovement::apply(rtecs::ECS &ecs)
void ApplyMovement::apply(rtecs::ECS& ecs)
{
using namespace components;
auto movable = ecs.group<Velocity, Position, Hitbox>();
auto colliders = ecs.group<Position, Hitbox>();
auto movable = ecs.group<Type, Velocity, Position, Hitbox, State>();
auto colliders = ecs.group<Position, Hitbox, State, Type>();

movable.apply(
[&](const rtecs::types::EntityID id, Velocity &vel, Position &pos, const Hitbox &box) {
std::optional<std::pair<Position, Hitbox>> collider = std::nullopt;
movable.apply([&](const rtecs::types::EntityID id,
const Type& type,
Velocity& vel,
Position& pos,
const Hitbox& box,
State& state) {
pos.isUpdated = vel.vx != 0 || vel.vy != 0;
if (type.type == entity::Type::kPlayer) {
const Position nextHorizontalPos = {pos.x + vel.vx, pos.y};
const std::optional<Collider> horizontalCollider =
findCollider(id, nextHorizontalPos, box, colliders, entity::Type::kPlayer);
handlePlayerHorizontalMovement(pos, vel, box, horizontalCollider);

pos.isUpdated = true;
colliders.apply([&](const rtecs::types::EntityID otherId,
const Position &otherPos,
const Hitbox &otherBox) {
if (id == otherId || collider.has_value()) {
return;
}
const Position nextVerticalPos = {pos.x, pos.y + vel.vy};
const std::optional<Collider> verticalCollider =
findCollider(id, nextVerticalPos, box, colliders, entity::Type::kPlayer);
handlePlayerVerticalMovement(pos, vel, box, verticalCollider);
} else {
const Position nextPos = {pos.x + vel.vx, pos.y + vel.vy};
const entity::Type expectedType =
type.type == entity::Type::kBullet ? entity::Type::kEnemy : entity::Type::kPlayer;
const std::optional<Collider> collider =
findCollider(id, nextPos, box, colliders, expectedType);
handleEntityMovement(pos, vel, box, state, collider);
}
});
}

std::optional<ApplyMovement::Collider> ApplyMovement::findCollider(
const rtecs::types::EntityID id,
const Position& pos,
const Hitbox& box,
rtecs::sparse::SparseGroup<Position,
Hitbox,
State,
Type>& colliders,
const entity::Type expectedColliderType)
{
bool isCollisionDetected = false;
std::optional<Collider> collider = std::nullopt;

const Position newRefPos = {pos.x + vel.vx, pos.y + vel.vy};
bool isColliding = collide(newRefPos.x, box.width, otherPos.x, otherBox.width);
isColliding &= collide(newRefPos.y, box.height, otherPos.y, otherBox.height);
colliders.apply([&](const rtecs::types::EntityID colliderId,
Position& colliderPos,
Hitbox& colliderBox,
State& colliderState,
Type& colliderType) {
if (colliderType.type != expectedColliderType || colliderId == id || isCollisionDetected) {
return;
}
if (collide(pos, box, colliderPos, colliderBox)) {
isCollisionDetected = true;
collider = std::tuple<Position&, Hitbox&, State&, Type&>(
colliderPos, colliderBox, colliderState, colliderType);
}
});
return collider;
}

if (isColliding) {
collider = {otherPos, otherBox};
}
});
applyHorizontalMovement(pos, vel, box, collider);
applyVerticalMovement(pos, vel, box, collider);
});
void ApplyMovement::handleEntityMovement(Position& pos,
Velocity& vel,
const Hitbox& box,
State& state,
const std::optional<Collider>& collider)
{
if (collider.has_value()) {
// Stop the entity movement, probably useless, but we never know
vel.vx = 0;
vel.vy = 0;
// Kill the movable entity and the collided entity
std::get<State&>(collider.value()).state = entity::state::EntityDead;
state.state = entity::state::EntityDead;
} else {
pos.x += vel.vx;
pos.y += vel.vy;
}
if (pos.x - box.width < 0 || pos.x > 1920 || pos.y - box.height < 0 || pos.y > 1080) {
state.state = entity::state::EntityDead;
}
}

void ApplyMovement::applyHorizontalMovement(Position &pos,
Velocity &vel,
const Hitbox &box,
const std::optional<std::pair<Position,
Hitbox>> &collider)
void ApplyMovement::handlePlayerHorizontalMovement(Position& pos,
Velocity& vel,
const Hitbox& box,
const std::optional<Collider>& collider)
{
if (vel.vx != 0 && collider.has_value()) {
if (vel.vx > 0) { // Going right
pos.x = collider.value().first.x - box.width;
} else { // Going left
pos.x = collider.value().first.x + collider.value().second.width;
if (collider.has_value()) {
const Position& colliderPos = std::get<Position&>(collider.value());
const Hitbox& colliderBox = std::get<Hitbox&>(collider.value());
if (vel.vx > 0) { // Right
pos.x = colliderPos.x - box.width;
} else if (vel.vx < 0) { // Left
pos.x = colliderPos.x + colliderBox.width;
}
} else {
pos.x += vel.vx;
}
vel.vx = 0;
}

void ApplyMovement::applyVerticalMovement(Position &pos,
Velocity &vel,
const Hitbox &box,
const std::optional<std::pair<Position,
Hitbox>> &collider)
void ApplyMovement::handlePlayerVerticalMovement(Position& pos,
Velocity& vel,
const Hitbox& box,
const std::optional<Collider>& collider)
{
if (vel.vy != 0 && collider.has_value()) {
if (vel.vy > 0) { // Going down
pos.y = collider.value().first.y - box.height;
} else { // Going up
pos.y = collider.value().first.y + collider.value().second.height;
if (collider.has_value()) {
const Position& colliderPos = std::get<Position&>(collider.value());
const Hitbox& colliderBox = std::get<Hitbox&>(collider.value());
if (vel.vy > 0) { // Down
pos.y = colliderPos.y - box.height;
} else if (vel.vy < 0) { // Up
pos.y = colliderPos.y + colliderBox.height;
}
} else {
pos.y += vel.vy;
}
vel.vy = 0;
}

bool ApplyMovement::collide(const float refPos,
const float refSize,
const float otherPos,
const float otherSize)
bool ApplyMovement::collide(const Position& refPos,
const Hitbox& refBox,
const Position& otherPos,
const Hitbox& otherBox)
{
return (refPos >= otherPos && refPos < (otherPos + otherSize)) ||
(refPos + refSize > otherPos && refPos + refSize <= otherPos + otherSize);
return (refPos.x < otherPos.x + otherBox.width && refPos.x + refBox.width > otherPos.x &&
refPos.y < otherPos.y + otherBox.height && refPos.y + refBox.height > otherPos.y);
}
Loading
Loading