#include "game/Mobs.h" #include "game/Player.h" #include "game/World.h" #include #include namespace td { namespace game { bool Mob::IsImmuneTo(TowerType type) { return std::find(GetTowerImmunities().begin(), GetTowerImmunities().end(), type) != GetTowerImmunities().end(); } bool Mob::IsImmuneTo(EffectType type) { return std::find(GetEffectImmunities().begin(), GetEffectImmunities().end(), type) != GetEffectImmunities().end(); } 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, Tower* tower) { if (IsImmuneTo(effectType)) return; if (HasEffect(effectType)) { EffectDuration& effect = GetEffect(effectType); if (effect.duration < durationSec) effect.duration = durationSec; // Setting new duration if it's greater then the actual } else { m_Effects.push_back({ effectType, durationSec, tower }); } } 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::Walk(std::uint64_t delta, World* world) { float mobWalkSpeed = GetStats()->GetMovementSpeed(); float walkAmount = mobWalkSpeed * (static_cast(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; } default: 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(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; } default: break; } } void Mob::ChangeDirection(const WalkableTile& tile, World* world) { if (GetDirection() == tile.direction) return; float tileX = static_cast(static_cast(GetCenterX())); float tileY = static_cast(static_cast(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; } default: break; } } bool Mob::IsTouchingCastle(const TeamCastle& enemyCastle) const { return enemyCastle.CollidesWith(*this); } void Mob::Tick(std::uint64_t delta, World* world) { m_HitCooldown = std::max(0.0f, m_HitCooldown - static_cast(delta / 1000.0f)); UpdateEffects(delta, world); Move(delta, world); AttackCastle(delta, world); } void Mob::UpdateEffects(std::uint64_t delta, World* world) { float deltaSec = static_cast(delta / 1000.0f); for (std::size_t i = 0; i < m_Effects.size(); i++) { EffectDuration& effect = m_Effects[i]; effect.duration -= deltaSec; if (effect.duration < 0) { // effect has gone m_Effects.erase(m_Effects.begin() + static_cast(i)); switch (effect.type) { case EffectType::Fire: { m_EffectFireTimer.Reset(); break; } case EffectType::Poison: { m_EffectPoisonTimer.Reset(); break; } case EffectType::Heal: { m_EffectHealTimer.Reset(); } default: break; } } } if (HasEffect(EffectType::Fire)) { if (m_EffectFireTimer.Update(delta)) { world->GetMobNotifier().NotifyListeners(&MobListener::OnMobDamage, this, 3.0f, GetEffect(EffectType::Fire).tower); } } if (HasEffect(EffectType::Poison)) { if (m_EffectPoisonTimer.Update(delta)) { world->GetMobNotifier().NotifyListeners(&MobListener::OnMobDamage, this, 1.0f, GetEffect(EffectType::Poison).tower); } } if (HasEffect(EffectType::Heal)) { if (m_EffectFireTimer.Update(delta)) { Heal(10); } } } bool Mob::HasEffect(EffectType type) { return std::find_if(m_Effects.begin(), m_Effects.end(), [&type](EffectDuration effect) { return effect.type == type;}) != m_Effects.end(); } typedef std::pair MobKey; static const std::map MobConstants = { // damage speed size money_cost exp_cost exp_reward max_health {{MobType::Zombie, 1},{MobStats{1, 1.6, {1, 1}, 15, 0, 7, 40}}}, {{MobType::Zombie, 2},{MobStats{1, 1.6, {1, 1}, 18, 88, 9, 56}}}, {{MobType::Zombie, 3},{MobStats{1, 1.6, {1, 1}, 22, 153, 10, 78}}}, {{MobType::Zombie, 4},{MobStats{1.5, 1.6, {1, 1}, 26, 268, 13, 110}}}, {{MobType::Zombie, 5},{MobStats{2, 1.6, {1, 1}, 31, 469, 15, 154}}}, {{MobType::Spider, 1},{MobStats{1, 1.6, {1, 1}, 25, 100, 15, 80}}}, {{MobType::Spider, 2},{MobStats{1, 2, {1, 1}, 30, 175, 16, 112}}}, {{MobType::Spider, 3},{MobStats{1.5, 2, {1, 1}, 36, 306, 18, 157}}}, {{MobType::Spider, 4},{MobStats{2.5, 2, {1, 1}, 43, 536, 19, 222}}}, {{MobType::Spider, 5},{MobStats{1.5, 2.5, {1, 1}, 52, 938, 22, 307}}}, {{MobType::Skeleton, 1},{MobStats{1, 1.6, {1, 1}, 120, 200, 30, 350}}}, {{MobType::Skeleton, 2},{MobStats{1, 1.6, {1, 1}, 144, 350, 33, 490}}}, {{MobType::Skeleton, 3},{MobStats{1, 1.6, {1, 1}, 173, 613, 36, 686}}}, {{MobType::Skeleton, 4},{MobStats{1.5, 1.6, {1, 1}, 225, 1072, 40, 960}}}, {{MobType::Skeleton, 5},{MobStats{2, 1.6, {1, 1}, 255, 1876, 43, 1345}}}, {{MobType::Pigman, 1},{MobStats{1, 2, {1, 1}, 100, 150, 22, 150}}}, {{MobType::Pigman, 2},{MobStats{1, 2, {1, 1}, 120, 263, 24, 210}}}, {{MobType::Pigman, 3},{MobStats{1, 2, {1, 1}, 144, 459, 25, 297}}}, {{MobType::Pigman, 4},{MobStats{1, 2, {1, 1}, 173, 804, 25, 412}}}, {{MobType::Pigman, 5},{MobStats{1.5, 2, {1, 1}, 207, 1407, 27, 576}}}, {{MobType::Creeper, 1},{MobStats{1, 2, {1, 1}, 250, 325, 46, 350}}}, {{MobType::Creeper, 2},{MobStats{1, 2, {1, 1}, 290, 550, 49, 460}}}, {{MobType::Creeper, 3},{MobStats{2, 2, {1, 1}, 350, 800, 55, 630}}}, {{MobType::Creeper, 4},{MobStats{3, 2, {1, 1}, 420, 1300, 61, 900}}}, {{MobType::Creeper, 5},{MobStats{4, 2, {1, 1}, 510, 1850, 67, 1250}}}, {{MobType::Silverfish, 1},{MobStats{1, 1.6, {1, 1}, 38, 125, 18, 120}}}, {{MobType::Silverfish, 2},{MobStats{1, 1.6, {1, 1}, 50, 230, 19, 170}}}, {{MobType::Silverfish, 3},{MobStats{1, 1.6, {1, 1}, 75, 340, 25, 225}}}, {{MobType::Silverfish, 4},{MobStats{1.5, 1.6, {1, 1}, 170, 700, 33, 310}}}, {{MobType::Silverfish, 5},{MobStats{1.5, 1.6, {1, 1}, 200, 1800, 36, 390}}}, {{MobType::Blaze, 1},{MobStats{1, 1.6, {1, 1}, 500, 500, 105, 410}}}, {{MobType::Blaze, 2},{MobStats{1, 1.6, {1, 1}, 600, 875, 111, 574}}}, {{MobType::Blaze, 3},{MobStats{1, 1.6, {1, 1}, 720, 1531, 115, 804}}}, {{MobType::Blaze, 4},{MobStats{1.5, 1.6, {1, 1}, 864, 2680, 121, 1125}}}, {{MobType::Blaze, 5},{MobStats{2, 1.6, {1, 1}, 1037, 4689, 127, 1575}}}, {{MobType::Witch, 1},{MobStats{1, 1.6, {1, 1}, 150, 300, 37, 300}}}, {{MobType::Witch, 2},{MobStats{1, 1.6, {1, 1}, 165, 525, 39, 405}}}, {{MobType::Witch, 3},{MobStats{1, 1.6, {1, 1}, 182, 918, 42, 547}}}, {{MobType::Witch, 4},{MobStats{1.5, 1.6, {1, 1}, 200, 1606, 43, 738}}}, {{MobType::Witch, 5},{MobStats{2, 1.6, {1, 1}, 220, 2810, 45, 996}}}, {{MobType::Slime, 1},{MobStats{1, 0.8, {1, 1}, 1500, 1000, 300, 800}}}, {{MobType::Slime, 2},{MobStats{1.5, 0.8, {1, 1}, 1800, 1750, 314, 880}}}, {{MobType::Slime, 3},{MobStats{2, 0.8, {1, 1}, 2160, 3063, 330, 968}}}, {{MobType::Slime, 4},{MobStats{2.5, 0.8, {1, 1}, 2592, 5359, 348, 1065}}}, {{MobType::Slime, 5},{MobStats{3, 0.8, {1, 1}, 3110, 9379, 366, 1171}}}, {{MobType::Giant, 1},{MobStats{10, 0.8, {1, 1}, 4000, 2250, 600, 6250}}}, {{MobType::Giant, 2},{MobStats{20, 0.8, {1, 1}, 4500, 4000, 612, 9375}}}, {{MobType::Giant, 3},{MobStats{30, 0.8, {1, 1}, 5062, 7250, 624, 14062}}}, {{MobType::Giant, 4},{MobStats{40, 0.8, {1, 1}, 5695, 12500, 636, 21093}}}, {{MobType::Giant, 5},{MobStats{50, 0.8, {1, 1}, 6407, 22000, 648, 31640}}}, }; const MobStats* GetMobStats(MobType type, std::uint8_t level) { return &MobConstants.at(MobKey{ type, level }); } static const std::map MobsTowerImmunities = { {{MobType::Zombie, 1},{}}, {{MobType::Zombie, 2},{}}, {{MobType::Zombie, 3},{}}, {{MobType::Zombie, 4},{}}, {{MobType::Zombie, 5},{}}, {{MobType::Spider, 1},{}}, {{MobType::Spider, 2},{}}, {{MobType::Spider, 3},{}}, {{MobType::Spider, 4},{TowerType::Archer, TowerType::Artillery, TowerType::Leach, TowerType::Necromancer, TowerType::Poison, TowerType::Quake, TowerType::Sorcerer, TowerType::Turret, TowerType::Zeus}}, {{MobType::Spider, 5},{TowerType::Archer, TowerType::Artillery, TowerType::Leach, TowerType::Necromancer, TowerType::Poison, TowerType::Quake, TowerType::Sorcerer, TowerType::Turret, TowerType::Zeus}}, {{MobType::Skeleton, 1},{}}, {{MobType::Skeleton, 2},{}}, {{MobType::Skeleton, 3},{}}, {{MobType::Skeleton, 4},{}}, {{MobType::Skeleton, 5},{}}, {{MobType::Pigman, 1},{TowerType::Zeus}}, {{MobType::Pigman, 2},{TowerType::Zeus}}, {{MobType::Pigman, 3},{TowerType::Zeus}}, {{MobType::Pigman, 4},{TowerType::Zeus}}, {{MobType::Pigman, 5},{TowerType::Zeus}}, {{MobType::Creeper, 1},{}}, {{MobType::Creeper, 2},{}}, {{MobType::Creeper, 3},{}}, {{MobType::Creeper, 4},{}}, {{MobType::Creeper, 5},{}}, {{MobType::Silverfish, 1},{}}, {{MobType::Silverfish, 2},{}}, {{MobType::Silverfish, 3},{}}, {{MobType::Silverfish, 4},{}}, {{MobType::Silverfish, 5},{}}, {{MobType::Blaze, 1},{}}, {{MobType::Blaze, 2},{}}, {{MobType::Blaze, 3},{}}, {{MobType::Blaze, 4},{}}, {{MobType::Blaze, 5},{}}, {{MobType::Witch, 1},{TowerType::Zeus, TowerType::Mage, TowerType::Poison}}, {{MobType::Witch, 2},{TowerType::Zeus, TowerType::Mage, TowerType::Poison}}, {{MobType::Witch, 3},{TowerType::Zeus, TowerType::Mage, TowerType::Poison}}, {{MobType::Witch, 4},{TowerType::Zeus, TowerType::Mage, TowerType::Poison}}, {{MobType::Witch, 5},{TowerType::Zeus, TowerType::Mage, TowerType::Poison}}, {{MobType::Slime, 1},{}}, {{MobType::Slime, 2},{}}, {{MobType::Slime, 3},{}}, {{MobType::Slime, 4},{}}, {{MobType::Slime, 5},{}}, {{MobType::Giant, 1},{}}, {{MobType::Giant, 2},{}}, {{MobType::Giant, 3},{}}, {{MobType::Giant, 4},{}}, {{MobType::Giant, 5},{}}, }; const TowerImmunities& GetMobTowerImmunities(MobType type, std::uint8_t level) { return MobsTowerImmunities.at({ type, level }); } static const std::map MobsEffectImmunities = { {{MobType::Zombie, 1},{}}, {{MobType::Zombie, 2},{}}, {{MobType::Zombie, 3},{}}, {{MobType::Zombie, 4},{}}, {{MobType::Zombie, 5},{}}, {{MobType::Spider, 1},{}}, {{MobType::Spider, 2},{}}, {{MobType::Spider, 3},{}}, {{MobType::Spider, 4},{}}, {{MobType::Spider, 5},{}}, {{MobType::Skeleton, 1},{}}, {{MobType::Skeleton, 2},{}}, {{MobType::Skeleton, 3},{}}, {{MobType::Skeleton, 4},{EffectType::Fire}}, {{MobType::Skeleton, 5},{EffectType::Fire}}, {{MobType::Pigman, 1},{EffectType::Fire}}, {{MobType::Pigman, 2},{EffectType::Fire}}, {{MobType::Pigman, 3},{EffectType::Fire}}, {{MobType::Pigman, 4},{EffectType::Fire}}, {{MobType::Pigman, 5},{EffectType::Fire}}, {{MobType::Creeper, 1},{}}, {{MobType::Creeper, 2},{}}, {{MobType::Creeper, 3},{}}, {{MobType::Creeper, 4},{}}, {{MobType::Creeper, 5},{}}, {{MobType::Silverfish, 1},{}}, {{MobType::Silverfish, 2},{}}, {{MobType::Silverfish, 3},{}}, {{MobType::Silverfish, 4},{EffectType::Fire}}, {{MobType::Silverfish, 5},{EffectType::Fire}}, {{MobType::Blaze, 1},{EffectType::Fire}}, {{MobType::Blaze, 2},{EffectType::Fire}}, {{MobType::Blaze, 3},{EffectType::Fire}}, {{MobType::Blaze, 4},{EffectType::Fire}}, {{MobType::Blaze, 5},{EffectType::Fire}}, {{MobType::Witch, 1},{}}, {{MobType::Witch, 2},{}}, {{MobType::Witch, 3},{}}, {{MobType::Witch, 4},{}}, {{MobType::Witch, 5},{}}, {{MobType::Slime, 1},{}}, {{MobType::Slime, 2},{}}, {{MobType::Slime, 3},{}}, {{MobType::Slime, 4},{EffectType::Fire}}, {{MobType::Slime, 5},{EffectType::Fire}}, {{MobType::Giant, 1},{EffectType::Stun}}, {{MobType::Giant, 2},{EffectType::Stun}}, {{MobType::Giant, 3},{EffectType::Stun}}, {{MobType::Giant, 4},{EffectType::Stun}}, {{MobType::Giant, 5},{EffectType::Stun}}, }; const EffectImmunities& GetMobEffectImmunities(MobType type, std::uint8_t level) { return MobsEffectImmunities.at({ type, level }); } MobPtr MobFactory::CreateMob(MobID mobId, MobType mobType, std::uint8_t mobLevel, PlayerID mobSender) { using MobCreator = std::function(MobID, std::uint8_t, PlayerID)>; static const std::map mobFactory = { {MobType::Zombie, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Spider, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Skeleton, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Pigman, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Creeper, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Silverfish, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Blaze, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Witch, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Slime, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, {MobType::Giant, [](MobID id, std::uint8_t level, PlayerID sender) -> MobPtr {return std::make_shared(id, level, sender);} }, }; return mobFactory.at(mobType)(mobId, mobLevel, mobSender); } std::string MobFactory::GetMobName(MobType type) { switch (type) { case MobType::Zombie: return "Zombie"; case MobType::Spider: return "Spider"; case MobType::Skeleton: return "Skeleton"; case MobType::Pigman: return "Pigman"; case MobType::Creeper: return "Creeper"; case MobType::Silverfish: return "Silverfish"; case MobType::Blaze: return "Blaze"; case MobType::Witch: return "Witch"; case MobType::Slime: return "Slime"; case MobType::Giant: return "Giant"; default: return "Unknow"; } } } // namespace game } // namespace td