343 lines
11 KiB
C++
343 lines
11 KiB
C++
#include "game/World.h"
|
|
#include "protocol/PacketDispatcher.h"
|
|
#include "protocol/Protocol.h"
|
|
#include "game/BaseGame.h"
|
|
#include "misc/Random.h"
|
|
|
|
#include <cmath>
|
|
#include "misc/Compression.h"
|
|
|
|
#include <iostream>
|
|
|
|
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)) {
|
|
std::cerr << "Failed to load map from file " << fileName << " !\n";
|
|
return false;
|
|
}
|
|
|
|
std::cout << "Read file : " << fileName << " (File size : " << buffer.GetSize() << ")" << std::endl;
|
|
|
|
DataBuffer mapHeaderPacketBuffer = utils::Decompress(buffer);
|
|
DataBuffer mapDataPacketBuffer = utils::Decompress(buffer);
|
|
|
|
protocol::PacketType packetType;
|
|
|
|
mapHeaderPacketBuffer >> packetType;
|
|
|
|
protocol::WorldBeginDataPacket headerPacket;
|
|
headerPacket.Deserialize(mapHeaderPacketBuffer);
|
|
|
|
mapDataPacketBuffer >> packetType;
|
|
|
|
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());
|
|
DataBuffer mapDataCompressed = utils::Compress(dataPacket.Serialize());
|
|
|
|
std::cout << "Header Packet Size : " << mapHeaderCompressed.GetSize() << std::endl;
|
|
std::cout << "World Data Packet Size : " << mapDataCompressed.GetSize() << std::endl;
|
|
|
|
DataBuffer buffer = mapHeaderCompressed << mapDataCompressed;
|
|
std::cout << "Total Size : " << buffer.GetSize() << std::endl;
|
|
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 = (TowerTile*)tile.get();
|
|
return &m_TowerPlacePalette[towerTile->color_palette_ref];
|
|
}
|
|
case TileType::Walk: {
|
|
return &m_WalkablePalette;
|
|
}
|
|
case TileType::Decoration: {
|
|
DecorationTile* towerTile = (DecorationTile*)tile.get();
|
|
return &m_DecorationPalette[towerTile->color_palette_ref];
|
|
break;
|
|
}
|
|
case TileType::None: {
|
|
return nullptr;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool World::CanPlaceLittleTower(const glm::vec2& 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 = (const 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 glm::vec2& 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 = (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::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()) {
|
|
m_Mobs.erase(m_Mobs.begin() + i);
|
|
}
|
|
}
|
|
}
|
|
|
|
TowerPtr World::getTower(const glm::vec2& 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
|