/* Copyright (c) 2013 yvt based on code of pysnip (c) Mathias Kaerlev 2011-2012. This file is part of OpenSpades. OpenSpades is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenSpades is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenSpades. If not, see . */ #include "World.h" #include "GameMap.h" #include "GameMapWrapper.h" #include "../Core/IStream.h" #include "../Core/FileManager.h" #include "../Core/Debug.h" #include "Player.h" #include "Weapon.h" #include "Grenade.h" #include "../Core/Debug.h" #include "IGameMode.h" #include #include #include "IWorldListener.h" #include #include "HitTestDebugger.h" #include SPADES_SETTING(cg_debugHitTest, "0"); namespace spades { namespace client { World::World(){ SPADES_MARK_FUNCTION(); listener = NULL; map = NULL; mapWrapper = NULL; localPlayerIndex = -1; for(int i = 0; i < 32; i++){ players.push_back((Player *)NULL); playerPersistents.push_back(PlayerPersistent()); } localPlayerIndex = 0; time = 0.f; mode = NULL; } World::~World() { SPADES_MARK_FUNCTION(); for(std::list::iterator it = grenades.begin(); it != grenades.end(); it++) delete *it; for(size_t i = 0; i < players.size(); i++) if(players[i]) delete players[i]; if(mode){ delete mode; } if(map){ delete mapWrapper; map->Release(); } } size_t World::GetNumPlayers() { size_t numPlayers = 0; for (auto *p: players) { if (p) ++numPlayers; } return numPlayers; } void World::Advance(float dt) { SPADES_MARK_FUNCTION(); ApplyBlockActions(); for(size_t i = 0; i < players.size(); i++) if(players[i]) players[i]->Update(dt); std::vector::iterator> removedGrenades; for(std::list::iterator it = grenades.begin(); it != grenades.end(); it++){ Grenade *g = *it; if(g->Update(dt)){ removedGrenades.push_back(it); } } for(size_t i = 0; i < removedGrenades.size(); i++) grenades.erase(removedGrenades[i]); time += dt; } void World::SetMap(spades::client::GameMap *newMap){ if(map == newMap) return; hitTestDebugger.reset(); if(map){ map->Release(); delete mapWrapper; } map = newMap; if(map){ map->AddRef(); mapWrapper = new GameMapWrapper(map); mapWrapper->Rebuild(); } } void World::AddGrenade(spades::client::Grenade *g){ SPADES_MARK_FUNCTION_DEBUG(); grenades.push_back(g); } std::vector World::GetAllGrenades() { SPADES_MARK_FUNCTION_DEBUG(); std::vector g; for(std::list::iterator it = grenades.begin(); it != grenades.end(); it++){ g.push_back(*it); } return g; } void World::SetPlayer(int i, spades::client::Player *p){ SPADES_MARK_FUNCTION(); SPAssert(i >= 0); SPAssert( i < (int)players.size()); if(players[i] == p) return; if(players[i]) delete players[i]; players[i] = p; if(listener) listener->PlayerObjectSet(i); } void World::SetMode(spades::client::IGameMode *m) { if(mode == m) return; if(mode) delete mode; mode = m; } static std::vector> ClusterizeBlocks(const std::vector& blocks) { std::unordered_map blockMap; for(const auto& block: blocks) { blockMap[block] = true; } std::vector> ret; std::deque queue; ret.reserve(64); // wish I could `reserve()` queue... std::size_t addedCount = 0; for(auto it = blockMap.begin(); it != blockMap.end(); it++) { SPAssert(queue.empty()); if(!it->second) continue; queue.emplace_back(it); std::vector outBlocks; while(!queue.empty()) { auto blockitem = queue.front(); queue.pop_front(); if(!blockitem->second) continue; auto pos = blockitem->first; outBlocks.emplace_back(pos); blockitem->second = false; decltype(blockMap)::iterator nextIt; nextIt = blockMap.find(CellPos(pos.x - 1, pos.y, pos.z)); if(nextIt != blockMap.end() && nextIt->second) { queue.emplace_back(nextIt); } nextIt = blockMap.find(CellPos(pos.x + 1, pos.y, pos.z)); if(nextIt != blockMap.end() && nextIt->second) { queue.emplace_back(nextIt); } nextIt = blockMap.find(CellPos(pos.x, pos.y - 1, pos.z)); if(nextIt != blockMap.end() && nextIt->second) { queue.emplace_back(nextIt); } nextIt = blockMap.find(CellPos(pos.x, pos.y + 1, pos.z)); if(nextIt != blockMap.end() && nextIt->second) { queue.emplace_back(nextIt); } nextIt = blockMap.find(CellPos(pos.x, pos.y, pos.z - 1)); if(nextIt != blockMap.end() && nextIt->second) { queue.emplace_back(nextIt); } nextIt = blockMap.find(CellPos(pos.x, pos.y, pos.z + 1)); if(nextIt != blockMap.end() && nextIt->second) { queue.emplace_back(nextIt); } } SPAssert(!outBlocks.empty()); addedCount += outBlocks.size(); ret.emplace_back(std::move(outBlocks)); } SPAssert(addedCount == blocks.size()); return std::move(ret); } void World::ApplyBlockActions() { for(const auto& creation: createdBlocks) { const auto& pos = creation.first; const auto& color = creation.second; if(map->IsSolid(pos.x, pos.y, pos.z)) { map->Set(pos.x, pos.y, pos.z, true, color.x | (color.y << 8) | (color.z << 16) | (100UL << 24)); continue; } mapWrapper->AddBlock(pos.x, pos.y, pos.z, color.x | (color.y << 8) | (color.z << 16) | (100UL << 24)); } std::vector cells; for(const auto& cell: destroyedBlocks) { if(!map->IsSolid(cell.x, cell.y, cell.z)) continue; cells.emplace_back(cell); } cells = mapWrapper->RemoveBlocks(cells); auto clusters = ClusterizeBlocks(cells); std::vector cells2; for(const auto& cluster: clusters) { cells2.resize(cluster.size()); for(std::size_t i = 0; i < cluster.size(); i++) { auto p = cluster[i]; cells2[i] = IntVector3(p.x, p.y, p.z); map->Set(p.x, p.y, p.z, false, 0); } if(listener) listener->BlocksFell(cells2); } createdBlocks.clear(); destroyedBlocks.clear(); } void World::CreateBlock(spades::IntVector3 pos, spades::IntVector3 color) { auto it = destroyedBlocks.find(CellPos(pos.x, pos.y, pos.z)); if(it != destroyedBlocks.end()) destroyedBlocks.erase(it); createdBlocks[CellPos(pos.x, pos.y, pos.z)] = color; } void World::DestroyBlock(std::vector& pos){ std::vector cells; bool allowToDestroyLand = pos.size() == 1; for(size_t i = 0; i < pos.size(); i++){ const IntVector3& p = pos[i]; if(p.z >= (allowToDestroyLand ? 63 : 62) || p.z < 0 || p.x < 0 || p.y < 0 || p.x >= map->Width() || p.y >= map->Height()) continue; CellPos cellp(p.x, p.y, p.z); auto it = createdBlocks.find(cellp); if(it != createdBlocks.end()) createdBlocks.erase(it); destroyedBlocks.insert(cellp); } } World::PlayerPersistent& World::GetPlayerPersistent(int index) { SPAssert(index >= 0); SPAssert(index < players.size()); return playerPersistents[index]; } std::vector World::CubeLine(spades::IntVector3 v1, spades::IntVector3 v2, int maxLength) { SPADES_MARK_FUNCTION_DEBUG(); IntVector3 c = v1; IntVector3 d = v2 - v1; long ixi, iyi, izi, dx, dy, dz, dxi, dyi, dzi; std::vector ret; int VSID = map->Width(); SPAssert(VSID == map->Height()); int MAXZDIM = map->Depth(); if (d.x < 0) ixi = -1; else ixi = 1; if (d.y < 0) iyi = -1; else iyi = 1; if (d.z < 0) izi = -1; else izi = 1; if ((abs(d.x) >= abs(d.y)) && (abs(d.x) >= abs(d.z))) { dxi = 1024; dx = 512; dyi = (long)(!d.y ? 0x3fffffff/VSID : abs(d.x*1024/d.y)); dy = dyi/2; dzi = (long)(!d.z ? 0x3fffffff/VSID : abs(d.x*1024/d.z)); dz = dzi/2; } else if (abs(d.y) >= abs(d.z)) { dyi = 1024; dy = 512; dxi = (long)(!d.x ? 0x3fffffff/VSID : abs(d.y*1024/d.x)); dx = dxi/2; dzi = (long)(!d.z ? 0x3fffffff/VSID : abs(d.y*1024/d.z)); dz = dzi/2; } else { dzi = 1024; dz = 512; dxi = (long)(!d.x ? 0x3fffffff/VSID : abs(d.z*1024/d.x)); dx = dxi/2; dyi = (long)(!d.y ? 0x3fffffff/VSID : abs(d.z*1024/d.y)); dy = dyi/2; } if (ixi >= 0) dx = dxi-dx; if (iyi >= 0) dy = dyi-dy; if (izi >= 0) dz = dzi-dz; while (1) { ret.push_back(c); if(ret.size() == (size_t)maxLength) break; if(c.x == v2.x && c.y == v2.y && c.z == v2.z) break; if ((dz <= dx) && (dz <= dy)) { c.z += izi; if (c.z < 0 || c.z >= MAXZDIM) break; dz += dzi; } else { if (dx < dy) { c.x += ixi; if ((unsigned long)c.x >= VSID) break; dx += dxi; } else { c.y += iyi; if ((unsigned long)c.y >= VSID) break; dy += dyi; } } } return ret; } World::WeaponRayCastResult World::WeaponRayCast(spades::Vector3 startPos, spades::Vector3 dir, Player *exclude) { WeaponRayCastResult result; Player *hitPlayer = NULL; float hitPlayerDistance = 0.f; hitTag_t hitFlag = hit_None; for(int i = 0; i < (int)players.size(); i++){ Player *p = players[i]; if(p == NULL || p == exclude) continue; if(p->GetTeamId() >= 2 || !p->IsAlive()) continue; if(!p->RayCastApprox(startPos, dir)) continue; Player::HitBoxes hb = p->GetHitBoxes(); Vector3 hitPos; if(hb.head.RayCast(startPos, dir, &hitPos)) { float dist = (hitPos - startPos).GetLength(); if(hitPlayer == NULL || dist < hitPlayerDistance){ if(hitPlayer != p){ hitPlayer = p; hitFlag = hit_None; } hitPlayerDistance = dist; hitFlag |= hit_Head; } } if(hb.torso.RayCast(startPos, dir, &hitPos)) { float dist = (hitPos - startPos).GetLength(); if(hitPlayer == NULL || dist < hitPlayerDistance){ if(hitPlayer != p){ hitPlayer = p; hitFlag = hit_None; } hitPlayerDistance = dist; hitFlag |= hit_Torso; } } for(int j = 0; j < 3 ;j++){ if(hb.limbs[j].RayCast(startPos, dir, &hitPos)) { float dist = (hitPos - startPos).GetLength(); if(hitPlayer == NULL || dist < hitPlayerDistance){ if(hitPlayer != p){ hitPlayer = p; hitFlag = hit_None; } hitPlayerDistance = dist; if(j == 2) { hitFlag |= hit_Arms; } else { hitFlag |= hit_Legs; } } } } } // map raycast GameMap::RayCastResult res2; res2 = map->CastRay2(startPos, dir, 256); if(res2.hit && (hitPlayer == NULL || (res2.hitPos - startPos).GetLength() < hitPlayerDistance)){ result.hit = true; result.startSolid = res2.startSolid; result.player = NULL; result.hitFlag = hit_None; result.blockPos = res2.hitBlock; result.hitPos = res2.hitPos; }else if(hitPlayer) { result.hit = true; result.startSolid = false; // FIXME: startSolid for player result.player = hitPlayer; result.hitPos = startPos + dir * hitPlayerDistance; result.hitFlag = hitFlag; }else{ result.hit = false; } return result; } HitTestDebugger *World::GetHitTestDebugger() { if(cg_debugHitTest) { if(hitTestDebugger == nullptr) { hitTestDebugger.reset(new HitTestDebugger(this)); } return hitTestDebugger.get(); } return nullptr; } } }