--- a/rts/Game/WebbarShields.h +++ b/rts/Game/WebbarShields.h @@ -3,6 +3,7 @@ #include "Sim/Weapons/PlasmaRepulser.h" #include "Lua/LuaRulesParams.h" +#include "WebbarShieldHits.h" // Read-only presentation, called outside synced code. No collision callbacks, // Lua calls, resource writes, or random numbers are introduced by this export. @@ -44,10 +45,13 @@ def->shieldGoodColor.x, def->shieldGoodColor.y, def->shieldGoodColor.z, def->shieldBadColor.x, def->shieldBadColor.y, def->shieldBadColor.z, def->shieldAlpha}); } } - const uint32_t header[] = {0x31484257, uint32_t(gs->frameNum), uint32_t(records.size()), dropped}; - std::vector packet(sizeof(header) + records.size() * sizeof(Record)); + const auto hits = WebbarShieldHits::TakeVisible(); + const uint32_t header[] = {0x32484257, uint32_t(gs->frameNum), uint32_t(records.size()), dropped, uint32_t(hits.size()), WebbarShieldHits::dropped}; + WebbarShieldHits::dropped = 0; + std::vector packet(sizeof(header) + records.size() * sizeof(Record) + hits.size() * sizeof(WebbarShieldHits::Record)); std::memcpy(packet.data(), header, sizeof(header)); if (!records.empty()) std::memcpy(packet.data() + sizeof(header), records.data(), records.size() * sizeof(Record)); + if (!hits.empty()) std::memcpy(packet.data() + sizeof(header) + records.size() * sizeof(Record), hits.data(), hits.size() * sizeof(WebbarShieldHits::Record)); MAIN_THREAD_EM_ASM({ if (Module['webbarShields']) Module['webbarShields'](HEAPU8.slice($0, $0 + $1).buffer); }, packet.data(), packet.size()); --- a/rts/Lua/LuaHandleSynced.cpp +++ b/rts/Lua/LuaHandleSynced.cpp @@ -4,6 +4,9 @@ #include "LuaHandleSynced.h" #include "LuaInclude.h" +#ifdef __EMSCRIPTEN__ +#include "Game/WebbarShieldHits.h" +#endif #include "LuaUtils.h" #include "LuaArchive.h" @@ -182,6 +185,22 @@ */ void CUnsyncedLuaHandle::RecvFromSynced(lua_State* srcState, int args) { +#ifdef __EMSCRIPTEN__ + // The original shield gadget already sends this presentation event. The + // browser has no native GL gadget, so observe it before the Lua early-out. + if (args == 8 && lua_type(srcState, 1) == LUA_TSTRING && + std::strcmp(lua_tostring(srcState, 1), "AddShieldHitDataHandler") == 0 && + lua_type(srcState, 8) == LUA_TBOOLEAN) { + bool numbers = true; + for (int i = 2; i <= 7; ++i) numbers = numbers && lua_type(srcState, i) == LUA_TNUMBER && std::isfinite(lua_tonumber(srcState, i)); + if (numbers) { + const double frame = lua_tonumber(srcState, 2), unit = lua_tonumber(srcState, 3); + if (frame >= 0 && frame <= gs->frameNum && frame == std::floor(frame) && unit >= 0 && unit < 65536 && unit == std::floor(unit)) { + WebbarShieldHits::Emit(int(frame), int(unit), lua_tonumber(srcState, 4), lua_tonumber(srcState, 5), lua_tonumber(srcState, 6), lua_tonumber(srcState, 7), lua_toboolean(srcState, 8)); + } + } + } +#endif if (!IsValid()) return; --- /dev/null +++ b/rts/Game/WebbarShieldHits.h @@ -0,0 +1,58 @@ +/* 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/Units/Unit.h" +#include "Sim/Units/UnitHandler.h" + +// Read-only observation of BAR's existing unsynced shield-hit message. This +// does not introduce a collision callback, execute Lua, or change absorption. +namespace WebbarShieldHits { +struct Record { + uint32_t id, frame, unit, flags; + float damage, dx, dy, dz; +}; +static_assert(sizeof(Record) == 32); +struct Pending { Record record; const CUnit* identity; }; +inline std::vector pending; +inline uint32_t nextId = 0, dropped = 0; + +inline bool Visible(const CUnit* unit) +{ + return unit != nullptr && !unit->isDead && unit->shieldWeapon != nullptr && + (unit->team == gu->myTeam || unit->IsInLosForAllyTeam(gu->myAllyTeam)); +} + +inline void Emit(int frame, int id, float damage, float dx, float dy, float dz, bool onlyMove) +{ + if (std::getenv("WEBBAR_PLAYABLE") == nullptr || frame < 0 || frame > gs->frameNum || gs->frameNum - frame > 3 || id < 0) return; + const CUnit* unit = unitHandler.GetUnit(id); + if (!Visible(unit) || !std::isfinite(damage) || damage < 0 || damage > 1.0e9f || + !std::isfinite(dx) || !std::isfinite(dy) || !std::isfinite(dz) || + std::abs(dx) > 1.0e6f || std::abs(dy) > 1.0e6f || std::abs(dz) > 1.0e6f) return; + if (pending.size() >= 1024) { ++dropped; return; } + if (++nextId == 0) ++nextId; + pending.push_back({{nextId, uint32_t(frame), uint32_t(id), uint32_t(onlyMove), damage, dx, dy, dz}, unit}); +} + +inline std::vector TakeVisible() +{ + std::vector result; + result.reserve(pending.size()); + for (const auto& hit: pending) { + const CUnit* unit = unitHandler.GetUnit(hit.record.unit); + // Visibility may change between collision and publication. Never replay + // a hidden/dead target's hit later when it is re-scouted. + if (unit != hit.identity || !Visible(unit) || hit.record.frame > uint32_t(gs->frameNum) || + uint32_t(gs->frameNum) - hit.record.frame > 6) continue; + result.push_back(hit.record); + } + pending.clear(); + return result; +} +}