15 Commits

14 changed files with 225 additions and 36 deletions

View File

@@ -76,7 +76,7 @@ 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:
@@ -86,6 +86,7 @@ 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;
@@ -93,7 +94,8 @@ private:
public:
Mob(MobID id, MobLevel level, PlayerID sender) : m_Sender(sender), m_Level(level),
m_EffectFireTimer(1000), m_EffectPoisonTimer(1000), m_EffectHealTimer(1000) {
m_HasReachedCastle(false), m_EffectFireTimer(1000), m_EffectPoisonTimer(1000),
m_EffectHealTimer(1000) {
}
@@ -112,9 +114,11 @@ 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; }
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
bool isImmuneTo(TowerType type);
@@ -122,6 +126,8 @@ public:
void addEffect(EffectType type, float durationSec, const 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; }

View File

@@ -163,6 +163,8 @@ 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;
virtual TowerType getType() const { return TowerType::Archer; }
virtual void tick(std::uint64_t delta, World* world);
};

View File

@@ -10,6 +10,8 @@
#include "Mobs.h"
#include "Team.h"
#include "misc/ObjectNotifier.h"
namespace td {
namespace game {
typedef std::pair<std::int16_t, std::int16_t> ChunkCoord;
@@ -114,7 +116,23 @@ typedef std::array<Color, 2> SpawnColorPalette;
typedef std::vector<TowerPtr> TowerList;
class World {
class WorldListener {
public:
WorldListener(){}
virtual void OnArcherTowerShot(MobPtr target, ArcherTower* shooter){}
virtual void OnArrowShot(MobPtr target, 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{
protected:
TowerTileColorPalette m_TowerPlacePalette;
Color m_WalkablePalette;
@@ -189,12 +207,19 @@ public:
const TowerList& getTowers() const { return m_Towers; };
TowerPtr getTowerById(TowerID tower);
// Archer Tower
// WorldListener
virtual void OnArcherTowerShot(MobPtr target, ArcherTower* shooter);
virtual void OnArrowShot(MobPtr target, Tower* shooter);
virtual void OnExplosion(utils::shape::Circle explosion, float centerDamage, Tower* shooter);
virtual void OnMobDamage(MobPtr target, float damage, Tower* source);
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

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

@@ -53,6 +53,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 Circle& circle) const;
float distanceSquared(const Circle& circle) const;
};
class Circle {
@@ -78,6 +82,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) {}
@@ -56,7 +56,7 @@ public:
// utililty class to call function at regular period of time with a cooldown (used for towers)
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

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

View File

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

View File

@@ -7,7 +7,7 @@
//============================================================================
#include "window/Display.h"
#include "misc/Updater.h"
#include "updater/Updater.h"
#ifdef __ANDROID__
extern "C"

View File

@@ -235,7 +235,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

@@ -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->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->notifyListeners(&WorldListener::OnMobDamage, mob, 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->notifyListeners(&WorldListener::OnMobDamage, mob, getStats()->getDamage(), this);
} else {
float durationSec;
switch (getLevel().getLevel()) {

View File

@@ -13,7 +13,7 @@ namespace td {
namespace game {
World::World(Game* game) : m_Game(game) {
bindListener(this);
}
TilePtr World::getTile(std::int32_t x, std::int32_t y) const {
@@ -128,20 +128,93 @@ void World::tickMobs(std::uint64_t 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) {
WalkableTile* walkTile = dynamic_cast<WalkableTile*>(tile.get());
mob->setDirection(walkTile->direction);
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;
const TeamCastle* enemyCastle = nullptr;
if (mobTeam == TeamColor::Red) {
enemyCastle = &getBlueTeam().getCastle();
@@ -151,6 +224,7 @@ void World::moveMobs(std::uint64_t delta) {
if (isMobTouchingCastle(mob, *enemyCastle)) {
moveBackMob(mob, *enemyCastle);
mob->setMobReachedCastle();
}
}
}
@@ -324,12 +398,32 @@ TowerPtr World::getTowerById(TowerID towerID) {
return *it;
}
void World::OnArrowShot(MobPtr target, Tower* shooter) {
void World::OnArcherTowerShot(MobPtr target, ArcherTower* shooter) {
bool explosiveArrows = shooter->getLevel().getPath() == TowerPath::Bottom;
notifyListeners(&WorldListener::OnArrowShot, target, shooter);
if (explosiveArrows) {
// aoe damage
} else {
target->damage(shooter->getStats()->getDamage(), shooter);
notifyListeners(&WorldListener::OnExplosion, utils::shape::Circle{ target->getCenterX(), target->getCenterY(), ArcherTower::ExplosionRadius }, shooter->getStats()->getDamage(), shooter);
}
}
void World::OnArrowShot(MobPtr target, Tower* shooter) {
notifyListeners(&WorldListener::OnMobDamage, target, shooter->getStats()->getDamage(), 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;
notifyListeners(&WorldListener::OnMobDamage, mob, explosionDamage, shooter);
}
}
}
void World::OnMobDamage(MobPtr target, float damage, Tower* source) {
target->damage(damage, source);
if (target->isDead()) {
notifyListeners(&WorldListener::OnMobDead, target);
}
}

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

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