--- a/rts/Game/WebbarBrowser.h +++ b/rts/Game/WebbarBrowser.h @@ -58,6 +58,7 @@ } #include "WebbarResurrection.h" +#include "WebbarNano.h" #include "WebbarSkirmish.h" #include "WebbarWorld.h" #include "WebbarTerrain.h" @@ -80,7 +81,7 @@ if (!catalogSent) { catalogSent = true; if (std::getenv("WEBBAR_NETWORK") != nullptr) { static WebbarNetworkResult result; } - MAIN_THREAD_EM_ASM({ if (Module['webbarProtocol']) Module['webbarProtocol'](8, 15 /* area restore | shield state | owner cloak | resurrection */); }); + MAIN_THREAD_EM_ASM({ if (Module['webbarProtocol']) Module['webbarProtocol'](8, 31 /* restore | shields | cloak | resurrection | native work effects */); }); MAIN_THREAD_EM_ASM({ if (Module['webbarIdentity']) Module['webbarIdentity']($0, $1, $2, $3); }, gu->myPlayerNum, gu->myTeam, gu->myAllyTeam, int(gu->spectating)); @@ -454,6 +455,7 @@ } if (browser) { if (std::getenv("WEBBAR_PLAYABLE") != nullptr) { + WebbarNano::Publish(); PublishWebbarTerrain(); PublishWebbarPlayState(); PublishWebbarPoses(); --- a/rts/Sim/Units/UnitTypes/Builder.cpp +++ b/rts/Sim/Units/UnitTypes/Builder.cpp @@ -3,6 +3,9 @@ #include #include #include "Builder.h" +#if defined(__EMSCRIPTEN__) +#include "Game/WebbarNano.h" +#endif #include "Building.h" #include "Game/GameHelper.h" #include "Game/GlobalUnsynced.h" @@ -998,6 +1001,15 @@ const float3 relNanoFirePos = localModel.GetRawPiecePos(modelNanoPiece); const float3 nanoPos = this->GetObjectSpacePos(relNanoFirePos); + #if defined(__EMSCRIPTEN__) + if (curResurrect != nullptr) WebbarNano::Emit(this, nanoPos, goal, radius, inverse, 3, nullptr, curResurrect); + else if (curReclaim != nullptr) { + if (reclaimingUnit) WebbarNano::Emit(this, nanoPos, goal, radius, inverse, 4, static_cast(curReclaim)); + else WebbarNano::Emit(this, nanoPos, goal, radius, inverse, 4, nullptr, static_cast(curReclaim)); + } else if (curCapture != nullptr) WebbarNano::Emit(this, nanoPos, goal, radius, inverse, 5, curCapture); + else if (curBuild != nullptr) WebbarNano::Emit(this, nanoPos, goal, radius, inverse, curBuild->beingBuilt ? 1 : 2, curBuild); + #endif + // unsynced projectileHandler.AddNanoParticle(nanoPos, goal, unitDef, team, radius, inverse, highPriority); } --- a/rts/Sim/Units/UnitTypes/Factory.cpp +++ b/rts/Sim/Units/UnitTypes/Factory.cpp @@ -2,6 +2,9 @@ #include "Factory.h" +#if defined(__EMSCRIPTEN__) +#include "Game/WebbarNano.h" +#endif #include "Game/GameHelper.h" #include "Game/WaitCommandsAI.h" #include "Map/Ground.h" @@ -546,6 +549,10 @@ const float3 relNanoFirePos = localModel.GetRawPiecePos(modelNanoPiece); const float3 nanoPos = this->GetObjectSpacePos(relNanoFirePos); + #if defined(__EMSCRIPTEN__) + WebbarNano::Emit(this, nanoPos, curBuild->midPos, curBuild->radius * 0.5f, false, 1, curBuild); + #endif + // unsynced projectileHandler.AddNanoParticle(nanoPos, curBuild->midPos, unitDef, team, highPriority); } --- /dev/null +++ b/rts/Game/WebbarNano.h @@ -0,0 +1,48 @@ +/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ +#pragma once + +#include +#include +#include +#include +#include "Game/GlobalUnsynced.h" +#include "Sim/Misc/GlobalSynced.h" +#include "Sim/Misc/LosHandler.h" +#include "Sim/Features/Feature.h" +#include "Sim/Units/Unit.h" +#include "Sim/Units/UnitDef.h" + +// Browser-only observation of native spray calls. Never query a script, move a +// unit, consume RNG, or change whether native build power is applied. +namespace WebbarNano { +struct Record { + uint32_t id, frame, source, target, kind, flags; + float start[3], end[3], radius, color[3]; +}; +static_assert(sizeof(Record) == 64); +inline std::vector pending; +inline uint32_t nextId = 0, dropped = 0; + +inline void Emit(const CUnit* source, const float3& start, const float3& end, float radius, bool inverse, uint32_t kind, const CUnit* unit, const CFeature* feature = nullptr) +{ + // Initial scope: local player's work only, with a currently visible target. + if (!source->unitDef->showNanoSpray || source->isDead || source->team != gu->myTeam) return; + if (unit != nullptr && (unit->isDead || (unit->team != gu->myTeam && !unit->IsInLosForAllyTeam(gu->myAllyTeam)))) return; + if (feature != nullptr && (feature->deleteMe || !losHandler->InLos(feature->pos, gu->myAllyTeam))) return; + if (unit == nullptr && feature == nullptr) return; + if (pending.size() >= 1024) { ++dropped; return; } + const float3 color = source->unitDef->nanoColor; + pending.push_back({++nextId, uint32_t(gs->frameNum), uint32_t(source->id), uint32_t(unit ? unit->id : feature->id), kind, uint32_t(inverse) | (feature ? 2u : 0u), + {start.x, start.y, start.z}, {end.x, end.y, end.z}, radius, {color.x, color.y, color.z}}); +} + +inline void Publish() +{ + const uint32_t header[] = {0x314e4257, uint32_t(gs->frameNum), uint32_t(pending.size()), dropped}; + std::vector packet(sizeof(header) + pending.size() * sizeof(Record)); + std::memcpy(packet.data(), header, sizeof(header)); + if (!pending.empty()) std::memcpy(packet.data() + sizeof(header), pending.data(), pending.size() * sizeof(Record)); + MAIN_THREAD_EM_ASM({ if (Module['webbarNano']) Module['webbarNano'](HEAPU8.slice($0, $0 + $1).buffer); }, packet.data(), packet.size()); + pending.clear(); dropped = 0; +} +}