Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
73 lines
6.1 KiB
Python
73 lines
6.1 KiB
Python
"""Execute actual HQ multi-convoy planning with delivered-report boundaries doubled."""
|
|
import os, re, sys
|
|
from pathlib import Path
|
|
ROOT=Path(os.environ.get('ERR_REPO','/home/jakub/devel/escapistsrereintroduced'))
|
|
sys.path.insert(0,str(ROOT/'tests'))
|
|
from policy_harness import body,run
|
|
p=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
|
|
methods='\n'.join(body(p,s) for s in ('void PlanResupply(float now)','bool PlanResupplyTask(ERR_InfantryResupply supply, float now)'))
|
|
methods=methods.replace('null','nullptr').replace('foreach (','for (')
|
|
methods=methods.replace('ERR_HQUnit donor;','ERR_HQUnit donor=nullptr;')
|
|
for name in ('m_Resupply','supply','task','unit','donor','recipient','mounted','mineRecipient','launcherRecipient','m_Watch'):
|
|
methods=methods.replace(name+'.',name+'->')
|
|
methods=methods.replace('task->Vehicles[1].','task->Vehicles[1]->')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <string>
|
|
using string=std::string;
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}};
|
|
struct Unit {int Group=0;string ForceSourceId;bool fighting=false;};using ERR_HQUnit=Unit*;
|
|
struct Entry {int value=0;};using ERR_HQWatchEntry=Entry*;
|
|
struct Watch {std::map<string,Entry*> delivered;Entry* Find(string id){return delivered.count(id)?delivered[id]:nullptr;}};
|
|
struct Vehicle{int Group=0;};
|
|
struct Task{bool Stopped=false,Ready=true;Array<Vehicle*> Vehicles;};using ERR_ConvoyTask=Task*;
|
|
struct Supply{
|
|
int state=1,calls=0,accepted=0,mountedCalls=0,mountedAccepted=0;int launcherCalls=0,launcherAccepted=0;bool launcher=false;bool infantry=true,mounted=false,mines=false;int mineCalls=0,mineAccepted=0;Task*task=nullptr;Unit* recipient=nullptr;Entry* demand=nullptr;Entry* donorReport=nullptr;
|
|
int State(){return state;}Task*Task(){return task;}
|
|
bool TryPlan(Unit*r,Unit*,Entry*d,Entry*s,float){calls++;if(!infantry||r!=recipient||!d||!s||d!=demand||s!=donorReport)return false;accepted++;state=2;return true;}
|
|
bool TryPlanMounted(Unit*r,Unit*,Entry*d,Entry*s,float){mountedCalls++;if(!mounted||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;mountedAccepted++;state=6;return true;}
|
|
bool TryPlanLauncher(Unit*r,Unit*,Entry*d,Entry*s,float){launcherCalls++;if(!launcher||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;launcherAccepted++;state=2;return true;}
|
|
bool TryPlanMines(Unit*r,Unit*,Entry*d,Entry*s,float){mineCalls++;if(!mines||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;mineAccepted++;state=2;return true;}
|
|
};using ERR_InfantryResupply=Supply*;
|
|
struct Pool{Array<Supply*> services;int Count(){return services.Count();}Supply*At(int i){return services[i];}};
|
|
struct HQ{
|
|
Pool*m_Resupply=nullptr;int m_NextResupply=0;Array<Unit*>m_Units;Watch*m_Watch=nullptr;
|
|
bool ConvoyContact(Unit*u,float){return u->fighting;}
|
|
METHODS
|
|
};
|
|
struct Fixture{
|
|
Pool pool;Watch watch;HQ hq;Supply first,second;Task a,b;Vehicle va{1},vb{2};Unit da{1,"a"},db{2,"b"},recipient{3,"recipient"};Entry aStatus,bStatus,demand;
|
|
Fixture(){a.Vehicles={&va,&va,&va,&va,&va,&va};b.Vehicles={&vb,&vb,&vb,&vb,&vb,&vb};
|
|
first.task=&a;second.task=&b;first.recipient=second.recipient=&recipient;first.demand=second.demand=&demand;first.donorReport=&aStatus;second.donorReport=&bStatus;
|
|
watch.delivered={{"a",&aStatus},{"b",&bStatus},{"recipient",&demand}};pool.services={&first,&second};hq.m_Resupply=&pool;hq.m_Watch=&watch;hq.m_Units={&da,&db,&recipient};}
|
|
};
|
|
int main(){
|
|
{Fixture f;f.first.infantry=false;f.first.launcher=true;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.launcherAccepted==1&&f.first.mineCalls==0&&f.second.calls==0);}
|
|
{Fixture f;f.first.launcher=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.launcherCalls==0);}
|
|
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.first.launcher=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==1&&f.first.launcherCalls==0);}
|
|
|
|
{Fixture f;f.first.infantry=false;f.first.mounted=false;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.mineAccepted==1&&f.first.mountedCalls>0&&f.second.calls==0);}
|
|
{Fixture f;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.mineCalls==0);}
|
|
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==1&&f.first.mineCalls==0);}
|
|
|
|
{Fixture f;f.first.mounted=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.mountedCalls==0);}
|
|
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.hq.PlanResupply(100);assert(f.first.accepted==0&&f.first.mountedAccepted==1&&f.second.calls==0);}
|
|
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.recipient.fighting=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==0);}
|
|
|
|
{Fixture f;f.first.state=0;f.hq.PlanResupply(100);assert(f.first.calls==0&&f.second.accepted==1);}
|
|
{Fixture f;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.second.calls==0);f.first.state=1;
|
|
f.hq.PlanResupply(105);assert(f.second.accepted==1&&f.first.accepted==1);}
|
|
{Fixture f;f.a.Stopped=true;f.hq.PlanResupply(100);assert(f.first.calls==0&&f.second.accepted==1);}
|
|
{Fixture f;f.da.fighting=true;f.hq.PlanResupply(100);assert(f.first.calls==0&&f.second.accepted==1);}
|
|
{Fixture f;f.watch.delivered.erase("a");f.hq.PlanResupply(100);assert(f.first.accepted==0&&f.second.accepted==1);}
|
|
{Fixture f;f.watch.delivered.erase("recipient");f.hq.PlanResupply(100);assert(f.first.accepted==0&&f.second.accepted==0);}
|
|
{Fixture f;f.pool.services={nullptr,&f.second};f.hq.m_NextResupply=17;f.hq.PlanResupply(100);assert(f.second.accepted==1);}
|
|
{Fixture f;f.a.Vehicles[1]=nullptr;f.hq.PlanResupply(100);assert(f.first.calls==0&&f.second.accepted==1);}
|
|
{Fixture f;f.pool.services.resize(21,&f.first);f.hq.PlanResupply(100);assert(f.first.calls==0);}
|
|
{Fixture f;f.pool.services.clear();f.hq.PlanResupply(100);f.hq.m_Resupply=nullptr;f.hq.PlanResupply(100);assert(f.first.calls==0);}
|
|
}
|
|
'''.replace('METHODS',methods).replace('Task*Task()','struct Task*Task()').replace('Task*task=','struct Task*task='))
|
|
print('PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence')
|