/*
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;
}
}
}