--- a/rts/Sim/Units/UnitTypes/Builder.cpp +++ b/rts/Sim/Units/UnitTypes/Builder.cpp @@ -5,6 +5,7 @@ #include "Builder.h" #if defined(__EMSCRIPTEN__) #include "Game/WebbarNano.h" +#include "Game/WebbarWorkAudio.h" #endif #include "Building.h" #include "Game/GameHelper.h" @@ -963,8 +964,12 @@ script->StartBuilding(ClampRadPi(h - heading * TAANG2RAD), p - pitch); } - if ((!silent || inBuildStance) && IsInLosForAllyTeam(gu->myAllyTeam)) + if ((!silent || inBuildStance) && IsInLosForAllyTeam(gu->myAllyTeam)) { +#if defined(__EMSCRIPTEN__) + webbarWorkAudio::Record(this, pos, 0); +#endif Channels::General->PlayRandomSample(unitDef->sounds.build, pos); + } return inBuildStance; } --- a/rts/Sim/Units/UnitTypes/Factory.cpp +++ b/rts/Sim/Units/UnitTypes/Factory.cpp @@ -4,6 +4,7 @@ #include "Factory.h" #if defined(__EMSCRIPTEN__) #include "Game/WebbarNano.h" +#include "Game/WebbarWorkAudio.h" #endif #include "Game/GameHelper.h" #include "Game/WaitCommandsAI.h" @@ -207,6 +208,9 @@ curBuildDef = nullptr; if (losStatus[gu->myAllyTeam] & LOS_INLOS) { +#if defined(__EMSCRIPTEN__) + webbarWorkAudio::Record(this, buildPos, 1); +#endif Channels::General->PlayRandomSample(unitDef->sounds.build, buildPos); } } --- a/rts/Sim/Units/Unit.cpp +++ b/rts/Sim/Units/Unit.cpp @@ -5,6 +5,7 @@ #include "Unit.h" #ifdef __EMSCRIPTEN__ #include "Game/WebbarCloakEvents.h" +#include "Game/WebbarWorkAudio.h" #endif #include "UnitHandler.h" #include "UnitDefHandler.h" @@ -108,6 +109,7 @@ { #ifdef __EMSCRIPTEN__ webbarCloakEvents::Forget(id); + webbarWorkAudio::Forget(id); #endif RECOIL_DETAILED_TRACY_ZONE; assert(unitMemPool.mapped(this)); --- a/rts/Game/WebbarBrowser.h +++ b/rts/Game/WebbarBrowser.h @@ -44,6 +44,7 @@ #include "WebbarWeapons.h" #include "WebbarShields.h" #include "WebbarCloak.h" +#include "WebbarWorkAudio.h" static bool WebbarSupports(const UnitDef* def) { @@ -156,6 +157,7 @@ }, packet.data(), packet.size()); PublishWebbarShields(); PublishWebbarCloak(); + webbarWorkAudio::Publish(); PublishWebbarWorld(); PublishWebbarResurrection(); } --- /dev/null +++ b/rts/Game/WebbarWorkAudio.h @@ -0,0 +1,63 @@ +/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "Game/GlobalUnsynced.h" +#include "Sim/Misc/GlobalSynced.h" +#include "Sim/Units/Unit.h" +#include "Sim/Units/UnitDef.h" +#include "Sim/Units/UnitHandler.h" + +// Read-only cues at the original sound callsites, not inferred nano activity. +// Local ownership narrows native LOS admission. Never inspect hidden targets, +// consume simulation RNG or change build power, scripts, commands or resources. +namespace webbarWorkAudio { +struct Event { + uint32_t id, frame, unit, definition; + float position[3]; + uint32_t kind; +}; +static_assert(sizeof(Event) == 32); +inline std::vector pending; +inline uint32_t nextID = 0, dropped = 0; + +inline void Record(const CUnit* unit, const float3& position, uint32_t kind) +{ + if (unit->isDead || unit->team != gu->myTeam || gs->frameNum < 0 || + !std::isfinite(position.x) || !std::isfinite(position.y) || !std::isfinite(position.z)) return; + if (pending.size() >= 1024) { ++dropped; return; } + if (++nextID == 0) ++nextID; + pending.push_back({nextID, uint32_t(gs->frameNum), uint32_t(unit->id), uint32_t(unit->unitDef->id), + {position.x, position.y, position.z}, kind}); +} + +inline void Forget(int unitID) +{ + pending.erase(std::remove_if(pending.begin(), pending.end(), [unitID](const Event& event) { + return event.unit == uint32_t(unitID); + }), pending.end()); +} + +inline void Publish() +{ + std::vector events; + for (const auto& event: pending) { + const CUnit* unit = unitHandler.GetUnit(event.unit); + if (unit == nullptr || unit->isDead || unit->team != gu->myTeam || unit->unitDef->id != int(event.definition) || + event.frame > uint32_t(gs->frameNum) || uint32_t(gs->frameNum) - event.frame > 6) continue; + events.push_back(event); + } + pending.clear(); + const uint32_t header[] = {0x31414257, uint32_t(gs->frameNum), uint32_t(events.size()), dropped}; + dropped = 0; + std::vector packet(sizeof(header) + events.size() * sizeof(Event)); + std::memcpy(packet.data(), header, sizeof(header)); + if (!events.empty()) std::memcpy(packet.data() + sizeof(header), events.data(), events.size() * sizeof(Event)); + MAIN_THREAD_EM_ASM({ if (Module['webbarWorkAudio']) Module['webbarWorkAudio'](HEAPU8.slice($0, $0 + $1).buffer); }, packet.data(), packet.size()); +} +}