6 Commits

Author SHA1 Message Date
d9baf7c5ef add basic timer 2026-01-01 22:33:58 +01:00
701dd6b120 add basic entity movement 2026-01-01 22:19:55 +01:00
2225151f72 add debug fastforward 2026-01-01 22:17:01 +01:00
aef0cf4d95 fix entity rendering 2026-01-01 21:40:33 +01:00
550ff3aeec add basic camera controls 2026-01-01 20:58:28 +01:00
127fa1fcb8 add vec operations 2026-01-01 20:57:57 +01:00
17 changed files with 195 additions and 10 deletions

View File

@@ -12,6 +12,7 @@ class GameState : public ClientState {
game::WorldPtr m_World;
sim::ClientSimulation m_Simulation;
float m_CurrentLerp;
float m_ElapsedTime;
public:
GameState(Client& a_Client, const game::WorldPtr& a_World, std::uint64_t a_StepTime, const std::vector<protocol::LockStep> a_FirstSteps);
@@ -23,6 +24,10 @@ class GameState : public ClientState {
return m_CurrentLerp;
}
float GetElapsedTime() const {
return m_ElapsedTime;
}
game::WorldPtr GetWorld() const {
return m_World;
}

View File

@@ -183,6 +183,51 @@ T Lerp(T v0, T v1, T t) {
} // namespace maths
template<typename T>
Vec2<T> operator+(const Vec2<T>& vect, const Vec2<T>& other) {
return {vect.x + other.x, vect.y + other.y};
}
template<typename T>
Vec2<T> operator- (const Vec2<T>& vect) {
return { -vect.x, -vect.y };
}
template<typename T>
Vec2<T> operator- (const Vec2<T>& vect, const Vec2<T>& other) {
return vect + (-other);
}
template<typename T>
Vec3<T> operator- (const Vec3<T>& vect) {
return { -vect.x, -vect.y, -vect.z };
}
template<typename T>
Vec3<T> operator+ (const Vec3<T>& vect, const Vec3<T>& other) {
return { vect.x + other.x, vect.y + other.y, vect.z + other.z };
}
template<typename T>
Vec3<T> operator- (const Vec3<T>& vect, const Vec3<T>& other) {
return vect + (-other);
}
template<typename T>
Vec4<T> operator- (const Vec4<T>& vect) {
return { -vect.x, -vect.y, -vect.z, -vect.w };
}
template<typename T>
Vec4<T> operator+ (const Vec4<T>& vect, const Vec4<T>& other) {
return { vect.x + other.x, vect.y + other.y, vect.z + other.z, vect.w + other.w };
}
template<typename T>
Vec4<T> operator- (const Vec4<T>& vect, const Vec4<T>& other) {
return vect + (-other);
}
template <typename T>
sp::DataBuffer& operator<<(sp::DataBuffer& a_Buffer, const Vec2<T>& a_Vec) {
return a_Buffer << a_Vec.x << a_Vec.y;

View File

@@ -35,6 +35,7 @@ class Camera {
void UpdatePerspective(float a_AspectRatio);
void SetCamPos(const Vec3f& a_NewPos);
const Vec3f& GetCamPos() const;
};
} // namespace render

View File

@@ -0,0 +1,21 @@
#pragma once
#include "client/state/GameState.h"
#include <td/render/Renderer.h>
#include <client/PlayerManager.h>
namespace td {
namespace render {
class TimerRenderer : public BasicRenderer {
private:
const client::GameState& m_State;
public:
virtual void Render(float a_Lerp) override;
TimerRenderer(const client::GameState& a_State);
~TimerRenderer() {}
};
} // namespace render
} // namespace td

View File

@@ -17,6 +17,9 @@ class WorldRenderer : public Renderer<shader::WorldShader> {
virtual ~WorldRenderer();
virtual void Render(float a_Lerp) override;
private:
void UpdateControls();
};
} // namespace render

View File

@@ -20,6 +20,7 @@ void GameState::Handle(const protocol::packets::LockStepResponsePacket& a_LockSt
void GameState::Update(float a_Delta) {
m_CurrentLerp = m_Simulation.Update(a_Delta);
m_ElapsedTime += a_Delta;
}
} // namespace client

View File

@@ -25,8 +25,8 @@ void GameState::Update(float a_Delta) {
// TODO: don't make STEP_TIME constant
static const float stepTimeSecond = static_cast<float>(STEP_TIME) / 1000.0f;
m_Time += a_Delta;
if (m_Time > stepTimeSecond) {
m_Time = std::fmod(m_Time, stepTimeSecond);
while (m_Time > stepTimeSecond) {
m_Time -= stepTimeSecond;
auto lockStepPacket = m_Simulation.Update();
BroadcastPacket(lockStepPacket);
}

View File

@@ -7,6 +7,7 @@
#include <td/protocol/packet/Packets.h>
#include <td/render/renderer/EntityRenderer.h>
#include <td/render/renderer/PlayerListRenderer.h>
#include <td/render/renderer/TimerRenderer.h>
#include <td/render/renderer/TowerRenderer.h>
#include <td/render/renderer/WorldRenderer.h>
@@ -41,6 +42,7 @@ DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) {
m_Renderer.AddRenderer<render::WorldRenderer>(m_Camera, clientWorld);
m_Renderer.AddRenderer<render::EntityRenderer>(m_Camera, clientWorld);
m_Renderer.AddRenderer<render::TowerRenderer>(m_Camera, clientWorld);
m_Renderer.AddRenderer<render::TimerRenderer>(*gameState);
auto& list = m_Renderer.AddRenderer<render::PlayerListRenderer>(m_Client->GetPlayers());
@@ -52,7 +54,7 @@ DebugWorldState::DebugWorldState(Display& a_Display) : DisplayState(a_Display) {
});
list.OnPlayerKick.Connect([this](PlayerID a_Player) {
auto it = std::find_if(m_FakeClients.begin(), m_FakeClients.end(), [a_Player](auto& clientPtr){
auto it = std::find_if(m_FakeClients.begin(), m_FakeClients.end(), [a_Player](auto& clientPtr) {
if (!clientPtr->GetId().has_value())
return false;
return clientPtr->GetId().value() == a_Player;
@@ -85,6 +87,7 @@ void DebugWorldState::OnAspectRatioChange(float a_Ratio) {
void DebugWorldState::OnKeyDown(SDL_Keycode a_Key) {
// temporary tests
constexpr int SECONDS = 10;
switch (a_Key) {
case SDLK_A:
m_Client->SendPacket(td::protocol::packets::SpawnTroopPacket(td::EntityType::Zombie, 1));
@@ -94,6 +97,11 @@ void DebugWorldState::OnKeyDown(SDL_Keycode a_Key) {
m_Client->SendPacket(td::protocol::packets::PlaceTowerPacket(td::TowerType::Archer, td::TowerCoords(77, 13)));
break;
case SDLK_F:
m_Server->Update(SECONDS);
m_Client->Update(SECONDS);
break;
default:
break;
}

View File

@@ -1,7 +1,10 @@
#include <cassert>
#include <td/Maths.h>
#include <td/game/World.h>
#include <td/game/WorldTypes.h>
#include <td/simulation/WorldTicker.h>
#include <td/protocol/packet/PacketSerialize.h>
#include <td/simulation/WorldTicker.h>
namespace td {
namespace game {
@@ -89,5 +92,14 @@ void World::ResetSnapshots(std::shared_ptr<sim::WorldSnapshot>& a_Current, std::
m_NextState = a_Next;
}
TilePtr World::GetTile(std::int32_t x, std::int32_t y) const {
ChunkCoord coords{static_cast<std::int16_t>(x / Chunk::ChunkWidth), static_cast<std::int16_t>(y / Chunk::ChunkHeight)};
auto it = m_Chunks.find(coords);
assert(it != m_Chunks.end());
auto chunk = it->second;
Vec2i inchunkCoords{x % Chunk::ChunkWidth, y % Chunk::ChunkHeight};
return GetTilePtr(chunk->GetTileIndex(inchunkCoords.y * Chunk::ChunkWidth + inchunkCoords.x));
}
} // namespace game
} // namespace td

View File

@@ -1,3 +1,4 @@
#include "td/Maths.h"
#include <td/render/Camera.h>
#include <cmath>
@@ -24,5 +25,9 @@ void Camera::SetCamPos(const Vec3f& a_NewPos) {
OnViewChange();
}
const Vec3f& Camera::GetCamPos() const {
return m_CamPos;
}
} // namespace render
} // namespace td

View File

@@ -22,7 +22,7 @@ void EntityRenderer::Render(float a_Lerp) {
float x = Lerp<game::Mob>(*mob, a_Lerp, [](const game::Mob& a_Mob) { return static_cast<float>(a_Mob.m_Position.x); });
float z = Lerp<game::Mob>(*mob, a_Lerp, [](const game::Mob& a_Mob) { return static_cast<float>(a_Mob.m_Position.y); });
m_Shader->SetModelPos({x, 1, z});
m_Shader->SetModelPos({x, .001, z});
Renderer::Render(*m_EntityVao);
}
}

View File

@@ -0,0 +1,18 @@
#include <client/state/GameState.h>
#include <td/render/renderer/TimerRenderer.h>
#include <imgui.h>
namespace td {
namespace render {
void TimerRenderer::Render(float a_Lerp) {
ImGui::Begin("Timer");
ImGui::Text("Time : %02d:%02d", static_cast<int>(m_State.GetElapsedTime()) / 60, static_cast<int>(m_State.GetElapsedTime()) % 60);
ImGui::End();
}
TimerRenderer::TimerRenderer(const client::GameState& a_State) : m_State(a_State) {}
} // namespace render
} // namespace td

View File

@@ -18,7 +18,7 @@ TowerRenderer::~TowerRenderer() {}
void TowerRenderer::Render(float a_Lerp) {
m_Shader->Start();
for (const auto& tower : m_World->GetTowers()) {
m_Shader->SetModelPos({tower->GetCenterX(), 1, tower->GetCenterY()});
m_Shader->SetModelPos({tower->GetCenterX(), .001, tower->GetCenterY()});
Renderer::Render(*m_EntityVao);
}
}

View File

@@ -1,3 +1,4 @@
#include <td/Maths.h>
#include <td/render/renderer/WorldRenderer.h>
#include <td/render/loader/WorldLoader.h>
@@ -13,7 +14,17 @@ WorldRenderer::WorldRenderer(Camera& a_Camera, const game::WorldPtr& a_World) :
WorldRenderer::~WorldRenderer() {}
void WorldRenderer::UpdateControls() {
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
constexpr float sensitivity = 1.0f;
float delta = ImGui::GetIO().DeltaTime;
auto mouseDelta = ImGui::GetIO().MouseDelta;
m_Camera.SetCamPos(m_Camera.GetCamPos() + Vec3f{-mouseDelta.x * delta * sensitivity, 0, -mouseDelta.y * delta * sensitivity});
}
}
void WorldRenderer::Render(float a_Lerp) {
UpdateControls();
m_Shader->Start();
Renderer::Render(*m_WorldVao);
}

View File

@@ -51,8 +51,8 @@ float ClientSimulation::Update(float a_Delta) {
// TODO: handle freezes (m_CurrentTime > 2 * m_StepTime)
static const float stepTimeSecond = static_cast<float>(m_StepTime) / 1000.0f;
m_CurrentTime += a_Delta;
if (m_CurrentTime > stepTimeSecond) {
m_CurrentTime = std::fmod(m_CurrentTime, stepTimeSecond);
while (m_CurrentTime > stepTimeSecond) {
m_CurrentTime -= stepTimeSecond;
Step();
}
return (float)m_CurrentTime / stepTimeSecond;

View File

@@ -1,3 +1,4 @@
#include <td/Types.h>
#include <td/simulation/CommandApply.h>
namespace td {
@@ -6,7 +7,7 @@ namespace sim {
CommandApply::CommandApply(const game::World& a_World, WorldSnapshot& a_Snapshot) : m_World(a_World), m_Snapshot(a_Snapshot) {}
void CommandApply::Handle(const protocol::commands::EndCommand& a_End) {
(void) m_World;
(void)m_World;
}
void CommandApply::Handle(const protocol::commands::PlaceTowerCommand& a_PlaceTower) {
@@ -24,6 +25,10 @@ void CommandApply::Handle(const protocol::commands::PlayerJoinCommand& a_PlayerJ
void CommandApply::Handle(const protocol::commands::SpawnTroopCommand& a_SpawnTroop) {
auto zombie = std::make_shared<game::Zombie>();
zombie->m_Position = a_SpawnTroop->m_Position;
// TODO: make it spawn dependant
zombie->m_Direction = Direction::PositiveY;
m_Snapshot.m_Mobs.push_back(zombie);
}

View File

@@ -1,11 +1,61 @@
#include <td/Maths.h>
#include <td/Types.h>
#include <td/game/WorldTypes.h>
#include <td/simulation/system/EntityMove.h>
#include <td/game/World.h>
namespace td {
namespace sim {
static Vec2i GetUnitDirection(Direction a_Direction) {
switch (a_Direction) {
case Direction::PositiveX:
return {1, 0};
case Direction::NegativeX:
return {-1, 0};
case Direction::PositiveY:
return {0, 1};
case Direction::NegativeY:
return {0, -1};
}
return {0, 0};
}
class DirectionTileVisitor : public game::TileHandler {
private:
Direction m_Direction;
public:
DirectionTileVisitor() {}
virtual void Handle(const game::EmptyTile& a_Tile) override {}
virtual void Handle(const game::TowerTile& a_Tile) override {}
virtual void Handle(const game::DecorationTile& a_Tile) override {}
virtual void Handle(const game::WalkableTile& a_Tile) override {
m_Direction = a_Tile->m_Direction;
}
const Direction GetDirection() {
return m_Direction;
}
};
void EntityMove::Tick(const game::World& a_World, WorldSnapshot& a_State, FpFloat a_Delta) {
for (auto& mob : a_State.m_Mobs) {
mob->m_Position.x += a_Delta;
auto tile = a_World.GetTile(static_cast<std::int32_t>(mob->m_Position.x), static_cast<std::int32_t>(mob->m_Position.y));
Direction direction = mob->m_Direction;
if (tile) {
DirectionTileVisitor visitor;
tile->Dispatch(visitor);
direction = visitor.GetDirection();
}
auto directVector = GetUnitDirection(direction);
mob->m_Position.x += directVector.x * a_Delta;
mob->m_Position.y += directVector.y * a_Delta;
}
}