65 lines
3.7 KiB
Python
65 lines
3.7 KiB
Python
"""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);
|
|
Waves configured{};
|
|
configured.Configure(2);
|
|
assert(!configured.Due(0,3,true));
|
|
assert(configured.Due(600000,3,true)&&configured.Target==6);
|
|
configured.Commit();assert(configured.Pending==5&&configured.Spawned==1);
|
|
configured.Configure(5); // Already sampled wave and deadline stay unchanged.
|
|
assert(configured.Pending==5&&configured.NextWave==1200000);
|
|
assert(configured.Due(1200000,30,true)&&configured.Target==30); // Keep the formation cap.
|
|
configured.Configure(0);assert(configured.Pending==0&&configured.Target==0);
|
|
assert(!configured.Due(9999999,30,true)&&configured.Spawned==1); // No waves; retain deployed count.
|
|
configured.Configure(2);assert(!configured.Due(10000000,3,true));
|
|
assert(!configured.Due(10599999,3,true)); // Re-enable waits normally, no backlog.
|
|
assert(configured.Due(10600000,3,true)&&configured.Target==6);
|
|
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: configurable anger multiplier, disable/re-enable, wave cap, staged retries, empty-round reset, no backlog, four seats and nearest-player road distance')
|