513 lines
12 KiB
C++
513 lines
12 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#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 <math.h>
|
|
#include <stdlib.h>
|
|
#include "IWorldListener.h"
|
|
#include <Core/Settings.h>
|
|
#include "HitTestDebugger.h"
|
|
#include <deque>
|
|
|
|
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<Grenade *>::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<std::list<Grenade *>::iterator> removedGrenades;
|
|
for(std::list<Grenade *>::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<Grenade *> World::GetAllGrenades() {
|
|
SPADES_MARK_FUNCTION_DEBUG();
|
|
|
|
std::vector<Grenade *> g;
|
|
for(std::list<Grenade *>::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<std::vector<CellPos>> ClusterizeBlocks(const std::vector<CellPos>& blocks) {
|
|
std::unordered_map<CellPos, bool, CellPosHash> blockMap;
|
|
for(const auto& block: blocks) {
|
|
blockMap[block] = true;
|
|
}
|
|
|
|
std::vector<std::vector<CellPos>> ret;
|
|
std::deque<decltype(blockMap)::iterator> 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<CellPos> 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<CellPos> 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<IntVector3> 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<spades::IntVector3>& pos){
|
|
std::vector<CellPos> 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<IntVector3> 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<IntVector3> 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;
|
|
}
|
|
|
|
}
|
|
}
|