Add anger-scaled Sarka hunter waves every ten minutes
This commit is contained in:
@@ -70,7 +70,7 @@ int main(){
|
||||
assert(!c.mode.ledger.r.Pinned&&c.unit.Entity==&c.vehicle&&c.unit.Group==&c.group);
|
||||
c.mode.roster.current=&c.player;c.Enter();assert(c.access.nativeCalls==2&&c.recovery.stops==1&&c.hq.unregistered==1);
|
||||
}
|
||||
for(int bad=0;bad<9;bad++){Case c;
|
||||
for(int bad=0;bad<8;bad++){Case c;
|
||||
if(bad==0)Replication::server=false;
|
||||
if(bad==1)c.service.m_Stopped=true;
|
||||
if(bad==2)c.mode.active=false;
|
||||
@@ -79,9 +79,9 @@ int main(){
|
||||
if(bad==5)c.hq.service=&c.foreign;
|
||||
if(bad==6)c.mode.roster.current=&c.unrelated;
|
||||
if(bad==7)c.unit.Entity=nullptr;
|
||||
if(bad==8)c.service.m_Hunters.resize(22,&c.unit);
|
||||
c.Enter();assert(!c.unit.PlayerCaptureObserved&&!c.unit.Released&&c.hq.unregistered==0&&c.access.nativeCalls==1);
|
||||
}
|
||||
{Case c;c.service.m_Hunters.resize(100,&c.unit);c.Enter();assert(c.unit.PlayerCaptureObserved&&c.unit.Released&&c.hq.unregistered==1);}
|
||||
{Case c;c.Enter(&c.unrelated);assert(!c.unit.Released&&c.access.nativeCalls==1);}
|
||||
{Case c;IEntity chain[33];for(int i=0;i<32;i++)chain[i].parent=&chain[i+1];chain[32].parent=&c.vehicle;c.Enter(&chain[0]);assert(!c.unit.Released);}
|
||||
{Case c;IEntity loop;loop.parent=&loop;c.Enter(&loop);assert(!c.unit.Released);}
|
||||
|
||||
@@ -31,7 +31,7 @@ struct ERR_SiteRuntime{struct Definition* Definition=nullptr;struct Group* Group
|
||||
struct Sites{Array<ERR_SiteRuntime*> sites;int Count(){return sites.Count();}ERR_SiteRuntime* At(int i){return sites[i];}};
|
||||
struct Forces{int count=0;int Count(){return count;}};
|
||||
struct Mode{struct Forces forces;struct Sites sites;struct Forces* Forces(){return &forces;}struct Sites* Sites(){return &sites;}int EnemyFaction(){return 1;}};
|
||||
struct ERR_HunterVehicle{bool Motorbike=false;bool Released=false;IEntity* Entity=nullptr;};
|
||||
struct ERR_HunterVehicle{bool Motorbike=false;bool Sarka=false;bool Released=false;IEntity* Entity=nullptr;};
|
||||
struct ERR_HQ{static void Event(string,string){}};
|
||||
namespace Math{float RandomFloat(float,float){return 0;}}
|
||||
namespace ERR_LocationKind{enum{RADIO=1,DEPOT=2,OUTPOST=3};}
|
||||
|
||||
@@ -27,7 +27,7 @@ template<class... T>string Format(string s,T...){return s;}
|
||||
struct ERR_HQWatchEntry{bool Closed=false;int Fit=3;float ObservedAt=-1;};
|
||||
struct Watch{std::map<string,ERR_HQWatchEntry*> entries;ERR_HQWatchEntry* Find(string s){return entries[s];}};
|
||||
struct ERR_HQ{struct Watch watch;struct Watch* Watch(){return &watch;}static void Event(string,string){}};
|
||||
struct ERR_HunterVehicle{bool Motorbike=false,ReserveCovered=false,Released=false;float LaunchedAt=0;string Source;};
|
||||
struct ERR_HunterVehicle{bool Motorbike=false,Sarka=false,ReserveCovered=false,Released=false;float LaunchedAt=0;string Source;};
|
||||
struct ERR_HunterFleetRules{
|
||||
'''+rules+r'''
|
||||
};
|
||||
@@ -52,8 +52,8 @@ int main(){
|
||||
assert(!ERR_HunterFleetRules::ReserveDue(4,3,1,0,960000,959999));
|
||||
assert(ERR_HunterFleetRules::ReserveDue(4,3,1,1,960000,960000));
|
||||
|
||||
Service s;ERR_HQ hq;ERR_HunterVehicle a,b,c,bike;a.Source="a";b.Source="b";c.Source="c";
|
||||
bike.Source="bike";bike.Motorbike=true;s.m_Hunters={&a,&b,&c,&bike};
|
||||
Service s;ERR_HQ hq;ERR_HunterVehicle a,b,c,bike,sarka;a.Source="a";b.Source="b";c.Source="c";
|
||||
bike.Source="bike";bike.Motorbike=true;sarka.Sarka=true;sarka.Source="sarka";s.m_Hunters={&sarka,&a,&b,&c,&bike};assert(s.Deployed()==3);
|
||||
a.Released=true;assert(s.AvailableToHQ(&hq,100)==3); // physical loss alone reveals nothing
|
||||
assert(!s.ReserveSource(&hq,479999));
|
||||
assert(s.ReserveSource(&hq,480000)==&a); // absence of a first check-in has a bounded deadline
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import re
|
||||
from policy_harness import ROOT,body,run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c'
|
||||
sarka_rules=body(p.parent/'ERR_HunterSarkaRules.c','static bool Seats(')
|
||||
bike_rules=body(p.parent/'ERR_OptionalMotorbikes.c','static bool Seats(')
|
||||
rules=body(p,'static bool Transport(')+body(p,'static bool Seats(')
|
||||
s=body(p,'override protected bool FillCrew(').replace('override protected ','')
|
||||
@@ -13,7 +14,7 @@ for typ in ['ERR_ConvoyVehicle','ERR_HunterVehicle','BaseCompartmentSlot','AIGro
|
||||
for obj in ['unit','seat','hunter','m_Mode']:
|
||||
s=s.replace(obj+'.',obj+'->')
|
||||
s=s.replace('unit->Seats[i].','unit->Seats[i]->')
|
||||
for cls in ['ERR_EnemyMedicalCatalog','ERR_EnemyLoadoutCatalog','ERR_ConvoyRules','ERR_HunterFleetRules','ERR_MotorbikeRules','ERR_ConvoyResources','ERR_HQ','ECompartmentType','string']:
|
||||
for cls in ['ERR_EnemyMedicalCatalog','ERR_EnemyLoadoutCatalog','ERR_ConvoyRules','ERR_HunterFleetRules','ERR_MotorbikeRules','ERR_SarkaRules','ERR_ConvoyResources','ERR_HQ','ECompartmentType','string']:
|
||||
s=s.replace(cls+'.',cls+'::')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
@@ -25,6 +26,9 @@ template<class T>struct Array:std::vector<T>{using std::vector<T>::operator=;voi
|
||||
struct ERR_HunterFleetRules{
|
||||
'''+rules+r'''
|
||||
};
|
||||
struct ERR_SarkaRules{
|
||||
'''+sarka_rules+r'''
|
||||
};
|
||||
struct ERR_MotorbikeRules{
|
||||
'''+bike_rules+r'''
|
||||
};
|
||||
@@ -33,7 +37,7 @@ struct IEntity{int prefab;};struct AIGroup{};
|
||||
struct BaseCompartmentSlot{int type;bool available=true,fail=false;IEntity*occupant=nullptr;bool IsCompartmentAccessible(){return available;}IEntity*GetOccupant(){return occupant;}int GetType(){return type;}
|
||||
IEntity*SpawnCharacterInCompartment(int prefab,AIGroup*&,int){if(fail)return nullptr;occupant=new IEntity{prefab};return occupant;}};
|
||||
struct Vehicle{Array<BaseCompartmentSlot*> seats;};
|
||||
struct ERR_ConvoyVehicle{Vehicle*Entity;AIGroup*Group;bool TroopTransport=false;bool Motorbike=false;Array<BaseCompartmentSlot*>Seats;Array<IEntity*>Members;IEntity*Driver=nullptr;};using ERR_HunterVehicle=ERR_ConvoyVehicle;
|
||||
struct ERR_ConvoyVehicle{Vehicle*Entity;AIGroup*Group;bool TroopTransport=false;bool Motorbike=false;bool Sarka=false;Array<BaseCompartmentSlot*>Seats;Array<IEntity*>Members;IEntity*Driver=nullptr;};using ERR_HunterVehicle=ERR_ConvoyVehicle;
|
||||
struct Mode{int owned=0;void Own(IEntity*){owned++;}};
|
||||
namespace ERR_EnemyMedicalCatalog{int Crew(Mode*,bool medic){return medic?2:1;}}
|
||||
namespace ERR_EnemyLoadoutCatalog{bool Available(int p){return p>0;}}
|
||||
@@ -58,6 +62,14 @@ int main(){
|
||||
BaseCompartmentSlot driver{ECompartmentType::PILOT},passenger{ECompartmentType::CARGO};vehicle.seats={};vehicle.seats.Insert(&driver);vehicle.seats.Insert(&passenger);
|
||||
assert(service.FillCrew(&bike)&&bike.Members.Count()==2&&bike.Driver==driver.occupant);
|
||||
assert(driver.occupant->prefab==1&&passenger.occupant->prefab==2);}
|
||||
for(int passengers=2;passengers<=4;passengers++){
|
||||
Mode mode;Service service{&mode};Vehicle vehicle;AIGroup group;ERR_HunterVehicle car{&vehicle,&group};car.Sarka=true;car.TroopTransport=true;
|
||||
BaseCompartmentSlot driver{ECompartmentType::PILOT},cargo[4];vehicle.seats.Insert(&driver);
|
||||
for(int i=0;i<passengers;i++){cargo[i].type=ECompartmentType::CARGO;vehicle.seats.Insert(&cargo[i]);}
|
||||
assert(service.FillCrew(&car)==(passengers==3));
|
||||
if(passengers==3){assert(car.Members.Count()==4&&car.Driver==driver.occupant);int medics=0;for(auto member:car.Members)if(member->prefab==2)medics++;assert(medics==1);}
|
||||
else assert(mode.owned==0);
|
||||
}
|
||||
assert(!ERR_HunterFleetRules::Seats(true,1,0,13,14)); // no silent population shrink
|
||||
assert(!ERR_HunterFleetRules::Seats(false,1,0,14,15)); // transport cannot pass as armed UAZ
|
||||
assert(!ERR_HunterFleetRules::Seats(true,2,0,14,16));
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Run production wave scheduling and multiplayer placement boundaries."""
|
||||
from policy_harness import ROOT, body, run
|
||||
|
||||
p = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterSarkaRules.c'
|
||||
clock = p.read_text().split('class ERR_SarkaWaves', 1)[1].split('class ERR_SarkaRules', 1)[0]
|
||||
clock = clock.replace('Math.Clamp', 'Math::Clamp')
|
||||
band = body(p, 'static bool InBand(').replace('array<vector> players', 'Array<vector> players')
|
||||
band = band.replace('foreach (', 'for (').replace('ERR_ForceRules.', 'ERR_ForceRules::').replace('Math.', 'Math::').replace('vector.DistanceXZ', 'vector::DistanceXZ')
|
||||
seats = body(p, 'static bool Seats(')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
namespace Math { int Clamp(int x,int lo,int hi){return std::clamp(x,lo,hi);} float Min(float a,float b){return std::min(a,b);} }
|
||||
struct vector { float x=0,y=0,z=0; static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);} };
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;bool IsEmpty(){return this->empty();}};
|
||||
namespace ERR_ForceRules { bool FiniteVector(vector v){return std::isfinite(v.x)&&std::isfinite(v.y)&&std::isfinite(v.z);} }
|
||||
struct Waves
|
||||
'''+clock+r''';
|
||||
struct Rules {'''+band+seats+r'''};
|
||||
int main(){
|
||||
Waves w{};
|
||||
assert(!w.Due(100,3,true)&&w.NextWave==600100);
|
||||
assert(!w.Due(600099,3,true));
|
||||
assert(w.Due(600100,3,true)&&w.Pending==3&&w.Wave==1);
|
||||
// Failed placement spends an attempt, but no car or anger point.
|
||||
assert(!w.Due(605099,3,true)&&w.Pending==3&&w.Spawned==0);
|
||||
assert(w.Due(605100,7,true));w.Commit();assert(w.Pending==2&&w.Spawned==1);
|
||||
assert(w.Due(610100,7,true));w.Commit();
|
||||
assert(w.Due(615100,7,true));w.Commit();
|
||||
assert(!w.Due(620100,7,true)); // anger changes do not enlarge a wave in progress
|
||||
assert(w.Due(1200100,7,true)&&w.Pending==7&&w.Wave==2);
|
||||
assert(w.Due(9999999,2,true)&&w.Pending==2&&w.Wave==3); // no missed-wave backlog
|
||||
w.Pause();assert(w.Pending==0);
|
||||
assert(!w.Due(10000000,30,true)&&w.NextWave==10600000);
|
||||
assert(w.Due(10600000,1000,true)&&w.Pending==30);
|
||||
assert(!w.Due(10600001,30,false)&&w.Pending==0&&w.NextWave==-1);
|
||||
assert(!w.Due(20000000,0,true));assert(!w.Due(20600000,0,true)&&w.Target==0);
|
||||
assert(!w.Due(21200000,-1,true)&&w.Pending==0);
|
||||
Array<vector> players{{0,10,0}};
|
||||
assert(!Rules::InBand({999,0,0},players));
|
||||
assert(Rules::InBand({1000,900,0},players)&&Rules::InBand({1250,0,0},players));
|
||||
assert(!Rules::InBand({1251,0,0},players));
|
||||
players.push_back({1100,0,100});assert(!Rules::InBand({1100,0,0},players));
|
||||
players[1]={3000,0,0};assert(Rules::InBand({1100,0,0},players));
|
||||
assert(!Rules::InBand({NAN,0,0},players)&&!Rules::InBand({0,0,0},{}));
|
||||
assert(Rules::Seats(1,0,3,4)&&!Rules::Seats(1,1,2,4)&&!Rules::Seats(1,0,4,5));
|
||||
}
|
||||
''')
|
||||
print('PASS: ten-minute anger waves, staged retries, empty-round reset, no backlog, four seats and nearest-player road distance')
|
||||
Reference in New Issue
Block a user