--- a/addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelDiscoveryFeedback.c +++ b/addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelDiscoveryFeedback.c @@ -5,6 +5,7 @@ protected ref map m_ERRIntelSeen = new map(); protected ref map m_ERRIntelPending = new map(); protected float m_ERRIntelMessageAt; + protected float m_ERRIntelExhaustedAt; protected bool m_ERRIntelFeedbackStopped; protected int m_ERRIntelMessageSequence; protected int m_ERRIntelReceivedSequence; @@ -63,6 +64,11 @@ void ERR_IntelExhausted(bool duplicate = false) { if (!Replication.IsServer() || m_ERRIntelFeedbackStopped || !Active()) return; + // Recover scans a whole inventory; surplus folders share one team notice. + // Keep useful new discoveries on their independent coalescing schedule. + float now = GetWorld().GetWorldTime(); + if (now < m_ERRIntelExhaustedAt) return; + m_ERRIntelExhaustedAt = now + 5000; string message = "Every known location has already been identified. This intel doesn't help us."; if (duplicate) message = "This document concerns a location we have already identified."; m_ERRIntelMessageSequence++; @@ -75,6 +81,7 @@ m_ERRIntelPending.Clear(); m_ERRIntelSeen.Clear(); m_ERRIntelMessageAt = 0; + m_ERRIntelExhaustedAt = 0; OnLocationDiscovered().Remove(ERR_QueueDiscoveryFeedback); OnTick().Remove(ERR_FlushDiscoveryFeedback); } --- a/tests/test_intel_discovery_feedback.py +++ b/tests/test_intel_discovery_feedback.py @@ -1,6 +1,8 @@ """Execute production feedback queue/delivery bodies with fake engine boundaries.""" from policy_harness import ROOT, body, run -P=ROOT/'addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelDiscoveryFeedback.c' +from pathlib import Path +import os +P=Path(os.environ.get('ERR_INTEL_FEEDBACK_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelDiscoveryFeedback.c')) source=P.read_text() def cpp(s): s=s.replace('protected ', '').replace('static ', '') @@ -42,7 +44,7 @@ static ERR_GameMode* current;static ERR_GameMode* Get(){return current;} bool active=true;int phase=ERR_Phase::RADIO_SEARCH;bool Active(){return active;}int Phase(){return phase;}World* GetWorld(){return &world;} Map m_ERRIntelSeen,m_ERRIntelPending; - float m_ERRIntelMessageAt=0;bool m_ERRIntelFeedbackStopped=false;int m_ERRIntelMessageSequence=0,m_ERRIntelReceivedSequence=0; + float m_ERRIntelMessageAt=0,m_ERRIntelExhaustedAt=0;bool m_ERRIntelFeedbackStopped=false;int m_ERRIntelMessageSequence=0,m_ERRIntelReceivedSequence=0; Events discovered,tick;Events& OnLocationDiscovered(){return discovered;}Events& OnTick(){return tick;} int rpcCount=0;void Rpc(int sequence,string message){rpcCount++;ERR_ReceiveDiscoveryFeedback(sequence,message);} '''+methods+r''' @@ -79,6 +81,21 @@ assert(ERR_IntelDiscoveryText::Type(ERR_LocationKind::MORTAR,"any")=="mortar site"); ERR_GameMode::current=&m;m.ERR_IntelExhausted();assert(SCR_ChatComponent::messages.back()=="Every known location has already been identified. This intel doesn't help us."); assert(ERR_IntelDiscoveryText::Message(0,0,"secret").empty());assert(ERR_IntelDiscoveryText::Message(1,0,"")=="Hey! A location was identified in the intel!"); + // Recovering a stack of spent-topic folders performs one exhaustion broadcast, + // not one chat line/RPC per physical document. Both variants share the budget. + ERR_GameMode surplus;ERR_GameMode::current=&surplus;world.time=10000; + for(int i=0;i<20;i++)surplus.ERR_IntelExhausted(i%2==0); + assert(surplus.rpcCount==1&&surplus.m_ERRIntelMessageSequence==1); + // Actual new discovery still follows its own coalescing window immediately. + world.time=10000;ERR_LocationComponent fresh;surplus.ERR_QueueDiscoveryFeedback(&fresh,0,1); + surplus.ERR_FlushDiscoveryFeedback(11500);assert(surplus.rpcCount==2); + assert(SCR_ChatComponent::messages.back()=="New location marked. Type unknown."); + world.time=14999;surplus.ERR_IntelExhausted();assert(surplus.rpcCount==2); + world.time=15000;surplus.ERR_IntelExhausted();assert(surplus.rpcCount==3&&surplus.m_ERRIntelMessageSequence==3); + world.time=20000;Replication::server=false;surplus.ERR_IntelExhausted();assert(surplus.rpcCount==3);Replication::server=true; + surplus.ERR_StopDiscoveryFeedback();surplus.ERR_IntelExhausted();assert(surplus.rpcCount==3); + ERR_GameMode freshRound;ERR_GameMode::current=&freshRound;world.time=0;freshRound.ERR_IntelExhausted();assert(freshRound.rpcCount==1); + } ''') for required in ('OnLocationDiscovered().Insert(ERR_QueueDiscoveryFeedback)', 'OnTick().Insert(ERR_FlushDiscoveryFeedback)', 'OnStop().Insert(ERR_StopDiscoveryFeedback)', '[RplRpc(RplChannel.Reliable, RplRcver.Broadcast)]'):