Add optional shoreline Motorboats and recommended addon controls
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -55,7 +55,7 @@ assert 'Apply_planeReconEnabled' not in names # Saved-state toggle pair, not a
|
||||
assert len(names) == len(set(names)), 'Menu widget names must be unique'
|
||||
for key in keys:
|
||||
assert all(name in names for name in (key, 'Apply_' + key, 'Saved_' + key, 'Help_' + key))
|
||||
if key not in ('crashLootMode','crashSiteCount','crashHelicopterChancePercent','crashRifleCount','crashMagazineCount','crashAntiAirChancePercent','crashRocketChancePercent','radioIntelSiteCount','droneReconCount','droneCooldownSeconds','sarkaAngerPerCar','emptyRestartHours','aiDoctrine','aiShadow','motorbikeCount','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','fieldCampCount','fieldHospitalCount','startMode','garageVehicleCount','garageChancePercent','garageRadiusMetres','garageSpawnIntervalSeconds','civilianTrafficCount','roadblockCount','largeRoadblockCount','mainRoadWidthMetres','hunterSpawnDistanceMetres','objectGuardCount','objectGuardEvery','objectGuardHQMode','startWeaponCount','startMagazineCount'): assert ('Default_' if key == 'viewDistanceMetres' else 'Random_') + key in names
|
||||
if key not in ('shoreBoatCount','shoreBoatChancePercent','shoreBoatCrossingChancePercent','crashLootMode','crashSiteCount','crashHelicopterChancePercent','crashRifleCount','crashMagazineCount','crashAntiAirChancePercent','crashRocketChancePercent','radioIntelSiteCount','droneReconCount','droneCooldownSeconds','sarkaAngerPerCar','emptyRestartHours','aiDoctrine','aiShadow','motorbikeCount','planeAltitudeMetres','planeReplacementSeconds','planeSectorSeconds','fieldCampCount','fieldHospitalCount','startMode','garageVehicleCount','garageChancePercent','garageRadiusMetres','garageSpawnIntervalSeconds','civilianTrafficCount','roadblockCount','largeRoadblockCount','mainRoadWidthMetres','hunterSpawnDistanceMetres','objectGuardCount','objectGuardEvery','objectGuardHQMode','startWeaponCount','startMagazineCount'): assert ('Default_' if key == 'viewDistanceMetres' else 'Random_') + key in names
|
||||
else: assert 'Random_startMode' not in names
|
||||
for name in ('Close', 'SettingsPage', 'HQPage', 'JournalPage', 'JournalText', 'JournalContent', 'JournalScroll', 'PauseJournal', 'LatestJournal', 'Status'):
|
||||
assert name in names
|
||||
|
||||
@@ -11,6 +11,10 @@ rules=(rules.replace('string ', 'std::string ').replace('out int ', 'int& ')
|
||||
.replace(' < 0) return false;', ' == std::string::npos) return false;').replace('text.ToInt()', 'std::stoi(text)'))
|
||||
run('#include <cassert>\n#include <string>\n'+rules+r'''
|
||||
int main(){
|
||||
assert(CoreSetting("mission","shoreboats")=="shoreBoatCount"); assert(Valid("shoreBoatCount",0)&&Valid("shoreBoatCount",16)&&!Valid("shoreBoatCount",-1)&&!Valid("shoreBoatCount",17));
|
||||
assert(CoreSetting("mission","boatchance")=="shoreBoatChancePercent"); assert(Valid("shoreBoatChancePercent",0)&&Valid("shoreBoatChancePercent",100)&&!Valid("shoreBoatChancePercent",-1)&&!Valid("shoreBoatChancePercent",101));
|
||||
assert(CoreSetting("mission","boatcrossings")=="shoreBoatCrossingChancePercent"); assert(Valid("shoreBoatCrossingChancePercent",0)&&Valid("shoreBoatCrossingChancePercent",100)&&!Valid("shoreBoatCrossingChancePercent",-1)&&!Valid("shoreBoatCrossingChancePercent",101));
|
||||
|
||||
assert(CoreSetting("mission","crashloot")=="crashLootMode");
|
||||
assert(Valid("crashLootMode",0)&&Valid("crashLootMode",1)&&!Valid("crashLootMode",-1)&&!Valid("crashLootMode",2));
|
||||
assert(CoreSetting("mission","crashsites")=="crashSiteCount");
|
||||
|
||||
@@ -7,7 +7,7 @@ methods=methods.replace('role.ToString()', 'string(std::to_string(role))').repla
|
||||
methods=methods.replace('protected ', '').replace('array<string>', 'Array<string>').replace('set<string> keys = new set<string>();','Array<string> keys;')
|
||||
methods=methods.replace('foreach (', 'for (').replace('ResourceName','string').replace('ERR_TerrainCatalog.Key(prefab)', 'prefab').replace('SCR_AddonTool.GetResourceAddons(prefab)', 'sources')
|
||||
methods=methods.replace('Array<string> sources = sources;', 'Array<string> resourceSources = sources;').replace('project : sources','project : resourceSources')
|
||||
methods=methods.replace('"01234".IndexOf', 'string("01234").IndexOf')
|
||||
methods=methods.replace('"012345".IndexOf', 'string("012345").IndexOf')
|
||||
methods=methods.replace('"0123456789abcdef".IndexOf','string("0123456789abcdef").IndexOf').replace('!records || ', '')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
@@ -88,9 +88,13 @@ int main(){
|
||||
assert(p.SetVehicleRole(a,j,3,false)&&!p.AllowsVehicleRole(j,3,false)&&p.AllowsVehicleRole(j,2,false));
|
||||
assert(p.Preset("save","replacement")&&p.SetVehicleRole(a,j,2,false)&&p.Preset("load","replacement")&&p.AllowsVehicleRole(j,2,false));
|
||||
assert(p.SetSwitch(a,"mod",false)&&!p.AllowsVehicleRole(j,2,true)&&p.SetSwitch(a,"mod",true)&&p.AllowsVehicleRole(j,2,false));
|
||||
assert(p.SetVehicleRole(a,j,5,false)&&!p.AllowsVehicleRole(j,5,true));
|
||||
assert(p.SetVehicleRole(a,j,5,true)&&p.AllowsVehicleRole(j,5,true));
|
||||
assert(p.Preset("save","replacement")&&p.SetVehicleRole(a,j,5,false)&&p.Preset("load","replacement")&&p.AllowsVehicleRole(j,5,true));
|
||||
assert(p.Valid({"active","active|1111111111111111|r5*|1"}));
|
||||
int before=p.m_Revision;p.fail=true;assert(!p.SetVehicleRole(a,j,2,false)&&p.m_Revision==before&&p.AllowsVehicleRole(j,2,false));p.fail=false;
|
||||
assert(!p.SetVehicleRole(a,j,5,true)&&!p.SetVehicleRole(a,j,-1,true)&&!p.SetVehicleRole(a,"bad",0,true));
|
||||
assert(!p.Valid({"active","active|1111111111111111|r5*|1"}));
|
||||
assert(!p.SetVehicleRole(a,j,6,true)&&!p.SetVehicleRole(a,j,-1,true)&&!p.SetVehicleRole(a,"bad",0,true));
|
||||
assert(!p.Valid({"active","active|1111111111111111|r6*|1"}));
|
||||
assert(!p.Set("../x",i,true)&&!p.Set(a,"unknown",true));
|
||||
}
|
||||
''')
|
||||
|
||||
@@ -5,7 +5,7 @@ from policy_harness import ROOT,body,run
|
||||
spec=importlib.util.spec_from_file_location('import_settings',BASE/'tools/import_settings.py')
|
||||
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
|
||||
result,deferred=m.merge({'configVersion':1,'reportIntervalSeconds':20.0,'supportSquadLimit':2,'responseRadiusMetres':1500,'convoyCount':3},{'aiIntelChancePercent':0,'intelRevealChancePercent':100})
|
||||
assert result==dict(crashLootMode=1,crashSiteCount=3,crashHelicopterChancePercent=50,crashRifleCount=3,crashMagazineCount=4,crashAntiAirChancePercent=25,crashRocketChancePercent=50,telemetryEnabled=1,droneReconCount=0,droneCooldownSeconds=300,aiDoctrine=0,aiShadow=0,planeAltitudeMetres=250,planeReplacementSeconds=60,planeSectorSeconds=120,planeReconEnabled=1,roadblockCount=4,largeRoadblockCount=4,mainRoadWidthMetres=6,civilianTrafficCount=3,garageVehicleCount=16,garageChancePercent=70,garageRadiusMetres=1000,garageSpawnIntervalSeconds=5,startMode=0,keepWeaponOnUnconscious=1,selfEpinephrine=0,restartOnEmpty=1,emptyRestartHours=5,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioIntelSiteCount=5,showAllSites=0,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15)
|
||||
assert result==dict(shoreBoatCount=6,shoreBoatChancePercent=20,shoreBoatCrossingChancePercent=70,crashLootMode=1,crashSiteCount=3,crashHelicopterChancePercent=50,crashRifleCount=3,crashMagazineCount=4,crashAntiAirChancePercent=25,crashRocketChancePercent=50,telemetryEnabled=1,droneReconCount=0,droneCooldownSeconds=300,aiDoctrine=0,aiShadow=0,planeAltitudeMetres=250,planeReplacementSeconds=60,planeSectorSeconds=120,planeReconEnabled=1,roadblockCount=4,largeRoadblockCount=4,mainRoadWidthMetres=6,civilianTrafficCount=3,garageVehicleCount=16,garageChancePercent=70,garageRadiusMetres=1000,garageSpawnIntervalSeconds=5,startMode=0,keepWeaponOnUnconscious=1,selfEpinephrine=0,restartOnEmpty=1,emptyRestartHours=5,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioIntelSiteCount=5,showAllSites=0,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15)
|
||||
assert deferred==[]
|
||||
for count in (0,1,3,20):
|
||||
values,notes=m.merge(ai={'convoyCount':count})
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Execute production finite boat-worker decisions with engine calls doubled."""
|
||||
from policy_harness import ROOT,body,run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ShoreBoats.c'
|
||||
step=body(p,'protected void Step(').replace('protected ','').replace('Replication.','Replication::').replace('ERR_GameMode.Get()','ERR_GameMode::Get()').replace('ERR_MissionRules.','ERR_MissionRules::').replace('m_Mode.','m_Mode->').replace('Math.RandomInt','RandomInt').replace('ERR_ShoreMap.Chance','Chance')
|
||||
chance=body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ShoreMap.c','static int Chance(').replace('Math.Max','std::max')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
using vector=int;
|
||||
namespace Replication {bool server=true;bool IsServer(){return server;}}
|
||||
namespace ERR_MissionRules {bool Terminal(int phase){return phase==2;}}
|
||||
struct ERR_GameMode {int phase=0;bool Active(){return phase==1;}int Phase(){return phase;}static ERR_GameMode* Get();} mode;
|
||||
ERR_GameMode* ERR_GameMode::Get(){return &mode;}
|
||||
struct Array:std::vector<int>{using std::vector<int>::vector;bool IsEmpty(){return empty();}int Count(){return size();}void Remove(int i){erase(begin()+i);}};
|
||||
struct Map {Array Edges={1,2,3,4,5};bool ready=false;int work=0;bool Ready(){return ready;}void Step(){work++;}};
|
||||
int roll=10;int RandomInt(int,int end){if(end==100)return roll;return 0;}
|
||||
''' + chance + r'''
|
||||
struct Worker {
|
||||
ERR_GameMode* m_Mode=&mode;Map m_Map;bool m_Stopped=false,clear=true,cross=false,success=true;
|
||||
int m_Target=2,m_Attempts=0,m_Chance=20,m_CrossingChance=70,created=0,spawns=0;std::string m_State;
|
||||
int Count(){return created;}void Stop(){m_Stopped=true;}
|
||||
bool Survey(int,vector&,vector&,bool& crossing){crossing=cross;return clear;}
|
||||
bool Spawn(vector,vector){spawns++;if(success)created++;return success;}
|
||||
''' + step + r'''
|
||||
};
|
||||
int main(){
|
||||
Worker w;for(int i=0;i<100;i++){w.Step();}assert(w.m_Map.work==0&&!w.m_Stopped);
|
||||
mode.phase=1;w.Step();assert(w.m_Map.work==1&&w.spawns==0);w.m_Map.ready=true;
|
||||
for(int i=0;i<100;i++){w.Step();}assert(w.spawns==2&&w.created==2&&w.m_Stopped);
|
||||
Worker ordinary;ordinary.m_Map.ready=true;roll=50;for(int i=0;i<20;i++){ordinary.Step();}assert(ordinary.spawns==0&&ordinary.m_Attempts==5&&ordinary.m_Stopped);
|
||||
Worker facing;facing.m_Map.ready=true;facing.cross=true;for(int i=0;i<20;i++){facing.Step();}assert(facing.created==2);
|
||||
Worker blocked;blocked.m_Map.ready=true;blocked.clear=false;for(int i=0;i<20;i++){blocked.Step();}assert(blocked.m_Stopped&&blocked.spawns==0);
|
||||
Worker failed;failed.m_Map.ready=true;failed.success=false;roll=10;for(int i=0;i<20;i++){failed.Step();}assert(failed.spawns==5&&failed.m_Stopped);
|
||||
Worker bounded;bounded.m_Map.ready=true;bounded.m_Attempts=4096;bounded.Step();assert(bounded.m_Stopped&&bounded.spawns==0);
|
||||
Worker client;Replication::server=false;client.Step();assert(client.m_Stopped&&client.m_Map.work==0);
|
||||
Replication::server=true;mode.phase=2;Worker ended;ended.Step();assert(ended.m_Stopped&&ended.spawns==0);
|
||||
}
|
||||
''')
|
||||
# The audited integration selects only the playable unarmed Motorboat, never scenery,
|
||||
# armed alternatives or a boat's child light entity.
|
||||
candidate=body(p,'static bool Candidate(').replace('string ','Text ')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
struct Text:std::string {using std::string::string;void ToLower(){std::transform(begin(),end(),begin(),::tolower);}bool EndsWith(std::string suffix){return size()>=suffix.size()&&compare(size()-suffix.size(),suffix.size(),suffix)==0;}};
|
||||
'''+candidate+r'''
|
||||
int main(){
|
||||
assert(Candidate("{ABC}Prefabs/Vehicles/Boats/Motorboat/Motorboat_Base.et"));
|
||||
for(auto s:{"Prefabs/Vehicles/RHIB/REAPER_RHIB_BK_ARMED.et","Prefabs/Vehicles/RHIB/REAPER_RHIB_Wreck.et","Prefabs/Vehicles/RHIB/REAPER_RHIB_LightEnt.et","Prefabs/Vehicles/Boats/Motorboat/Lights/Left_Red_Nav_ES.et","Prefabs/Vehicles/Boats/MOD_Boat_Base.et","Prefabs/Props/Boat.et"})assert(!Candidate(s));
|
||||
}
|
||||
''')
|
||||
print('PASS: preparation/client/end guards, finite shuffled candidates, probability once per edge, no replenishment, failed placement bounds and unarmed boat catalog')
|
||||
|
||||
start=body(p,'void Start(').replace('Replication.','Replication::').replace('m_Mode.Settings().Get(', 'settings.Get(').replace('vector low, high; GetGame().GetWorldEntity().GetWorldBounds(low, high);','vector low=0, high=100;').replace('GetGame().GetCallqueue().CallLater(Step, 100, true)','schedules++')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <map>
|
||||
using vector=int;
|
||||
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
||||
struct Settings{std::map<std::string,int> v={{"shoreBoatCount",6},{"shoreBoatChancePercent",20},{"shoreBoatCrossingChancePercent",70}};int Get(std::string key){return v.at(key);}};
|
||||
struct Map{int setups=0;void Setup(vector,vector){setups++;}};
|
||||
struct Pool{bool empty=true;bool IsEmpty(){return empty;}};
|
||||
struct Catalog{Pool Prefabs;bool loaded=false;int finds=0;void Find(){finds++;Prefabs.empty=!loaded;}};
|
||||
struct Service{
|
||||
bool m_Started=false,m_Stopped=false,m_Mode=true;int m_Target=0,m_Chance=0,m_CrossingChance=0,schedules=0;std::string m_State;
|
||||
Settings settings;Catalog m_Catalog;Map m_Map;
|
||||
''' + start + r'''
|
||||
};
|
||||
int main(){
|
||||
Service normal;normal.m_Catalog.loaded=true;normal.Start();normal.Start();assert(normal.schedules==1&&normal.m_Map.setups==1&&normal.m_Target==6&&normal.m_CrossingChance==70&&normal.m_Chance==20);
|
||||
normal.settings.v["shoreBoatCount"]=0;normal.Start();assert(normal.m_Target==6); // saved edits wait until next round
|
||||
Service disabled;disabled.settings.v["shoreBoatCount"]=0;disabled.Start();assert(disabled.schedules==0&&disabled.m_Catalog.finds==0&&disabled.m_State=="disabled");
|
||||
Service missing;missing.Start();assert(missing.schedules==0&&missing.m_Map.setups==0&&missing.m_State=="Motorboat not loaded");
|
||||
Service stopped;stopped.m_Stopped=true;stopped.Start();assert(stopped.schedules==0&&stopped.m_Catalog.finds==0);
|
||||
Service client;Replication::server=false;client.Start();assert(client.schedules==0&&client.m_Catalog.finds==0);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual startup snapshots settings once, skips disabled/missing-mod surveys, and refuses client/stopped scheduling')
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Execute production coastal topology and island-facing selection, not an engine test."""
|
||||
from policy_harness import ROOT,body,run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ShoreMap.c'
|
||||
methods='\n'.join(body(p,s) for s in ['void Setup(', 'vector Point(', 'int Index(', 'int Root(', 'protected void Join(', 'int Neighbor(', 'bool Ready(', 'void Step(', 'bool FacingIsland(', 'static int Chance('])
|
||||
methods=methods.replace('protected ','').replace('vector.Lerp','Vec::Lerp').replace('vector ','Vec ').replace('Math.Max','Max').replace('Math.Ceil','ceil').replace('Math.Floor','floor').replace('Math.Clamp','Clamp').replace('Vector(','Vec(').replace('System.GetTickCount()','0').replace('ERR_CampTerrain.Rotate','Rotate')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
float Max(float a,float b){return std::max(a,b);} int Clamp(int v,int a,int b){return std::clamp(v,a,b);}
|
||||
struct Vec { float v[3]; Vec(float x=0,float y=0,float z=0):v{x,y,z}{} float& operator[](int i){return v[i];}
|
||||
Vec operator+(Vec b){return {v[0]+b[0],v[1]+b[1],v[2]+b[2]};} Vec operator*(float n){return {v[0]*n,v[1]*n,v[2]*n};}
|
||||
static Vec Lerp(Vec a,Vec b,float t){return a*(1-t)+b*t;} };
|
||||
Vec Rotate(Vec v,float angle){float a=angle*3.14159265/180;return {v[0]*cosf(a)+v[2]*sinf(a),0,v[2]*cosf(a)-v[0]*sinf(a)};}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}void Insert(T v){this->push_back(v);}int Count(){return this->size();}};
|
||||
struct Map {
|
||||
Vec Low;float Cell=64;int Width=0,Height=0,m_Cursor=0,m_Phase=0;Array<int> Parents,Edges;
|
||||
std::function<bool(Vec)> terrain=[](Vec){return false;};int samples=0;
|
||||
bool Land(Vec p){samples++;return terrain(p);}
|
||||
''' + methods + r'''
|
||||
void Build(){for(int i=0;i<10000&&!Ready();i++){int n=samples;Step();assert(samples-n<=64*7);}assert(Ready());}
|
||||
};
|
||||
int main(){
|
||||
Map m;m.Setup({0,0,0},{1024,0,1024});assert(m.Width==16&&m.Height==16&&m.Cell==64);
|
||||
// Two islands separated by a four-cell sea channel.
|
||||
m.terrain=[](Vec p){return p[0]<256 || (p[0]>=512&&p[0]<768);};m.Build();
|
||||
assert(m.Root(0)!=m.Root(8));assert(m.FacingIsland(0,{256,0,400},{1,0,0}));
|
||||
assert(!m.FacingIsland(0,{0,0,400},{-1,0,0})); // open sea
|
||||
assert(m.Index({-1,0,0})==-1&&m.Index({1024,0,0})==-1);
|
||||
assert(m.Neighbor(15,0)==-1&&m.Neighbor(0,1)==-1);
|
||||
assert(m.Chance(true,20,70)==70&&m.Chance(false,20,70)==20);
|
||||
assert(m.Chance(true,80,30)==80&&m.Chance(false,0,70)==0);
|
||||
// A land connection along the north edge makes the same shape a bay, not islands.
|
||||
m.Setup({0,0,0},{1024,0,1024});m.terrain=[](Vec p){return p[2]<64||p[0]<256||(p[0]>=512&&p[0]<768);};m.Build();
|
||||
assert(m.Root(0)==m.Root(8));assert(!m.FacingIsland(0,{256,0,400},{1,0,0}));
|
||||
// Channels smaller than one cell still prevent a link when intermediate samples see water.
|
||||
m.Setup({0,0,0},{256,0,256});m.terrain=[](Vec p){return p[0]<50||p[0]>78;};m.Build();assert(m.Root(0)!=m.Root(1));
|
||||
// All water / empty terrain creates no shore candidates; very large worlds stay bounded.
|
||||
m.Setup({0,0,0},{256,0,256});m.terrain=[](Vec){return false;};m.Build();assert(m.Edges.empty());
|
||||
m.Setup({0,0,0},{0,0,0});m.Build();assert(m.Edges.empty());
|
||||
m.Setup({0,0,0},{100000,0,100000});assert(m.Width<=256&&m.Height<=256);
|
||||
}
|
||||
''')
|
||||
print('PASS: actual bounded shoreline grid, connected bays versus separate islands, narrow channels, outward fan, map limits and probability boost')
|
||||
Reference in New Issue
Block a user