Expand roadblocks with defensive bays and checkpoint equipment
This commit is contained in:
@@ -162,7 +162,7 @@ modded class ERR_GameMode
|
|||||||
}
|
}
|
||||||
if (posts.ERR_ProbeBackgroundPending()) return;
|
if (posts.ERR_ProbeBackgroundPending()) return;
|
||||||
if (!posts.ERR_ProbeCompleteRoadblocks() || posts.ERR_ProbePostCount() <= m_ERRRoadblockInitial) { ERR_RoadblockProbeFail("physical four-guard posts, replicated props, ledger and HQ admission"); return; }
|
if (!posts.ERR_ProbeCompleteRoadblocks() || posts.ERR_ProbePostCount() <= m_ERRRoadblockInitial) { ERR_RoadblockProbeFail("physical four-guard posts, replicated props, ledger and HQ admission"); return; }
|
||||||
PrintFormat("ERR SMOKE PASS: roadblocks %1 physical posts / %2 guards / %3 replicated checkpoint props with distance streaming disabled; verified model footprints and centre lane; background completed after prison unlock, no extra posts on repeat tick", posts.ERR_ProbePostCount(), posts.ERR_ProbePostCount() * 4, posts.ERR_ProbePostCount() * 10);
|
PrintFormat("ERR SMOKE PASS: roadblocks %1 physical posts / %2 guards / %3 replicated checkpoint props with distance streaming disabled; verified model footprints and centre lane; background completed after prison unlock, no extra posts on repeat tick", posts.ERR_ProbePostCount(), posts.ERR_ProbePostCount() * 4, posts.ERR_ProbePostCount() * ERR_RoadblockRules.PropCount());
|
||||||
ERR_StopRoadblockProbe();
|
ERR_StopRoadblockProbe();
|
||||||
Transition(ERR_Phase.DEFEAT);
|
Transition(ERR_Phase.DEFEAT);
|
||||||
Print("ERR SMOKE COMPLETE: roadblocks background completion on Everon; scripted local fixture");
|
Print("ERR SMOKE COMPLETE: roadblocks background completion on Everon; scripted local fixture");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class ERR_RoadblockRules
|
|||||||
{
|
{
|
||||||
// Admission never waits longer for a high count. Remaining posts are built
|
// Admission never waits longer for a high count. Remaining posts are built
|
||||||
// by the same safe placement path in background, including after prison unlock.
|
// by the same safe placement path in background, including after prison unlock.
|
||||||
|
static int PropCount() { return 18; }
|
||||||
static int SearchMilliseconds() { return 12000; }
|
static int SearchMilliseconds() { return 12000; }
|
||||||
static int SearchAttempts() { return 300; }
|
static int SearchAttempts() { return 300; }
|
||||||
static bool Road(float width, float length, float heightDifference)
|
static bool Road(float width, float length, float heightDifference)
|
||||||
@@ -62,6 +63,8 @@ class ERR_Roadblocks
|
|||||||
static const ResourceName BARRIER = "{64C9C1D1C2C1B38D}Prefabs/Props/Infrastructure/Roadblocks/RoadBlock_01.et";
|
static const ResourceName BARRIER = "{64C9C1D1C2C1B38D}Prefabs/Props/Infrastructure/Roadblocks/RoadBlock_01.et";
|
||||||
static const ResourceName OBSTACLE = "{A46D45922F8CAD6C}Prefabs/Props/Military/Fortification/CzechHedgehog_01_painted.et";
|
static const ResourceName OBSTACLE = "{A46D45922F8CAD6C}Prefabs/Props/Military/Fortification/CzechHedgehog_01_painted.et";
|
||||||
static const ResourceName SIGN = "{C1977A326F2AA011}Prefabs/Structures/Signs/Military/SignCheckpoint_01_stop.et";
|
static const ResourceName SIGN = "{C1977A326F2AA011}Prefabs/Structures/Signs/Military/SignCheckpoint_01_stop.et";
|
||||||
|
static const ResourceName SHELTER = "{01AE5FD77A9A4C21}Prefabs/Structures/Military/Camps/TentSmallUS_01/TentSmallUS_01.et";
|
||||||
|
static const ResourceName TABLE = "{53506DCB398C7EBE}Prefabs/Props/Furniture/Table_01.et";
|
||||||
void ERR_Roadblocks(ERR_GameMode mode) { m_Mode = mode; m_Mining = new ERR_RoadblockMining(mode); }
|
void ERR_Roadblocks(ERR_GameMode mode) { m_Mode = mode; m_Mining = new ERR_RoadblockMining(mode); }
|
||||||
void MineTick(array<ref ERR_HQUnit> units, array<IEntity> attempted, float now) { if (!m_Stopped) m_Mining.Tick(units, attempted, now); }
|
void MineTick(array<ref ERR_HQUnit> units, array<IEntity> attempted, float now) { if (!m_Stopped) m_Mining.Tick(units, attempted, now); }
|
||||||
bool Contains(SCR_AIGroup group) { return group && m_Groups.Contains(group); }
|
bool Contains(SCR_AIGroup group) { return group && m_Groups.Contains(group); }
|
||||||
@@ -90,7 +93,7 @@ class ERR_Roadblocks
|
|||||||
if (profile && profile.IsValid()) source = profile.GetResource().ToEntitySource();
|
if (profile && profile.IsValid()) source = profile.GetResource().ToEntitySource();
|
||||||
if (!source || !source.Get("m_aUnitPrefabSlots", m_Roster) || m_Roster.IsEmpty()) return false;
|
if (!source || !source.Get("m_aUnitPrefabSlots", m_Roster) || m_Roster.IsEmpty()) return false;
|
||||||
// Preload before physical creation. A failed prefab never leaves half a post.
|
// Preload before physical creation. A failed prefab never leaves half a post.
|
||||||
array<ResourceName> resources = {COVER, BARRIER, OBSTACLE, SIGN, "{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", ERR_Resources.DefendWaypoint()};
|
array<ResourceName> resources = {COVER, BARRIER, OBSTACLE, SIGN, SHELTER, TABLE, "{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", ERR_Resources.DefendWaypoint()};
|
||||||
foreach (ResourceName actor : m_Roster) resources.Insert(actor);
|
foreach (ResourceName actor : m_Roster) resources.Insert(actor);
|
||||||
foreach (ResourceName prefab : resources)
|
foreach (ResourceName prefab : resources)
|
||||||
{
|
{
|
||||||
@@ -187,15 +190,23 @@ class ERR_Roadblocks
|
|||||||
// Footprints enclose the native model bounds, including small placement margins.
|
// Footprints enclose the native model bounds, including small placement margins.
|
||||||
for (int side = -1; side <= 1; side += 2)
|
for (int side = -1; side <= 1; side += 2)
|
||||||
{
|
{
|
||||||
// Two long sandbag walls shelter each pair from the road and its approach.
|
// Two front walls, an approach wall and an outer return form
|
||||||
AddProp(covers, COVER, side * m_Shoulder, 0, m_Right, 1.9, 0.7, 1);
|
// a sheltered bay for each pair. The rear stays open for guard access.
|
||||||
AddProp(covers, COVER, side * (m_Shoulder + 2.8), side * 2.8, m_Forward, 1.9, 0.7, 1);
|
AddProp(covers, COVER, side * m_Shoulder, -2, m_Right, 1.9, 0.7, 1);
|
||||||
// Opposing, staggered stop lines: the inner ends leave a >=4m centre lane.
|
AddProp(covers, COVER, side * m_Shoulder, 2, m_Right, 1.9, 0.7, 1);
|
||||||
|
AddProp(covers, COVER, side * (m_Shoulder + 2.8), side * 4.8, m_Forward, 1.9, 0.7, 1);
|
||||||
|
AddProp(covers, COVER, side * (m_Shoulder + 5.6), side * 2, m_Right, 1.9, 0.7, 1);
|
||||||
|
// Paired stop-line barriers funnel traffic into a >=4m centre passage.
|
||||||
AddProp(covers, BARRIER, side * 3.3, side * 8, m_Forward, 1.2, 0.4, 1.3);
|
AddProp(covers, BARRIER, side * 3.3, side * 8, m_Forward, 1.2, 0.4, 1.3);
|
||||||
AddProp(covers, OBSTACLE, side * (m_Shoulder + 4), side * 8, m_Forward, 1, 1, 1.6);
|
AddProp(covers, BARRIER, side * 5.9, side * 8, m_Forward, 1.2, 0.4, 1.3);
|
||||||
|
AddProp(covers, OBSTACLE, side * (m_Shoulder + 4.5), side * 8, m_Forward, 1, 1, 1.6);
|
||||||
AddProp(covers, SIGN, side * 3, side * 12, m_Forward * (side * 1.0), 0.45, 0.4, 0.8);
|
AddProp(covers, SIGN, side * 3, side * 12, m_Forward * (side * 1.0), 0.45, 0.4, 0.8);
|
||||||
}
|
}
|
||||||
|
// A small inspection/rest area behind one bay, away from the vehicle lane.
|
||||||
|
AddProp(covers, TABLE, m_Shoulder + 3, -4.8, m_Forward, 0.9, 0.7, 1.1);
|
||||||
|
AddProp(covers, SHELTER, m_Shoulder + 3, -8.5, m_Forward, 1.2, 2.4, 1.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void AddProp(array<ref ERR_RoadblockProp> covers, ResourceName prefab, float across, float along, vector forward, float halfWidth, float halfDepth, float height)
|
protected void AddProp(array<ref ERR_RoadblockProp> covers, ResourceName prefab, float across, float along, vector forward, float halfWidth, float halfDepth, float height)
|
||||||
{
|
{
|
||||||
vector point = ERR_Actors.Ground(m_Centre + m_Right * across + m_Forward * along);
|
vector point = ERR_Actors.Ground(m_Centre + m_Right * across + m_Forward * along);
|
||||||
@@ -209,7 +220,7 @@ class ERR_Roadblocks
|
|||||||
// Start a transaction; physical pieces are created separately in BuildPost.
|
// Start a transaction; physical pieces are created separately in BuildPost.
|
||||||
protected bool SpawnPost(array<vector> positions, array<ref ERR_RoadblockProp> covers)
|
protected bool SpawnPost(array<vector> positions, array<ref ERR_RoadblockProp> covers)
|
||||||
{
|
{
|
||||||
if (!PlayersClear() || m_Mode.Forces().Count() >= 250 || m_BuildGroup || positions.Count() != 4 || covers.Count() != 10) return false;
|
if (!PlayersClear() || m_Mode.Forces().Count() >= 250 || m_BuildGroup || positions.Count() != 4 || covers.Count() != ERR_RoadblockRules.PropCount()) return false;
|
||||||
m_BuildGroup = SCR_AIGroup.Cast(ERR_Actors.Spawn("{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", m_Centre));
|
m_BuildGroup = SCR_AIGroup.Cast(ERR_Actors.Spawn("{EC9F1AA42D0B7863}Prefabs/ERR/PrisonPatrol.et", m_Centre));
|
||||||
if (!m_BuildGroup) return false;
|
if (!m_BuildGroup) return false;
|
||||||
m_BuildGroup.SetFaction(m_Mode.EnemyFaction()); m_BuildGroup.SetSpawnImmediately(false); m_BuildGroup.SetDeleteWhenEmpty(false); m_BuildGroup.SetPermanentLOD(0);
|
m_BuildGroup.SetFaction(m_Mode.EnemyFaction()); m_BuildGroup.SetSpawnImmediately(false); m_BuildGroup.SetDeleteWhenEmpty(false); m_BuildGroup.SetPermanentLOD(0);
|
||||||
@@ -225,20 +236,20 @@ class ERR_Roadblocks
|
|||||||
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
||||||
}
|
}
|
||||||
// A free Game Master camera does not force-stream bare, non-editable props
|
// A free Game Master camera does not force-stream bare, non-editable props
|
||||||
// with the nearby editable guards. Keep this bounded bank (<=300 objects) on
|
// with the nearby editable guards. Keep this bounded bank (<=540 props) on
|
||||||
// clients, including late joiners, rather than tying it to the player's body.
|
// clients, including late joiners, rather than tying it to the player's body.
|
||||||
// Do not report a completed post if any physical prop lacks a network node.
|
// Do not report a completed post if any physical prop lacks a network node.
|
||||||
protected bool ShareProps()
|
protected bool ShareProps()
|
||||||
{
|
{
|
||||||
if (m_BuildCovers.Count() != 10 || m_BuildSpawned.Count() != 15) return false;
|
if (m_BuildCovers.Count() != ERR_RoadblockRules.PropCount() || m_BuildSpawned.Count() != m_BuildPositions.Count() + m_BuildCovers.Count() + 1) return false;
|
||||||
for (int i = 4; i < 14; i++)
|
for (int i = m_BuildPositions.Count(); i < m_BuildPositions.Count() + m_BuildCovers.Count(); i++)
|
||||||
{
|
{
|
||||||
IEntity entity = m_BuildSpawned[i];
|
IEntity entity = m_BuildSpawned[i];
|
||||||
RplComponent component;
|
RplComponent component;
|
||||||
if (entity) component = RplComponent.Cast(entity.FindComponent(RplComponent));
|
if (entity) component = RplComponent.Cast(entity.FindComponent(RplComponent));
|
||||||
if (!component || component.Id() == RplId.Invalid()) return false;
|
if (!component || component.Id() == RplId.Invalid()) return false;
|
||||||
}
|
}
|
||||||
for (int i = 4; i < 14; i++)
|
for (int i = m_BuildPositions.Count(); i < m_BuildPositions.Count() + m_BuildCovers.Count(); i++)
|
||||||
{
|
{
|
||||||
RplComponent component = RplComponent.Cast(m_BuildSpawned[i].FindComponent(RplComponent));
|
RplComponent component = RplComponent.Cast(m_BuildSpawned[i].FindComponent(RplComponent));
|
||||||
component.EnableStreaming(false);
|
component.EnableStreaming(false);
|
||||||
@@ -290,7 +301,7 @@ class ERR_Roadblocks
|
|||||||
m_BuildGroup.AddWaypoint(m_BuildHold);
|
m_BuildGroup.AddWaypoint(m_BuildHold);
|
||||||
m_Mode.RegisterGroup(m_BuildGroup); m_BuildGroup.ActivateAI();
|
m_Mode.RegisterGroup(m_BuildGroup); m_BuildGroup.ActivateAI();
|
||||||
m_Mining.Register(m_BuildGroup, m_BuildHold, m_Right);
|
m_Mining.Register(m_BuildGroup, m_BuildHold, m_Right);
|
||||||
PrintFormat("Reintroduced [roadblocks]: %1 at %2 guards=4 props=10 networkedProps=10 propStreaming=always roadPassage=open HQ=registered", id, m_Centre);
|
PrintFormat("Reintroduced [roadblocks]: %1 at %2 guards=4 props=%3 networkedProps=%3 propStreaming=always roadPassage=open HQ=registered", id, m_Centre, m_BuildCovers.Count());
|
||||||
m_BuildSpawned.Clear(); m_BuildGroup = null; m_BuildHold = null;
|
m_BuildSpawned.Clear(); m_BuildGroup = null; m_BuildHold = null;
|
||||||
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
m_BuildPositions.Clear(); m_BuildCovers.Clear();
|
||||||
m_NextSearch = System.GetTickCount() + 2000;
|
m_NextSearch = System.GetTickCount() + 2000;
|
||||||
|
|||||||
@@ -44,17 +44,20 @@ in `Escapists.json`: **0–30**, default **4**; **0 disables** them. The chat eq
|
|||||||
is `/esc mission roadblocks set N` (also `show` and `help`). Changes apply on the
|
is `/esc mission roadblocks set N` (also `show` and `help`). Changes apply on the
|
||||||
next round/restart, leaving current units and cover in place.
|
next round/restart, leaving current units and cover in place.
|
||||||
|
|
||||||
These are simple roadside checkpoints: two sandbag positions and four soldiers
|
Each checkpoint has two defensive bays built from eight sandbag walls, four
|
||||||
from the selected enemy faction. They use normal equipment rules and physical
|
approach barriers, two anti-vehicle obstacles, two stop signs, a small rest tent
|
||||||
|
and an inspection table. Four soldiers come from the selected enemy faction. They use normal equipment rules and physical
|
||||||
radio reporting to VirtualHQ; HQ can send their squad to respond to contacts.
|
radio reporting to VirtualHQ; HQ can send their squad to respond to contacts.
|
||||||
They do not reveal player locations to HQ merely by being created. Soldiers and
|
They do not reveal player locations to HQ merely by being created. Soldiers and
|
||||||
cover are owned by the round; killed guards are not replaced.
|
cover are owned by the round; killed guards are not replaced.
|
||||||
|
|
||||||
Placement uses the loaded terrain's road network and Soldiers navmesh before
|
Placement uses the loaded terrain's road network and Soldiers navmesh after
|
||||||
player admission. Posts stay at least 600 m apart, 700 m from the prison and any
|
terrain/site preparation. The initial search yields after 12 seconds; unfinished
|
||||||
|
placement continues in small background steps while players are connected,
|
||||||
|
including while the prison is locked and after escape. Posts stay at least 600 m apart, 700 m from the prison and any
|
||||||
controlled player body, 150 m from mission sites and outside settlement centres.
|
controlled player body, 150 m from mission sites and outside settlement centres.
|
||||||
Elevated bridge segments, narrow roads, steep or obstructed shoulders are rejected.
|
Elevated bridge segments, narrow roads, steep or obstructed shoulders are rejected.
|
||||||
Cover stays outside the travelled road to preserve vehicle passage. The requested
|
All prop footprints preserve a straight vehicle passage at least four metres wide. The requested
|
||||||
count is a maximum: a bounded search skips unsuitable posts and logs placed versus
|
count is a maximum: a bounded search skips unsuitable posts and logs placed versus
|
||||||
requested totals instead of preventing mission startup. The admin Status page
|
requested totals instead of preventing mission startup. The admin Status page
|
||||||
also shows these totals. This is an interim checkpoint system, independent of the
|
also shows these totals. This is an interim checkpoint system, independent of the
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ Before requesting `System.MakeScreenshot("$logs:err-ordinary-prison")`, the help
|
|||||||
|
|
||||||
Roadblock props remain replicated independently of player distance so an admin
|
Roadblock props remain replicated independently of player distance so an admin
|
||||||
viewing the island through Game Master can see the same barriers as nearby
|
viewing the island through Game Master can see the same barriers as nearby
|
||||||
players. This is limited to the ten props per post (at most 300); guard and
|
players. This is limited to the 18 props per post (at most 540); guard and
|
||||||
vehicle streaming is unchanged. A post is counted only after all ten props have
|
vehicle streaming is unchanged. A post is counted only after all 18 props have
|
||||||
valid replication nodes. Local Workbench validation does not by itself prove
|
valid replication nodes. Local Workbench validation does not by itself prove
|
||||||
remote-client visibility.
|
remote-client visibility.
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ s=s.replace('m_BuildGroup.', 'm_BuildGroup->').replace('m_BuildHold.', 'm_BuildH
|
|||||||
s=s.replace('IEntity entity', 'Node* entity').replace('RplComponent component;', 'Replica* component=nullptr;').replace('RplComponent component =', 'Replica* component =')
|
s=s.replace('IEntity entity', 'Node* entity').replace('RplComponent component;', 'Replica* component=nullptr;').replace('RplComponent component =', 'Replica* component =')
|
||||||
s=s.replace('RplComponent.Cast', 'Replica::Cast').replace('FindComponent(RplComponent)', 'FindComponent()').replace('RplId.Invalid()', '-1')
|
s=s.replace('RplComponent.Cast', 'Replica::Cast').replace('FindComponent(RplComponent)', 'FindComponent()').replace('RplId.Invalid()', '-1')
|
||||||
s=s.replace('entity.FindComponent', 'entity->FindComponent').replace('m_BuildSpawned[i].FindComponent', 'm_BuildSpawned[i]->FindComponent').replace('component.', 'component->')
|
s=s.replace('entity.FindComponent', 'entity->FindComponent').replace('m_BuildSpawned[i].FindComponent', 'm_BuildSpawned[i]->FindComponent').replace('component.', 'component->')
|
||||||
for ns in ('ERR_Actors','ERR_Resources','ERR_ForceKind'):s=s.replace(ns+'.',ns+'::')
|
for ns in ('ERR_Actors','ERR_Resources','ERR_ForceKind','ERR_RoadblockRules'):s=s.replace(ns+'.',ns+'::')
|
||||||
|
rule=body(p,'static int PropCount(')
|
||||||
run(r'''
|
run(r'''
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -128,6 +129,7 @@ struct Mode{Ledger ledger;int owned=0,groups=0;Ledger& Forces(){return ledger;}i
|
|||||||
struct Placement{bool clear=true;bool Clear(int,void*){return clear;}};
|
struct Placement{bool clear=true;bool Clear(int,void*){return clear;}};
|
||||||
struct Mining{int registered=0;void Register(Node*,Node*,int){registered++;}};
|
struct Mining{int registered=0;void Register(Node*,Node*,int){registered++;}};
|
||||||
struct Prop{string Prefab="cover";int Position=5,Angles=0;};
|
struct Prop{string Prefab="cover";int Position=5,Angles=0;};
|
||||||
|
namespace ERR_RoadblockRules{'''+rule+r'''}
|
||||||
struct Build{
|
struct Build{
|
||||||
Mode m_Mode;Placement m_Placement;Mining m_Mining;Node* m_BuildGroup=nullptr;Node* m_BuildHold=nullptr;
|
Mode m_Mode;Placement m_Placement;Mining m_Mining;Node* m_BuildGroup=nullptr;Node* m_BuildHold=nullptr;
|
||||||
Array<int> m_BuildPositions,m_Positions;Array<Prop>m_BuildCovers;Array<Node*>m_BuildSpawned,m_Groups;Array<string>m_Roster={"one","two"};
|
Array<int> m_BuildPositions,m_Positions;Array<Prop>m_BuildCovers;Array<Node*>m_BuildSpawned,m_Groups;Array<string>m_Roster={"one","two"};
|
||||||
@@ -136,31 +138,31 @@ struct Build{
|
|||||||
void Rollback(Array<Node*>&entities,Node*group){rolledBack+=entities.Count()+(group?1:0);}
|
void Rollback(Array<Node*>&entities,Node*group){rolledBack+=entities.Count()+(group?1:0);}
|
||||||
'''+s+r'''
|
'''+s+r'''
|
||||||
};
|
};
|
||||||
int main(){Array<int> positions={1,2,3,4};Array<Prop>covers(10);
|
int main(){Array<int> positions={1,2,3,4};Array<Prop>covers(ERR_RoadblockRules::PropCount());const int pieces=4+covers.Count()+1;
|
||||||
{Build b;int start=ERR_Actors::calls;assert(b.SpawnPost(positions,covers));assert(ERR_Actors::calls==start+1);
|
{Build b;int start=ERR_Actors::calls;assert(b.SpawnPost(positions,covers));assert(ERR_Actors::calls==start+1);
|
||||||
for(int piece=0;piece<15;piece++){int calls=ERR_Actors::calls;b.BuildPost();assert(ERR_Actors::calls==calls+1&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);}
|
for(int piece=0;piece<pieces;piece++){int calls=ERR_Actors::calls;b.BuildPost();assert(ERR_Actors::calls==calls+1&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);}
|
||||||
auto shared=b.m_BuildSpawned;for(int i=4;i<14;i++)assert(shared[i]->replica.streaming);
|
auto shared=b.m_BuildSpawned;for(int i=4;i<4+covers.Count();i++)assert(shared[i]->replica.streaming);
|
||||||
b.BuildPost();for(int i=4;i<14;i++)assert(!shared[i]->replica.streaming);assert(!b.m_BuildGroup&&b.m_Positions.Count()==1&&b.m_Groups.Count()==1&&b.m_Mode.owned==16&&b.m_Mode.groups==1&&b.m_Mining.registered==1&&b.m_NextSearch==2100);
|
b.BuildPost();for(int i=4;i<4+covers.Count();i++)assert(!shared[i]->replica.streaming);assert(!b.m_BuildGroup&&b.m_Positions.Count()==1&&b.m_Groups.Count()==1&&b.m_Mode.owned==pieces+1&&b.m_Mode.groups==1&&b.m_Mining.registered==1&&b.m_NextSearch==2100);
|
||||||
}
|
}
|
||||||
// A server-only or unregistered prop must never count as a complete roadblock.
|
// A server-only or unregistered prop must never count as a complete roadblock.
|
||||||
for(int bad=4;bad<14;bad++)for(bool missing:{false,true}){
|
for(int bad=4;bad<4+covers.Count();bad++)for(bool missing:{false,true}){
|
||||||
Build b;b.SpawnPost(positions,covers);for(int i=0;i<15;i++)b.BuildPost();
|
Build b;b.SpawnPost(positions,covers);for(int i=0;i<pieces;i++)b.BuildPost();
|
||||||
auto pieces=b.m_BuildSpawned;pieces[bad]->missing=missing;pieces[bad]->replica.id=-1;
|
auto spawned=b.m_BuildSpawned;spawned[bad]->missing=missing;spawned[bad]->replica.id=-1;
|
||||||
b.BuildPost();assert(b.m_Positions.Count()==0&&!b.m_BuildGroup&&b.m_Mode.owned==0&&b.rolledBack==16);
|
b.BuildPost();assert(b.m_Positions.Count()==0&&!b.m_BuildGroup&&b.m_Mode.owned==0&&b.rolledBack==pieces+1);
|
||||||
for(int i=4;i<14;i++)assert(pieces[i]->replica.streaming); // reject before changing any streaming rule
|
for(int i=4;i<4+covers.Count();i++)assert(spawned[i]->replica.streaming); // reject before changing any streaming rule
|
||||||
}
|
}
|
||||||
// Player approaches at any stage: no registered post and every owned piece removed.
|
// Player approaches at any stage: no registered post and every owned piece removed.
|
||||||
for(int stage=0;stage<=15;stage++){Build b;assert(b.SpawnPost(positions,covers));for(int i=0;i<stage;i++)b.BuildPost();b.playersClear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.m_Positions.Count()==0&&b.m_Mode.owned==0&&b.rolledBack==stage+1);}
|
for(int stage=0;stage<=pieces;stage++){Build b;assert(b.SpawnPost(positions,covers));for(int i=0;i<stage;i++)b.BuildPost();b.playersClear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.m_Positions.Count()==0&&b.m_Mode.owned==0&&b.rolledBack==stage+1);}
|
||||||
// Any native entity creation failure rolls back rather than consuming a post.
|
// Any native entity creation failure rolls back rather than consuming a post.
|
||||||
for(int stage=0;stage<16;stage++){Build b;ERR_Actors::failAt=ERR_Actors::calls+stage+1;bool begun=b.SpawnPost(positions,covers);
|
for(int stage=0;stage<pieces+1;stage++){Build b;ERR_Actors::failAt=ERR_Actors::calls+stage+1;bool begun=b.SpawnPost(positions,covers);
|
||||||
if(stage==0)assert(!begun);else{for(int i=0;i<stage;i++)b.BuildPost();assert(b.rolledBack==stage);}
|
if(stage==0)assert(!begun);else{for(int i=0;i<stage;i++)b.BuildPost();assert(b.rolledBack==stage);}
|
||||||
assert(!b.m_BuildGroup&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);ERR_Actors::failAt=0;
|
assert(!b.m_BuildGroup&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);ERR_Actors::failAt=0;
|
||||||
}
|
}
|
||||||
{Build b;b.SpawnPost(positions,covers);b.m_Placement.clear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==1);}
|
{Build b;b.SpawnPost(positions,covers);b.m_Placement.clear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==1);}
|
||||||
{Build b;b.SpawnPost(positions,covers);for(int i=0;i<4;i++)b.BuildPost();b.coversClear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==5);}
|
{Build b;b.SpawnPost(positions,covers);for(int i=0;i<4;i++)b.BuildPost();b.coversClear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==5);}
|
||||||
{Build b;b.SpawnPost(positions,covers);for(int i=0;i<15;i++)b.BuildPost();b.m_Mode.ledger.accept=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==16&&b.m_Positions.Count()==0);}
|
{Build b;b.SpawnPost(positions,covers);for(int i=0;i<pieces;i++)b.BuildPost();b.m_Mode.ledger.accept=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==pieces+1&&b.m_Positions.Count()==0);}
|
||||||
{Build b;b.SpawnPost(positions,covers);b.BuildPost();b.CancelPost();assert(!b.m_BuildGroup&&b.rolledBack==2&&b.m_Mode.owned==0);}
|
{Build b;b.SpawnPost(positions,covers);b.BuildPost();b.CancelPost();assert(!b.m_BuildGroup&&b.rolledBack==2&&b.m_Mode.owned==0);}
|
||||||
for(auto*n:ERR_Actors::all)delete n;
|
for(auto*n:ERR_Actors::all)delete n;
|
||||||
}
|
}
|
||||||
''')
|
''')
|
||||||
print('PASS: actual staged post creation spawns at most one entity per callback, commits only all four guards/ten props/waypoint, and rolls back player approach, obstructions, spawn/replication/ledger failure and cancellation; all ten committed props bypass distance streaming')
|
print('PASS: actual staged post creation spawns at most one entity per callback, commits only all four guards/all planned props/waypoint, and rolls back player approach, obstructions, spawn/replication/ledger failure and cancellation; all committed props bypass distance streaming')
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ struct Prop{string Prefab;Vec Position,Forward,Right;double HalfWidth,HalfDepth,
|
|||||||
Prop(string p,Vec v,Vec f,double w,double d,double h):Prefab(p),Position(v),Forward(f),Right({f.z,0,-f.x}),HalfWidth(w),HalfDepth(d),Height(h){}
|
Prop(string p,Vec v,Vec f,double w,double d,double h):Prefab(p),Position(v),Forward(f),Right({f.z,0,-f.x}),HalfWidth(w),HalfDepth(d),Height(h){}
|
||||||
};
|
};
|
||||||
struct Checkpoint{Vec m_Centre={1000,50,2000},m_Forward,m_Right;double m_Shoulder;
|
struct Checkpoint{Vec m_Centre={1000,50,2000},m_Forward,m_Right;double m_Shoulder;
|
||||||
string COVER="cover",BARRIER="barrier",OBSTACLE="obstacle",SIGN="sign";
|
string COVER="cover",BARRIER="barrier",OBSTACLE="obstacle",SIGN="sign",TABLE="table",SHELTER="shelter";
|
||||||
'''+s+r'''
|
'''+s+r'''
|
||||||
};
|
};
|
||||||
int main(){
|
int main(){
|
||||||
for(double width:{4.,6.,10.,16.})for(int degrees=0;degrees<360;degrees+=15){
|
for(double width:{4.,6.,10.,16.})for(int degrees=0;degrees<360;degrees+=15){
|
||||||
Checkpoint l;double angle=degrees*3.141592653589793/180;
|
Checkpoint l;double angle=degrees*3.141592653589793/180;
|
||||||
l.m_Forward={std::sin(angle),0,std::cos(angle)};l.m_Right={std::cos(angle),0,-std::sin(angle)};
|
l.m_Forward={std::sin(angle),0,std::cos(angle)};l.m_Right={std::cos(angle),0,-std::sin(angle)};
|
||||||
l.m_Shoulder=std::max(width*.5+1.5,4.);Array<Prop*>props;l.Layout(props);assert(props.size()==10);
|
l.m_Shoulder=std::max(width*.5+1.5,4.);Array<Prop*>props;l.Layout(props);assert(props.size()==18);
|
||||||
std::map<string,int>counts;std::vector<double>barriers;
|
std::map<string,int>counts;std::vector<double>barriers;
|
||||||
for(auto*p:props){counts[p->Prefab]++;
|
for(auto*p:props){counts[p->Prefab]++;
|
||||||
Vec delta=p->Position-l.m_Centre;double across=dot(delta,l.m_Right),along=dot(delta,l.m_Forward);
|
Vec delta=p->Position-l.m_Centre;double across=dot(delta,l.m_Right),along=dot(delta,l.m_Forward);
|
||||||
@@ -40,11 +40,11 @@ int main(){
|
|||||||
for(int side:{-1,1})for(int row:{-1,1}){
|
for(int side:{-1,1})for(int row:{-1,1}){
|
||||||
Vec guard=l.m_Centre+l.m_Right*(side*(l.m_Shoulder+2.5))+l.m_Forward*(side*(row-1.5));
|
Vec guard=l.m_Centre+l.m_Right*(side*(l.m_Shoulder+2.5))+l.m_Forward*(side*(row-1.5));
|
||||||
Vec rel=guard-p->Position;
|
Vec rel=guard-p->Position;
|
||||||
assert(std::abs(dot(rel,p->Right))>p->HalfWidth+.45 || std::abs(dot(rel,p->Forward))>p->HalfDepth+.45);
|
assert(std::abs(dot(rel,p->Right))>p->HalfWidth+.6 || std::abs(dot(rel,p->Forward))>p->HalfDepth+.6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(counts["cover"]==4&&counts["barrier"]==2&&counts["obstacle"]==2&&counts["sign"]==2);
|
assert(counts["cover"]==8&&counts["barrier"]==4&&counts["obstacle"]==2&&counts["sign"]==2&&counts["table"]==1&&counts["shelter"]==1);
|
||||||
assert(barriers.size()==2&&std::abs(barriers[0]-barriers[1])>15.9);
|
assert(barriers.size()==4&&std::abs(barriers[0]-barriers[2])>15.9);
|
||||||
// No physical prop footprint intersects another (separating axes).
|
// No physical prop footprint intersects another (separating axes).
|
||||||
for(size_t i=0;i<props.size();i++)for(size_t j=i+1;j<props.size();j++){
|
for(size_t i=0;i<props.size();i++)for(size_t j=i+1;j<props.size();j++){
|
||||||
auto*a=props[i];auto*b=props[j];bool separate=false;
|
auto*a=props[i];auto*b=props[j];bool separate=false;
|
||||||
|
|||||||
Reference in New Issue
Block a user