--- a/rts/Game/WebbarBrowser.h +++ b/rts/Game/WebbarBrowser.h @@ -42,6 +42,7 @@ } #include "WebbarWeapons.h" +#include "WebbarShields.h" static bool WebbarSupports(const UnitDef* def) { @@ -77,7 +78,7 @@ if (!catalogSent) { catalogSent = true; if (std::getenv("WEBBAR_NETWORK") != nullptr) { static WebbarNetworkResult result; } - MAIN_THREAD_EM_ASM({ if (Module['webbarProtocol']) Module['webbarProtocol'](8, 1 /* area restore */); }); + MAIN_THREAD_EM_ASM({ if (Module['webbarProtocol']) Module['webbarProtocol'](8, 3 /* area restore | shield state */); }); MAIN_THREAD_EM_ASM({ if (Module['webbarIdentity']) Module['webbarIdentity']($0, $1, $2, $3); }, gu->myPlayerNum, gu->myTeam, gu->myAllyTeam, int(gu->spectating)); @@ -150,6 +151,7 @@ MAIN_THREAD_EM_ASM({ if (Module['webbarUnitState']) Module['webbarUnitState'](HEAPU8.slice($0, $0 + $1).buffer); }, packet.data(), packet.size()); + PublishWebbarShields(); PublishWebbarWorld(); } --- /dev/null +++ b/rts/Game/WebbarShields.h @@ -0,0 +1,54 @@ +/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */ +#pragma once + +#include "Sim/Weapons/PlasmaRepulser.h" +#include "Lua/LuaRulesParams.h" + +// Read-only presentation, called outside synced code. No collision callbacks, +// Lua calls, resource writes, or random numbers are introduced by this export. +static void PublishWebbarShields() +{ + struct Record { + uint32_t unit, weapon, flags; + int32_t hitFrame; + float x, y, z, radius, power, maximum, ux, uy, uz; + float goodR, goodG, goodB, badR, badG, badB, alpha; + }; + static_assert(sizeof(Record) == 80); + std::vector records; + uint32_t dropped = 0; + for (const CUnit* unit: unitHandler.GetActiveUnits()) { + if (unit->isDead || !WebbarVisible(unit)) continue; + const auto rule = [&](const char* name, float fallback) { + const auto it = unit->modParams.find(name); + if (it == unit->modParams.end()) return fallback; + const int mask = unit->team == gu->myTeam ? LuaRulesParams::RULESPARAMLOS_PRIVATE_MASK : LuaRulesParams::RULESPARAMLOS_INLOS_MASK; + if (!(it->second.los & mask)) return fallback; + const float* value = std::get_if(&it->second.value); + return value != nullptr && std::isfinite(*value) ? *value : fallback; + }; + // These are BAR's published INLOS rules parameters, not its private Lua + // overkill/debt state. Unknown hit times stay unknown (never frame zero). + const float gameOn = rule("531313", -1.0f); + const float hit = rule("shieldHitFrame", -1.0f); + for (const CWeapon* weapon: unit->weapons) { + const auto* shield = dynamic_cast(weapon); + if (shield == nullptr) continue; + if (records.size() >= 512) { ++dropped; continue; } + const auto* def = weapon->weaponDef; + const float3& p = weapon->weaponMuzzlePos; + const uint32_t flags = uint32_t(shield->IsEnabled()) | (uint32_t(shield->IsActive()) << 1) | (uint32_t(unit->activated) << 2) | + (uint32_t(unit->beingBuilt) << 3) | (uint32_t(unit->IsStunned()) << 4) | (uint32_t(gameOn >= 0) << 5) | (uint32_t(gameOn > 0) << 6); + records.push_back({uint32_t(unit->id), uint32_t(weapon->weaponNum), flags, hit >= 0 && hit <= gs->frameNum ? int32_t(hit) : -1, + p.x, p.y, p.z, shield->GetRadius(), shield->GetCurPower(), def->shieldPower, unit->pos.x, unit->pos.y, unit->pos.z, + 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)); + std::memcpy(packet.data(), header, sizeof(header)); + if (!records.empty()) std::memcpy(packet.data() + sizeof(header), records.data(), records.size() * sizeof(Record)); + MAIN_THREAD_EM_ASM({ + if (Module['webbarShields']) Module['webbarShields'](HEAPU8.slice($0, $0 + $1).buffer); + }, packet.data(), packet.size()); +}