4 Commits

Author SHA1 Message Date
2e4a32fed7 feat(gameplay): add server-side entity movement and AI
Refactor LocalPlayer::update_physical to operate on a passed position, and run speed, physical, and wander AI systems in ServerEntityManager.
2026-07-31 20:51:14 +08:00
c5ce91e653 refactor(ecs): move entity physics to tick-based systems
Update PhysicalSystem and SpeedSystem to operate on entt::registry
instead of individual components. Add TickVelocity for server creatures
so movement is calculated per tick without frame delta time. LocalPlayer
now implements its own client-side physics with collision detection.
2026-07-31 20:39:40 +08:00
412f88565a feat(ai): add wander AI system with move boost
Add AIBase and WanderAITag components, a WanderAISystem, and a MoveBoost component to control wandering behavior. Replace the old MoveState with MoveBoost and include a horizontal random direction helper.
2026-07-31 19:59:33 +08:00
8451921161 feat(gameplay): implement entity update packets
Add S2CEntityUpdate to sync entity positions from server to client, including update handling in the client entity manager and server-side AI updates.
2026-07-31 17:44:33 +08:00
21 changed files with 438 additions and 129 deletions

View File

@@ -10,14 +10,15 @@ namespace Cubed {
class ClientWorld;
class ClientEntityManager {
public:
enum class Command { CREATE, DESTORY };
enum class Command { CREATE, DESTORY, UPDATE };
ClientEntityManager(ClientWorld& world);
void update();
void init();
void receive_entity_create(S2CEntityCreate& s2c);
void receive_entity_create(S2CEntityCreate& msg);
void receive_entity_destory(EntityID id);
void receive_entity_update(S2CEntityUpdate& msg);
void destory(EntityID id);
void create(std::string_view name, const glm::vec3& pos);
@@ -30,11 +31,17 @@ private:
std::string name;
glm::vec3 pos;
};
struct UpdateInfo {
EntityID id;
glm::vec3 pos;
};
using EntityMap = tbb::concurrent_hash_map<EntityID, entt::entity>;
using acc = EntityMap::accessor;
using cacc = EntityMap::const_accessor;
using CreateFunc = std::function<void(EntityID id)>;
using TaskElement = std::variant<EntityCreateElement, EntityID>;
using TaskElement = std::variant<EntityCreateElement, EntityID, UpdateInfo>;
using TaskPair = std::pair<Command, TaskElement>;
ClientWorld& m_world;
@@ -48,6 +55,7 @@ private:
// not thread safe
void handle_entity_create(EntityID id, std::string_view name,
const glm::vec3& pos);
void handle_entity_update(UpdateInfo& info);
template <typename... Args>
void create_entity_in_registry(EntityID id, Args&&... args) {
{

View File

@@ -0,0 +1,17 @@
#pragma once
#include "Cubed/gameplay/game_time.hpp"
namespace Cubed {
struct AIBase {
TickType interval = 1;
TickType count = 0;
};
struct WanderAITag {};
struct MoveBoost {
TickType duration = 0;
TickType count = 0;
};
} // namespace Cubed

View File

@@ -3,10 +3,17 @@
#include <glm/glm.hpp>
namespace Cubed {
struct TickVelocity {
glm::vec3 value{0.0f};
// blocks/tick!!! -1 for in
glm::vec3 max{1.0f, -1.0f, 1.0f};
};
struct Velocity {
glm::vec3 value{0.0f};
// blocks/second!!!
glm::vec3 max{4.5f, 7.5f, 7.5f};
};
@@ -23,4 +30,5 @@ struct Gravity {
float value = DEFAULT_G;
};
} // namespace Cubed

View File

@@ -1,7 +1,6 @@
#pragma once
#include "Cubed/gameplay/ecs/health.hpp"
#include "Cubed/gameplay/ecs/movement.hpp"
#include "Cubed/gameplay/ecs/state.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include "Cubed/gameplay/hitbox.hpp"
namespace Cubed {
@@ -9,12 +8,10 @@ namespace Cubed {
struct BaseServerCreature {
Transform transform{};
Velocity velocity{};
TickVelocity velocity{};
Movement movement{};
MoveState move_state{};
Direction direction{};
Gravity gravity{};

View File

@@ -149,12 +149,14 @@ private:
void update_direction();
void update_lookup_block();
void update_move(float delta_time);
void update_move(float dt);
void update_player_chunk();
void play_walk_sound(float dt);
Gait compute_gait() const;
void update_speed(float dt);
std::tuple<bool, bool, bool> update_physical(float dt, glm::vec3& pos);
glm::vec3 get_move_distance(float dt);
};
} // namespace Cubed

View File

@@ -51,10 +51,12 @@ enum class PacketEnum : uint16_t {
LOGIN_RSP = 1002,
LOGOUT_REQ = 1003,
LOGOUT_RSP = 1004,
PLAYER_INFO = 2001,
C2S_PLAYER_INFO = 2002,
PLAYER_INFO_RSP = 2003,
PLAYER_WATER_SOUND = 2004,
CHUNK_DATA_REQ = 3001,
CHUNK_DATA_RSP = 3002,
BLOCK_CHANGE_REQ = 3003,
@@ -65,8 +67,11 @@ enum class PacketEnum : uint16_t {
S2C_ENTITY_DESTORY = 3008,
C2S_ENTITY_CREATE_REQUEST = 3009,
C2S_ENTITY_DESTORY_REQUEST = 3010,
S2C_ENTITY_UPDATE = 3011,
CHAT_MSG = 4001,
VOICE_MSG = 4002,
PING = 9001,
PONG = 9002
@@ -145,6 +150,9 @@ template <> constexpr uint16_t get_packet_id<ChatMsg>() {
template <> constexpr uint16_t get_packet_id<VoiceMsg>() {
return std::to_underlying(PacketEnum::VOICE_MSG);
}
template <> constexpr uint16_t get_packet_id<S2CEntityUpdate>() {
return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE);
}
template <typename T>
requires std::derived_from<T, google::protobuf::Message>

View File

@@ -44,6 +44,8 @@ private:
void handle_entity_destory(EntityID id);
void handle_task();
void send_all_entities(std::shared_ptr<Session>& session);
void update_ai();
void update_move();
template <typename... Args>
EntityID create_entity_in_factory(Args&&... args) {
auto entity = m_registry.create();

View File

@@ -1,18 +1,16 @@
#pragma once
#include "Cubed/gameplay/ecs/movement.hpp"
#include "Cubed/gameplay/ecs/state.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include "Cubed/gameplay/world.hpp"
namespace Cubed {
#include <entt/entt.hpp>
namespace Cubed {
class ServerWorld;
class PhysicalSystem {
public:
static glm::vec3 get_move_distance(float dt, const Direction& d,
const Velocity& v);
static glm::vec3 get_move_distance(const Direction& d,
const TickVelocity& v);
static std::tuple<bool, bool, bool>
update(float dt, World& world, glm::vec3& moved_pos, Velocity& v,
Direction& direction, MoveState& move_state, HitboxID hitbox);
static void update(ServerWorld& world, entt::registry& registry);
private:
};

View File

@@ -1,15 +1,10 @@
#pragma once
#include "Cubed/gameplay/ecs/movement.hpp"
#include "Cubed/gameplay/ecs/state.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include <entt/entt.hpp>
namespace Cubed {
class SpeedSystem {
public:
static void update(float dt, Velocity& v, MoveState& move_state,
Movement& movement, Direction& direction,
const Gravity& g);
static void update(float dt, entt::registry& registry);
private:
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "Cubed/gameplay/ecs/ai_struct.hpp"
#include "Cubed/gameplay/ecs/server_entity.hpp"
#include <entt/entt.hpp>
namespace Cubed {
class WanderAISystem {
public:
static void update(entt::registry& registry);
private:
static void do_ai(BaseServerCreature& creature, MoveBoost& move_boost);
};
} // namespace Cubed

View File

@@ -1,4 +1,6 @@
#pragma once
#include "glm/ext/vector_float3.hpp"
#include <random>
namespace Cubed {
@@ -14,6 +16,8 @@ public:
int random_int(int min, int max);
float random_float(float min, float max);
glm::vec3 random_direction_horizontal();
private:
unsigned int m_seed = 0;
std::mt19937 m_engine;

View File

@@ -102,4 +102,5 @@ target_sources(${PROJECT_NAME}
gameplay/client_player_manager.cpp
gameplay/server_entity_manager.cpp
gameplay/client_entity_manager.cpp
gameplay/systems/wander_ai_system.cpp
)

View File

@@ -36,8 +36,23 @@ void ClientEntityManager::handle_entity_create(EntityID id,
c->transform.position.value = pos;
}
void ClientEntityManager::handle_entity_update(UpdateInfo& info) {
entt::entity e;
{
cacc a;
if (m_entities.find(a, info.id)) {
e = a->second;
} else {
return;
}
}
auto creature = m_registry.try_get<BaseClientCreature>(e);
ASSERT(creature);
creature->transform.position.value = info.pos;
}
void ClientEntityManager::receive_entity_create(S2CEntityCreate& s2c) {
EntityCreateElement c;
EntityCreateElement c{};
c.id = s2c.id();
c.name = s2c.name();
c.pos = {s2c.pos().x(), s2c.pos().y(), s2c.pos().z()};
@@ -48,6 +63,13 @@ void ClientEntityManager::receive_entity_destory(EntityID id) {
m_tasks.emplace(Command::DESTORY, id);
}
void ClientEntityManager::receive_entity_update(S2CEntityUpdate& msg) {
UpdateInfo e;
e.id = msg.id();
e.pos = Tools::get_net_pos(msg.pos());
m_tasks.emplace(Command::UPDATE, std::move(e));
}
void ClientEntityManager::destory(EntityID id) {
auto client = m_world.get_client();
Arena arena;
@@ -81,6 +103,11 @@ void ClientEntityManager::handle_task() {
ASSERT(p);
handle_entity_destory(*p);
} break;
case Command::UPDATE: {
auto* p = std::get_if<UpdateInfo>(&pair.second);
ASSERT(p);
handle_entity_update(*p);
}
}
}
}

View File

@@ -5,12 +5,110 @@
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/client_world.hpp"
#include "Cubed/gameplay/hitbox_manager.hpp"
#include "Cubed/gameplay/systems/physical_system.hpp"
#include "Cubed/gameplay/systems/speed_system.hpp"
namespace {} // namespace
namespace Cubed {
LocalPlayer::LocalPlayer(ClientWorld& world) : m_world(world) {}
namespace {
bool update_x(const glm::vec3& pos, const glm::vec3& distance,
ClientWorld& world, const Hitbox& box) {
glm::vec3 p = pos;
p.x += distance.x;
Hitbox b = box;
b.center += p;
glm::vec3 min = b.min();
glm::vec3 max = b.max();
int minx = std::floor(min.x);
int maxx = std::floor(max.x);
int miny = std::floor(min.y);
int maxy = std::floor(max.y);
int minz = std::floor(min.z);
int maxz = std::floor(max.z);
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
glm::ivec3 block_pos{x, y, z};
if (!world.can_pass_block(block_pos)) {
Hitbox block_box = World::get_block_aabb(block_pos);
if (b.intersects(block_box)) {
return false;
}
}
}
}
}
return true;
}
bool update_y(const glm::vec3& pos, const glm::vec3& distance,
ClientWorld& world, const Hitbox& box) {
glm::vec3 p = pos;
p.y += distance.y;
Hitbox b = box;
b.center += p;
glm::vec3 min = b.min();
glm::vec3 max = b.max();
int minx = std::floor(min.x);
int maxx = std::floor(max.x);
int miny = std::floor(min.y);
int maxy = std::floor(max.y);
int minz = std::floor(min.z);
int maxz = std::floor(max.z);
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
glm::ivec3 block_pos{x, y, z};
if (!world.can_pass_block(block_pos)) {
Hitbox block_box = World::get_block_aabb(block_pos);
if (b.intersects(block_box)) {
return false;
}
}
}
}
}
return true;
}
bool update_z(const glm::vec3& pos, const glm::vec3& distance,
ClientWorld& world, const Hitbox& box) {
glm::vec3 p = pos;
p.z += distance.z;
Hitbox b = box;
b.center += p;
glm::vec3 min = b.min();
glm::vec3 max = b.max();
int minx = std::floor(min.x);
int maxx = std::floor(max.x);
int miny = std::floor(min.y);
int maxy = std::floor(max.y);
int minz = std::floor(min.z);
int maxz = std::floor(max.z);
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
glm::ivec3 block_pos{x, y, z};
if (!world.can_pass_block(block_pos)) {
Hitbox block_box = World::get_block_aabb(block_pos);
if (b.intersects(block_box)) {
return false;
}
}
}
}
}
return true;
}
} // namespace
LocalPlayer::LocalPlayer(ClientWorld& world) : m_world(world) {
m_hitbox = HitboxManager::instance().get_hitbox_id("cubed:player");
}
LocalPlayer::~LocalPlayer() {}
const glm::vec3& LocalPlayer::get_front() const { return m_front; }
@@ -333,9 +431,9 @@ LocalPlayer::get_hotbar() const {
return m_hotbar;
}
void LocalPlayer::update_move(float delta_time) {
void LocalPlayer::update_move(float dt) {
// if frame rate less than 1 frame per second, don't update
if (delta_time > 1.0f) {
if (dt > 1.0f) {
return;
}
@@ -343,7 +441,7 @@ void LocalPlayer::update_move(float delta_time) {
m_sprinting = false;
}
if (space_on) {
space_on_time += delta_time;
space_on_time += dt;
if (space_on_time >= MAX_SPACE_ON_TIME) {
space_on = false;
space_on_time = 0.0f;
@@ -360,8 +458,7 @@ void LocalPlayer::update_move(float delta_time) {
glm::vec3{m_max_run_speed, m_max_y_speed, m_max_run_speed};
}
SpeedSystem::update(delta_time, m_velocity, m_move_state, m_movement,
m_direction, m_gravity);
update_speed(dt);
update_direction();
@@ -374,12 +471,9 @@ void LocalPlayer::update_move(float delta_time) {
}
if (m_game_mode == SPECTATOR) {
player_pos += PhysicalSystem::get_move_distance(delta_time, m_direction,
m_velocity);
player_pos += get_move_distance(dt);
} else {
auto [x, y, z] =
PhysicalSystem::update(delta_time, m_world, player_pos, m_velocity,
m_direction, m_move_state, m_hitbox);
auto [x, y, z] = update_physical(dt, player_pos);
if (!x || !z) {
m_sprinting = false;
}
@@ -406,7 +500,7 @@ void LocalPlayer::update_move(float delta_time) {
}
for (auto& [key, timer] : m_timers) {
timer.update(delta_time);
timer.update(dt);
}
}
@@ -593,6 +687,97 @@ void LocalPlayer::init(std::string_view name) {
}
}
void LocalPlayer::update_speed(float dt) {
// calculate speed
auto& v = m_velocity;
if (m_move_state.forward || m_move_state.back || m_move_state.left ||
m_move_state.right) {
m_direction.value = glm::vec3(0.0f, 0.0f, 0.0f);
v.value.x += m_movement.acceleration * dt;
v.value.z += m_movement.acceleration * dt;
if (v.value.x > v.max.x) {
v.value.x = v.max.x;
}
if (v.value.z > v.max.z) {
v.value.z = v.max.z;
}
} else {
v.value.x += -m_movement.deceleration * dt;
v.value.z += -m_movement.deceleration * dt;
if (v.value.z < 0.0f) {
v.value.z = 0.0f;
}
if (v.value.x < 0.0f) {
v.value.x = 0.0f;
}
if (v.value.z < 0.0f && v.value.x < 0.0f) {
m_direction.value = glm::vec3(0.0f, 0.0f, 0.0f);
}
}
if (m_move_state.is_fly) {
if (m_move_state.up) {
v.value.y = v.max.y;
}
if (m_move_state.down) {
v.value.y = -v.max.y;
}
if (!m_move_state.down && !m_move_state.up) {
v.value.y = 0.0f;
}
} else {
if (m_move_state.up && m_move_state.can_up) {
v.value.y = m_movement.jump_power;
m_move_state.can_up = false;
}
v.value.y += -m_gravity.value * dt;
}
}
std::tuple<bool, bool, bool> LocalPlayer::update_physical(float dt,
glm::vec3& pos) {
auto distance = get_move_distance(dt);
auto box = HitboxManager::hitbox(m_hitbox);
bool x = false;
bool y = false;
bool z = false;
if (update_x(pos, distance, m_world, box.box)) {
pos.x += distance.x;
x = true;
} else {
m_velocity.value.x = 0.0f;
}
if (update_y(pos, distance, m_world, box.box)) {
pos.y += distance.y;
y = true;
} else {
m_velocity.value.y = 0.0f;
if (distance.y < 0) {
m_move_state.can_up = true;
m_move_state.is_fly = false;
}
}
if (update_z(pos, distance, m_world, box.box)) {
pos.z += distance.z;
z = true;
} else {
m_velocity.value.z = 0.0f;
}
return {x, y, z};
}
glm::vec3 LocalPlayer::get_move_distance(float dt) {
const auto& d = m_direction;
const auto& v = m_velocity;
return glm::vec3{d.value.x * v.value.x * dt, v.value.y * dt,
d.value.z * v.value.z * dt};
}
bool LocalPlayer::is_underwater() const { return m_underwater; }
void LocalPlayer::set_underwater(bool u) { m_underwater = u; }

View File

@@ -156,6 +156,12 @@ asio::awaitable<void> NetworkClient::read_loop() {
m_world.entity_manager().receive_entity_destory(msg->id());
}
} break;
case std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE): {
auto* msg = Arena::Create<S2CEntityUpdate>(&arena);
if (decode_packet(*msg, body_data, header)) {
m_world.entity_manager().receive_entity_update(*msg);
}
} break;
}
}
} catch (const asio::system_error& e) {

View File

@@ -6,6 +6,9 @@
#include "Cubed/gameplay/hitbox_manager.hpp"
#include "Cubed/gameplay/server_world.hpp"
#include "Cubed/gameplay/session.hpp"
#include "Cubed/gameplay/systems/physical_system.hpp"
#include "Cubed/gameplay/systems/speed_system.hpp"
#include "Cubed/gameplay/systems/wander_ai_system.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/net_utils.hpp"
using namespace google::protobuf;
@@ -22,8 +25,18 @@ void ServerEntityManager::init() {
std::move(c), PigTag{});
});
}
void ServerEntityManager::update() { handle_task(); }
void ServerEntityManager::update() {
handle_task();
update_ai();
update_move();
}
void ServerEntityManager::update_ai() { WanderAISystem::update(m_registry); }
void ServerEntityManager::update_move() {
SpeedSystem::update(
static_cast<float>(m_world.get_per_tick_time()) / 1000.0f, m_registry);
PhysicalSystem::update(m_world, m_registry);
}
void ServerEntityManager::handle_task() {
TaskPair pair;
while (m_tasks.try_pop(pair)) {

View File

@@ -1,12 +1,14 @@
#include "Cubed/gameplay/systems/physical_system.hpp"
#include "Cubed/gameplay/ecs/server_entity.hpp"
#include "Cubed/gameplay/hitbox_manager.hpp"
#include "Cubed/gameplay/server_world.hpp"
namespace Cubed {
namespace {
bool update_x(const glm::vec3& pos, const glm::vec3& distance, World& world,
const Hitbox& box) {
bool update_x(const glm::vec3& pos, const glm::vec3& distance,
ServerWorld& world, const Hitbox& box) {
glm::vec3 p = pos;
p.x += distance.x;
Hitbox b = box;
@@ -36,8 +38,8 @@ bool update_x(const glm::vec3& pos, const glm::vec3& distance, World& world,
return true;
}
bool update_y(const glm::vec3& pos, const glm::vec3& distance, World& world,
const Hitbox& box) {
bool update_y(const glm::vec3& pos, const glm::vec3& distance,
ServerWorld& world, const Hitbox& box) {
glm::vec3 p = pos;
p.y += distance.y;
Hitbox b = box;
@@ -67,8 +69,8 @@ bool update_y(const glm::vec3& pos, const glm::vec3& distance, World& world,
return true;
}
bool update_z(const glm::vec3& pos, const glm::vec3& distance, World& world,
const Hitbox& box) {
bool update_z(const glm::vec3& pos, const glm::vec3& distance,
ServerWorld& world, const Hitbox& box) {
glm::vec3 p = pos;
p.z += distance.z;
Hitbox b = box;
@@ -100,45 +102,41 @@ bool update_z(const glm::vec3& pos, const glm::vec3& distance, World& world,
} // namespace
std::tuple<bool, bool, bool>
PhysicalSystem::update(float dt, World& world, glm::vec3& moved_pos,
Velocity& v, Direction& direction, MoveState& move_state,
HitboxID hitbox) {
auto distance = get_move_distance(dt, direction, v);
auto box = HitboxManager::hitbox(hitbox);
bool x = false;
bool y = false;
bool z = false;
if (update_x(moved_pos, distance, world, box.box)) {
moved_pos.x += distance.x;
x = true;
void PhysicalSystem::update(ServerWorld& world, entt::registry& registry) {
auto view = registry.view<BaseServerCreature>();
for (auto e : view) {
auto& creature = view.get<BaseServerCreature>(e);
auto distance =
get_move_distance(creature.direction, creature.velocity);
auto box = HitboxManager::hitbox(creature.hitbox);
auto& pos = creature.transform.position.value;
auto& v = creature.velocity;
if (update_x(pos, distance, world, box.box)) {
pos.x += distance.x;
} else {
v.value.x = 0.0f;
}
if (update_y(moved_pos, distance, world, box.box)) {
moved_pos.y += distance.y;
y = true;
if (update_y(pos, distance, world, box.box)) {
pos.y += distance.y;
} else {
v.value.y = 0.0f;
if (distance.y < 0) {
move_state.can_up = true;
move_state.is_fly = false;
}
}
if (update_z(moved_pos, distance, world, box.box)) {
moved_pos.z += distance.z;
z = true;
if (update_z(pos, distance, world, box.box)) {
pos.z += distance.z;
} else {
v.value.z = 0.0f;
}
return {x, y, z};
}
}
glm::vec3 PhysicalSystem::get_move_distance(float dt, const Direction& d,
const Velocity& v) {
return glm::vec3{d.value.x * v.value.x * dt, v.value.y * dt,
d.value.z * v.value.z * dt};
glm::vec3 PhysicalSystem::get_move_distance(const Direction& d,
const TickVelocity& v) {
return glm::vec3{d.value.x * v.value.x, v.value.y, d.value.z * v.value.z};
}
} // namespace Cubed

View File

@@ -1,54 +1,24 @@
#include "Cubed/gameplay/systems/speed_system.hpp"
#include "Cubed/gameplay/ecs/ai_struct.hpp"
#include "Cubed/gameplay/ecs/server_entity.hpp"
namespace Cubed {
void SpeedSystem::update(float dt, Velocity& v, MoveState& move_state,
Movement& movement, Direction& direction,
const Gravity& g) {
// calculate speed
if (move_state.forward || move_state.back || move_state.left ||
move_state.right) {
direction.value = glm::vec3(0.0f, 0.0f, 0.0f);
v.value.x += movement.acceleration * dt;
v.value.z += movement.acceleration * dt;
if (v.value.x > v.max.x) {
v.value.x = v.max.x;
}
if (v.value.z > v.max.z) {
v.value.z = v.max.z;
}
void SpeedSystem::update(float dt, entt::registry& registry) {
auto view = registry.view<BaseServerCreature, MoveBoost>();
for (auto e : view) {
auto [creature, moveboost] = view.get<BaseServerCreature, MoveBoost>(e);
if (moveboost.count <= moveboost.duration) {
++moveboost.count;
creature.velocity.value +=
creature.direction.value * creature.movement.acceleration;
} else {
v.value.x += -movement.deceleration * dt;
v.value.z += -movement.deceleration * dt;
if (v.value.z < 0.0f) {
v.value.z = 0.0f;
creature.velocity.value -=
creature.direction.value * creature.movement.deceleration;
}
if (v.value.x < 0.0f) {
v.value.x = 0.0f;
}
if (v.value.z < 0.0f && v.value.x < 0.0f) {
direction.value = glm::vec3(0.0f, 0.0f, 0.0f);
}
}
if (move_state.is_fly) {
if (move_state.up) {
v.value.y = v.max.y;
}
if (move_state.down) {
v.value.y = -v.max.y;
}
if (!move_state.down && !move_state.up) {
v.value.y = 0.0f;
}
} else {
if (move_state.up && move_state.can_up) {
v.value.y = movement.jump_power;
move_state.can_up = false;
}
v.value.y += -g.value * dt;
creature.velocity.value.y = creature.gravity.value * dt;
}
}

View File

@@ -0,0 +1,40 @@
#include "Cubed/gameplay/systems/wander_ai_system.hpp"
#include "Cubed/gameplay/ecs/ai_struct.hpp"
#include "Cubed/gameplay/ecs/server_entity.hpp"
#include "Cubed/tools/cubed_random.hpp"
namespace {
constexpr double DIRECTION_PROBABILITY = 0.1;
constexpr double MOVE_PROBABILITY = 0.1;
} // namespace
namespace Cubed {
void WanderAISystem::update(entt::registry& registry) {
auto view =
registry.view<AIBase, WanderAITag, BaseServerCreature, MoveBoost>();
for (auto e : view) {
auto [ai, creature, move_boost] =
view.get<AIBase, BaseServerCreature, MoveBoost>(e);
++ai.count;
if (ai.count >= ai.interval) {
ai.count = 0;
do_ai(creature, move_boost);
}
}
}
void WanderAISystem::do_ai(BaseServerCreature& creature,
MoveBoost& move_boost) {
thread_local Random r{std::random_device()()};
if (r.random_bool(DIRECTION_PROBABILITY)) {
creature.direction.value = r.random_direction_horizontal();
}
if (r.random_bool(MOVE_PROBABILITY)) {
move_boost.duration = r.random_int(5, 8);
move_boost.count = 0;
}
}
} // namespace Cubed

View File

@@ -22,3 +22,8 @@ message C2SEntityCreateRequest {
string name = 2;
Vec3 pos = 3;
}
message S2CEntityUpdate {
uint64 id = 1;
Vec3 pos = 2;
}

View File

@@ -1,8 +1,11 @@
#include "Cubed/tools/cubed_random.hpp"
#include <cmath>
#include <numbers>
namespace Cubed {
Random::Random() {}
Random::Random(unsigned seed) { init(seed); }
bool Random::random_bool(double probability) {
if (probability <= 0.0)
@@ -44,4 +47,12 @@ float Random::random_float(float min, float max) {
return result;
}
glm::vec3 Random::random_direction_horizontal() {
float x = std::cos(random_float(0.0f, 2 * std::numbers::pi));
float z = std::sin(random_float(0.0f, 2 * std::numbers::pi));
return glm::vec3{x, 0.0f, z};
}
} // namespace Cubed