12 Commits

24 changed files with 430 additions and 234 deletions

View File

@@ -19,7 +19,7 @@ typedef std::map<std::uint8_t, Player> PlayerList;
class Game {
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 +46,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,7 +73,7 @@ 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);
@@ -86,22 +90,24 @@ private:
Direction m_Direction;
std::vector<EffectDuration> m_Effects;
const Tower* m_LastDamage; // the last tower that damaged the mob
bool m_HasReachedCastle; // if true, no need to walk anymore
utils::Timer m_EffectFireTimer;
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_HasReachedCastle(false), 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; }
@@ -114,16 +120,16 @@ public:
bool isDead() const { return m_Health <= 0; }
bool isAlive() const { return m_Health > 0; }
const Tower* getLastDamageTower() { return m_LastDamage; }
bool hasReachedEnemyCastle() { return m_HasReachedCastle; }
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() { m_HasReachedCastle = true; } // used when mob is in front of the castle
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
@@ -137,7 +143,13 @@ protected:
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);
};
@@ -218,6 +230,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

@@ -10,8 +10,6 @@
#include "Mobs.h"
#include "Team.h"
#include "misc/ObjectNotifier.h"
namespace td {
namespace game {
typedef std::pair<std::int16_t, std::int16_t> ChunkCoord;
@@ -127,15 +125,11 @@ public:
virtual void OnArrowShot(MobPtr target, bool fire, Tower* shooter) {}
virtual void OnExplosion(utils::shape::Circle explosion, float centerDamage, Tower* shooter) {}
virtual void OnMobDamage(MobPtr target, float damage, Tower* damager) {}
virtual void OnMobDead(MobPtr mob) {}
};
typedef utils::ObjectNotifier<WorldListener> WorldNotifier;
class World : public WorldNotifier, public WorldListener {
class World : public WorldListener, public MobListener {
protected:
TowerTileColorPalette m_TowerPlacePalette;
Color m_WalkablePalette;
@@ -152,6 +146,9 @@ protected:
TowerList m_Towers;
Game* m_Game;
WorldNotifier m_WorldNotifier;
MobNotifier m_MobNotifier;
public:
World(Game* game);
@@ -208,9 +205,16 @@ 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);
const Player* getPlayerById(PlayerID id) const;
WorldNotifier& getWorldNotifier() { return m_WorldNotifier; }
MobNotifier& getMobNotifier() { return m_MobNotifier; }
// WorldListener
virtual void OnArcherTowerShot(MobPtr target, ArcherTower* shooter);
@@ -218,13 +222,13 @@ public:
virtual void OnArrowShot(MobPtr target, bool fire, Tower* shooter);
virtual void OnExplosion(utils::shape::Circle explosion, float centerDamage, Tower* shooter);
virtual void OnMobDamage(MobPtr target, float damage, Tower* source);
// MobListener
virtual void OnMobDamage(Mob* target, float damage, Tower* source);
virtual void OnMobDie(Mob* mob);
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);
void changeMobDirection(MobPtr mob, WalkableTilePtr tile);
bool isMobTouchingCastle(MobPtr mob, const TeamCastle& castle) const;
void tickMobs(std::uint64_t delta);
void cleanDeadMobs();
};

View File

@@ -14,6 +14,8 @@ 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);

View File

@@ -47,6 +47,9 @@ 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; }

View File

@@ -53,7 +53,7 @@ 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; // in millis

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"
@@ -40,6 +41,7 @@ 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();
@@ -69,8 +71,11 @@ 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,27 @@
#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; }
};
} // namespace gui
} // namespace td

View File

@@ -2,9 +2,7 @@
#include "misc/DataBuffer.h"
#include <ctime>
#define TD_VERSION "alpha-0.0.4"
#define TD_VERSION "alpha-0.1.0"
namespace td {
namespace utils {

View File

@@ -20,8 +20,10 @@ 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();

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)) {

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

@@ -213,7 +213,7 @@ 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->notifyListeners(&WorldListener::OnArcherTowerShot, mob, this);
world->getWorldNotifier().notifyListeners(&WorldListener::OnArcherTowerShot, mob, this);
m_Timer.applyCooldown();
arrowsShot++;
if (arrowsShot >= arrows)
@@ -229,8 +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
if(damage > 0)
world->notifyListeners(&WorldListener::OnMobDamage, mob, damage, this);
if (damage > 0)
world->getMobNotifier().notifyListeners(&MobListener::OnMobDamage, mob.get(), damage, this);
m_Timer.applyCooldown();
}
}
@@ -253,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) {
world->notifyListeners(&WorldListener::OnMobDamage, mob, 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) {
bindListener(this);
getWorldNotifier().bindListener(this);
getMobNotifier().bindListener(this);
}
TilePtr World::getTile(std::int32_t x, std::int32_t y) const {
@@ -43,7 +44,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 +105,6 @@ bool World::saveMap(const std::string& fileName) const {
}
void World::tick(std::uint64_t delta) {
moveMobs(delta);
tickMobs(delta);
for (TowerPtr tower : m_Towers) {
tower->tick(delta, this);
@@ -114,6 +117,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) {
@@ -135,161 +139,7 @@ TowerPtr World::removeTower(TowerID towerId) {
void World::tickMobs(std::uint64_t delta) {
for (MobPtr mob : m_Mobs) {
mob->tick(delta);
}
}
void World::changeMobDirection(MobPtr mob, WalkableTilePtr tile) {
if (mob->getDirection() == tile->direction) return;
float tileX = static_cast<float>(static_cast<std::int32_t>(mob->getCenterX()));
float tileY = static_cast<float>(static_cast<std::int32_t>(mob->getCenterY()));
switch (mob->getDirection()) {
case Direction::PositiveY: {
if (tile->direction == Direction::NegativeX) {
if (mob->getTileY() > mob->getTileX()) {
mob->setCenterY(tileY + mob->getTileX());
mob->setDirection(tile->direction);
}
} else { // tile->direction = Direction::PositiveX
if (mob->getTileY() > 1 - mob->getTileX()) {
mob->setCenterY(tileY + (1 - mob->getTileX()));
mob->setDirection(tile->direction);
}
}
return;
}
case Direction::NegativeY: {
if (tile->direction == Direction::PositiveX) {
if (mob->getTileY() < mob->getTileX()) {
mob->setCenterY(tileY + mob->getTileX());
mob->setDirection(tile->direction);
}
} else { // tile->direction = Direction::NegativeX
if (mob->getTileY() < 1 - mob->getTileX()) {
mob->setCenterY(tileY + (1 - mob->getTileX()));
mob->setDirection(tile->direction);
}
}
return;
}
case Direction::PositiveX: {
if (tile->direction == Direction::NegativeY) {
if (mob->getTileX() > mob->getTileY()) {
mob->setCenterX(tileX + mob->getTileY());
mob->setDirection(tile->direction);
}
} else { // tile->direction = Direction::PositiveY
if (mob->getTileX() > 1 - mob->getTileY()) {
mob->setCenterX(tileX + (1 - mob->getTileY()));
mob->setDirection(tile->direction);
}
}
return;
}
case Direction::NegativeX: {
if (tile->direction == Direction::PositiveY) {
if (mob->getTileX() < mob->getTileY()) {
mob->setCenterX(tileX + mob->getTileY());
mob->setDirection(tile->direction);
}
} else { // tile->direction = Direction::NegativeY
if (mob->getTileX() < 1 - mob->getTileY()) {
mob->setCenterX(tileX + (1 - mob->getTileY()));
mob->setDirection(tile->direction);
}
}
return;
}
}
}
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) {
WalkableTilePtr walkTile = std::static_pointer_cast<WalkableTile>(tile);
changeMobDirection(mob, walkTile);
}
if (mob->hasReachedEnemyCastle()) continue;
moveMob(mob, delta);
TeamColor mobTeam = m_Game->getPlayerById(mob->getSender()).getTeamColor();
const TeamCastle* enemyCastle = nullptr;
if (mobTeam == TeamColor::Red) {
enemyCastle = &getBlueTeam().getCastle();
} else if (mobTeam == TeamColor::Blue) {
enemyCastle = &getRedTeam().getCastle();
}
if (isMobTouchingCastle(mob, *enemyCastle)) {
moveBackMob(mob, *enemyCastle);
mob->setMobReachedCastle();
}
}
}
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);
}
}
@@ -368,20 +218,23 @@ bool World::CanPlaceBigTower(const glm::vec2& worldPos, PlayerID playerID) const
return false;
}
void World::OnMobDie(Mob* mob) {
if (mob->OnDeath(this)) { // check if the mob is actually dead (slimes ...)
//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());
}
}
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);
}
}
}
@@ -413,14 +266,14 @@ void World::OnArcherTowerShot(MobPtr target, ArcherTower* shooter) {
bool fireArrows = shooter->getLevel().getPath() == TowerPath::Bottom;
bool explosiveArrows = shooter->getLevel().getLevel() == 4 && fireArrows;
notifyListeners(&WorldListener::OnArrowShot, target, fireArrows, shooter);
getWorldNotifier().notifyListeners(&WorldListener::OnArrowShot, target, fireArrows, shooter);
if (explosiveArrows) {
notifyListeners(&WorldListener::OnExplosion, utils::shape::Circle{ target->getCenterX(), target->getCenterY(), ArcherTower::ExplosionRadius }, 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) {
notifyListeners(&WorldListener::OnMobDamage, target, shooter->getStats()->getDamage(), shooter);
getMobNotifier().notifyListeners(&MobListener::OnMobDamage, target.get(), shooter->getStats()->getDamage(), shooter);
if (fireArrow) {
target->addEffect(EffectType::Fire, ArcherTower::FireDurationSec, shooter);
}
@@ -431,15 +284,22 @@ void World::OnExplosion(utils::shape::Circle explosion, float centerDamage, Towe
if (mob->isAlive() && mob->collidesWith(explosion)) {
// linear distance damage reduction
float explosionDamage = mob->distance(explosion) / explosion.getRadius() * centerDamage;
notifyListeners(&WorldListener::OnMobDamage, mob, explosionDamage, shooter);
getMobNotifier().notifyListeners(&MobListener::OnMobDamage, mob.get(), explosionDamage, shooter);
}
}
}
void World::OnMobDamage(MobPtr target, float damage, Tower* source) {
void World::OnMobCastleDamage(Mob* damager, TeamCastle* enemyCastle, float damage) {
enemyCastle->damage(damage);
if (enemyCastle->getLife() <= 0) {
// TODO: a team has won
}
}
void World::OnMobDamage(Mob* target, float damage, Tower* source) {
target->damage(damage, source);
if (target->isDead()) {
notifyListeners(&WorldListener::OnMobDead, target);
getMobNotifier().notifyListeners(&MobListener::OnMobDie, target);
}
}
@@ -467,5 +327,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

@@ -37,14 +37,14 @@ void WorldClient::HandlePacket(const protocol::UpgradeTowerPacket* packet) {
void WorldClient::HandlePacket(const protocol::WorldAddTowerPacket* packet) {
game::TowerPtr newTower = placeTowerAt(packet->getTowerID(), packet->getTowerType(), packet->getTowerX(), packet->getTowerY(), packet->getBuilder());
notifyListeners(&WorldListener::OnTowerAdd, newTower);
getWorldNotifier().notifyListeners(&WorldListener::OnTowerAdd, newTower);
}
void WorldClient::HandlePacket(const protocol::RemoveTowerPacket* packet) {
game::TowerPtr tower = removeTower(packet->getTowerID());
if (tower != nullptr) {
notifyListeners(&WorldListener::OnTowerRemove, tower);
getWorldNotifier().notifyListeners(&WorldListener::OnTowerRemove, tower);
}
}

View File

@@ -160,7 +160,7 @@ void ServerConnexion::HandlePacket(const protocol::PlaceTowerPacket* packet) {
game::TowerPtr tower = world->placeTowerAt(towerType, packet->getTowerX(), packet->getTowerY(), m_ID);
world->notifyListeners(&game::WorldListener::OnTowerAdd, tower);
world->getWorldNotifier().notifyListeners(&game::WorldListener::OnTowerAdd, tower);
protocol::WorldAddTowerPacket addTowerPacket(tower->getID(), packet->getTowerX(), packet->getTowerY(), packet->getTowerType(), m_ID);
m_Server->broadcastPacket(&addTowerPacket);

View File

@@ -37,8 +37,8 @@ void ServerWorld::spawnMobs(game::MobType type, std::uint8_t level, game::Player
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, maxSpawnX);
float mobY = utils::getRandomReal<float>(minSpawnY, maxSpawnY);
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());

View File

@@ -94,11 +94,11 @@ DataBuffer WorldBeginDataPacket::SerializeCustom() const {
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;
@@ -126,11 +126,11 @@ DataBuffer WorldBeginDataPacket::Serialize() const {
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 +155,13 @@ 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;
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;

View File

@@ -37,7 +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_Client->getWorld().bindListener(this);
m_CastleTooltip = std::make_unique<gui::CastleTooltip>(m_Client->getClient());
m_Client->getWorld().getWorldNotifier().bindListener(this);
}
void WorldRenderer::updateCursorPos() {
@@ -119,7 +120,7 @@ void WorldRenderer::render() {
renderMobs();
renderTowers();
renderTileSelect();
renderMobTooltip();
renderTooltips();
renderPopups();
detectClick();
}
@@ -128,6 +129,11 @@ WorldRenderer::~WorldRenderer() {
}
void WorldRenderer::renderTooltips() const {
renderMobTooltip();
renderCastleTooltip();
}
void WorldRenderer::moveCam(float relativeX, float relativeY, float aspectRatio) {
if (m_WorldVao == nullptr)
return;
@@ -245,6 +251,13 @@ 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()) {
@@ -256,6 +269,17 @@ void WorldRenderer::detectMobHovering() const {
m_MobTooltip->setMob(nullptr);
}
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);

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

@@ -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

@@ -38,6 +38,9 @@ target("TowerDefense")
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