#include "td/game/World.h" #include "td/protocol/PacketDispatcher.h" #include "td/protocol/Protocol.h" #include "td/protocol/packets/WorldBeginDataPacket.h" #include "td/protocol/packets/WorldDataPacket.h" #include "td/game/BaseGame.h" #include "td/misc/Random.h" #include "td/misc/Compression.h" #include "td/misc/Format.h" #include 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 { std::int16_t chunkX = x / Chunk::ChunkWidth; std::int16_t chunkY = y / Chunk::ChunkHeight; std::uint16_t subChunkX = std::abs(x % Chunk::ChunkWidth); std::uint16_t subChunkY = std::abs(y % Chunk::ChunkHeight); auto chunkIt = m_Chunks.find({ chunkX, chunkY }); if (chunkIt == m_Chunks.end()) return nullptr; ChunkPtr chunk = chunkIt->second; return GetTilePtr(chunk->GetTileIndex(subChunkY * Chunk::ChunkWidth + subChunkX)); } 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(); m_SpawnColorPalette = worldHeader->GetSpawnPalette(); GetRedTeam().GetCastle() = worldHeader->GetRedCastle(); GetRedTeam().GetCastle().SetTeam(&GetRedTeam()); GetBlueTeam().GetCastle() = worldHeader->GetBlueCastle(); GetBlueTeam().GetCastle().SetTeam(&GetBlueTeam()); m_TilePalette = worldHeader->GetTilePalette(); return true; } bool World::LoadMap(const protocol::WorldDataPacket* worldData) { m_Chunks = worldData->GetChunks(); return true; } bool World::LoadMapFromFile(const std::string& fileName) { DataBuffer buffer; if (!buffer.ReadFile(fileName)) { utils::LOGE(utils::format("Failed to load map from file %s", fileName.c_str())); return false; } utils::LOG(utils::format("Read file : %s (File size : %u)", fileName.c_str(), buffer.GetSize())); DataBuffer mapHeaderPacketBuffer = utils::Decompress(buffer); DataBuffer mapDataPacketBuffer = utils::Decompress(buffer); protocol::WorldBeginDataPacket headerPacket; headerPacket.Deserialize(mapHeaderPacketBuffer); protocol::WorldDataPacket dataPacket; dataPacket.Deserialize(mapDataPacketBuffer); LoadMap(&headerPacket); LoadMap(&dataPacket); return true; } bool World::SaveMap(const std::string& fileName) const { protocol::WorldBeginDataPacket headerPacket(this); protocol::WorldDataPacket dataPacket(this); DataBuffer mapHeaderCompressed = utils::Compress(headerPacket.Serialize(false)); DataBuffer mapDataCompressed = utils::Compress(dataPacket.Serialize(false)); utils::LOG(utils::format("Header Packet Size : %u", mapHeaderCompressed.GetSize())); utils::LOG(utils::format("World Data Packet Size : %u", mapDataCompressed.GetSize())); DataBuffer buffer = mapHeaderCompressed << mapDataCompressed; utils::LOG(utils::format("Total Size : %u", buffer.GetSize())); return buffer.WriteFile(fileName); } void World::Tick(std::uint64_t delta) { if (m_Game->GetGameState() != GameState::Game) return; TickMobs(delta); for (TowerPtr tower : m_Towers) { tower->Tick(delta, this); } CleanDeadMobs(); } void World::Reset() { m_Towers.clear(); m_Mobs.clear(); } void World::SpawnMobAt(MobID id, MobType type, std::uint8_t level, PlayerID sender, float x, float y, Direction dir) { MobPtr mob = MobFactory::CreateMob(id, type, level, sender); mob->SetCenter({ x, y }); mob->SetDirection(dir); m_Mobs.push_back(mob); GetMobNotifier().NotifyListeners(&MobListener::OnMobSpawn, mob.get()); } MobPtr World::RemoveMob(MobID mobId) { auto it = std::find_if(m_Mobs.begin(), m_Mobs.end(), [mobId](MobPtr mob) { return mob->GetMobID() == mobId;}); if (it == m_Mobs.end()) return nullptr; MobPtr mob = *it; m_Mobs.erase(it); return mob; } TowerPtr World::PlaceTowerAt(TowerID id, TowerType type, std::int32_t x, std::int32_t y, PlayerID builder) { TowerPtr tower = TowerFactory::CreateTower(type, id, x, y, builder); m_Towers.push_back(tower); 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, this); } } const Color* World::GetTileColor(TilePtr tile) const { switch (tile->GetType()) { case TileType::Tower: { TowerTile* towerTile = dynamic_cast(tile.get()); return &m_TowerPlacePalette[towerTile->color_palette_ref]; } case TileType::Walk: { return &m_WalkablePalette; } case TileType::Decoration: { DecorationTile* towerTile = dynamic_cast(tile.get()); return &m_DecorationPalette[towerTile->color_palette_ref]; break; } default: { return nullptr; } } return nullptr; } bool World::CanPlaceLittleTower(const Vec2f& worldPos, PlayerID playerID) const { TilePtr tile = GetTile(worldPos.x, worldPos.y); const Player& player = m_Game->GetPlayers()[playerID]; if (tile == nullptr) { return false; } if (tile->GetType() == game::TileType::Tower) { const TowerTile* towerTile = dynamic_cast(tile.get()); if (towerTile->team_owner != player.GetTeamColor()) return false; for (int x = -1; x < 2; x++) { for (int y = -1; y < 2; y++) { game::TilePtr adjacentTile = GetTile(worldPos.x + x, worldPos.y + y); if (adjacentTile == nullptr || adjacentTile->GetType() != game::TileType::Tower || GetTower({ worldPos.x + x, worldPos.y + y }) != nullptr) { return false; } } } return true; } return false; } bool World::CanPlaceBigTower(const Vec2f& worldPos, PlayerID playerID) const { if (!CanPlaceLittleTower(worldPos, playerID)) return false; TilePtr tile = GetTile(worldPos.x, worldPos.y); const Player& player = m_Game->GetPlayers()[playerID]; if (tile == nullptr) { return false; } if (tile->GetType() == game::TileType::Tower) { const TowerTile* towerTile = dynamic_cast(tile.get()); if (towerTile->team_owner != player.GetTeamColor()) return false; for (int x = -2; x < 3; x++) { for (int y = -2; y < 3; y++) { game::TilePtr adjacentTile = GetTile(worldPos.x + x, worldPos.y + y); if (adjacentTile == nullptr || adjacentTile->GetType() != game::TileType::Tower || GetTower({ worldPos.x + x, worldPos.y + y }) != nullptr) { return false; } } } return true; } return false; } 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()) { m_Mobs.erase(m_Mobs.begin() + static_cast(i)); } } } TowerPtr World::GetTower(const Vec2f& position) const { for (TowerPtr tower : m_Towers) { if (tower->GetSize() == TowerSize::Big) { if (tower->GetCenterX() - 2.5f < position.x && tower->GetCenterX() + 2.5f > position.x && tower->GetCenterY() - 2.5f < position.y && tower->GetCenterY() + 2.5f > position.y) { return tower; } } else { // little tower if (tower->GetCenterX() - 1.5f < position.x && tower->GetCenterX() + 1.5f > position.x && tower->GetCenterY() - 1.5f < position.y && tower->GetCenterY() + 1.5f > position.y) { return tower; } } } return nullptr; } TowerPtr World::GetTowerById(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; return *it; } 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) { 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); } } Team& World::GetRedTeam() { return m_Game->GetRedTeam(); } const Team& World::GetRedTeam() const { return m_Game->GetRedTeam(); } Team& World::GetBlueTeam() { return m_Game->GetBlueTeam(); } const Team& World::GetBlueTeam() const { return m_Game->GetBlueTeam(); } Team& World::GetTeam(TeamColor team) { return m_Game->GetTeam(team); } 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