uint32_t Bitmap::GetPixel(int x, int y) { SPAssert(x >= 0); SPAssert(y >= 0); SPAssert(x < w); SPAssert(y < h); return pixels[x + y * w]; }
void Corpse::Spring(NodeType n1, NodeType n2, float distance, float dt){ SPADES_MARK_FUNCTION_DEBUG(); SPAssert(n1 >= 0); SPAssert(n1 < NodeCount); SPAssert(n2 >= 0); SPAssert(n2 < NodeCount); Node& a = nodes[n1]; Node& b = nodes[n2]; Vector3 diff = b.pos - a.pos; float dist = diff.GetLength(); Vector3 force = diff.Normalize() * (distance - dist); force *= dt * 50.f; b.vel += force; a.vel -= force; b.pos += force / (dt * 50.f) * 0.5f; a.pos -= force / (dt * 50.f) * 0.5f; Vector3 velMid = (a.vel + b.vel) * .5f; float dump = 1.f - powf(.1f, dt); a.vel += (velMid - a.vel) * dump; b.vel += (velMid - b.vel) * dump; }
void Bitmap::SetPixel(int x, int y, uint32_t p) { SPAssert(x >= 0); SPAssert(y >= 0); SPAssert(x < w); SPAssert(y < h); pixels[x + y * w] = p; }
void GLVoxelModel::BuildVertices(spades::VoxelModel *model) { SPADES_MARK_FUNCTION(); SPAssert(vertices.empty()); SPAssert(indices.empty()); int w = model->GetWidth(); int h = model->GetHeight(); int d = model->GetDepth(); for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { for (int z = 0; z < d; z++) { if (!model->IsSolid(x, y, z)) continue; uint32_t color = model->GetColor(x, y, z); color |= 0xff000000UL; if (!model->IsSolid(x - 1, y, z)) EmitFace(model, x, y, z, -1, 0, 0, color); if (!model->IsSolid(x + 1, y, z)) EmitFace(model, x, y, z, 1, 0, 0, color); if (!model->IsSolid(x, y - 1, z)) EmitFace(model, x, y, z, 0, -1, 0, color); if (!model->IsSolid(x, y + 1, z)) EmitFace(model, x, y, z, 0, 1, 0, color); if (!model->IsSolid(x, y, z - 1)) EmitFace(model, x, y, z, 0, 0, -1, color); if (!model->IsSolid(x, y, z + 1)) EmitFace(model, x, y, z, 0, 0, 1, color); } } } }
void DeflateStream::CompressBuffer() { SPADES_MARK_FUNCTION(); SPAssert(mode == CompressModeCompress); char outputBuffer[chunkSize]; if(!valid){ SPRaise("State is invalid"); } zstream.avail_in = (unsigned int) buffer.size(); zstream.next_in = (Bytef*)buffer.data(); do{ zstream.avail_out = chunkSize; zstream.next_out = (Bytef*)outputBuffer; int ret = deflate(&zstream, Z_NO_FLUSH); if(ret == Z_STREAM_ERROR) { valid = false; deflateEnd(&zstream); SPRaise("Error while deflating: %s", zError(ret)); } int got = chunkSize - zstream.avail_out; baseStream->Write(outputBuffer, got); }while(zstream.avail_out == 0); SPAssert(zstream.avail_in == 0); std::vector<char>().swap(buffer); }
std::string TrimSpaces(const std::string& str){ size_t pos = str.find_first_not_of(" \t\n\r"); if(pos == std::string::npos) return std::string(); size_t po2 = str.find_last_not_of(" \t\n\r"); SPAssert(po2 != std::string::npos); SPAssert(po2 >= pos); return str.substr(pos, po2 - pos + 1); }
void Corpse::SetNode(NodeType n, spades::Vector3 v){ SPAssert(n >= 0); SPAssert(n < NodeCount); nodes[n].pos = v; nodes[n].vel = MakeVector3(VelNoise(), VelNoise(), 0.f); nodes[n].lastPos = v; nodes[n].lastForce = MakeVector3(0, 0,0); }
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; }
static float MyACos(float v){ SPAssert(!isnan(v)); if(v >= 1.f) return 0.f; if(v <= -1.f) return static_cast<float>(M_PI); float vv = acosf(v); if(isnan(vv)){ vv = acosf(v * .9999f); } SPAssert(!isnan(vv)); return vv; }
size_t DeflateStream::Read(void *data, size_t bytes) { SPADES_MARK_FUNCTION(); size_t readBytes = 0; while(bytes > 0){ if(bufferPos >= buffer.size()){ FillBuffer(); if(reachedEOF && bufferPos >= buffer.size()) break; } size_t copySize = bytes; if(copySize > (buffer.size() - bufferPos)) copySize = buffer.size() - bufferPos; memcpy(data, buffer.data() + bufferPos, copySize); reinterpret_cast<char *&>(data) += copySize; SPAssert(bytes >= copySize); bytes -= copySize; readBytes += copySize; bufferPos += copySize; position += copySize; } return readBytes; }
GLFramebufferManager::BufferHandle::BufferHandle(GLFramebufferManager*m, size_t index): manager(m), bufferIndex(index), valid(true){ SPAssert(bufferIndex < manager->buffers.size()); Buffer&b = manager->buffers[bufferIndex]; b.refCount++; }
void GLFramebufferManager::MakeSureAllBuffersReleased(){ SPADES_MARK_FUNCTION(); for(size_t i = 0; i < buffers.size(); i++){ SPAssert(buffers[i].refCount == 0); } }
void GLSpriteRenderer::Flush() { SPADES_MARK_FUNCTION_DEBUG(); if(vertices.empty()) return; device->VertexAttribPointer(positionAttribute(), 4, IGLDevice::FloatType, false, sizeof(Vertex), &(vertices[0].x)); device->VertexAttribPointer(spritePosAttribute(), 4, IGLDevice::FloatType, false, sizeof(Vertex), &(vertices[0].sx)); device->VertexAttribPointer(colorAttribute(), 4, IGLDevice::FloatType, false, sizeof(Vertex), &(vertices[0].r)); SPAssert(lastImage); lastImage->Bind(IGLDevice::Texture2D); device->DrawElements(IGLDevice::Triangles, indices.size(), IGLDevice::UnsignedInt, indices.data()); vertices.clear(); indices.clear(); }
void Corpse::AngleSpring(NodeType n1id, NodeType n2id, Vector3 dir, float minDot, float maxDot, float dt){ Node& n1 = nodes[n1id]; Node& n2 = nodes[n2id]; Vector3 diff = n2.pos - n1.pos; float ln1 = diff.GetLength(); float dot = Vector3::Dot(diff, dir) / (ln1 + 0.000000001f); if(dot >= minDot && dot <= maxDot) return; float strength = 0.f; Vector3 a1 = Vector3::Cross(dir, diff); a1 = Vector3::Cross(diff, a1).Normalize(); Vector3 a2 = -a1; //a1=-a1; a2=-a2; //a1 = -a1; if(dot > maxDot){ strength = MyACos(dot) - MyACos(maxDot); }else if(dot < minDot){ strength = MyACos(dot) - MyACos(minDot); } SPAssert(!isnan(strength)); strength *= 100.f; strength *= dt; a1 *= strength; a2 *= strength; n2.vel += a1; n1.vel += a2; //nBase.vel -= a1 + a2; /* d1 += a1 * 0.01; d2 += a2 * 0.01; float nd = Vector3::Dot(d1, d2) / (d1.GetLength() * d2.GetLength()); if(dot > maxDot){ if(nd < dot) printf("GOOD %f -> %f\n", dot, nd); else printf("BAD %f -> %f\n", dot, nd); }else{ if(nd > dot) printf("GOOD %f -> %f\n", dot, nd); else printf("BAD %f -> %f\n", dot, nd); }*/ }
Vector3 Matrix4::GetAxis(int axis) const { switch (axis) { case 0: return MakeVector3(m[0], m[1], m[2]); case 1: return MakeVector3(m[4], m[5], m[6]); default: SPAssert(false); case 2: return MakeVector3(m[8], m[9], m[10]); } }
void GLFramebufferManager::BufferHandle::Release(){ if(valid){ Buffer&b = manager->buffers[bufferIndex]; SPAssert(b.refCount > 0); b.refCount--; valid = false; } }
virtual int GetDamage(HitType type, float distance) { switch (type) { case HitTypeTorso: return 27; case HitTypeHead: return 37; case HitTypeArms: return 16; case HitTypeLegs: return 16; case HitTypeBlock: return 34; default: SPAssert(false); return 0; } }
Bitmap::Bitmap(uint32_t *pixels, int w, int h): pixels(pixels), w(w), h(h), autoDelete(false) { SPADES_MARK_FUNCTION(); if(w < 1 || h < 1 || w > 8192 || h > 8192) { SPRaise("Invalid dimension: %dx%d", w, h); } SPAssert(pixels != NULL); }
bool SmokeSpriteEntity::Update(float dt) { frame += dt * fps; frame = fmodf(frame, 180.f); int fId = (int)floorf(frame); SPAssert(fId >= 0 && fId < 180); SetImage(GetSequence(fId, GetRenderer())); return ParticleSpriteEntity::Update(dt); }
Bitmap::Bitmap(int ww, int hh): w(ww), h(hh), autoDelete(true), pixels(nullptr) { SPADES_MARK_FUNCTION(); if(w < 1 || h < 1 || w > 8192 || h > 8192) { SPRaise("Invalid dimension: %dx%d", w, h); } pixels = new uint32_t[w * h]; SPAssert(pixels != NULL); }
int DeflateStream::ReadByte() { SPADES_MARK_FUNCTION(); if(bufferPos >= buffer.size()){ FillBuffer(); } if(bufferPos >= buffer.size()){ SPAssert(reachedEOF); return -1; } position++; return (unsigned char)buffer[bufferPos++]; }
void Corpse::Spring(NodeType n1a, NodeType n1b, NodeType n2, float distance, float dt){ SPADES_MARK_FUNCTION_DEBUG(); SPAssert(n1a >= 0); SPAssert(n1a < NodeCount); SPAssert(n1b >= 0); SPAssert(n1b < NodeCount); SPAssert(n2 >= 0); SPAssert(n2 < NodeCount); Node& x = nodes[n1a]; Node& y = nodes[n1b]; Node& b = nodes[n2]; Vector3 diff = b.pos - (x.pos + y.pos) * .5f; float dist = diff.GetLength(); Vector3 force = diff.Normalize() * (distance - dist); force *= dt * 50.f; b.vel += force; force *= .5f; x.vel -= force; y.vel -= force; Vector3 velMid = (x.vel + y.vel) * .25f + b.vel * .5f; float dump = 1.f - powf(.05f, dt); x.vel += (velMid - x.vel) * dump; y.vel += (velMid - y.vel) * dump; b.vel += (velMid - b.vel) * dump; }
virtual int GetDamage(HitType type, float distance) { switch (type) { case HitTypeTorso: return 27; case HitTypeHead: return 37; case HitTypeArms: return 16; case HitTypeLegs: return 16; case HitTypeBlock: // Actually, you cast a hit per pallet. This value is a guess, by the way. // --GM return 34; default: SPAssert(false); return 0; } }
/** Combines `dest` and `src`. Group `src` will be * no longer valid. */ void CombineGroup(size_t dest, size_t src) { Group& g1 = groups[dest]; Group& g2 = groups[src]; FillGroupMap(g2.tile1, g2.tile2, dest); // extend the area g1.tile1.x = std::min(g1.tile1.x, g2.tile1.x); g1.tile1.y = std::min(g1.tile1.y, g2.tile1.y); g1.tile2.x = std::max(g1.tile2.x, g2.tile2.x); g1.tile2.y = std::max(g1.tile2.y, g2.tile2.y); g1.lod = std::max(g1.lod, g2.lod); // combine the instance list // [g1] + [g2] -> [g1, g2] Instance& destLast = allInstances[g1.lastInstance]; Instance& srcFirst = allInstances[g2.firstInstance]; SPAssert(destLast.next == NoInstance); SPAssert(srcFirst.prev == NoInstance); destLast.next = g2.firstInstance; srcFirst.prev = g1.lastInstance; g1.lastInstance = g2.lastInstance; // make sure the area is filled with the group `dest` IntVector3 tile1 = g1.tile1, tile2 = g1.tile2; for(int x = tile1.x; x < tile2.x; x++) for(int y = tile1.y; y < tile2.y; y++){ size_t g = groupMap[x][y]; SPAssert(g != src); if(g != NoGroup && g != dest) { CombineGroup(dest, g); SPAssert(groupMap[x][y] == dest); }else{ groupMap[x][y] = dest; } } g2.valid = false; }
DeflateStream::~DeflateStream() { SPADES_MARK_FUNCTION(); if(valid){ if(mode == CompressModeCompress){ deflateEnd(&zstream); }else if(mode == CompressModeDecompress){ inflateEnd(&zstream); }else{ SPAssert(false); } } if(autoClose){ delete baseStream; } }
virtual int GetDamage(HitType type, float distance) { switch (type) { // These are the 0.75 damage values. // To be honest, we don't need this information, as the server decides the // damage. // EXCEPT for blocks, that is. // --GM case HitTypeTorso: return 49; case HitTypeHead: return 100; case HitTypeArms: return 33; case HitTypeLegs: return 33; case HitTypeBlock: return 50; default: SPAssert(false); return 0; } }
static void CheckEscape(GameMap *map, IntVector3 hitBlock, IntVector3 a, IntVector3 b, IntVector3 dir, float& bestDist, IntVector3& bestDir) { hitBlock += dir; IntVector3 aa = a + dir; IntVector3 bb = b + dir; if(map->IsSolidWrapped(hitBlock.x, hitBlock.y, hitBlock.z)) return; if(map->IsSolidWrapped(aa.x, aa.y, aa.z)) return; if(map->IsSolidWrapped(bb.x, bb.y, bb.z)) return; float dist; if(dir.x == 1) { dist = 1.f - fractf(a.x); dist += 1.f - fractf(b.x); }else if(dir.x == -1) { dist = fractf(a.x); dist += fractf(b.x); }else if(dir.y == 1) { dist = 1.f - fractf(a.y); dist += 1.f - fractf(b.y); }else if(dir.y == -1) { dist = fractf(a.y); dist += fractf(b.y); }else if(dir.z == 1) { dist = 1.f - fractf(a.z); dist += 1.f - fractf(b.z); }else if(dir.z == -1) { dist = fractf(a.z); dist += fractf(b.z); }else{ SPAssert(false); return; } if(dist < bestDist){ bestDist = dist; bestDir = dir; } }
void GLVoxelModel::EmitFace(spades::VoxelModel *model, int x, int y, int z, int nx, int ny, int nz, uint32_t color) { SPADES_MARK_FUNCTION_DEBUG(); // decide face tangent int ux = ny, uy = nz, uz = nx; int vx = nz, vy = nx, vz = ny; if (nx < 0 || ny < 0 || nz < 0) { vx = -vx; vy = -vy; vz = -vz; } // now cross(u, v) = n (somehow) SPAssert(ux * vx + uy * vy + uz * vz == 0); SPAssert(ux * nx + uy * ny + uz * nz == 0); SPAssert(nx * vx + ny * vy + nz * vz == 0); SPAssert(uy * vz - uz * vy == -nx); SPAssert(uz * vx - ux * vz == -ny); SPAssert(ux * vy - uy * vx == -nz); // decide face origin int fx = 0, fy = 0, fz = 0; if (ux == -1 || vx == -1) { fx = 1; SPAssert(ux + vx == -1); } if (uy == -1 || vy == -1) { fy = 1; SPAssert(uy + vy == -1); } if (uz == -1 || vz == -1) { fz = 1; SPAssert(uz + vz == -1); } SPAssert(nx * fx + ny * fy + nz * fz == 0); uint8_t aoID = calcAOID(model, x + nx, y + ny, z + nz, ux, uy, uz, vx, vy, vz); Vertex v; uint32_t idx = (uint32_t)vertices.size(); v.aoID = aoID; v.red = (uint8_t)(color); v.green = (uint8_t)(color >> 8); v.blue = (uint8_t)(color >> 16); v.diffuse = 255; v.nx = nx; v.ny = ny; v.nz = nz; x += fx; y += fy; z += fz; if (nx > 0) x++; if (ny > 0) y++; if (nz > 0) z++; SPAssert(x >= 0); SPAssert(y >= 0); SPAssert(y >= 0); SPAssert(x + ux >= 0); SPAssert(y + uy >= 0); SPAssert(z + uz >= 0); SPAssert(x + vx >= 0); SPAssert(y + vy >= 0); SPAssert(z + vz >= 0); SPAssert(x + ux + vx >= 0); SPAssert(y + uy + vy >= 0); SPAssert(z + uz + vz >= 0); v.u = 0; v.v = 0; v.x = x; v.y = y; v.z = z; vertices.push_back(v); v.u = 1; v.v = 0; v.x = x + ux; v.y = y + uy; v.z = z + uz; vertices.push_back(v); v.u = 0; v.v = 1; v.x = x + vx; v.y = y + vy; v.z = z + vz; vertices.push_back(v); v.u = 1; v.v = 1; v.x = x + ux + vx; v.y = y + uy + vy; v.z = z + uz + vz; vertices.push_back(v); indices.push_back(idx); indices.push_back(idx + 1); indices.push_back(idx + 2); indices.push_back(idx + 1); indices.push_back(idx + 3); indices.push_back(idx + 2); }
void SWMapRenderer::BuildLine(Line& line, float minPitch, float maxPitch) { // hard code for further optimization enum { w = 512, h = 512 }; SPAssert(map->Width() == 512); SPAssert(map->Height() == 512); const auto *rle = this->rle.data(); auto& rleHeap = this->rleHeap; client::GameMap *map = this->map; // pitch culling { const auto& frustrum = renderer->frustrum; static const float pi = M_PI; const auto& horz = line.horizonDir; minPitch = -pi * 0.4999f; maxPitch = pi * 0.4999f; auto cull = [&minPitch, &maxPitch]() { minPitch = 2.f; maxPitch = -2.f; }; auto clip = [&minPitch, &maxPitch, &horz, &cull](Vector3 plane) { if(plane.x == 0.f && plane.y == 0.f) { if(plane.z > 0.f) { minPitch = std::max(minPitch, 0.f); }else{ maxPitch = std::min(maxPitch, 0.f); } }else if(plane.z == 0.f){ if(Vector3::Dot(plane, horz) < 0.f) { cull(); } }else{ Vector3 prj = plane; prj.z = 0.f; prj = prj.Normalize(); float zv = fabsf(plane.z); float cs = Vector3::Dot(prj, horz); float ang = zv * zv * (1.f - cs * cs) / (cs * cs); ang = -cs * fastSqrt(1.f + ang); ang = zv / ang; if(isnan(ang) || isinf(ang) || ang == 0.f) return; // convert to tan ang = fastSqrt(1.f - ang * ang) / ang; // convert to angle ang = atanf(ang); if(isnan(ang) || isinf(ang)) return; if(plane.z > 0.f) { minPitch = std::max(minPitch, ang - 0.01f); }else{ maxPitch = std::min(maxPitch, -ang + 0.01f); } } }; clip(frustrum[2].n); clip(frustrum[3].n); clip(frustrum[4].n); clip(frustrum[5].n); } float minTan = SpecialTan(minPitch); float maxTan = SpecialTan(maxPitch); { float minDiff = lineResolution / 10000.f; if(maxTan < minTan + minDiff) { // too little difference; scale value might overflow. maxTan = minTan + minDiff; } } line.pitchTanMin = minTan; line.pitchScale = lineResolution / (maxTan - minTan); line.pitchTanMinI = static_cast<int>(minTan * 65536.f); line.pitchScaleI = static_cast<int>(line.pitchScale * 65536.f); // TODO: pitch culling // ray direction float dirX = line.horizonDir.x; float dirY = line.horizonDir.y; if(fabsf(dirY) < 1.e-4f) dirY = 1.e-4f; if(fabsf(dirX) < 1.e-4f) dirX = 1.e-4f; float invDirX = 1.f / dirX; float invDirY = 1.f / dirY; std::int_fast8_t signX = dirX > 0.f ? 1 : -1; std::int_fast8_t signY = dirY > 0.f ? 1 : -1; int invDirXI = static_cast<int>(invDirX * 256.f); int invDirYI = static_cast<int>(invDirY * 256.f); int dirXI = static_cast<int>(dirX * 512.f); int dirYI = static_cast<int>(dirY * 512.f); if(invDirXI < 0) invDirXI = -invDirXI; if(invDirYI < 0) invDirYI = -invDirYI; if(dirXI < 0) dirXI = -dirXI; if(dirYI < 0) dirYI = -dirYI; // camera position float cx = sceneDef.viewOrigin.x; float cy = sceneDef.viewOrigin.y; float cz = sceneDef.viewOrigin.z; int icz = static_cast<int>(floorf(cz)); // ray position //float rx = cx, ry = cy; int rx = static_cast<int>(cx * 512.f); int ry = static_cast<int>(cy * 512.f); // ray position in integer std::int_fast16_t irx = rx >> 9; //static_cast<int>(floorf(rx)); std::int_fast16_t iry = ry >> 9; //static_cast<int>(floorf(ry)); float fogDist = 128.f; float distance = 1.e-20f; // traveled path float invDist = 1.f / distance; //auto& pixels = line.pixels; line.pixels.resize(lineResolution); auto *pixels = line.pixels.data(); // std::vector feels slow... const float transScale = static_cast<float>(lineResolution) / (maxTan - minTan); const float transOffset = -minTan * transScale; #if ENABLE_SSE if(lineResolution > 4){ static_assert(sizeof(LinePixel) == 8, "size of LinePixel has changed; needs code modification"); union { LinePixel pxs[2]; __m128 m; }; pxs[0].Clear(); pxs[1].Clear(); auto *ptr = pixels; for(auto *e = pixels + lineResolution; (reinterpret_cast<size_t>(ptr) & 0xf) && (ptr < e); ptr++) { ptr->Clear(); } for(auto *e = pixels + lineResolution - 2; ptr < e; ptr += 2) { _mm_store_ps(reinterpret_cast<float *>(ptr), m); } for(auto *e = pixels + lineResolution; ptr < e; ptr++) { ptr->Clear(); } }else #endif for(size_t i = 0; i < lineResolution; i++) pixels[i].Clear(); // if culled out, bail out now (pixels are filled) if(minPitch >= maxPitch) return; std::array<float, 65> zval; // precompute (z - cz) * some for(size_t i = 0; i < zval.size(); i++) zval[i] = (static_cast<float>(i) - cz); float vmax = lineResolution + 0.5f; auto transform = [&zval, &transOffset, vmax, &transScale](float invDist, int z) { float p = ToSpecialTan(invDist * zval[z]) * transScale + transOffset; p = std::max(p, 0.f); p = std::min(p, vmax); return static_cast<std::uint_fast16_t>(p); }; float zscale; // travel distance -> view Z value factor zscale = Vector3::Dot(line.horizonDir, sceneDef.viewAxis[2]); float heightScale; // Z value -> view Z value factor heightScale = sceneDef.viewAxis[2].z; std::array<float, 65> heightScaleVal; // precompute (heightScale * z) for(size_t i = 0; i < zval.size(); i++) heightScaleVal[i] = (static_cast<float>(i) * heightScale); float depthBias; depthBias = -cz * heightScale; RleData *lastRle; { auto ref = rle[(irx & w-1) + ((iry & h-1) * w)]; lastRle = rleHeap.Dereference<RleData>(ref); } std::uint_fast16_t count = 1; std::uint_fast16_t cnt2 = static_cast<int>(fogDist * 8.f); while(distance < fogDist && (--cnt2) > 0) { std::int_fast16_t nextIRX, nextIRY; auto oirx = irx, oiry = iry; // DDE Face wallFace; if(signX > 0){ nextIRX = irx + 1; if(signY > 0) { nextIRY = iry + 1; unsigned int timeToNextX = (512 - (rx & 511)) * invDirXI; unsigned int timeToNextY = (512 - (ry & 511)) * invDirYI; if(timeToNextX < timeToNextY) { // go across x plane irx = nextIRX; rx = irx << 9; ry += (dirYI * timeToNextX) >> 17; distance += static_cast<float>(timeToNextX) * (1.f / 512.f / 256.f); wallFace = Face::NegX; }else{
void Player::FireWeapon() { SPADES_MARK_FUNCTION(); Vector3 muzzle = GetEye(); muzzle += GetFront() * 0.01f; // for hit-test debugging std::map<int, HitTestDebugger::PlayerHit> playerHits; std::vector<Vector3> bulletVectors; //Vector3 right = GetRight(); //Vector3 up = GetUp(); int pellets = weapon->GetPelletSize(); float spread = weapon->GetSpread(); GameMap *map = world->GetMap(); // pyspades takes destroying more than one block as a // speed hack (shotgun does this) bool blockDestroyed = false; Vector3 dir2 = GetFront(); for(int i =0 ; i < pellets; i++){ // AoS 0.75's way (dir2 shouldn't be normalized!) dir2.x += (GetRandom() - GetRandom()) * spread; dir2.y += (GetRandom() - GetRandom()) * spread; dir2.z += (GetRandom() - GetRandom()) * spread; Vector3 dir = dir2.Normalize(); bulletVectors.push_back(dir); // first do map raycast GameMap::RayCastResult mapResult; mapResult = map->CastRay2(muzzle, dir, 500); Player *hitPlayer = NULL; float hitPlayerDistance = 0.f; HitBodyPart hitPart = HitBodyPart::None; for(int i = 0; i < world->GetNumPlayerSlots(); i++){ Player *other = world->GetPlayer(i); if(other == this || other == NULL) continue; if(other == this || !other->IsAlive() || other->GetTeamId() >= 2) continue; // quickly reject players unlikely to be hit if(!other->RayCastApprox(muzzle, dir)) continue; HitBoxes hb = other->GetHitBoxes(); Vector3 hitPos; if(hb.head.RayCast(muzzle, dir, &hitPos)) { float dist = (hitPos - muzzle).GetLength(); if(hitPlayer == NULL || dist < hitPlayerDistance){ hitPlayer = other; hitPlayerDistance = dist; hitPart = HitBodyPart::Head; } } if(hb.torso.RayCast(muzzle, dir, &hitPos)) { float dist = (hitPos - muzzle).GetLength(); if(hitPlayer == NULL || dist < hitPlayerDistance){ hitPlayer = other; hitPlayerDistance = dist; hitPart = HitBodyPart::Torso; } } for(int j = 0; j < 3 ;j++){ if(hb.limbs[j].RayCast(muzzle, dir, &hitPos)) { float dist = (hitPos - muzzle).GetLength(); if(hitPlayer == NULL || dist < hitPlayerDistance){ hitPlayer = other; hitPlayerDistance = dist; switch(j) { case 0: hitPart = HitBodyPart::Limb1; break; case 1: hitPart = HitBodyPart::Limb2; break; case 2: hitPart = HitBodyPart::Arms; break; } } } } } Vector3 finalHitPos; finalHitPos = muzzle + dir * 128.f; if(hitPlayer == nullptr && !mapResult.hit) { // might hit water surface. } if(mapResult.hit && (mapResult.hitPos - muzzle).GetLength() < 128.f && (hitPlayer == NULL || (mapResult.hitPos - muzzle).GetLength() < hitPlayerDistance)){ IntVector3 outBlockCoord = mapResult.hitBlock; // TODO: set correct ray distance // FIXME: why ray casting twice? finalHitPos = mapResult.hitPos; if(outBlockCoord.x >= 0 && outBlockCoord.y >= 0 && outBlockCoord.z >= 0 && outBlockCoord.x < map->Width() && outBlockCoord.y < map->Height() && outBlockCoord.z < map->Depth()){ if(outBlockCoord.z == 63) { if(world->GetListener()) world->GetListener()->BulletHitBlock(mapResult.hitPos, mapResult.hitBlock, mapResult.normal); }else if(outBlockCoord.z == 62) { // blocks at this level cannot be damaged if(world->GetListener()) world->GetListener()->BulletHitBlock(mapResult.hitPos, mapResult.hitBlock, mapResult.normal); }else{ int x = outBlockCoord.x; int y = outBlockCoord.y; int z = outBlockCoord.z; SPAssert(map->IsSolid(x, y, z)); Vector3 blockF = {x + .5f, y + .5f, z + .5f}; float distance = (blockF - muzzle).GetLength(); uint32_t color = map->GetColor(x, y, z); int health = color >> 24; health -= weapon->GetDamage(HitTypeBlock, distance); if(health <= 0 && !blockDestroyed){ health = 0; blockDestroyed = true; //send destroy cmd if(world->GetListener() && world->GetLocalPlayer() == this) world->GetListener()->LocalPlayerBlockAction (outBlockCoord, BlockActionTool); } color = (color & 0xffffff) | ((uint32_t)health << 24); if(map->IsSolid(x, y, z)) map->Set(x, y, z, true, color); if(world->GetListener()) world->GetListener()->BulletHitBlock(mapResult.hitPos, mapResult.hitBlock, mapResult.normal); } } }else if(hitPlayer != NULL){