restructure project
This commit is contained in:
326
src/td/game/World.cpp
Normal file
326
src/td/game/World.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
#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 <cmath>
|
||||
|
||||
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::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());
|
||||
}
|
||||
|
||||
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<TowerTile*>(tile.get());
|
||||
return &m_TowerPlacePalette[towerTile->color_palette_ref];
|
||||
}
|
||||
case TileType::Walk: {
|
||||
return &m_WalkablePalette;
|
||||
}
|
||||
case TileType::Decoration: {
|
||||
DecorationTile* towerTile = dynamic_cast<DecorationTile*>(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<TowerTile*>(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<const TowerTile*>(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<int>(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
|
||||
Reference in New Issue
Block a user