52 Commits

Author SHA1 Message Date
784c558840 chore: bump version to "alpha-0.2.0" 2021-12-19 18:40:44 +01:00
5d366b6f6c feat: sync game 2021-12-19 18:40:14 +01:00
f70661694b chore: bump version to "alpha-0.1.2" 2021-12-19 11:58:36 +01:00
36a1ab0572 feat: close server when game finished 2021-12-19 11:57:47 +01:00
409268b604 refactor: use of GuiManager 2021-12-19 11:56:41 +01:00
174d144d26 refactor: add default server tps 2021-12-19 11:38:54 +01:00
360258e4cf feat: add end game disconnect 2021-12-13 20:05:20 +01:00
2148c0050c refactor: add game listener 2021-12-13 19:45:01 +01:00
61166023df feat: don't show tile select when tooltip 2021-12-12 16:59:57 +01:00
e7bf22cea6 feat: add pseudo end game 2021-12-12 16:59:35 +01:00
f09f79198d fix: windows build 2021-12-12 16:41:48 +01:00
8c19d3cc3c refactor: removed unused constant 2021-12-12 16:27:59 +01:00
208892d266 chore: update to alpha-0.1.1 2021-12-12 16:17:53 +01:00
4384806cf0 refactor: removed unused comment 2021-12-12 16:14:24 +01:00
1a091baeaf feat: add map background color 2021-12-12 16:13:05 +01:00
43f21ffd44 chore: bump verssion to alpha-0.1.0 2021-12-12 13:33:12 +01:00
24617c539f feat: add castle tooltip 2021-12-12 13:32:39 +01:00
4611a198c9 fix: add mob spawn border 2021-12-12 13:31:01 +01:00
d40ffe8f6c refactor: removed unused include 2021-12-12 11:36:56 +01:00
384b7ad51e chore: bump version to alpha-0.0.5 2021-12-11 20:07:26 +01:00
fd9b448fa4 feat: mob castle damage 2021-12-11 20:06:52 +01:00
c4a2b08416 feat: add mob spawn notification 2021-12-11 19:56:03 +01:00
a241d7691b refactor: mob die event 2021-12-11 19:51:45 +01:00
0af4cd506c refactor: moved mob functions 2021-12-11 19:30:31 +01:00
6b5c56b37d feat: notify when mob touches castle 2021-12-11 18:46:47 +01:00
1474220a77 refactor: separate mob ans world listener 2021-12-11 18:43:49 +01:00
a802b5cef5 fix: windows compilation 2021-12-08 17:24:29 +01:00
7e91b863da bump version to alpha-0.0.4 2021-12-08 15:39:26 +01:00
129bb47286 feat: debug library 2021-12-08 15:38:20 +01:00
e4a9c5f763 feat: remove tower 2021-12-08 15:34:20 +01:00
78cf2d0f18 Merge branch 'master' of 192.168.0.5:repos/Tower-Defense 2021-12-05 12:01:50 +01:00
00d2b5394f refactor: removed unused include 2021-12-05 12:01:17 +01:00
00414abe61 refactor: comment main 2021-12-04 19:09:26 +01:00
cb847d0edd refactor: removed unused declarations 2021-12-04 13:49:50 +01:00
2da0bd9b79 refactor: tower add event processing 2021-12-04 13:44:34 +01:00
cf9633c061 fix: mob spawning repartition 2021-12-03 18:47:55 +01:00
7a6fdc30b6 add fire arrow and fix world events 2021-12-03 18:30:45 +01:00
657fe761ab feat: bump version to alpha-0.0.3 2021-11-30 18:45:42 +01:00
687944bf51 fix: don't call onmobdamage with null damage 2021-11-30 18:45:09 +01:00
93e079ddb3 refactor: archer tower aoe damage 2021-11-30 18:38:40 +01:00
8171a91ed5 feat: add world notifier 2021-11-30 18:36:29 +01:00
0c1824e40b refactor: movd Updater 2021-11-30 18:07:25 +01:00
f91ab4bc83 fix: compilation error 2021-11-30 18:06:33 +01:00
1843fc6f4d feat: add archer tower aoe 2021-11-30 18:01:48 +01:00
6a51638b7e feat: add more shape distances 2021-11-30 18:01:07 +01:00
8351c5d09f refactor: add typo 2021-11-30 18:00:41 +01:00
ee94c811e1 feat: add archer tower damage 2021-11-30 18:00:19 +01:00
50a8269773 feat: add better mob pathing 2021-11-30 11:53:58 +01:00
2996c88d94 feat: add mob coords in tile coords space 2021-11-30 11:23:03 +01:00
55263fa582 fix: changed skel effect immunities 2021-11-29 16:39:29 +01:00
adeddee09d refactor: mob move lock (Castle) 2021-11-29 16:36:27 +01:00
f0115166c0 fix: compiler warning 2021-11-22 19:39:09 +01:00
56 changed files with 5456 additions and 304 deletions

View File

@@ -12,14 +12,28 @@ enum class GameState : std::uint8_t {
Game,
EndGame,
Disconnected,
Closed
};
typedef std::map<std::uint8_t, Player> PlayerList;
class Game {
class GameListener {
public:
virtual void OnPlayerJoin(PlayerID player) {}
virtual void OnPlayerLeave(PlayerID player) {}
virtual void OnGameStateUpdate(GameState newState) {}
virtual void OnGameBegin() {}
virtual void OnGameEnd() {}
virtual void OnGameClose() {}
};
typedef utils::ObjectNotifier<GameListener> GameNotifier;
class Game : public GameNotifier {
protected:
World* m_World;
std::array<Team, 2> m_Teams = { Team{TeamColor::Red}, Team{TeamColor::Blue} };
TeamList m_Teams = { Team{TeamColor::Red}, Team{TeamColor::Blue} };
GameState m_GameState = GameState::Lobby;
PlayerList m_Players;
public:
@@ -46,8 +60,10 @@ public:
const PlayerList& getPlayers() const { return m_Players; }
PlayerList& getPlayers() { return m_Players; }
const Player& getPlayerById(PlayerID id) const { return m_Players.find(id)->second; }
Player& getPlayerById(PlayerID id) { return m_Players.find(id)->second; }
const Player* getPlayerById(PlayerID id) const;
Player* getPlayerById(PlayerID id);
const TeamList& getTeams() const { return m_Teams; }
};

View File

@@ -4,6 +4,8 @@
#include "Types.h"
#include "Team.h"
#include "misc/ObjectNotifier.h"
#include <vector>
#include <memory>
@@ -12,6 +14,8 @@
namespace td {
namespace game {
struct WalkableTile;
enum class EffectType : std::uint8_t {
Slowness = 0,
Stun,
@@ -69,14 +73,14 @@ public:
struct EffectDuration {
EffectType type;
float duration; // in seconds
const Tower* tower; // the tower that gived the effect
Tower* tower; // the tower that gived the effect
};
const MobStats* getMobStats(MobType type, std::uint8_t level);
const TowerImmunities& getMobTowerImmunities(MobType type, std::uint8_t level);
const EffectImmunities& getMobEffectImmunities(MobType type, std::uint8_t level);
class Mob : public utils::shape::Rectangle{
class Mob : public utils::shape::Rectangle {
protected:
float m_Health;
private:
@@ -91,47 +95,63 @@ private:
utils::Timer m_EffectPoisonTimer;
utils::Timer m_EffectHealTimer;
TeamCastle* m_CastleTarget;
utils::CooldownTimer m_AttackTimer;
public:
Mob(MobID id, MobLevel level, PlayerID sender) : m_Sender(sender), m_Level(level),
m_EffectFireTimer(1000), m_EffectPoisonTimer(1000), m_EffectHealTimer(1000) {
m_EffectFireTimer(1000), m_EffectPoisonTimer(1000),
m_EffectHealTimer(1000), m_CastleTarget(nullptr), m_AttackTimer(1000) {
}
virtual MobType getType() const = 0;
virtual void tick(std::uint64_t delta);
virtual void tick(std::uint64_t delta, World* world);
virtual bool OnDeath(World* world) { return true; }
MobID getMobID() const { return m_ID; }
const TowerImmunities& getTowerImmunities() const { return getMobTowerImmunities(getType(), m_Level); }
const EffectImmunities& getEffectImmunities() const { return getMobEffectImmunities(getType(), m_Level); }
PlayerID getSender() const { return m_Sender; }
MobLevel getLevel() const { return m_Level; }
const MobStats* getStats() const { return getMobStats(getType(), m_Level); }
void setHealth(float newHealth) { m_Health = newHealth; }
float getHealth() const { return m_Health; }
bool isDead() const { return m_Health <= 0; }
bool isAlive() const { return m_Health > 0; }
const Tower* getLastDamageTower() { return m_LastDamage; }
bool hasReachedEnemyCastle() { return m_CastleTarget != nullptr; }
void damage(float dmg, const Tower* damager) { m_Health = std::max(0.0f, m_Health - dmg); m_LastDamage = damager; }
void heal(float heal) { m_Health = std::min(static_cast<float>(getStats()->getMaxLife()), m_Health + heal); }
void setMobReachedCastle(TeamCastle* castle) { m_CastleTarget = castle; } // used when mob is in front of the castle
bool isImmuneTo(TowerType type);
bool isImmuneTo(EffectType type);
void addEffect(EffectType type, float durationSec, const Tower* tower);
void addEffect(EffectType type, float durationSec, Tower* tower);
bool hasEffect(EffectType type);
float getTileX() { return getCenterX() - static_cast<float>(static_cast<std::int32_t>(getCenterX())); } // returns a float between 0 and 1 excluded
float getTileY() { return getCenterY() - static_cast<float>(static_cast<std::int32_t>(getCenterY())); } // returns a float between 0 and 1 excluded
Direction getDirection() const { return m_Direction; }
void setDirection(Direction dir) { m_Direction = dir; }
protected:
void initMob() {
m_Health = static_cast<float>(getStats()->getMaxLife());
void initMob() {
m_Health = static_cast<float>(getStats()->getMaxLife());
setSize(getStats()->getSize().x, getStats()->getSize().y);
}
private:
void updateEffects(std::uint64_t delta);
void updateEffects(std::uint64_t delta, World* world);
void attackCastle(std::uint64_t delta, World* world);
void move(std::uint64_t delta, World* world);
void walk(std::uint64_t delta, World* world);
void moveBack(const TeamCastle& castle, World* world);
void changeDirection(const WalkableTile& tile, World* world);
bool isTouchingCastle(const TeamCastle& castle) const;
EffectDuration& getEffect(EffectType type);
};
@@ -212,6 +232,18 @@ MobPtr createMob(MobID id, MobType type, std::uint8_t level, PlayerID sender);
std::string getMobName(MobType type);
}
class MobListener {
public:
virtual void OnMobSpawn(Mob* mob) {}
virtual void OnMobDie(Mob* mob) {}
virtual void OnMobDamage(Mob* target, float damage, Tower* damager) {}
virtual void OnMobTouchCastle(Mob* damager, TeamCastle* enemyCastle) {}
virtual void OnMobCastleDamage(Mob* damager, TeamCastle* enemyCastle, float damage) {}
};
typedef utils::ObjectNotifier<MobListener> MobNotifier;
} // namespace game
} // namespace td

View File

@@ -23,7 +23,7 @@ class Spawn : public utils::shape::Rectangle {
private:
Direction m_Direction;
public:
Spawn(){
Spawn() {
setWidth(5);
setHeight(5);
}
@@ -33,19 +33,34 @@ public:
void setDirection(Direction direction) { m_Direction = direction; }
};
struct TeamCastle : public utils::shape::Rectangle{
class Team;
class TeamCastle : public utils::shape::Rectangle {
private:
const Team* m_Team;
float m_Life;
public:
TeamCastle() : m_Life(1000) {
static constexpr int CastleMaxLife = 1000;
TeamCastle(const Team* team) : m_Team(team), m_Life(CastleMaxLife) {
setWidth(5);
setHeight(5);
}
TeamCastle() : TeamCastle(nullptr) {}
float getLife() const { return m_Life; }
const Team* getTeam() const { return m_Team; }
void setTeam(const Team* team) { m_Team = team; }
void setLife(float life) { m_Life = life; }
void damage(float damage) { m_Life = std::max(0.0f, m_Life - damage); }
void setShape(utils::shape::Rectangle rect) {
setCenter(rect.getCenter());
setSize(rect.getSize());
}
};
class Team {
@@ -71,5 +86,7 @@ public:
std::uint8_t getPlayerCount() const;
};
typedef std::array<Team, 2> TeamList;
} // namespace game
} // namespace td

View File

@@ -163,6 +163,9 @@ class ArcherTower : public LittleTower {
public:
ArcherTower(TowerID id, std::uint16_t x, std::uint16_t y, PlayerID builder) : LittleTower(id, getType(), x, y, builder) {}
constexpr static float ExplosionRadius = 1.5f;
constexpr static float FireDurationSec = 10.0f;
virtual TowerType getType() const { return TowerType::Archer; }
virtual void tick(std::uint64_t delta, World* world);
};

View File

@@ -56,6 +56,13 @@ struct Color {
std::uint8_t r, g, b;
};
static constexpr Color BLACK{ 0, 0, 0 };
static constexpr Color WHITE{ 255, 255, 255 };
static constexpr Color RED{ 255, 0, 0 };
static constexpr Color GREEN{ 0, 255, 0 };
static constexpr Color BLUE{ 0, 0, 255 };
struct Tile {
virtual TileType getType() const = 0;
};
@@ -114,11 +121,27 @@ typedef std::array<Color, 2> SpawnColorPalette;
typedef std::vector<TowerPtr> TowerList;
class World {
class WorldListener {
public:
WorldListener() {}
virtual void OnTowerAdd(TowerPtr tower) {}
virtual void OnTowerRemove(TowerPtr tower) {}
virtual void OnArcherTowerShot(MobPtr target, ArcherTower* shooter) {}
virtual void OnArrowShot(MobPtr target, bool fire, Tower* shooter) {}
virtual void OnExplosion(utils::shape::Circle explosion, float centerDamage, Tower* shooter) {}
};
typedef utils::ObjectNotifier<WorldListener> WorldNotifier;
class World : public WorldListener, public MobListener {
protected:
TowerTileColorPalette m_TowerPlacePalette;
Color m_WalkablePalette;
std::vector<Color> m_DecorationPalette;
Color m_Background;
std::unordered_map<ChunkCoord, ChunkPtr> m_Chunks;
@@ -131,11 +154,12 @@ protected:
TowerList m_Towers;
Game* m_Game;
WorldNotifier m_WorldNotifier;
MobNotifier m_MobNotifier;
public:
World(Game* game);
static constexpr std::uint8_t CastleWidth = 5, CastleHeight = 5;
bool loadMap(const protocol::WorldBeginDataPacket* worldHeader);
bool loadMap(const protocol::WorldDataPacket* worldData);
@@ -147,12 +171,14 @@ public:
void spawnMobAt(MobID id, MobType type, std::uint8_t level, PlayerID sender, float x, float y, Direction dir);
TowerPtr placeTowerAt(TowerID id, TowerType type, std::int32_t x, std::int32_t y, PlayerID builder);
TowerPtr removeTower(TowerID id);
TilePtr getTile(std::int32_t x, std::int32_t y) const;
const TowerTileColorPalette& getTowerTileColorPalette() const { return m_TowerPlacePalette; }
const Color& getWalkableTileColor() const { return m_WalkablePalette; }
const std::vector<Color>& getDecorationPalette() const { return m_DecorationPalette; }
const Color& getBackgroundColor() const { return m_Background; }
const TilePalette& getTilePalette() const { return m_TilePalette; }
@@ -186,16 +212,29 @@ public:
Team& getTeam(TeamColor team);
const Team& getTeam(TeamColor team) const;
const TeamList& getTeams() const;
const TowerList& getTowers() const { return m_Towers; };
TowerPtr getTowerById(TowerID tower);
// Archer Tower
virtual void OnArrowShot(MobPtr target, Tower* shooter);
const Player* getPlayerById(PlayerID id) const;
WorldNotifier& getWorldNotifier() { return m_WorldNotifier; }
MobNotifier& getMobNotifier() { return m_MobNotifier; }
// WorldListener
virtual void OnArcherTowerShot(MobPtr target, ArcherTower* shooter);
virtual void OnArrowShot(MobPtr target, bool fire, Tower* shooter);
virtual void OnExplosion(utils::shape::Circle explosion, float centerDamage, Tower* shooter);
// MobListener
virtual void OnMobDamage(Mob* target, float damage, Tower* source);
virtual void OnMobCastleDamage(Mob* damager, TeamCastle* enemyCastle, float damage);
private:
void moveMobs(std::uint64_t delta);
void moveMob(MobPtr mob, std::uint64_t delta);
void moveBackMob(MobPtr mob, const TeamCastle& castle);
bool isMobTouchingCastle(MobPtr mob, const TeamCastle& castle) const;
void tickMobs(std::uint64_t delta);
void cleanDeadMobs();
};

View File

@@ -19,16 +19,16 @@ class Client {
private:
render::Renderer* m_Renderer;
ClientConnexion m_Connexion;
ClientGame m_Game;
std::unique_ptr<ClientGame> m_Game;
bool m_Connected;
public:
Client(render::Renderer* renderer) : m_Renderer(renderer), m_Game(this), m_Connected(false) {}
Client(render::Renderer* renderer) : m_Renderer(renderer), m_Game(std::make_unique<ClientGame>(this)), m_Connected(false) {}
const ClientGame& getGame() const { return m_Game; }
const ClientGame& getGame() const { return *m_Game; }
const ClientConnexion& getConnexion() const { return m_Connexion; }
render::Renderer* getRenderer() const { return m_Renderer; }
ClientGame& getGame() { return m_Game; }
ClientGame& getGame() { return *m_Game; }
ClientConnexion& getConnexion() { return m_Connexion; }
void tick(std::uint64_t delta);
@@ -44,6 +44,9 @@ public:
void sendMobs(const std::vector<protocol::MobSend>& mobSends);
void placeTower(game::TowerType type, const glm::vec2& position);
void upgradeTower(game::TowerID tower, game::TowerLevel level);
void removeTower(game::TowerID tower);
private:
void reset();
};
} // namespace client

View File

@@ -37,6 +37,7 @@ public:
Client* getClient() const { return m_Client; }
render::Renderer* getRenderer() const { return m_Renderer; }
WorldClient& getWorldClient() { return m_WorldClient; }
virtual void HandlePacket(const protocol::ConnexionInfoPacket* packet);
virtual void HandlePacket(const protocol::PlayerJoinPacket* packet);
@@ -48,7 +49,7 @@ public:
virtual void HandlePacket(const protocol::UpdateMoneyPacket* packet);
virtual void HandlePacket(const protocol::DisconnectPacket* packet);
virtual void HandlePacket(const protocol::WorldDataPacket* packet);
virtual void HandlePacket(const protocol::WorldAddTowerPacket* packet);
};
} // namespace client

View File

@@ -14,12 +14,15 @@ private:
public:
WorldClient(ClientGame* game);
virtual void HandlePacket(const protocol::WorldBeginDataPacket* packet) override;
virtual void HandlePacket(const protocol::WorldDataPacket* packet) override;
virtual void HandlePacket(const protocol::SpawnMobPacket* packet) override;
virtual void HandlePacket(const protocol::UpgradeTowerPacket* packet) override;
virtual void HandlePacket(const protocol::WorldBeginDataPacket* packet);
virtual void HandlePacket(const protocol::WorldDataPacket* packet);
virtual void HandlePacket(const protocol::SpawnMobPacket* packet);
virtual void HandlePacket(const protocol::UpgradeTowerPacket* packet);
virtual void HandlePacket(const protocol::WorldAddTowerPacket* packet);
virtual void HandlePacket(const protocol::RemoveTowerPacket* packet);
virtual void HandlePacket(const protocol::UpdateMobStatesPacket* packet);
virtual void HandlePacket(const protocol::UpdateCastleLifePacket* packet);
virtual void OnArrowShot(game::MobPtr target, game::Tower* shooter) override;
};
} // namespace client

View File

@@ -61,12 +61,11 @@ private:
bool m_ServerRunning;
public:
Server(const std::string& worldFilePath);
virtual ~Server() {}
virtual ~Server();
bool start(std::uint16_t port);
void stop();
void lauchGame();
void stop(); // force the server to stop
void close(); // at the end of a game
void removeConnexion(std::uint8_t connexionID);
@@ -74,6 +73,8 @@ public:
float getTPS() const { return m_TickCounter.getTPS(); }
bool isRunning() { return m_ServerRunning; }
const ServerGame& getGame() const { return m_Game; }
ServerGame& getGame() { return m_Game; }
@@ -88,6 +89,7 @@ private:
void accept();
void updateSockets();
void clean();
void startThread();
void stopThread();
void tick(std::uint64_t delta);

View File

@@ -40,6 +40,7 @@ public:
virtual void HandlePacket(const protocol::PlaceTowerPacket* packet);
virtual void HandlePacket(const protocol::SendMobsPacket* packet);
virtual void HandlePacket(const protocol::UpgradeTowerPacket* packet);
virtual void HandlePacket(const protocol::RemoveTowerPacket* packet);
std::uint8_t getID() const { return m_ID; }
const game::Player* getPlayer() const { return m_Player; }

View File

@@ -9,11 +9,13 @@ namespace server {
class Server;
class ServerGame : public game::Game {
class ServerGame : public game::Game, public game::GameListener {
private:
Server* m_Server;
ServerWorld m_ServerWorld;
utils::AutoTimer m_GoldMineTimer{ 1000, std::bind(&ServerGame::updateGoldMines, this) };
utils::AutoTimer m_GoldMineTimer;
utils::AutoTimer m_MobStatesTimer;
utils::CooldownTimer m_EndGameCooldown;
public:
ServerGame(Server* server);
~ServerGame() {}
@@ -22,8 +24,16 @@ public:
virtual void tick(std::uint64_t delta);
void startGame();
// GameListener
virtual void OnGameStateUpdate(game::GameState newState);
virtual void OnGameBegin();
virtual void OnGameEnd();
virtual void OnGameClose();
private:
void balanceTeams();
void updateMobStates();
void updateGoldMines();
void updatePlayerStats();
};

View File

@@ -14,12 +14,17 @@ private:
game::TowerID m_CurrentTowerID;
Server* m_Server;
public:
static constexpr float MobSpawnBorder = 0.01f;
ServerWorld(Server* server, ServerGame* game);
void spawnMobs(game::MobType type, std::uint8_t level, game::PlayerID sender, std::uint8_t count);
game::TowerPtr placeTowerAt(game::TowerType type, std::int32_t x, std::int32_t y, game::PlayerID builder);
virtual void OnArrowShot(game::MobPtr target, game::Tower* shooter) override;
virtual void OnMobDie(game::Mob* mob);
virtual void OnMobCastleDamage(game::Mob* damager, game::TeamCastle* enemyCastle, float damage);
};
} // namespace server

4465
include/misc/Backward.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
#pragma once
#include <algorithm>
#include <functional>
#include <vector>
namespace td {
namespace utils {
template <typename Listener>
class ObjectNotifier {
protected:
std::vector<Listener*> m_Listeners;
public:
void bindListener(Listener* listener) {
m_Listeners.push_back(listener);
}
void unbindListener(Listener* listener) {
auto iter = std::find(m_Listeners.begin(), m_Listeners.end(), listener);
if(iter == m_Listeners.end()) return;
m_Listeners.erase(iter);
}
template <typename Func, typename... Args>
void notifyListeners(Func function, Args... args) {
for (Listener* listener : m_Listeners)
std::bind(function, listener, args...)();
}
};
} // namespace utils
} // namespace td

View File

@@ -47,12 +47,19 @@ public:
void setCenterY(float y) { m_Center.setY(y); }
void setSize(float width, float height) { setWidth(width); setHeight(height); }
void setSize(Point size) { setSize(size.getX(), size.getY()); }
Point getSize() { return { m_Width, m_Height }; }
void setWidth(float width) { m_Width = width; }
void setHeight(float height) { m_Height = height; }
bool collidesWith(const Point& point) const;
bool collidesWith(const Rectangle& rect) const;
bool collidesWith(const Circle& circle) const;
// distance from the closest side of the rectangle
float distance(const Circle& circle) const;
float distanceSquared(const Circle& circle) const;
};
class Circle {
@@ -78,6 +85,10 @@ public:
bool collidesWith(const Point& point) const;
bool collidesWith(const Rectangle& rect) const;
bool collidesWith(const Circle& circle) const;
// distance from the closest side of the rectangle
float distance(const Rectangle& rect) const;
float distanceSquared(const Rectangle& rect) const;
};
} // namespace shape

View File

@@ -39,7 +39,7 @@ public:
// utililty class to call function at regular period of time
class Timer {
private:
std::uint64_t m_Interval;
std::uint64_t m_Interval; // in millis
std::uint64_t m_InternalTime = 0;
public:
Timer() : m_Interval(0) {}
@@ -53,10 +53,10 @@ public:
std::uint64_t getInterval() const { return m_Interval; }
};
// utililty class to call function at regular period of time with a cooldown (used for towers)
// utililty class to call function at regular period of time with a cooldown (used for towers and mobs )
class CooldownTimer {
private:
std::uint64_t m_Cooldown;
std::uint64_t m_Cooldown; // in millis
std::uint64_t m_CooldownTime;
public:
CooldownTimer() : m_Cooldown(0), m_CooldownTime(0) {}

View File

@@ -35,9 +35,11 @@ public:
virtual void HandlePacket(const SpawnMobPacket* packet) {}
virtual void HandlePacket(const PlaceTowerPacket* packet) {}
virtual void HandlePacket(const WorldAddTowerPacket* packet) {}
virtual void HandlePacket(const WorldRemoveTowerPacket* packet) {}
virtual void HandlePacket(const RemoveTowerPacket* packet) {}
virtual void HandlePacket(const SendMobsPacket* packet) {}
virtual void HandlePacket(const UpgradeTowerPacket* packet) {}
virtual void HandlePacket(const UpdateMobStatesPacket* packet) {}
virtual void HandlePacket(const UpdateCastleLifePacket* packet) {}
};
} // namespace protocol

View File

@@ -33,18 +33,21 @@ enum class PacketType : std::uint8_t {
UpdatePlayerTeam,
ServerTps,
WorldAddTower,
WorldRemoveTower,
UpdateMobStates,
UpdateCastleLife,
// client <--> server
KeepAlive,
Disconnect,
UpgradeTower,
RemoveTower,
};
struct WorldHeader {
game::TowerTileColorPalette m_TowerPlacePalette;
game::Color m_WalkablePalette;
std::vector<game::Color> m_DecorationPalette;
game::Color m_Background;
game::SpawnColorPalette m_SpawnColorPalette;
@@ -128,6 +131,7 @@ public:
const game::TowerTileColorPalette& getTowerTilePalette() const { return m_Header.m_TowerPlacePalette; }
const game::Color& getWalkableTileColor() const { return m_Header.m_WalkablePalette; }
const std::vector<game::Color>& getDecorationPalette() const { return m_Header.m_DecorationPalette; }
const game::Color& getBackgroundColor() const { return m_Header.m_Background; }
const game::Spawn& getRedSpawn() const { return m_Header.m_RedSpawn; }
const game::Spawn& getBlueSpawn() const { return m_Header.m_BlueSpawn; }
@@ -477,13 +481,13 @@ public:
virtual PacketType getType() const { return PacketType::WorldAddTower; }
};
class WorldRemoveTowerPacket : public Packet {
class RemoveTowerPacket : public Packet {
private:
game::TowerID m_TowerID;
public:
WorldRemoveTowerPacket() {}
WorldRemoveTowerPacket(game::TowerID id) : m_TowerID(id) {}
virtual ~WorldRemoveTowerPacket() {}
RemoveTowerPacket() {}
RemoveTowerPacket(game::TowerID id) : m_TowerID(id) {}
virtual ~RemoveTowerPacket() {}
virtual DataBuffer Serialize() const;
virtual void Deserialize(DataBuffer& data);
@@ -491,7 +495,7 @@ public:
game::TowerID getTowerID() const { return m_TowerID; }
virtual PacketType getType() const { return PacketType::WorldRemoveTower; }
virtual PacketType getType() const { return PacketType::RemoveTower; }
};
class UpgradeTowerPacket : public Packet {
@@ -513,5 +517,60 @@ public:
virtual PacketType getType() const { return PacketType::UpgradeTower; }
};
class MobState {
using Point = utils::shape::Point;
private:
game::MobID m_MobID;
Point m_MobPosition;
float m_MobLife;
game::Direction m_MobDirection;
public:
MobState() {}
MobState(game::MobID id, Point position, float life, game::Direction direction) :
m_MobID(id), m_MobPosition(position), m_MobLife(life), m_MobDirection(direction) {}
game::MobID getMobId() const { return m_MobID; }
Point getMobPosition() const { return m_MobPosition; }
float getMobLife() const { return m_MobLife; }
game::Direction getMobDirection() const { return m_MobDirection; }
};
class UpdateMobStatesPacket : public Packet {
private:
std::vector<MobState> m_MobStates;
public:
UpdateMobStatesPacket() {}
virtual ~UpdateMobStatesPacket() {}
virtual DataBuffer Serialize() const;
virtual void Deserialize(DataBuffer& data);
virtual void Dispatch(PacketHandler* handler) const;
void addMobState(MobState mobState) { m_MobStates.push_back(mobState); }
const std::vector<MobState>& getMobStates() const { return m_MobStates; }
virtual PacketType getType() const { return PacketType::UpdateMobStates; }
};
class UpdateCastleLifePacket : public Packet {
private:
std::uint16_t m_CastleLife;
game::TeamColor m_Team;
public:
UpdateCastleLifePacket() {}
UpdateCastleLifePacket(std::uint16_t life, game::TeamColor team) : m_CastleLife(life), m_Team(team) {}
virtual ~UpdateCastleLifePacket() {}
virtual DataBuffer Serialize() const;
virtual void Deserialize(DataBuffer& data);
virtual void Dispatch(PacketHandler* handler) const;
std::uint16_t getCastleLife() const { return m_CastleLife; }
game::TeamColor getTeamColor() const { return m_Team; }
virtual PacketType getType() const { return PacketType::UpdateCastleLife; }
};
}
}

View File

@@ -29,6 +29,8 @@ private:
std::unique_ptr<WorldShader> m_WorldShader;
std::unique_ptr<EntityShader> m_EntityShader;
glm::vec3 m_BackgroundColor;
bool m_IsometricView = true;
float m_IsometricShade = m_IsometricView;
glm::vec2 m_CamPos{};
@@ -49,6 +51,8 @@ public:
void setCamPos(const glm::vec2& newPos);
void setIsometricView(bool isometric); // false = 2D true = Isometric
void setBackgroundColor(const glm::vec3& color) { m_BackgroundColor = color; }
glm::vec2 getCursorWorldPos(const glm::vec2& cursorPos, float aspectRatio, float zoom, float windowWidth, float windowHeight);
private:
void updateIsometricView();

View File

@@ -12,15 +12,19 @@ namespace render {
class VertexCache {
typedef std::vector<float> Vector;
typedef std::pair<Vector::iterator, Vector::iterator> ElementsIndex;
typedef std::pair<ElementsIndex, ElementsIndex> DataIndex;
struct DataIndex {
Vector position;
Vector color;
};
private:
Vector m_Positions;
Vector m_Colors;
std::size_t m_VertexCount;
std::unordered_map<std::uint64_t, DataIndex> m_Indexes;
std::unique_ptr<GL::VertexArray> m_VertexArray;
public:
VertexCache() : m_VertexCount(0) {}
void addData(std::uint64_t index, std::vector<float> positions, std::vector<float> colors);
void removeData(std::uint64_t index);
void clear();

View File

@@ -7,6 +7,7 @@
#include "render/gui/TowerPlacePopup.h"
#include "render/gui/MobTooltip.h"
#include "render/gui/CastleTooltip.h"
#include "render/gui/imgui/imgui.h"
@@ -23,7 +24,7 @@ class ClientGame;
namespace render {
class WorldRenderer {
class WorldRenderer : public game::WorldListener {
private:
client::ClientGame* m_Client;
Renderer* m_Renderer;
@@ -40,15 +41,13 @@ private:
std::unique_ptr<gui::TowerPlacePopup> m_TowerPlacePopup;
std::unique_ptr<gui::MobTooltip> m_MobTooltip;
std::unique_ptr<gui::CastleTooltip> m_CastleTooltip;
public:
WorldRenderer(game::World* world, client::ClientGame* client);
~WorldRenderer();
void loadModels();
void addTower(game::TowerPtr tower);
void removeTower(game::TowerPtr tower);
static ImVec4 getImGuiTeamColor(game::TeamColor color);
void update();
@@ -58,6 +57,11 @@ public:
void moveCam(float relativeX, float relativeY, float aspectRatio);
void changeZoom(float zoom);
// WorldListener
virtual void OnTowerAdd(game::TowerPtr tower);
virtual void OnTowerRemove(game::TowerPtr tower);
private:
void click();
void renderWorld() const;
@@ -67,8 +71,12 @@ private:
void renderPopups();
void renderTowerUpgradePopup();
void renderMobTooltip() const;
void renderCastleTooltip() const;
void detectClick();
void detectMobHovering() const;
void detectCastleHovering() const;
void renderTooltips() const;
void removeTower();
glm::vec2 getCursorWorldPos() const;
glm::vec2 getClickWorldPos() const;

View File

@@ -0,0 +1,28 @@
#pragma once
#include "GuiWidget.h"
namespace td {
namespace game {
class TeamCastle;
} // namespace game
namespace gui {
class CastleTooltip : public GuiWidget {
private:
const game::TeamCastle* m_Castle;
public:
CastleTooltip(client::Client* client);
virtual void render();
void setCastle(const game::TeamCastle* castle) { m_Castle = castle; }
bool isShown() { return m_Castle != nullptr; }
};
} // namespace gui
} // namespace td

View File

@@ -10,15 +10,17 @@ namespace gui {
class GuiManager {
private:
std::vector<std::shared_ptr<GuiWidget>> m_Widgets;
std::vector<std::unique_ptr<GuiWidget>> m_Widgets;
public:
GuiManager(){}
void renderWidgets() {
for (auto widget : m_Widgets) {
for (auto& widget : m_Widgets) {
widget->render();
}
}
void addWidgets(const std::shared_ptr<GuiWidget>& widget) {
void addWidget(std::unique_ptr<GuiWidget>&& widget) {
m_Widgets.push_back(std::move(widget));
}
};

View File

@@ -21,6 +21,7 @@ public:
virtual void render();
void setMob(const game::Mob* mob) { m_Mob = mob; }
bool isShown() { return m_Mob != nullptr; }
};
} // namespace gui

View File

@@ -9,6 +9,8 @@
#include <memory>
#include "render/gui/GuiManager.h"
struct SDL_Window;
typedef void* SDL_GLContext;
@@ -20,15 +22,6 @@ class Client;
} // namespace client
namespace gui {
class MainMenu;
class GameMenu;
class FrameMenu;
class UpdateMenu;
} // namespace gui
namespace render {
class Renderer;
@@ -38,11 +31,8 @@ private:
SDL_Window* m_Window;
SDL_GLContext m_GlContext;
td::render::Renderer* m_Renderer;
td::gui::GuiManager m_GuiManager;
std::unique_ptr<td::client::Client> m_Client;
std::unique_ptr<td::gui::MainMenu> m_MainMenu;
std::unique_ptr<td::gui::GameMenu> m_GameMenu;
std::unique_ptr<td::gui::FrameMenu> m_FrameMenu;
std::unique_ptr<td::gui::UpdateMenu> m_UpdateMenu;
public:
TowerGui(SDL_Window* wndow, SDL_GLContext glContext, td::render::Renderer* renderer);
~TowerGui();

View File

@@ -2,7 +2,7 @@
#include "GuiWidget.h"
#include "misc/Updater.h"
#include "updater/Updater.h"
#include <future>

View File

@@ -1,10 +1,8 @@
#pragma once
#include "DataBuffer.h"
#include "misc/DataBuffer.h"
#include <ctime>
#define TD_VERSION "alpha-0.0.2"
#define TD_VERSION "alpha-0.2.0"
namespace td {
namespace utils {

View File

@@ -6,14 +6,26 @@
// Description : Hello World in C++, Ansi-style
//============================================================================
#if !defined(NDEBUG) && !defined(_WIN32)
#define BACKWARD_HAS_UNWIND 1
#define BACKWARD_HAS_DW 1
#endif
#include "window/Display.h"
#include "misc/Updater.h"
#include "updater/Updater.h"
#include "misc/Backward.hpp"
#ifdef __ANDROID__
extern "C"
#endif
int main(int argc, const char* args[]) {
#if !defined(NDEBUG)
// setup signal handling
backward::SignalHandling sh;
#endif
// remove the outdated binary
td::utils::Updater::removeOldFile();
Display::create();

View File

@@ -17,5 +17,21 @@ void Game::tick(std::uint64_t delta) {
}
}
Player* Game::getPlayerById(PlayerID id) {
auto it = m_Players.find(id);
if (it == m_Players.end()) return nullptr;
return &it->second;
}
const Player* Game::getPlayerById(PlayerID id) const {
auto it = m_Players.find(id);
if (it == m_Players.end()) return nullptr;
return &it->second;
}
} // namespace game
} // namespace td

View File

@@ -1,4 +1,6 @@
#include "game/Mobs.h"
#include "game/Player.h"
#include "game/World.h"
#include <map>
#include <algorithm>
@@ -18,7 +20,7 @@ EffectDuration& Mob::getEffect(EffectType effectType) {
return *std::find_if(m_Effects.begin(), m_Effects.end(), [&effectType](EffectDuration effect) { return effect.type == effectType;});
}
void Mob::addEffect(EffectType effectType, float durationSec, const Tower* tower) {
void Mob::addEffect(EffectType effectType, float durationSec, Tower* tower) {
if (isImmuneTo(effectType))
return;
if (hasEffect(effectType)) {
@@ -30,11 +32,176 @@ void Mob::addEffect(EffectType effectType, float durationSec, const Tower* tower
}
}
void Mob::tick(std::uint64_t delta) {
updateEffects(delta);
void Mob::attackCastle(std::uint64_t delta, World* world) {
if(!hasReachedEnemyCastle()) return;
if (m_AttackTimer.update(delta)) {
world->getMobNotifier().notifyListeners(&MobListener::OnMobCastleDamage, this, m_CastleTarget, getStats()->getDamage());
m_AttackTimer.applyCooldown();
}
}
void Mob::updateEffects(std::uint64_t delta) {
void Mob::walk(std::uint64_t delta, World* world) {
float mobWalkSpeed = getStats()->getMovementSpeed();
float walkAmount = mobWalkSpeed * ((float)delta / 1000.0f);
if (hasEffect(EffectType::Slowness))
walkAmount *= 0.70; // walk 30% slower
switch (getDirection()) {
case Direction::NegativeX: {
setCenterX(getCenterX() - walkAmount);
break;
}
case Direction::PositiveX: {
setCenterX(getCenterX() + walkAmount);
break;
}
case Direction::NegativeY: {
setCenterY(getCenterY() - walkAmount);
break;
}
case Direction::PositiveY: {
setCenterY(getCenterY() + walkAmount);
break;
}
}
}
void Mob::move(std::uint64_t delta, World* world) {
TilePtr tile = world->getTile(getCenter().getX(), getCenter().getY());
if (tile != nullptr && tile->getType() == TileType::Walk) {
WalkableTilePtr walkTile = std::static_pointer_cast<WalkableTile>(tile);
changeDirection(*walkTile, world);
}
if (hasReachedEnemyCastle()) return;
walk(delta, world);
TeamColor mobTeam = world->getPlayerById(getSender())->getTeamColor();
TeamCastle* enemyCastle = nullptr;
if (mobTeam == TeamColor::Red) {
enemyCastle = &world->getBlueTeam().getCastle();
} else if (mobTeam == TeamColor::Blue) {
enemyCastle = &world->getRedTeam().getCastle();
}
if (isTouchingCastle(*enemyCastle)) {
moveBack(*enemyCastle, world);
setMobReachedCastle(enemyCastle);
world->getMobNotifier().notifyListeners(&MobListener::OnMobTouchCastle, this, enemyCastle);
}
}
void Mob::moveBack(const TeamCastle& enemyCastle, World* world) {
switch (getDirection()) {
case Direction::NegativeX: {
setCenterX(enemyCastle.getBottomRight().getX() + getWidth() / 2.0f);
break;
}
case Direction::PositiveX: {
setCenterX(enemyCastle.getTopLeft().getX() - getWidth() / 2.0f);
break;
}
case Direction::NegativeY: {
setCenterY(enemyCastle.getBottomRight().getY() + getHeight() / 2.0f);
break;
}
case Direction::PositiveY: {
setCenterY(enemyCastle.getTopLeft().getY() - getHeight() / 2.0f);
break;
}
}
}
void Mob::changeDirection(const WalkableTile& tile, World* world) {
if (getDirection() == tile.direction) return;
float tileX = static_cast<float>(static_cast<std::int32_t>(getCenterX()));
float tileY = static_cast<float>(static_cast<std::int32_t>(getCenterY()));
switch (getDirection()) {
case Direction::PositiveY: {
if (tile.direction == Direction::NegativeX) {
if (getTileY() > getTileX()) {
setCenterY(tileY + getTileX());
setDirection(tile.direction);
}
} else { // tile->direction = Direction::PositiveX
if (getTileY() > 1 - getTileX()) {
setCenterY(tileY + (1 - getTileX()));
setDirection(tile.direction);
}
}
return;
}
case Direction::NegativeY: {
if (tile.direction == Direction::PositiveX) {
if (getTileY() < getTileX()) {
setCenterY(tileY + getTileX());
setDirection(tile.direction);
}
} else { // tile.direction = Direction::NegativeX
if (getTileY() < 1 - getTileX()) {
setCenterY(tileY + (1 - getTileX()));
setDirection(tile.direction);
}
}
return;
}
case Direction::PositiveX: {
if (tile.direction == Direction::NegativeY) {
if (getTileX() > getTileY()) {
setCenterX(tileX + getTileY());
setDirection(tile.direction);
}
} else { // tile.direction = Direction::PositiveY
if (getTileX() > 1 - getTileY()) {
setCenterX(tileX + (1 - getTileY()));
setDirection(tile.direction);
}
}
return;
}
case Direction::NegativeX: {
if (tile.direction == Direction::PositiveY) {
if (getTileX() < getTileY()) {
setCenterX(tileX + getTileY());
setDirection(tile.direction);
}
} else { // tile.direction = Direction::NegativeY
if (getTileX() < 1 - getTileY()) {
setCenterX(tileX + (1 - getTileY()));
setDirection(tile.direction);
}
}
return;
}
}
}
bool Mob::isTouchingCastle(const TeamCastle& enemyCastle) const {
return enemyCastle.collidesWith(*this);
}
void Mob::tick(std::uint64_t delta, World* world) {
updateEffects(delta, world);
move(delta, world);
attackCastle(delta, world);
}
void Mob::updateEffects(std::uint64_t delta, World* world) {
float deltaSec = (float)delta / 1000.0f;
for (std::size_t i = 0; i < m_Effects.size(); i++) {
EffectDuration& effect = m_Effects[i];
@@ -63,12 +230,12 @@ void Mob::updateEffects(std::uint64_t delta) {
}
if (hasEffect(EffectType::Fire)) {
if (m_EffectFireTimer.update(delta)) {
damage(3, getEffect(EffectType::Fire).tower);
world->getMobNotifier().notifyListeners(&MobListener::OnMobDamage, this, 3.0f, getEffect(EffectType::Fire).tower);
}
}
if (hasEffect(EffectType::Poison)) {
if (m_EffectPoisonTimer.update(delta)) {
damage(1, getEffect(EffectType::Poison).tower);
world->getMobNotifier().notifyListeners(&MobListener::OnMobDamage, this, 1.0f, getEffect(EffectType::Poison).tower);
}
}
if (hasEffect(EffectType::Heal)) {
@@ -235,7 +402,7 @@ const std::map<MobKey, EffectImmunities> MobsEffectImmunities = {
{{MobType::Skeleton, 1},{}},
{{MobType::Skeleton, 2},{}},
{{MobType::Skeleton, 3},{EffectType::Fire}},
{{MobType::Skeleton, 3},{}},
{{MobType::Skeleton, 4},{EffectType::Fire}},
{{MobType::Skeleton, 5},{EffectType::Fire}},

View File

@@ -5,7 +5,7 @@
namespace td {
namespace game {
Team::Team(TeamColor color) : m_Color(color) {}
Team::Team(TeamColor color) : m_Color(color), m_TeamCastle(this) {}
void Team::addPlayer(Player* newPlayer) {
m_Players.push_back(newPlayer);

View File

@@ -21,8 +21,8 @@ const std::map<std::pair<TowerType, TowerLevel>, TowerStats> TowerConstants = {
{{TowerType::Archer, {4, TowerPath::Top}}, {0.8, 0, 15}},
{{TowerType::Archer, {2, TowerPath::Bottom}}, {2, 10, 12}},
{{TowerType::Archer, {3, TowerPath::Bottom}}, {2, 0, 13}},
{{TowerType::Archer, {4, TowerPath::Bottom}}, {2, 0, 15}},
{{TowerType::Archer, {3, TowerPath::Bottom}}, {2, 10, 13}},
{{TowerType::Archer, {4, TowerPath::Bottom}}, {2, 10, 15}},
//-----------------------------------------------------------------
@@ -213,11 +213,11 @@ void ArcherTower::tick(std::uint64_t delta, World* world) {
std::uint8_t arrows = explosiveArrows ? 2 : getLevel().getLevel();
for (MobPtr mob : world->getMobList()) {
if (isMobInRange(mob)) {
world->OnArrowShot(mob, this);
world->getWorldNotifier().notifyListeners(&WorldListener::OnArcherTowerShot, mob, this);
m_Timer.applyCooldown();
arrowsShot++;
if (arrowsShot >= arrows)
break;
m_Timer.applyCooldown();
}
}
}
@@ -229,7 +229,8 @@ void IceTower::tick(std::uint64_t delta, World* world) {
for (MobPtr mob : world->getMobList()) {
if (isMobInRange(mob)) {
mob->addEffect(EffectType::Slowness, 1, this); // slowness for 1s every second
mob->damage(damage, this);
if (damage > 0)
world->getMobNotifier().notifyListeners(&MobListener::OnMobDamage, mob.get(), damage, this);
m_Timer.applyCooldown();
}
}
@@ -252,7 +253,7 @@ void PoisonTower::tick(std::uint64_t delta, World* world) {
for (MobPtr mob : world->getMobList()) {
if (isMobInRange(mob)) {
if (getLevel().getPath() == TowerPath::Bottom) {
mob->damage(getStats()->getDamage(), this);
world->getMobNotifier().notifyListeners(&MobListener::OnMobDamage, mob.get(), getStats()->getDamage(), this);
} else {
float durationSec;
switch (getLevel().getLevel()) {

View File

@@ -13,7 +13,8 @@ namespace td {
namespace game {
World::World(Game* game) : m_Game(game) {
getWorldNotifier().bindListener(this);
getMobNotifier().bindListener(this);
}
TilePtr World::getTile(std::int32_t x, std::int32_t y) const {
@@ -36,6 +37,7 @@ bool World::loadMap(const protocol::WorldBeginDataPacket* worldHeader) {
m_TowerPlacePalette = worldHeader->getTowerTilePalette();
m_WalkablePalette = worldHeader->getWalkableTileColor();
m_DecorationPalette = worldHeader->getDecorationPalette();
m_Background = worldHeader->getBackgroundColor();
getRedTeam().getSpawn() = worldHeader->getRedSpawn();
getBlueTeam().getSpawn() = worldHeader->getBlueSpawn();
@@ -43,7 +45,10 @@ bool World::loadMap(const protocol::WorldBeginDataPacket* worldHeader) {
m_SpawnColorPalette = worldHeader->getSpawnPalette();
getRedTeam().getCastle() = worldHeader->getRedCastle();
getRedTeam().getCastle().setTeam(&getRedTeam());
getBlueTeam().getCastle() = worldHeader->getBlueCastle();
getBlueTeam().getCastle().setTeam(&getBlueTeam());
m_TilePalette = worldHeader->getTilePalette();
@@ -101,7 +106,8 @@ bool World::saveMap(const std::string& fileName) const {
}
void World::tick(std::uint64_t delta) {
moveMobs(delta);
if (m_Game->getGameState() != GameState::Game) return;
tickMobs(delta);
for (TowerPtr tower : m_Towers) {
tower->tick(delta, this);
@@ -114,6 +120,7 @@ void World::spawnMobAt(MobID id, MobType type, std::uint8_t level, PlayerID send
mob->setCenter({ x, y });
mob->setDirection(dir);
m_Mobs.push_back(mob);
getMobNotifier().notifyListeners(&MobListener::OnMobSpawn, mob.get());
}
TowerPtr World::placeTowerAt(TowerID id, TowerType type, std::int32_t x, std::int32_t y, PlayerID builder) {
@@ -122,89 +129,20 @@ TowerPtr World::placeTowerAt(TowerID id, TowerType type, std::int32_t x, std::in
return tower;
}
TowerPtr World::removeTower(TowerID towerId) {
auto it = std::find_if(m_Towers.begin(), m_Towers.end(), [towerId](TowerPtr tower) { return tower->getID() == towerId;});
if (it == m_Towers.end()) return nullptr;
TowerPtr tower = *it;
m_Towers.erase(it);
return tower;
}
void World::tickMobs(std::uint64_t delta) {
for (MobPtr mob : m_Mobs) {
mob->tick(delta);
}
}
void World::moveMobs(std::uint64_t delta) {
for (MobPtr mob : m_Mobs) {
TilePtr tile = getTile(mob->getCenter().getX(), mob->getCenter().getY());
if (tile != nullptr && tile->getType() == TileType::Walk) {
WalkableTile* walkTile = dynamic_cast<WalkableTile*>(tile.get());
mob->setDirection(walkTile->direction);
}
moveMob(mob, delta);
TeamColor mobTeam = m_Game->getPlayerById(mob->getSender()).getTeamColor();
const TeamCastle* enemyCastle;
if (mobTeam == TeamColor::Red) {
enemyCastle = &getBlueTeam().getCastle();
} else if (mobTeam == TeamColor::Blue) {
enemyCastle = &getRedTeam().getCastle();
}
if (isMobTouchingCastle(mob, *enemyCastle)) {
moveBackMob(mob, *enemyCastle);
}
}
}
void World::moveMob(MobPtr mob, std::uint64_t delta) {
float mobWalkSpeed = mob->getStats()->getMovementSpeed();
float walkAmount = mobWalkSpeed * ((float)delta / 1000.0f);
if (mob->hasEffect(EffectType::Slowness))
walkAmount *= 0.70; // walk 30% slower
switch (mob->getDirection()) {
case Direction::NegativeX: {
mob->setCenterX(mob->getCenterX() - walkAmount);
break;
}
case Direction::PositiveX: {
mob->setCenterX(mob->getCenterX() + walkAmount);
break;
}
case Direction::NegativeY: {
mob->setCenterY(mob->getCenterY() - walkAmount);
break;
}
case Direction::PositiveY: {
mob->setCenterY(mob->getCenterY() + walkAmount);
break;
}
}
}
bool World::isMobTouchingCastle(MobPtr mob, const TeamCastle& enemyCastle) const {
return enemyCastle.collidesWith(*mob);
}
void World::moveBackMob(MobPtr mob, const TeamCastle& enemyCastle) {
switch (mob->getDirection()) {
case Direction::NegativeX: {
mob->setCenterX(enemyCastle.getBottomRight().getX() + mob->getWidth() / 2.0f);
break;
}
case Direction::PositiveX: {
mob->setCenterX(enemyCastle.getTopLeft().getX() - mob->getWidth() / 2.0f);
break;
}
case Direction::NegativeY: {
mob->setCenterY(enemyCastle.getBottomRight().getY() + mob->getHeight() / 2.0f);
break;
}
case Direction::PositiveY: {
mob->setCenterY(enemyCastle.getTopLeft().getY() - mob->getHeight() / 2.0f);
break;
}
mob->tick(delta, this);
}
}
@@ -284,19 +222,11 @@ bool World::CanPlaceBigTower(const glm::vec2& worldPos, PlayerID playerID) const
}
void World::cleanDeadMobs() {
// safely remove mobs when unused
for (std::size_t i = 0; i < m_Mobs.size(); i++) {
MobPtr mob = m_Mobs[i];
if (mob->isDead()) {
if (mob->OnDeath(this)) {
//reward players
Player& sender = m_Game->getPlayerById(mob->getSender());
sender.addExp(mob->getStats()->getExpReward());
Player& killer = m_Game->getPlayerById(mob->getLastDamageTower()->getBuilder());
killer.addGold(mob->getStats()->getMoneyCost());
m_Mobs.erase(m_Mobs.begin() + i);
}
m_Mobs.erase(m_Mobs.begin() + i);
}
}
}
@@ -324,12 +254,44 @@ TowerPtr World::getTowerById(TowerID towerID) {
return *it;
}
void World::OnArrowShot(MobPtr target, Tower* shooter) {
bool explosiveArrows = shooter->getLevel().getPath() == TowerPath::Bottom;
void World::OnArcherTowerShot(MobPtr target, ArcherTower* shooter) {
bool fireArrows = shooter->getLevel().getPath() == TowerPath::Bottom;
bool explosiveArrows = shooter->getLevel().getLevel() == 4 && fireArrows;
getWorldNotifier().notifyListeners(&WorldListener::OnArrowShot, target, fireArrows, shooter);
if (explosiveArrows) {
// aoe damage
} else {
target->damage(shooter->getStats()->getDamage(), shooter);
getWorldNotifier().notifyListeners(&WorldListener::OnExplosion, utils::shape::Circle{ target->getCenterX(), target->getCenterY(), ArcherTower::ExplosionRadius }, shooter->getStats()->getDamage(), shooter);
}
}
void World::OnArrowShot(MobPtr target, bool fireArrow, Tower* shooter) {
getMobNotifier().notifyListeners(&MobListener::OnMobDamage, target.get(), shooter->getStats()->getDamage(), shooter);
if (fireArrow) {
target->addEffect(EffectType::Fire, ArcherTower::FireDurationSec, shooter);
}
}
void World::OnExplosion(utils::shape::Circle explosion, float centerDamage, Tower* shooter) {
for (MobPtr mob : m_Mobs) {
if (mob->isAlive() && mob->collidesWith(explosion)) {
// linear distance damage reduction
float explosionDamage = mob->distance(explosion) / explosion.getRadius() * centerDamage;
getMobNotifier().notifyListeners(&MobListener::OnMobDamage, mob.get(), explosionDamage, shooter);
}
}
}
void World::OnMobCastleDamage(Mob* damager, TeamCastle* enemyCastle, float damage) {
enemyCastle->damage(damage);
if (enemyCastle->getLife() <= 0) {
m_Game->notifyListeners(&GameListener::OnGameEnd);
}
}
void World::OnMobDamage(Mob* target, float damage, Tower* source) {
target->damage(damage, source);
if (target->isDead()) {
getMobNotifier().notifyListeners(&MobListener::OnMobDie, target);
}
}
@@ -357,5 +319,13 @@ const Team& World::getTeam(TeamColor team) const {
return m_Game->getTeam(team);
}
const Player* World::getPlayerById(PlayerID id) const {
return m_Game->getPlayerById(id);
}
const TeamList& World::getTeams() const {
return m_Game->getTeams();
}
} // namespace game
} // namespace td

View File

@@ -40,13 +40,19 @@ void Client::tick(std::uint64_t delta) {
m_Connected = m_Connexion.updateSocket();
if (!m_Connected) {
std::cout << "Disconnected ! (Reason : " << m_Connexion.getDisconnectReason() << ")\n";
reset();
} else {
m_Game.tick(delta);
m_Game->tick(delta);
}
}
void Client::render() {
m_Game.renderWorld();
m_Game->renderWorld();
}
void Client::reset() {
m_Game.reset(0);
m_Game = std::make_unique<ClientGame>(this);
}
void Client::sendMobs(const std::vector<protocol::MobSend>& mobSends) {
@@ -64,5 +70,10 @@ void Client::upgradeTower(game::TowerID tower, game::TowerLevel level) {
m_Connexion.sendPacket(&packet);
}
void Client::removeTower(game::TowerID tower) {
protocol::RemoveTowerPacket packet(tower);
m_Connexion.sendPacket(&packet);
}
} // namespace client
} // namespace td

View File

@@ -4,7 +4,7 @@
namespace td {
namespace client {
ClientConnexion::ClientConnexion() : Connexion(&m_Dispatcher) {
ClientConnexion::ClientConnexion() : Connexion(&m_Dispatcher), m_ServerTPS(0) {
registerHandlers();
}

View File

@@ -1,10 +1,7 @@
#include "game/client/ClientGame.h"
#include "protocol/PacketDispatcher.h"
//#include "game/Team.h"
#include "game/client/Client.h"
#include <iostream>
namespace td {
namespace client {
@@ -21,7 +18,6 @@ m_WorldRenderer(&m_WorldClient, this) {
GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateMoney, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::Disconnect, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::WorldData, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::WorldAddTower, this);
}
ClientGame::~ClientGame() {
@@ -97,6 +93,7 @@ void ClientGame::HandlePacket(const protocol::UpdateMoneyPacket* packet) {
void ClientGame::HandlePacket(const protocol::DisconnectPacket* packet) {
m_GameState = game::GameState::Disconnected;
m_Renderer->setBackgroundColor({ 0, 0, 0 });
}
void ClientGame::HandlePacket(const protocol::WorldDataPacket* packet) {
@@ -112,9 +109,5 @@ void ClientGame::renderWorld() {
}
}
void ClientGame::HandlePacket(const protocol::WorldAddTowerPacket* packet) {
m_WorldRenderer.addTower(m_WorldClient.placeTowerAt(packet->getTowerID(), packet->getTowerType(), packet->getTowerX(), packet->getTowerY(), packet->getBuilder()));
}
} // namespace client
} // namespace td

View File

@@ -7,14 +7,23 @@ namespace td {
namespace client {
WorldClient::WorldClient(ClientGame* game) : game::World(game), protocol::PacketHandler(game->GetDispatcher()), m_Game(game) {
GetDispatcher()->RegisterHandler(protocol::PacketType::WorldAddTower, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::WorldBeginData, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::WorldData, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::SpawnMob, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::UpgradeTower, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::RemoveTower, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateCastleLife, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::UpdateMobStates, this);
}
void WorldClient::HandlePacket(const protocol::WorldBeginDataPacket* packet) {
loadMap(packet);
if (m_Game->getGameState() == game::GameState::Game) {
const game::Color& backgroundColor = getBackgroundColor();
m_Game->getRenderer()->setBackgroundColor({ static_cast<float>(backgroundColor.r / 255.0f), static_cast<float>(backgroundColor.g / 255.0f),
static_cast<float>(backgroundColor.b / 255.0f) });
}
}
void WorldClient::HandlePacket(const protocol::WorldDataPacket* packet) {
@@ -32,8 +41,35 @@ void WorldClient::HandlePacket(const protocol::UpgradeTowerPacket* packet) {
tower->upgrade(packet->getTowerLevel().getLevel(), packet->getTowerLevel().getPath());
}
void WorldClient::OnArrowShot(game::MobPtr target, game::Tower* tower) {
World::OnArrowShot(target, tower);
void WorldClient::HandlePacket(const protocol::WorldAddTowerPacket* packet) {
game::TowerPtr newTower = placeTowerAt(packet->getTowerID(), packet->getTowerType(), packet->getTowerX(), packet->getTowerY(), packet->getBuilder());
getWorldNotifier().notifyListeners(&WorldListener::OnTowerAdd, newTower);
}
void WorldClient::HandlePacket(const protocol::RemoveTowerPacket* packet) {
game::TowerPtr tower = removeTower(packet->getTowerID());
if (tower != nullptr) {
getWorldNotifier().notifyListeners(&WorldListener::OnTowerRemove, tower);
}
}
void WorldClient::HandlePacket(const protocol::UpdateMobStatesPacket* packet) {
for (auto mobState : packet->getMobStates()) {
game::MobID mobId = mobState.getMobId();
auto it = std::find_if(getMobList().begin(), getMobList().end(), [mobId](game::MobPtr mob) { return mob->getMobID() == mobId; });
if (it != getMobList().end()) {
game::MobPtr& mob = *it;
mob->setCenter(mobState.getMobPosition());
mob->setDirection(mobState.getMobDirection());
mob->setHealth(mobState.getMobLife());
}
}
}
void WorldClient::HandlePacket(const protocol::UpdateCastleLifePacket* packet) {
getTeam(packet->getTeamColor()).getCastle().setLife(packet->getCastleLife());
}
} // namespace client

View File

@@ -37,10 +37,8 @@ void Lobby::tick() {
return;
if (utils::getTime() - m_StartTimerTime >= LobbyWaitingTime) {
protocol::UpdateGameStatePacket packet(game::GameState::Game);
m_Server->broadcastPacket(&packet);
m_Server->getGame().notifyListeners(&game::GameListener::OnGameBegin);
m_GameStarted = true;
m_Server->lauchGame();
return;
}

View File

@@ -9,8 +9,9 @@ Server::Server(const std::string& worldFilePath) : m_ServerRunning(false) {
m_Game.getWorld()->loadMapFromFile(worldFilePath);
}
void Server::lauchGame() {
m_Game.startGame();
Server::~Server() {
if (m_Thread.joinable())
m_Thread.join();
}
void Server::startThread() {
@@ -29,13 +30,16 @@ void Server::startThread() {
}
}
clean();
});
}
void Server::close() {
stopThread();
}
void Server::stopThread() {
m_ServerRunning = false;
if (m_Thread.joinable())
m_Thread.join();
}
bool Server::start(std::uint16_t port) {
@@ -54,19 +58,24 @@ bool Server::start(std::uint16_t port) {
return true;
}
void Server::stop() {
if (!m_ServerRunning)
return;
stopThread();
protocol::DisconnectPacket packet("Server closed");
broadcastPacket(&packet);
void Server::clean() {
m_Listener.close();
m_Listener.destroy();
m_Connections.clear();
getPlayers().clear();
std::cout << "Server successfully stopped !\n";
}
void Server::stop() {
if (!m_ServerRunning)
return;
protocol::DisconnectPacket packet("Server closed");
broadcastPacket(&packet);
stopThread();
}
void Server::tick(std::uint64_t delta) {

View File

@@ -41,6 +41,7 @@ void ServerConnexion::registerHandlers() {
GetDispatcher()->RegisterHandler(protocol::PacketType::PlaceTower, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::SendMobs, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::UpgradeTower, this);
GetDispatcher()->RegisterHandler(protocol::PacketType::RemoveTower, this);
}
bool ServerConnexion::updateSocket() {
@@ -159,6 +160,8 @@ void ServerConnexion::HandlePacket(const protocol::PlaceTowerPacket* packet) {
game::TowerPtr tower = world->placeTowerAt(towerType, packet->getTowerX(), packet->getTowerY(), m_ID);
world->getWorldNotifier().notifyListeners(&game::WorldListener::OnTowerAdd, tower);
protocol::WorldAddTowerPacket addTowerPacket(tower->getID(), packet->getTowerX(), packet->getTowerY(), packet->getTowerType(), m_ID);
m_Server->broadcastPacket(&addTowerPacket);
}
@@ -179,6 +182,12 @@ void ServerConnexion::HandlePacket(const protocol::UpgradeTowerPacket* packet) {
m_Server->broadcastPacket(packet);
}
void ServerConnexion::HandlePacket(const protocol::RemoveTowerPacket* packet) {
//TODO: verify the packet
m_Server->broadcastPacket(packet);
}
ServerConnexion::~ServerConnexion() {
if (GetDispatcher() != nullptr)
GetDispatcher()->UnregisterHandler(this);

View File

@@ -4,14 +4,22 @@
namespace td {
namespace server {
ServerGame::ServerGame(server::Server* server) : game::Game(&m_ServerWorld), m_Server(server), m_ServerWorld(server, this) {
ServerGame::ServerGame(server::Server* server) : game::Game(&m_ServerWorld), m_Server(server), m_ServerWorld(server, this),
m_GoldMineTimer{ 1000, std::bind(&ServerGame::updateGoldMines, this) },
m_MobStatesTimer{ 5000, std::bind(&ServerGame::updateMobStates, this) },
m_EndGameCooldown{ 1000 * 10 } {
bindListener(this);
}
void ServerGame::tick(std::uint64_t delta) {
if (m_GameState == game::GameState::Game) {
Game::tick(delta);
m_MobStatesTimer.update(delta);
updatePlayerStats();
} else if (m_GameState == game::GameState::EndGame) {
if (m_EndGameCooldown.update(delta)) {
notifyListeners(&game::GameListener::OnGameClose);
}
}
}
@@ -51,6 +59,14 @@ void ServerGame::updateGoldMines() {
}
}
void ServerGame::updateMobStates() {
protocol::UpdateMobStatesPacket packet;
for (auto mob : m_World->getMobList()) {
packet.addMobState({ mob->getMobID(), mob->getCenter(), mob->getHealth(), mob->getDirection() });
}
m_Server->broadcastPacket(&packet);
}
void ServerGame::balanceTeams() {
for (auto& playerInfo : Game::m_Players) {
game::Player& player = playerInfo.second;
@@ -70,5 +86,31 @@ void ServerGame::balanceTeams() {
}
}
void ServerGame::OnGameStateUpdate(game::GameState newState) {
setGameState(newState);
protocol::UpdateGameStatePacket packet(newState);
m_Server->broadcastPacket(&packet);
}
void ServerGame::OnGameBegin() {
notifyListeners(&game::GameListener::OnGameStateUpdate, game::GameState::Game);
startGame();
}
void ServerGame::OnGameEnd() {
notifyListeners(&game::GameListener::OnGameStateUpdate, game::GameState::EndGame);
m_EndGameCooldown.applyCooldown();
}
void ServerGame::OnGameClose() {
notifyListeners(&game::GameListener::OnGameStateUpdate, game::GameState::Closed);
// Disconnect clients
protocol::DisconnectPacket packet("Game finished");
m_Server->broadcastPacket(&packet);
// Closing server
m_Server->close();
}
} // namespace game
} // namespace td

View File

@@ -22,17 +22,23 @@ void ServerWorld::spawnMobs(game::MobType type, std::uint8_t level, game::Player
enemyMobSpawn = &getTeam(game::TeamColor::Red).getSpawn();
}
std::int32_t spawnCenterX = enemyMobSpawn->getCenterX();
std::int32_t spawnCenterY = enemyMobSpawn->getCenterY();
float spawnWidth = enemyMobSpawn->getWidth();
float spawnHeight = enemyMobSpawn->getHeight();
std::int32_t minSpawnY = spawnCenterY - 2;
std::int32_t maxSpawnY = spawnCenterY + 2;
float spawnCenterX = enemyMobSpawn->getCenterX();
float spawnCenterY = enemyMobSpawn->getCenterY();
std::int32_t minSpawnX = spawnCenterX - 2;
std::int32_t maxSpawnX = spawnCenterX + 2;
auto mobStats = getMobStats(type, level);
auto mobSize = mobStats->getSize();
float mobX = utils::getRandomReal<float>(minSpawnX, maxSpawnX);
float mobY = utils::getRandomReal<float>(minSpawnY, maxSpawnY);
float minSpawnX = spawnCenterX - spawnWidth / 2.0f + mobSize.x / 2.0f;
float maxSpawnX = spawnCenterX + spawnWidth / 2.0f - mobSize.x / 2.0f;
float minSpawnY = spawnCenterY - spawnHeight / 2.0f + mobSize.y / 2.0f;
float maxSpawnY = spawnCenterY + spawnHeight / 2.0f - mobSize.y / 2.0f;
float mobX = utils::getRandomReal<float>(minSpawnX + MobSpawnBorder, maxSpawnX - MobSpawnBorder);
float mobY = utils::getRandomReal<float>(minSpawnY + MobSpawnBorder, maxSpawnY - MobSpawnBorder);
spawnMobAt(m_CurrentMobID, type, level, sender, mobX, mobY, enemyMobSpawn->getDirection());
@@ -49,8 +55,23 @@ game::TowerPtr ServerWorld::placeTowerAt(game::TowerType type, std::int32_t x, s
return tower;
}
void ServerWorld::OnArrowShot(game::MobPtr target, game::Tower* shooter) {
World::OnArrowShot(target, shooter);
void ServerWorld::OnMobDie(game::Mob* mob) {
if (mob->OnDeath(this)) { // check if the mob is actually dead (slimes ...)
//reward players
game::Player* sender = m_Game->getPlayerById(mob->getSender());
sender->addExp(mob->getStats()->getExpReward());
game::Player* killer = m_Game->getPlayerById(mob->getLastDamageTower()->getBuilder());
killer->addGold(mob->getStats()->getMoneyCost());
}
}
void ServerWorld::OnMobCastleDamage(game::Mob* damager, game::TeamCastle* enemyCastle, float damage) {
// calling base class event
World::OnMobCastleDamage(damager, enemyCastle, damage);
protocol::UpdateCastleLifePacket packet(enemyCastle->getLife(), enemyCastle->getTeam()->getColor());
m_Server->broadcastPacket(&packet);
}
} // namespace server

View File

@@ -39,6 +39,14 @@ bool Rectangle::collidesWith(const Circle& circle) const {
return circle.collidesWith(*this);
}
float Rectangle::distance(const Circle& circle) const {
return circle.distance(*this);
}
float Rectangle::distanceSquared(const Circle& circle) const {
return circle.distanceSquared(*this);
}
@@ -48,21 +56,29 @@ bool Circle::collidesWith(const Point& point) const {
}
bool Circle::collidesWith(const Rectangle& rect) const {
float closestX = std::clamp(m_Center.getX(), rect.getTopLeft().getX(), rect.getBottomRight().getX());
float closestY = std::clamp(m_Center.getY(), rect.getTopLeft().getY(), rect.getBottomRight().getY());
float distanceSquared_ = distanceSquared(rect);
float distanceX = m_Center.getX() - closestX;
float distanceY = m_Center.getY() - closestY;
float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY);
return distanceSquared < m_Radius * m_Radius;
return distanceSquared_ < m_Radius* m_Radius;
}
bool Circle::collidesWith(const Circle& circle) const {
return m_Radius + circle.getRadius() > m_Center.distanceSquared(circle.getCenter());
}
float Circle::distance(const Rectangle& rect) const {
return std::sqrt(distanceSquared(rect));
}
float Circle::distanceSquared(const Rectangle& rect) const {
float closestX = std::clamp(m_Center.getX(), rect.getTopLeft().getX(), rect.getBottomRight().getX());
float closestY = std::clamp(m_Center.getY(), rect.getTopLeft().getY(), rect.getBottomRight().getY());
float distanceX = m_Center.getX() - closestX;
float distanceY = m_Center.getY() - closestY;
return (distanceX * distanceX) + (distanceY * distanceY);
}
} // namespace shape
} // namespace utils
} // namespace td

View File

@@ -28,9 +28,11 @@ static std::map<PacketType, PacketCreator> packets = {
{PacketType::SpawnMob, []() -> PacketPtr {return std::make_unique<SpawnMobPacket>(); } },
{PacketType::PlaceTower, []() -> PacketPtr {return std::make_unique<PlaceTowerPacket>(); } },
{PacketType::WorldAddTower, []() -> PacketPtr {return std::make_unique<WorldAddTowerPacket>(); } },
{PacketType::WorldRemoveTower, []() -> PacketPtr {return std::make_unique<WorldRemoveTowerPacket>(); } },
{PacketType::RemoveTower, []() -> PacketPtr {return std::make_unique<RemoveTowerPacket>(); } },
{PacketType::SendMobs, []() -> PacketPtr {return std::make_unique<SendMobsPacket>(); } },
{PacketType::UpgradeTower, []() -> PacketPtr {return std::make_unique<UpgradeTowerPacket>(); } },
{PacketType::UpdateCastleLife, []() -> PacketPtr {return std::make_unique<UpdateCastleLifePacket>(); } },
{PacketType::UpdateMobStates, []() -> PacketPtr {return std::make_unique<UpdateMobStatesPacket>(); } },
};
PacketPtr createPacket(PacketType type, DataBuffer& buffer) {

View File

@@ -91,14 +91,16 @@ DataBuffer WorldBeginDataPacket::SerializeCustom() const {
memcpy((std::uint8_t*)data.data() + bufferSize, decoTilePalette.data(), decoTilePalette.size() * sizeof(game::Color));
data << m_Header.m_Background;
const game::Spawn& redSpawn = m_Header.m_RedSpawn, blueSpawn = m_Header.m_BlueSpawn;
const game::TeamCastle& redCastle = m_Header.m_RedCastle, blueCastle = m_Header.m_BlueCastle;
data << redSpawn << redCastle;
data << blueSpawn << blueCastle;
data << redSpawn << static_cast<utils::shape::Rectangle>(redCastle);
data << blueSpawn << static_cast<utils::shape::Rectangle>(blueCastle);
// tile palette
data << m_Header.m_TilePalette.size();
data << static_cast<std::uint64_t>(m_Header.m_TilePalette.size());
for (game::TilePtr tile : m_Header.m_TilePalette) {
data << tile;
@@ -123,14 +125,16 @@ DataBuffer WorldBeginDataPacket::Serialize() const {
memcpy((std::uint8_t*)data.data() + bufferSize, decoTilePalette.data(), decoTilePalette.size() * sizeof(game::Color));
data << m_Header.m_World->getBackgroundColor();
const game::Spawn& redSpawn = m_Header.m_World->getRedTeam().getSpawn(), blueSpawn = m_Header.m_World->getBlueTeam().getSpawn();
const game::TeamCastle& redCastle = m_Header.m_World->getRedTeam().getCastle(), blueCastle = m_Header.m_World->getBlueTeam().getCastle();
data << redSpawn << redCastle;
data << blueSpawn << blueCastle;
data << redSpawn << static_cast<utils::shape::Rectangle>(redCastle);
data << blueSpawn << static_cast<utils::shape::Rectangle>(blueCastle);
// tile palette
data << m_Header.m_World->getTilePalette().size();
data << static_cast<std::uint64_t>(m_Header.m_World->getTilePalette().size());
for (game::TilePtr tile : m_Header.m_World->getTilePalette()) {
data << tile;
@@ -155,8 +159,15 @@ void WorldBeginDataPacket::Deserialize(DataBuffer& data) {
data.SetReadOffset(data.GetReadOffset() + decoPalletteSizeByte);
data >> m_Header.m_RedSpawn >> m_Header.m_RedCastle;
data >> m_Header.m_BlueSpawn >> m_Header.m_BlueCastle;
data >> m_Header.m_Background;
utils::shape::Rectangle redCastle, blueCastle;
data >> m_Header.m_RedSpawn >> redCastle;
data >> m_Header.m_BlueSpawn >> blueCastle;
m_Header.m_RedCastle.setShape(redCastle);
m_Header.m_BlueCastle.setShape(blueCastle);
std::uint64_t tilePaletteSize;
data >> tilePaletteSize;
@@ -485,13 +496,13 @@ void WorldAddTowerPacket::Deserialize(DataBuffer& data) {
data >> m_TowerID >> m_TowerX >> m_TowerY >> m_TowerType >> m_Builder;
}
DataBuffer WorldRemoveTowerPacket::Serialize() const {
DataBuffer RemoveTowerPacket::Serialize() const {
DataBuffer data;
data << getID() << m_TowerID;
return data;
}
void WorldRemoveTowerPacket::Deserialize(DataBuffer& data) {
void RemoveTowerPacket::Deserialize(DataBuffer& data) {
data >> m_TowerID;
}
@@ -524,6 +535,34 @@ void UpgradeTowerPacket::Deserialize(DataBuffer& data) {
data >> m_TowerID >> m_TowerLevel;
}
DataBuffer UpdateCastleLifePacket::Serialize() const {
DataBuffer data;
data << getID() << m_CastleLife << m_Team;
return data;
}
void UpdateCastleLifePacket::Deserialize(DataBuffer& data) {
data >> m_CastleLife >> m_Team;
}
DataBuffer UpdateMobStatesPacket::Serialize() const {
DataBuffer data;
data << getID() << static_cast<std::uint64_t>(m_MobStates.size());
for (auto mobState : m_MobStates) {
data << mobState;
}
return data;
}
void UpdateMobStatesPacket::Deserialize(DataBuffer& data) {
std::uint64_t mobCount;
data >> mobCount;
m_MobStates.resize(mobCount);
for (std::uint64_t mobIndex = 0; mobIndex < mobCount; mobIndex++) {
data >> m_MobStates[mobIndex];
}
}
REGISTER_DISPATCH_CLASS(PlayerLoginPacket);
REGISTER_DISPATCH_CLASS(WorldBeginDataPacket);
REGISTER_DISPATCH_CLASS(WorldDataPacket);
@@ -543,9 +582,11 @@ REGISTER_DISPATCH_CLASS(ServerTpsPacket);
REGISTER_DISPATCH_CLASS(SpawnMobPacket);
REGISTER_DISPATCH_CLASS(PlaceTowerPacket);
REGISTER_DISPATCH_CLASS(WorldAddTowerPacket);
REGISTER_DISPATCH_CLASS(WorldRemoveTowerPacket);
REGISTER_DISPATCH_CLASS(RemoveTowerPacket);
REGISTER_DISPATCH_CLASS(SendMobsPacket);
REGISTER_DISPATCH_CLASS(UpgradeTowerPacket);
REGISTER_DISPATCH_CLASS(UpdateCastleLifePacket);
REGISTER_DISPATCH_CLASS(UpdateMobStatesPacket);
} // namespace protocol
} // namespace td

View File

@@ -17,7 +17,7 @@
namespace td {
namespace render {
Renderer::Renderer() {
Renderer::Renderer() : m_BackgroundColor(0, 0, 0) {
}
@@ -86,7 +86,7 @@ void Renderer::updateIsometricFade() {
void Renderer::prepare() {
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0, 0, 0, 0);
glClearColor(m_BackgroundColor.r, m_BackgroundColor.g, m_BackgroundColor.b, 0);
updateIsometricFade();
}

View File

@@ -5,46 +5,43 @@ namespace td {
namespace render {
void VertexCache::addData(std::uint64_t index, std::vector<float> positions, std::vector<float> colors) {
ElementsIndex positionsIndexes;
positionsIndexes.first = m_Positions.end();
m_Positions.insert(m_Positions.end(), positions.begin(), positions.end());
positionsIndexes.second = m_Positions.end() - 1;
ElementsIndex colorsIndexes;
colorsIndexes.first = m_Colors.end();
m_Colors.insert(m_Colors.end(), colors.begin(), colors.end());
colorsIndexes.second = m_Colors.end() - 1;
m_Indexes.insert({ index, {positionsIndexes, colorsIndexes} });
m_Indexes.insert({ index, {positions, colors} });
m_VertexCount += colors.size(); // one color per vertex
}
void VertexCache::removeData(std::uint64_t index) {
auto it = m_Indexes.find(index);
if (it != m_Indexes.end()) {
DataIndex indexes = it->second;
ElementsIndex positionsIndexes = indexes.first;
ElementsIndex colorsIndexes = indexes.second;
m_Positions.erase(positionsIndexes.first, positionsIndexes.second);
m_Colors.erase(colorsIndexes.first, colorsIndexes.second);
m_Indexes.erase(it);
m_VertexCount -= it->second.color.size(); // one color per vertex
}
}
void VertexCache::clear() {
m_Positions.clear();
m_Colors.clear();
m_Indexes.clear();
m_VertexCount = 0;
}
void VertexCache::updateVertexArray() {
m_VertexArray = std::make_unique<GL::VertexArray>(m_Colors.size()); // one color per vertex
m_VertexArray = std::make_unique<GL::VertexArray>(m_VertexCount); // one color per vertex
GL::VertexBuffer positionsBuffer(m_Positions, 2);
Vector positions;
positions.reserve(m_VertexCount * 2);
Vector colors;
colors.reserve(m_VertexCount);
for (auto it = m_Indexes.begin(); it != m_Indexes.end(); it++) {
const DataIndex& data = it->second;
positions.insert(positions.end(), data.position.begin(), data.position.end());
colors.insert(colors.end(), data.color.begin(), data.color.end());
}
GL::VertexBuffer positionsBuffer(positions, 2);
positionsBuffer.addVertexAttribPointer(0, 2, 0);
GL::VertexBuffer colorsBuffer(m_Colors, 1);
GL::VertexBuffer colorsBuffer(colors, 1);
colorsBuffer.addVertexAttribPointer(1, 1, 0);
m_VertexArray->bind();

View File

@@ -37,6 +37,8 @@ WorldRenderer::WorldRenderer(game::World* world, client::ClientGame* client) : m
m_Renderer->setCamMovement({});
m_TowerPlacePopup = std::make_unique<gui::TowerPlacePopup>(m_Client->getClient());
m_MobTooltip = std::make_unique<gui::MobTooltip>(m_Client->getClient());
m_CastleTooltip = std::make_unique<gui::CastleTooltip>(m_Client->getClient());
m_Client->getWorldClient().getWorldNotifier().bindListener(this);
}
void WorldRenderer::updateCursorPos() {
@@ -64,6 +66,18 @@ void WorldRenderer::update() {
m_PopupOpened = false;
}
}
if (ImGui::IsMouseDoubleClicked(1)) removeTower();
}
void WorldRenderer::removeTower() {
glm::vec2 cursorPos = getCursorWorldPos();
game::TowerPtr clickedTower = m_World->getTower(cursorPos);
if (clickedTower != nullptr) {
m_Client->getClient()->removeTower(clickedTower->getID());
}
}
void WorldRenderer::renderWorld() const {
@@ -87,6 +101,8 @@ void WorldRenderer::renderTowers() const {
void WorldRenderer::renderTileSelect() const {
if (ImGui::IsAnyItemHovered()) return;
if(m_MobTooltip->isShown() || m_CastleTooltip->isShown()) return;
Renderer::Model tileSelectModel;
tileSelectModel.vao = m_SelectTileVao.get();
tileSelectModel.positon = { (int)m_CursorPos.x, (int)m_CursorPos.y };
@@ -106,7 +122,7 @@ void WorldRenderer::render() {
renderMobs();
renderTowers();
renderTileSelect();
renderMobTooltip();
renderTooltips();
renderPopups();
detectClick();
}
@@ -115,6 +131,11 @@ WorldRenderer::~WorldRenderer() {
}
void WorldRenderer::renderTooltips() const {
renderMobTooltip();
renderCastleTooltip();
}
void WorldRenderer::moveCam(float relativeX, float relativeY, float aspectRatio) {
if (m_WorldVao == nullptr)
return;
@@ -232,10 +253,17 @@ void WorldRenderer::renderMobTooltip() const {
m_MobTooltip->render();
}
void WorldRenderer::renderCastleTooltip() const {
if (ImGui::IsAnyItemHovered()) return;
detectCastleHovering();
m_CastleTooltip->render();
}
void WorldRenderer::detectMobHovering() const {
glm::vec2 cursorWorldPos = getCursorWorldPos();
for (game::MobPtr mob : m_World->getMobList()) {
if(mob->collidesWith({cursorWorldPos.x, cursorWorldPos.y})){
if (mob->collidesWith({ cursorWorldPos.x, cursorWorldPos.y })) {
m_MobTooltip->setMob(mob.get());
return;
}
@@ -243,13 +271,24 @@ void WorldRenderer::detectMobHovering() const {
m_MobTooltip->setMob(nullptr);
}
void WorldRenderer::addTower(game::TowerPtr tower) {
const WorldLoader::RenderData& renderData = WorldLoader::loadTowerModel(tower);
void WorldRenderer::detectCastleHovering() const {
glm::vec2 cursorWorldPos = getCursorWorldPos();
for (const game::Team& team : m_World->getTeams()) {
if (team.getCastle().collidesWith({ cursorWorldPos.x, cursorWorldPos.y })) {
m_CastleTooltip->setCastle(&team.getCastle());
return;
}
}
m_CastleTooltip->setCastle(nullptr);
}
void WorldRenderer::OnTowerAdd(game::TowerPtr tower) {
WorldLoader::RenderData renderData = WorldLoader::loadTowerModel(tower);
m_TowersCache.addData(tower->getID(), renderData.positions, renderData.colors);
m_TowersCache.updateVertexArray();
}
void WorldRenderer::removeTower(game::TowerPtr tower) {
void WorldRenderer::OnTowerRemove(game::TowerPtr tower) {
m_TowersCache.removeData(tower->getID());
m_TowersCache.updateVertexArray();
}

View File

@@ -0,0 +1,27 @@
#include "render/gui/CastleTooltip.h"
#include "render/gui/imgui/imgui.h"
#include "render/WorldRenderer.h"
#include "game/client/Client.h"
namespace td {
namespace gui {
CastleTooltip::CastleTooltip(client::Client* client) : GuiWidget(client) {
}
void CastleTooltip::render() {
if (m_Castle == nullptr) return;
ImGui::BeginTooltip();
ImGui::PushStyleColor(ImGuiCol_Text, render::WorldRenderer::getImGuiTeamColor(m_Castle->getTeam()->getColor()));
ImGui::Text("Castle : ");
ImGui::PopStyleColor();
ImGui::Text("\tCastle HP : %i/%i", static_cast<int>(m_Castle->getLife()), game::TeamCastle::CastleMaxLife);
ImGui::EndTooltip();
}
} // namespace gui
} // namespace td

View File

@@ -14,6 +14,8 @@ GameMenu::GameMenu(client::Client* client) : GuiWidget(client), m_SummonMenu(std
}
void GameMenu::render() {
if(!m_Client->isConnected()) return;
if (getClient()->getGame().getGameState() == td::game::GameState::Lobby) {
ImGui::Begin("Lobby");

View File

@@ -18,6 +18,12 @@ MainMenu::~MainMenu() {
}
void MainMenu::render() {
if (m_Server != nullptr && !m_Server->isRunning()) {
m_Server.reset(0); // destroying server if it stoped
}
if(m_Client->isConnected()) return;
ImGui::Begin("Main Menu");
if (ImGui::Button("Rejoindre une partie##join")) {
ImGui::OpenPopup("Rejoindre une partie##join_popup");

View File

@@ -17,12 +17,14 @@ MobTooltip::MobTooltip(client::Client* client) : GuiWidget(client) {
void MobTooltip::render() {
if (m_Mob == nullptr) return;
const game::Player& sender = getClient()->getGame().getPlayerById(m_Mob->getSender());
// TODO: add sender null check
const game::Player* sender = getClient()->getGame().getPlayerById(m_Mob->getSender());
ImGui::BeginTooltip();
ImGui::Text("Sender :");
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Text, render::WorldRenderer::getImGuiTeamColor(sender.getTeamColor()));
ImGui::Text("%s", sender.getName().c_str());
ImGui::PushStyleColor(ImGuiCol_Text, render::WorldRenderer::getImGuiTeamColor(sender->getTeamColor()));
ImGui::Text("%s", sender->getName().c_str());
ImGui::PopStyleColor();
ImGui::Text("Mob HP : %.1f/%i", m_Mob->getHealth(), m_Mob->getStats()->getMaxLife());
ImGui::Text("Mob Type : %s", game::MobFactory::getMobName(m_Mob->getType()).c_str());

View File

@@ -22,10 +22,10 @@ namespace td {
namespace render {
void TowerGui::initWidgets() {
m_MainMenu = std::make_unique<td::gui::MainMenu>(m_Client.get());
m_GameMenu = std::make_unique<td::gui::GameMenu>(m_Client.get());
m_FrameMenu = std::make_unique<td::gui::FrameMenu>(m_Client.get());
m_UpdateMenu = std::make_unique<td::gui::UpdateMenu>(m_Client.get());
m_GuiManager.addWidget(std::make_unique<td::gui::MainMenu>(m_Client.get()));
m_GuiManager.addWidget(std::make_unique<td::gui::GameMenu>(m_Client.get()));
m_GuiManager.addWidget(std::make_unique<td::gui::FrameMenu>(m_Client.get()));
m_GuiManager.addWidget(std::make_unique<td::gui::UpdateMenu>(m_Client.get()));
}
TowerGui::TowerGui(SDL_Window* sdl_window, SDL_GLContext glContext, td::render::Renderer* renderer) : m_Window(sdl_window),
@@ -69,13 +69,8 @@ void TowerGui::render() {
beginFrame();
m_Client->render();
if (m_Client->isConnected())
m_GameMenu->render();
else
m_MainMenu->render();
m_FrameMenu->render();
m_UpdateMenu->render();
m_GuiManager.renderWidgets();
endFrame();
}

View File

@@ -1,10 +1,3 @@
/*
* EntityShader.cpp
*
* Created on: 4 nov. 2020
* Author: simon
*/
#include "render/shaders/EntityShader.h"
#ifdef __ANDROID__

View File

@@ -1,4 +1,5 @@
#include "misc/Updater.h"
#include "updater/Updater.h"
#include "misc/Platform.h"
#include "misc/DataBuffer.h"

View File

@@ -35,6 +35,12 @@ target("TowerDefense")
end
else
if is_os("linux") then
add_links("dw")
end
if is_os("windows") then
add_links("dbghelp", "psapi", "kernel32", "msvcr90")
end
add_cxflags("-pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused")
end