--- a/rts/Game/WebbarBrowser.h +++ b/rts/Game/WebbarBrowser.h @@ -57,6 +57,7 @@ return def != nullptr && names.contains(def->name); } +#include "WebbarResurrection.h" #include "WebbarSkirmish.h" #include "WebbarWorld.h" #include "WebbarTerrain.h" @@ -79,7 +80,7 @@ if (!catalogSent) { catalogSent = true; if (std::getenv("WEBBAR_NETWORK") != nullptr) { static WebbarNetworkResult result; } - MAIN_THREAD_EM_ASM({ if (Module['webbarProtocol']) Module['webbarProtocol'](8, 7 /* area restore | shield state | owner cloak */); }); + MAIN_THREAD_EM_ASM({ if (Module['webbarProtocol']) Module['webbarProtocol'](8, 15 /* area restore | shield state | owner cloak | resurrection */); }); MAIN_THREAD_EM_ASM({ if (Module['webbarIdentity']) Module['webbarIdentity']($0, $1, $2, $3); }, gu->myPlayerNum, gu->myTeam, gu->myAllyTeam, int(gu->spectating)); @@ -155,6 +156,7 @@ PublishWebbarShields(); PublishWebbarCloak(); PublishWebbarWorld(); + PublishWebbarResurrection(); } // Input is polled on the engine thread outside the synced simulation. Orders use @@ -299,6 +301,13 @@ if (ids.empty()) status = 8; else if (target == nullptr || target->team != gu->myTeam || target->isDead) status = 4; else command.PushParam(float(order.target)); + } else if (order.kind == CMD_RESURRECT) { + requireCapability(CMD_RESURRECT); + std::erase_if(ids, [&](int id) { const auto* def = unitHandler.GetUnit(id)->unitDef; return !def->IsBuilderUnit() || !def->canResurrect; }); + const CFeature* feature = featureHandler.GetFeature(order.target); + if (ids.empty()) status = 8; + else if (feature == nullptr || feature->deleteMe || !WebbarSupports(feature->udef) || !losHandler->InLos(feature->pos, gu->myAllyTeam)) status = 4; + else command.PushParam(float(order.target + unitHandler.MaxUnits())); } else if (order.kind == 1004) { // Wreck IDs have a distinct command kind; never trust a browser-supplied offset. std::erase_if(ids, [&](int id) { return !unitHandler.GetUnit(id)->unitDef->canReclaim; }); --- /dev/null +++ b/rts/Game/WebbarResurrection.h @@ -0,0 +1,36 @@ +/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ +#pragma once + +// Read-only visible wreck progress and owned constructors' native targets. +// No resurrection, resource or completion logic is implemented by the bridge. +static void PublishWebbarResurrection() +{ + if (gs->frameNum % 15 != 0) return; + struct FeatureRecord { uint32_t feature, definition; float progress, reclaim; }; + struct BuilderRecord { uint32_t unit, feature; }; + static_assert(sizeof(FeatureRecord) == 16 && sizeof(BuilderRecord) == 8); + std::vector features; + std::vector builders; + std::unordered_set published; + uint32_t featureDrops = 0, builderDrops = 0; + for (const int id: featureHandler.GetActiveFeatureIDs()) { + const CFeature* feature = featureHandler.GetFeature(id); + if (feature->deleteMe || !WebbarSupports(feature->udef) || !losHandler->InLos(feature->pos, gu->myAllyTeam)) continue; + if (features.size() >= 4096) { ++featureDrops; continue; } + features.push_back({uint32_t(id), uint32_t(feature->udef->id), feature->resurrectProgress, feature->reclaimLeft}); + published.insert(id); + } + for (const CUnit* unit: unitHandler.GetActiveUnits()) { + if (unit->isDead || unit->team != gu->myTeam || !unit->unitDef->IsBuilderUnit() || !unit->unitDef->canResurrect) continue; + const CFeature* feature = static_cast(unit)->curResurrect; + if (feature == nullptr || !published.contains(feature->id)) continue; + if (builders.size() >= 4096) { ++builderDrops; continue; } + builders.push_back({uint32_t(unit->id), uint32_t(feature->id)}); + } + const uint32_t header[] = {0x31524257, uint32_t(gs->frameNum), uint32_t(features.size()), uint32_t(builders.size()), featureDrops, builderDrops}; + std::vector packet(sizeof(header) + features.size() * sizeof(FeatureRecord) + builders.size() * sizeof(BuilderRecord)); + std::memcpy(packet.data(), header, sizeof(header)); + if (!features.empty()) std::memcpy(packet.data() + sizeof(header), features.data(), features.size() * sizeof(FeatureRecord)); + if (!builders.empty()) std::memcpy(packet.data() + sizeof(header) + features.size() * sizeof(FeatureRecord), builders.data(), builders.size() * sizeof(BuilderRecord)); + MAIN_THREAD_EM_ASM({ if (Module['webbarResurrection']) Module['webbarResurrection'](HEAPU8.slice($0, $0 + $1).buffer); }, packet.data(), packet.size()); +}