second commit

This commit is contained in:
2026-10-18 11:39:58 -04:00
parent aff6a4f902
commit 78e77a0cdc
4 changed files with 466 additions and 55 deletions
+176 -1
View File
@@ -38,6 +38,35 @@ namespace {
};
#pragma pack(pop)
constexpr uint32_t NET_MAGIC_BLAST = 0x45494942; // "EIIB"
constexpr int BLAST_COPIES = 4; // each blast is sent this many times
#pragma pack(push, 1)
struct BlastPacket {
uint32_t magic;
uint16_t version;
uint8_t sender;
uint32_t id;
float center[3];
float radius;
float power;
};
#pragma pack(pop)
constexpr uint32_t NET_MAGIC_HIT = 0x45494948; // "EIIH"
#pragma pack(push, 1)
struct HitPacket {
uint32_t magic;
uint16_t version;
uint8_t sender;
uint8_t target;
uint32_t id;
int16_t damage;
float knock[3];
};
#pragma pack(pop)
constexpr int MAX_ENTRIES = Lan::MaxPlayers;
constexpr int MAX_DATAGRAM = (int)sizeof(Header) + MAX_ENTRIES * (int)sizeof(Entry);
@@ -92,6 +121,33 @@ struct Lan::Impl {
uint32_t lastSeq = 0;
double lastRecvTime = 0.0;
// Blasts waiting for their remaining copies to go out
struct PendingBlast { BlastPacket pkt; int copiesLeft; };
std::vector<PendingBlast> pendingBlasts;
uint32_t blastSeq = 0;
std::vector<uint64_t> seenBlasts; // (type, sender, id) of recent blasts/hits, for de-duplication
struct PendingHit { HitPacket pkt; int copiesLeft; };
std::vector<PendingHit> pendingHits;
uint32_t hitSeq = 0;
void SendHitPacket(const sockaddr_in& to, const HitPacket& pkt) {
sendto(sock, (const char*)&pkt, (int)sizeof(pkt), 0, (const sockaddr*)&to, (int)sizeof(to));
}
// True the first time a (type, sender, id) triple is seen. type: 0 = blast, 1 = hit.
bool FirstSighting(uint8_t type, uint8_t sender, uint32_t id) {
const uint64_t key = ((uint64_t)type << 40) | ((uint64_t)sender << 32) | id;
if (std::find(seenBlasts.begin(), seenBlasts.end(), key) != seenBlasts.end()) return false;
seenBlasts.push_back(key);
if (seenBlasts.size() > 64) seenBlasts.erase(seenBlasts.begin());
return true;
}
void SendBlastPacket(const sockaddr_in& to, const BlastPacket& pkt) {
sendto(sock, (const char*)&pkt, (int)sizeof(pkt), 0, (const sockaddr*)&to, (int)sizeof(to));
}
void SendDatagram(const sockaddr_in& to, const std::vector<Entry>& entries) {
char buf[MAX_DATAGRAM];
Header h;
@@ -174,14 +230,68 @@ void Lan::Stop() {
impl->wsaStarted = false;
}
impl->peers.clear();
impl->pendingBlasts.clear();
impl->pendingHits.clear();
impl->seenBlasts.clear();
impl->anyRecv = false;
active = false;
mode = Mode::Off;
}
void Lan::SendBlast(const NetBlast& b) {
if (!active) return;
Impl::PendingBlast pb;
pb.pkt.magic = NET_MAGIC_BLAST;
pb.pkt.version = NET_VERSION;
pb.pkt.sender = 0; // the host stamps the real id when relaying a client's blast
pb.pkt.id = ++impl->blastSeq;
pb.pkt.center[0] = b.center.x; pb.pkt.center[1] = b.center.y; pb.pkt.center[2] = b.center.z;
pb.pkt.radius = b.radius;
pb.pkt.power = b.power;
pb.copiesLeft = BLAST_COPIES;
impl->pendingBlasts.push_back(pb);
}
void Lan::SendHit(uint8_t target, const NetHit& h) {
if (!active) return;
Impl::PendingHit ph;
ph.pkt.magic = NET_MAGIC_HIT;
ph.pkt.version = NET_VERSION;
ph.pkt.sender = 0; // the host stamps the real id when relaying a client's hit
ph.pkt.target = target;
ph.pkt.id = ++impl->hitSeq;
ph.pkt.damage = (int16_t)std::clamp(h.damage, 0, 1000);
ph.pkt.knock[0] = h.knockback.x; ph.pkt.knock[1] = h.knockback.y; ph.pkt.knock[2] = h.knockback.z;
ph.copiesLeft = BLAST_COPIES;
impl->pendingHits.push_back(ph);
}
void Lan::Send(const NetState& s) {
if (!active) return;
// 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);
else for (const Impl::Peer& to : impl->peers)
if (to.id == ph.pkt.target) impl->SendHitPacket(to.addr, ph.pkt);
ph.copiesLeft--;
}
impl->pendingHits.erase(
std::remove_if(impl->pendingHits.begin(), impl->pendingHits.end(),
[](const Impl::PendingHit& ph) { return ph.copiesLeft <= 0; }),
impl->pendingHits.end());
// One more copy of every pending blast (to the host, or from the host to everyone)
for (Impl::PendingBlast& pb : impl->pendingBlasts) {
if (mode == Mode::Client) impl->SendBlastPacket(impl->hostAddr, pb.pkt);
else for (const Impl::Peer& to : impl->peers) impl->SendBlastPacket(to.addr, pb.pkt);
pb.copiesLeft--;
}
impl->pendingBlasts.erase(
std::remove_if(impl->pendingBlasts.begin(), impl->pendingBlasts.end(),
[](const Impl::PendingBlast& pb) { return pb.copiesLeft <= 0; }),
impl->pendingBlasts.end());
if (mode == Mode::Client) {
impl->SendDatagram(impl->hostAddr, { MakeEntry(0, s) });
return;
@@ -199,7 +309,7 @@ void Lan::Send(const NetState& s) {
}
}
void Lan::Receive(std::vector<NetUpdate>& out) {
void Lan::Receive(std::vector<NetUpdate>& out, std::vector<NetBlast>* blasts, std::vector<NetHit>* hits) {
if (!active) return;
char buf[MAX_DATAGRAM + 64];
@@ -213,6 +323,71 @@ void Lan::Receive(std::vector<NetUpdate>& out) {
}
if (r < (int)sizeof(Header)) continue;
uint32_t magic;
std::memcpy(&magic, buf, sizeof(magic));
if (magic == NET_MAGIC_BLAST) {
if (r != (int)sizeof(BlastPacket)) continue;
BlastPacket bp;
std::memcpy(&bp, buf, sizeof(bp));
if (bp.version != NET_VERSION) continue;
if (mode == Mode::Host) {
// Only known players may blast. Stamp their id and relay to everyone else.
const Impl::Peer* sender = nullptr;
for (const Impl::Peer& pe : impl->peers)
if (SameAddress(pe.addr, from)) { sender = &pe; break; }
if (!sender) continue;
bp.sender = sender->id;
for (const Impl::Peer& to : impl->peers)
if (to.id != sender->id) impl->SendBlastPacket(to.addr, bp);
}
else if (!SameAddress(from, impl->hostAddr)) continue; // clients only listen to the host
if (blasts && impl->FirstSighting(0, bp.sender, bp.id)) {
NetBlast nb;
nb.sender = bp.sender;
nb.id = bp.id;
nb.center = glm::vec3(bp.center[0], bp.center[1], bp.center[2]);
nb.radius = bp.radius;
nb.power = bp.power;
blasts->push_back(nb);
}
continue;
}
if (magic == NET_MAGIC_HIT) {
if (r != (int)sizeof(HitPacket)) continue;
HitPacket hp;
std::memcpy(&hp, buf, sizeof(hp));
if (hp.version != NET_VERSION) continue;
bool forUs = true;
if (mode == Mode::Host) {
// Only known players may shoot. Stamp their id; deliver to us or forward to the target.
const Impl::Peer* sender = nullptr;
for (const Impl::Peer& pe : impl->peers)
if (SameAddress(pe.addr, from)) { sender = &pe; break; }
if (!sender) continue;
hp.sender = sender->id;
if (hp.target != 0) {
forUs = false;
for (const Impl::Peer& to : impl->peers)
if (to.id == hp.target) impl->SendHitPacket(to.addr, hp);
}
}
else if (!SameAddress(from, impl->hostAddr)) continue; // clients only listen to the host
if (forUs && hits && impl->FirstSighting(1, hp.sender, hp.id)) {
NetHit nh;
nh.sender = hp.sender;
nh.id = hp.id;
nh.damage = hp.damage;
nh.knockback = glm::vec3(hp.knock[0], hp.knock[1], hp.knock[2]);
hits->push_back(nh);
}
continue;
}
Header h;
std::memcpy(&h, buf, sizeof(h));
if (h.magic != NET_MAGIC || h.version != NET_VERSION) continue;