bigger commits
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#define ANIMATOR_CLASS
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
@@ -9,6 +10,9 @@
|
||||
#include <assimp/Importer.hpp>
|
||||
#include "Rig.h"
|
||||
|
||||
// Scale of the "Head" bone (and what hangs off it), around the neck. 1 = as authored.
|
||||
inline float HeadScale = 0.25f;
|
||||
|
||||
struct AssimpNodeData
|
||||
{
|
||||
glm::mat4 transformation;
|
||||
@@ -199,6 +203,8 @@ private:
|
||||
}
|
||||
|
||||
glm::mat4 globalTransform = parentTransform * nodeTransform;
|
||||
if (nodeName == "Head")
|
||||
globalTransform = glm::scale(globalTransform, glm::vec3(HeadScale));
|
||||
|
||||
auto it = m_BoneInfoMap.find(nodeName);
|
||||
if (it != m_BoneInfoMap.end()) {
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
const glm::vec3 worldUp = { 0.0f, 1.0f, 0.0f };
|
||||
|
||||
// Mouse sensitivity asked at startup (see main.cpp). 0 = keep the camera's built-in default.
|
||||
inline float StartupCmPer360 = 0.0f;
|
||||
inline float StartupMouseDpi = 800.0f;
|
||||
|
||||
class Camera {
|
||||
public:
|
||||
bool lockedcursor = true;
|
||||
@@ -28,7 +32,15 @@ public:
|
||||
float FOV = 110.0f;
|
||||
float screenshake = 0.0f;
|
||||
|
||||
Camera() {}
|
||||
Camera() {
|
||||
if (StartupCmPer360 > 0.0f) SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
||||
}
|
||||
|
||||
// Turn sensitivity into "this many cm of mouse travel per full 360 degree turn" for a mouse
|
||||
// of the given DPI (mouse counts per inch).
|
||||
void SetSensitivityCm(float cmPer360, float dpi) {
|
||||
sensitivity = 6.2831853f / (cmPer360 / 2.54f * dpi);
|
||||
}
|
||||
|
||||
void AddScreenshake(float amount) {
|
||||
screenshake += amount;
|
||||
|
||||
+128
-20
@@ -145,7 +145,7 @@ void Engine::Initiate()
|
||||
Character* testcharacter = new Character();
|
||||
testcharacter->name = "character";
|
||||
testcharacter->castshadow = true;
|
||||
testcharacter->body->t.ScaleBy({ 0.6f, 2.1f, 0.6f });
|
||||
testcharacter->body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
||||
testcharacter->body->t.TranslateTo({ 0.0f, 10.0f, 0.0f });
|
||||
testcharacter->body->SetRig(testrig);
|
||||
|
||||
@@ -172,8 +172,8 @@ void Engine::Initiate()
|
||||
Character* npc = new Character();
|
||||
npc->name = "npc";
|
||||
npc->castshadow = true;
|
||||
npc->body->t.ScaleBy({ 0.6f, 2.1f, 0.6f });
|
||||
npc->body->t.TranslateTo({ 3.0f, 1.05f, 3.0f });
|
||||
npc->body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
||||
npc->body->t.TranslateTo({ 3.0f, 1.25f, 3.0f });
|
||||
npc->body->SetRig(testrig);
|
||||
npc->body->SetAnimation(testanimation);
|
||||
npc->BindToRenderSystem();
|
||||
@@ -197,7 +197,7 @@ void Engine::Initiate()
|
||||
Character* c = new Character();
|
||||
c->name = "remote";
|
||||
c->castshadow = true;
|
||||
c->body->t.ScaleBy({ 0.6f, 2.1f, 0.6f });
|
||||
c->body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
||||
c->body->SetRig(testrig);
|
||||
c->body->SetAnimation(testanimation);
|
||||
c->BindToRenderSystem();
|
||||
@@ -393,13 +393,13 @@ void Engine::Initiate()
|
||||
|
||||
// Map is a rectangle: x in [-60, 60], z in [-30, 30] (authored at 1x, scaled by mapScale)
|
||||
// Centre dome
|
||||
AddTiers("dome", { 0.0f, 0.0f }, 18.0f, 1.0f, 5);
|
||||
AddTiers("dome", { 0.0f, 0.0f }, 18.0f, 2.0f, 5);
|
||||
// Cones, opposite corners of the dome
|
||||
AddTiers("cone", { -12.0f, -13.0f }, 6.0f, 1.0f, 5);
|
||||
AddTiers("cone", { 12.0f, 13.0f }, 6.0f, 1.0f, 5);
|
||||
// Big cubes either side of the dome
|
||||
AddBlock("cube", { -24.0f, 0.0f, 0.0f }, { 7.0f, 7.0f, 7.0f });
|
||||
AddBlock("cube", { 24.0f, 0.0f, 0.0f }, { 7.0f, 7.0f, 7.0f });
|
||||
AddBlock("cube", { -24.0f, 0.0f, 0.0f }, { 7.0f, 8.4f, 7.0f });
|
||||
AddBlock("cube", { 24.0f, 0.0f, 0.0f }, { 7.0f, 8.4f, 7.0f });
|
||||
// Stairs climbing to a tall back wall (the ramp), back right
|
||||
AddBlock("stair", { 29.0f, 0.0f, -12.0f }, { 2.5f, 1.0f, 10.0f });
|
||||
AddBlock("stair", { 31.5f, 0.0f, -12.0f }, { 2.5f, 2.0f, 10.0f });
|
||||
@@ -437,7 +437,10 @@ void Engine::Initiate()
|
||||
// body = 50 damage (two shots). Respawns the target when it dies.
|
||||
// Movement tuning
|
||||
const float airControl = 0.5f; // fraction of ground acceleration available in the air
|
||||
const float grappleRange = 12.0f; // metres from the player
|
||||
const float shockedAirControl = 0.1f; // air control after being hit by another player's shockwave
|
||||
const float shockedDuration = 2.0f; // seconds the reduced air control lasts
|
||||
float shockedTime = 0.0f;
|
||||
const float grappleRange = 30.0f; // metres from the player
|
||||
const float grappleSpeed = 28.0f; // launch speed toward the hit point
|
||||
const float grappleCooldownTime = 2.5f; // seconds
|
||||
float grappleCooldown = 0.0f;
|
||||
@@ -448,7 +451,7 @@ void Engine::Initiate()
|
||||
const float slideDuration = 0.8f;
|
||||
const float slideFalloffTime = 0.7f;
|
||||
const float slideDropY = 0.65f; // standing centre height (0.9) minus lying (0.25)
|
||||
const float standEyeOffset = 0.85f; // eyes 0.85 above body centre (body centre 1.05, height 2.1)
|
||||
const float standEyeOffset = 1.05f; // eyes 1.05 above body centre (body centre 1.25, height 2.5)
|
||||
const float slideEyeOffset = 0.25f; // lying down, eyes just above the floor
|
||||
float eyeOffset = standEyeOffset;
|
||||
bool sliding = false;
|
||||
@@ -459,6 +462,33 @@ void Engine::Initiate()
|
||||
float dashCooldown = 0.0f;
|
||||
const float shootCooldownTime = 0.5f; // seconds between shots
|
||||
float shootCooldown = 0.0f;
|
||||
|
||||
// Cubes (E). Whoever hosts (or an offline player) owns and simulates them and decides when they
|
||||
// despawn; LAN clients only ask the host for one and draw the poses it streams back.
|
||||
const float cubeCooldownTime = 0.5f; // seconds between cubes per player
|
||||
const double cubeLifetime = 30.0; // seconds before a cube despawns
|
||||
float cubeCooldown = 0.0f;
|
||||
// Cubes also rain from the sky on their own (host / offline only; clients see them via the stream)
|
||||
const float cubeRainInterval = 0.5f; // seconds between rain cubes
|
||||
const float cubeRainHeight = 45.0f;
|
||||
const size_t cubeRainMax = 200; // stop raining while this many cubes exist
|
||||
float cubeRainTimer = 0.0f;
|
||||
struct HostCube { uint32_t id; Mesh* mesh; double spawnTime; };
|
||||
std::vector<HostCube> hostCubes;
|
||||
uint32_t nextCubeId = 0;
|
||||
std::map<uint8_t, double> lastCubeSpawn; // host: when each client last spawned one (rate limit)
|
||||
struct PuppetCube { Mesh* mesh; double lastSeen; };
|
||||
std::map<uint32_t, PuppetCube> puppetCubes; // client: the host's cubes, keyed by host id
|
||||
auto SpawnCube = [&](const glm::vec3& at) {
|
||||
Mesh* nc = CreateCubeMesh();
|
||||
nc->name = "cube!";
|
||||
nc->t.ScaleTo(1.0f);
|
||||
nc->t.TranslateTo(at);
|
||||
nc->BindToRenderSystem();
|
||||
mainf->AddChild(nc);
|
||||
physicsengine->AddObject(new p(nc));
|
||||
hostCubes.push_back({ ++nextCubeId, nc, glfwGetTime() });
|
||||
};
|
||||
glm::vec3 slideDir(0.0f);
|
||||
|
||||
// Shockwave (key Q): projectile that explodes on the first surface it touches. The blast
|
||||
@@ -508,6 +538,9 @@ void Engine::Initiate()
|
||||
body->linearvelocity += dir * power * falloff;
|
||||
body->WakeUp();
|
||||
|
||||
// Another player's blast (not our own) takes away most of our air control for a while
|
||||
if (body == characterBody && fromNet) shockedTime = shockedDuration;
|
||||
|
||||
if (body == characterBody && sliding) {
|
||||
// The slide overrides horizontal velocity every frame, so end it and stand back up
|
||||
sliding = false;
|
||||
@@ -673,15 +706,12 @@ void Engine::Initiate()
|
||||
// Input � keyboard events
|
||||
// --------------------------------------------------------------------
|
||||
while (Keyboard::Event buffer = keyboard->ReadKey()) {
|
||||
if (buffer.GetCode() == 'E' && buffer.IsPress()) {
|
||||
Mesh* nc = CreateCubeMesh();
|
||||
nc->name = "cube!";
|
||||
nc->t.ScaleTo(1.0f);
|
||||
nc->t.TranslateTo(camera->t.GetTranslation());
|
||||
nc->t.RotateByEulerAngles({ 0.0f, 0.0f, 0.0f });
|
||||
nc->BindToRenderSystem();
|
||||
mainf->AddChild(nc);
|
||||
physicsengine->AddObject(new p(nc));
|
||||
if (buffer.GetCode() == 'E' && buffer.IsPress() && cubeCooldown <= 0.0f) {
|
||||
cubeCooldown = cubeCooldownTime;
|
||||
if (lan && lan->IsActive() && lan->GetMode() == Lan::Mode::Client)
|
||||
lan->SendCubeSpawn(camera->t.GetTranslation()); // the host spawns it
|
||||
else
|
||||
SpawnCube(camera->t.GetTranslation());
|
||||
}
|
||||
if (buffer.GetCode() == 'F' && buffer.IsPress()) {
|
||||
const glm::vec3 playerPos = testcharacter->body->t.GetTranslation();
|
||||
@@ -808,6 +838,7 @@ void Engine::Initiate()
|
||||
slideCooldown = std::max(slideCooldown - deltatime, 0.0f);
|
||||
dashCooldown = std::max(dashCooldown - deltatime, 0.0f);
|
||||
shootCooldown = std::max(shootCooldown - deltatime, 0.0f);
|
||||
cubeCooldown = std::max(cubeCooldown - deltatime, 0.0f);
|
||||
const float accel = 40.0f;
|
||||
const float damp = 0.75f;
|
||||
const float groundTopSpeed = movespeed * accel * refDt * damp / (1.0f - damp);
|
||||
@@ -842,7 +873,7 @@ void Engine::Initiate()
|
||||
const float current = glm::dot(glm::vec3(characterBody->linearvelocity.x, 0.0f,
|
||||
characterBody->linearvelocity.z), wishDir);
|
||||
const float add = glm::clamp(groundTopSpeed - current, 0.0f,
|
||||
accel * airControl * movespeed * deltatime);
|
||||
accel * (shockedTime > 0.0f ? shockedAirControl : airControl) * movespeed * deltatime);
|
||||
characterBody->linearvelocity.x += wishDir.x * add;
|
||||
characterBody->linearvelocity.z += wishDir.z * add;
|
||||
}
|
||||
@@ -860,6 +891,7 @@ void Engine::Initiate()
|
||||
|
||||
// Shockwave projectile: arc forward, explode on the first collider along this frame's path
|
||||
shockCooldown = std::max(shockCooldown - deltatime, 0.0f);
|
||||
shockedTime = std::max(shockedTime - deltatime, 0.0f);
|
||||
if (shockActive) {
|
||||
shockVel.y -= shockGravity * deltatime;
|
||||
shockAge += deltatime;
|
||||
@@ -983,6 +1015,36 @@ void Engine::Initiate()
|
||||
// --------------------------------------------------------------------
|
||||
// LAN: send our transform, apply the other player's
|
||||
// --------------------------------------------------------------------
|
||||
// Rain cubes from the sky at random spots over the map (host / offline)
|
||||
if (!lan || !lan->IsActive() || lan->GetMode() == Lan::Mode::Host) {
|
||||
cubeRainTimer -= deltatime;
|
||||
if (cubeRainTimer <= 0.0f) {
|
||||
cubeRainTimer += cubeRainInterval;
|
||||
if (hostCubes.size() < cubeRainMax) {
|
||||
glm::vec3 at = RandomSpawn();
|
||||
at.y = cubeRainHeight;
|
||||
SpawnCube(at);
|
||||
std::uniform_real_distribution<float> spin(0.0f, 6.28318f);
|
||||
hostCubes.back().mesh->t.RotateToEulerAngles({ spin(respawnRng), spin(respawnRng), spin(respawnRng) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Despawn cubes that have lived long enough (host / offline)
|
||||
{
|
||||
const double cubeNow = glfwGetTime();
|
||||
for (size_t i = 0; i < hostCubes.size();) {
|
||||
if (cubeNow - hostCubes[i].spawnTime > cubeLifetime) {
|
||||
Mesh* m = hostCubes[i].mesh;
|
||||
rendersystem->RemoveRenderable(m);
|
||||
physicsengine->RemoveObject(m);
|
||||
m->Delete();
|
||||
hostCubes.erase(hostCubes.begin() + i);
|
||||
}
|
||||
else ++i;
|
||||
}
|
||||
}
|
||||
|
||||
if (lan && lan->IsActive()) {
|
||||
NetState mine;
|
||||
mine.position = testcharacter->body->t.GetTranslation();
|
||||
@@ -994,7 +1056,53 @@ void Engine::Initiate()
|
||||
std::vector<NetUpdate> updates;
|
||||
std::vector<NetBlast> blasts;
|
||||
std::vector<NetHit> hits;
|
||||
lan->Receive(updates, &blasts, &hits);
|
||||
std::vector<NetCubeRequest> cubeRequests;
|
||||
std::vector<NetCube> cubeStates;
|
||||
lan->Receive(updates, &blasts, &hits, &cubeRequests, &cubeStates);
|
||||
|
||||
// Host: spawn the cubes clients asked for (rate limited per client), then stream every cube's pose
|
||||
for (const NetCubeRequest& rq : cubeRequests) {
|
||||
double& last = lastCubeSpawn[rq.sender];
|
||||
if (now - last < cubeCooldownTime * 0.9) continue;
|
||||
last = now;
|
||||
SpawnCube(glm::clamp(rq.position, glm::vec3(-200.0f), glm::vec3(200.0f)));
|
||||
}
|
||||
if (lan->GetMode() == Lan::Mode::Host) {
|
||||
std::vector<NetCube> snapshot;
|
||||
snapshot.reserve(hostCubes.size());
|
||||
for (const HostCube& hc : hostCubes) {
|
||||
NetCube nc;
|
||||
nc.id = hc.id;
|
||||
nc.position = hc.mesh->t.GetTranslation();
|
||||
nc.rotation = hc.mesh->t.GetRotationQuaternion();
|
||||
snapshot.push_back(nc);
|
||||
}
|
||||
lan->SendCubes(snapshot);
|
||||
}
|
||||
|
||||
// Client: show the host's cubes, and drop the ones it stopped sending (despawned)
|
||||
for (const NetCube& nc : cubeStates) {
|
||||
auto it = puppetCubes.find(nc.id);
|
||||
if (it == puppetCubes.end()) {
|
||||
Mesh* m = CreateCubeMesh();
|
||||
m->name = "cube!";
|
||||
m->t.ScaleTo(1.0f);
|
||||
m->BindToRenderSystem();
|
||||
mainf->AddChild(m);
|
||||
it = puppetCubes.emplace(nc.id, PuppetCube{ m, now }).first;
|
||||
}
|
||||
it->second.mesh->t.TranslateTo(nc.position);
|
||||
it->second.mesh->t.RotateToQuaternion(nc.rotation);
|
||||
it->second.lastSeen = now;
|
||||
}
|
||||
for (auto it = puppetCubes.begin(); it != puppetCubes.end();) {
|
||||
if (now - it->second.lastSeen > 1.5) {
|
||||
rendersystem->RemoveRenderable(it->second.mesh);
|
||||
it->second.mesh->Delete();
|
||||
it = puppetCubes.erase(it);
|
||||
}
|
||||
else ++it;
|
||||
}
|
||||
for (const NetBlast& b : blasts)
|
||||
Explode(b.center, true, b.radius, b.power);
|
||||
// Damage from other players (respawn handles dying, see the player respawn check)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t NET_MAGIC = 0x45494946; // "EIIF"
|
||||
constexpr uint16_t NET_VERSION = 2;
|
||||
constexpr uint16_t NET_VERSION = 3;
|
||||
constexpr unsigned short NET_PORT = 27015;
|
||||
constexpr double NET_TIMEOUT = 2.0; // seconds of silence before a player is dropped
|
||||
|
||||
@@ -67,6 +67,30 @@ namespace {
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
constexpr uint32_t NET_MAGIC_SPAWN = 0x45494953; // "EIIS"
|
||||
constexpr uint32_t NET_MAGIC_CUBES = 0x45494943; // "EIIC"
|
||||
constexpr int CUBES_PER_PACKET = 36;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct SpawnPacket {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint8_t sender;
|
||||
uint32_t id;
|
||||
float pos[3];
|
||||
};
|
||||
struct CubesHeader {
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint8_t count;
|
||||
};
|
||||
struct CubeEntry {
|
||||
uint32_t id;
|
||||
float pos[3];
|
||||
float rot[4]; // x, y, z, w
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
constexpr int MAX_ENTRIES = Lan::MaxPlayers;
|
||||
constexpr int MAX_DATAGRAM = (int)sizeof(Header) + MAX_ENTRIES * (int)sizeof(Entry);
|
||||
|
||||
@@ -127,6 +151,11 @@ struct Lan::Impl {
|
||||
uint32_t blastSeq = 0;
|
||||
std::vector<uint64_t> seenBlasts; // (type, sender, id) of recent blasts/hits, for de-duplication
|
||||
|
||||
struct PendingSpawn { SpawnPacket pkt; int copiesLeft; };
|
||||
std::vector<PendingSpawn> pendingSpawns;
|
||||
uint32_t spawnSeq = 0;
|
||||
size_t cubeCursor = 0; // host: where the next slice of cubes starts
|
||||
|
||||
struct PendingHit { HitPacket pkt; int copiesLeft; };
|
||||
std::vector<PendingHit> pendingHits;
|
||||
uint32_t hitSeq = 0;
|
||||
@@ -232,6 +261,7 @@ void Lan::Stop() {
|
||||
impl->peers.clear();
|
||||
impl->pendingBlasts.clear();
|
||||
impl->pendingHits.clear();
|
||||
impl->pendingSpawns.clear();
|
||||
impl->seenBlasts.clear();
|
||||
impl->anyRecv = false;
|
||||
active = false;
|
||||
@@ -266,9 +296,60 @@ void Lan::SendHit(uint8_t target, const NetHit& h) {
|
||||
impl->pendingHits.push_back(ph);
|
||||
}
|
||||
|
||||
void Lan::SendCubeSpawn(const glm::vec3& position) {
|
||||
if (!active || mode != Mode::Client) return;
|
||||
Impl::PendingSpawn ps;
|
||||
ps.pkt.magic = NET_MAGIC_SPAWN;
|
||||
ps.pkt.version = NET_VERSION;
|
||||
ps.pkt.sender = 0; // the host stamps the real id
|
||||
ps.pkt.id = ++impl->spawnSeq;
|
||||
ps.pkt.pos[0] = position.x; ps.pkt.pos[1] = position.y; ps.pkt.pos[2] = position.z;
|
||||
ps.copiesLeft = BLAST_COPIES;
|
||||
impl->pendingSpawns.push_back(ps);
|
||||
}
|
||||
|
||||
void Lan::SendCubes(const std::vector<NetCube>& cubes) {
|
||||
if (!active || mode != Mode::Host || cubes.empty() || impl->peers.empty()) return;
|
||||
|
||||
const size_t n = cubes.size();
|
||||
const size_t count = std::min(n, (size_t)CUBES_PER_PACKET);
|
||||
if (impl->cubeCursor >= n) impl->cubeCursor = 0;
|
||||
|
||||
char buf[sizeof(CubesHeader) + CUBES_PER_PACKET * sizeof(CubeEntry)];
|
||||
CubesHeader h;
|
||||
h.magic = NET_MAGIC_CUBES;
|
||||
h.version = NET_VERSION;
|
||||
h.count = (uint8_t)count;
|
||||
std::memcpy(buf, &h, sizeof(h));
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
const NetCube& c = cubes[(impl->cubeCursor + i) % n];
|
||||
CubeEntry e;
|
||||
e.id = c.id;
|
||||
e.pos[0] = c.position.x; e.pos[1] = c.position.y; e.pos[2] = c.position.z;
|
||||
e.rot[0] = c.rotation.x; e.rot[1] = c.rotation.y; e.rot[2] = c.rotation.z; e.rot[3] = c.rotation.w;
|
||||
std::memcpy(buf + sizeof(h) + i * sizeof(e), &e, sizeof(e));
|
||||
}
|
||||
impl->cubeCursor = (impl->cubeCursor + count) % n;
|
||||
|
||||
const int len = (int)(sizeof(h) + count * sizeof(CubeEntry));
|
||||
for (const Impl::Peer& to : impl->peers)
|
||||
sendto(impl->sock, buf, len, 0, (const sockaddr*)&to.addr, (int)sizeof(to.addr));
|
||||
}
|
||||
|
||||
void Lan::Send(const NetState& s) {
|
||||
if (!active) return;
|
||||
|
||||
// One more copy of every pending cube spawn request (client -> host)
|
||||
for (Impl::PendingSpawn& ps : impl->pendingSpawns) {
|
||||
sendto(impl->sock, (const char*)&ps.pkt, (int)sizeof(ps.pkt), 0,
|
||||
(const sockaddr*)&impl->hostAddr, (int)sizeof(impl->hostAddr));
|
||||
ps.copiesLeft--;
|
||||
}
|
||||
impl->pendingSpawns.erase(
|
||||
std::remove_if(impl->pendingSpawns.begin(), impl->pendingSpawns.end(),
|
||||
[](const Impl::PendingSpawn& ps) { return ps.copiesLeft <= 0; }),
|
||||
impl->pendingSpawns.end());
|
||||
|
||||
// One more copy of every pending hit: clients send to the host, the host to the target only
|
||||
for (Impl::PendingHit& ph : impl->pendingHits) {
|
||||
if (mode == Mode::Client) impl->SendHitPacket(impl->hostAddr, ph.pkt);
|
||||
@@ -309,10 +390,11 @@ void Lan::Send(const NetState& s) {
|
||||
}
|
||||
}
|
||||
|
||||
void Lan::Receive(std::vector<NetUpdate>& out, std::vector<NetBlast>* blasts, std::vector<NetHit>* hits) {
|
||||
void Lan::Receive(std::vector<NetUpdate>& out, std::vector<NetBlast>* blasts, std::vector<NetHit>* hits,
|
||||
std::vector<NetCubeRequest>* spawns, std::vector<NetCube>* cubes) {
|
||||
if (!active) return;
|
||||
|
||||
char buf[MAX_DATAGRAM + 64];
|
||||
char buf[1500];
|
||||
for (int i = 0; i < 512; i++) { // bounded so a flood can't stall the frame
|
||||
sockaddr_in from{};
|
||||
int fromLen = (int)sizeof(from);
|
||||
@@ -355,6 +437,50 @@ void Lan::Receive(std::vector<NetUpdate>& out, std::vector<NetBlast>* blasts, st
|
||||
continue;
|
||||
}
|
||||
|
||||
if (magic == NET_MAGIC_SPAWN) {
|
||||
if (mode != Mode::Host || r != (int)sizeof(SpawnPacket)) continue;
|
||||
SpawnPacket sp;
|
||||
std::memcpy(&sp, buf, sizeof(sp));
|
||||
if (sp.version != NET_VERSION) continue;
|
||||
|
||||
// Only known players may spawn cubes
|
||||
const Impl::Peer* sender = nullptr;
|
||||
for (const Impl::Peer& pe : impl->peers)
|
||||
if (SameAddress(pe.addr, from)) { sender = &pe; break; }
|
||||
if (!sender) continue;
|
||||
|
||||
if (spawns && impl->FirstSighting(2, sender->id, sp.id)) {
|
||||
NetCubeRequest req;
|
||||
req.sender = sender->id;
|
||||
req.id = sp.id;
|
||||
req.position = glm::vec3(sp.pos[0], sp.pos[1], sp.pos[2]);
|
||||
spawns->push_back(req);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (magic == NET_MAGIC_CUBES) {
|
||||
if (mode != Mode::Client || !SameAddress(from, impl->hostAddr)) continue;
|
||||
CubesHeader ch;
|
||||
if (r < (int)sizeof(ch)) continue;
|
||||
std::memcpy(&ch, buf, sizeof(ch));
|
||||
if (ch.version != NET_VERSION || ch.count > CUBES_PER_PACKET) continue;
|
||||
if (r != (int)(sizeof(ch) + ch.count * sizeof(CubeEntry))) continue;
|
||||
|
||||
if (cubes) {
|
||||
for (int k = 0; k < ch.count; k++) {
|
||||
CubeEntry e;
|
||||
std::memcpy(&e, buf + sizeof(ch) + k * sizeof(e), sizeof(e));
|
||||
NetCube c;
|
||||
c.id = e.id;
|
||||
c.position = glm::vec3(e.pos[0], e.pos[1], e.pos[2]);
|
||||
c.rotation = glm::quat(e.rot[3], e.rot[0], e.rot[1], e.rot[2]);
|
||||
cubes->push_back(c);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (magic == NET_MAGIC_HIT) {
|
||||
if (r != (int)sizeof(HitPacket)) continue;
|
||||
HitPacket hp;
|
||||
|
||||
@@ -43,6 +43,21 @@ struct NetHit {
|
||||
glm::vec3 knockback = glm::vec3(0.0f); // added to the victim's velocity
|
||||
};
|
||||
|
||||
// A physics cube owned by the host. The host simulates every cube and streams their poses to the
|
||||
// clients, which only draw them. The host also decides when a cube despawns.
|
||||
struct NetCube {
|
||||
uint32_t id = 0;
|
||||
glm::vec3 position = glm::vec3(0.0f);
|
||||
glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
};
|
||||
|
||||
// A client asking the host to spawn a cube. Re-sent a few times (UDP) and de-duplicated by the host.
|
||||
struct NetCubeRequest {
|
||||
uint8_t sender = 0; // filled in by the host
|
||||
uint32_t id = 0;
|
||||
glm::vec3 position = glm::vec3(0.0f);
|
||||
};
|
||||
|
||||
class Lan {
|
||||
public:
|
||||
enum class Mode { Off, Host, Client };
|
||||
@@ -65,8 +80,16 @@ public:
|
||||
// Drains all waiting packets and appends the other players' newest states to 'out'.
|
||||
// If 'blasts' is given, new (de-duplicated) shockwave blasts from other players are appended to it.
|
||||
// 'hits' gets damage aimed at us (de-duplicated).
|
||||
// 'spawns' (host only) gets cube spawn requests from clients; 'cubes' (client only) gets the
|
||||
// host's cube poses.
|
||||
void Receive(std::vector<NetUpdate>& out, std::vector<NetBlast>* blasts = nullptr,
|
||||
std::vector<NetHit>* hits = nullptr);
|
||||
std::vector<NetHit>* hits = nullptr, std::vector<NetCubeRequest>* spawns = nullptr,
|
||||
std::vector<NetCube>* cubes = nullptr);
|
||||
// Client: ask the host to spawn a cube at 'position'. Sent over the next few Send() calls.
|
||||
void SendCubeSpawn(const glm::vec3& position);
|
||||
// Host: stream cube poses to every client. Call once per frame; large sets go out in rotating
|
||||
// slices, so a cube is refreshed every few frames.
|
||||
void SendCubes(const std::vector<NetCube>& cubes);
|
||||
// Damage another player. 'target' is the id from their NetUpdate (the host is 0).
|
||||
void SendHit(uint8_t target, const NetHit& hit);
|
||||
// Tell the other players about a blast we caused. Sent over the next few Send() calls.
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
#include "SoundSystem.h"
|
||||
#include "ResourceManager.h"
|
||||
#include "Net.h"
|
||||
#include "Camera.h"
|
||||
|
||||
// Asks in the console whether to host, join or play offline (before the window opens,
|
||||
// so the window isn't frozen while waiting for typing).
|
||||
@@ -115,8 +116,29 @@ static void PromptNetworkMode() {
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse sensitivity as cm of mouse travel per 360 degree turn. Enter keeps the default.
|
||||
static void PromptSensitivity() {
|
||||
auto ask = [](const char* label, float& target) {
|
||||
std::cout << label;
|
||||
std::string line;
|
||||
std::getline(std::cin, line);
|
||||
try {
|
||||
const float v = std::stof(line);
|
||||
if (v > 0.0f) target = v;
|
||||
}
|
||||
catch (...) {} // empty / not a number: keep the default
|
||||
};
|
||||
std::cout << "\n=== MOUSE ===\n";
|
||||
float cm = 0.0f, dpi = 800.0f;
|
||||
ask("Sensitivity in cm per 360 degrees [default]: ", cm);
|
||||
if (cm > 0.0f) ask("Mouse DPI [800]: ", dpi);
|
||||
StartupCmPer360 = cm;
|
||||
StartupMouseDpi = dpi;
|
||||
}
|
||||
|
||||
int main() {
|
||||
PromptNetworkMode();
|
||||
PromptSensitivity();
|
||||
glfwInit();
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
|
||||
Reference in New Issue
Block a user