Configure shared starting backpack contents from the admin menu

This commit is contained in:
2026-10-15 14:36:18 +02:00
parent 0a7102cd51
commit a2106cfa26
15 changed files with 514 additions and 26 deletions
+52
View File
@@ -0,0 +1,52 @@
"""Execute production starting-supply validation, edits, mode switches and save guards."""
from policy_harness import ROOT, body, run
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_StartBackpack.c'
methods='\n'.join(body(p,n) for n in ['static bool Valid(', 'protected bool Save(', 'bool Custom(', 'bool SetMode(', 'bool Set(', 'bool Remove('])
methods=methods.replace('protected ','').replace('array<string>', 'Array<string>').replace('set<string> seen = new set<string>();','Array<string> seen;').replace('!records || ', '').replace('ResourceName','string').replace('quantity.ToString()', 'string(std::to_string(quantity))')
methods=methods.replace('int total;', 'int total = 0;')
methods=methods.replace('ERR_TerrainCatalog.Key','Key').replace('ERR_ItemPreferences.Guid','Guid').replace('ERR_CommandArguments.Integer','Integer').replace('Replication.IsServer()', 'authority').replace('ERR_ProfileConfig.Save(PATH, json)', 'Persist(json)').replace('JsonSaveContext json = new JsonSaveContext();','JsonSaveContext json;')
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
struct string;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T x){this->push_back(x);}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}void Copy(Array<T> x){*this=x;}};
struct string:std::string{using std::string::string;string(std::string x):std::string(x){}int Length(){return size();}bool EndsWith(string x){return size()>=x.size()&&substr(size()-x.size())==x;}void Split(string delim,Array<string>& out,bool){size_t a=0;while(true){auto b=find(delim,a);out.Insert(substr(a,b==npos?b:b-a));if(b==npos)break;a=b+delim.size();}}};
bool Guid(string s){return s.size()==16&&s.find_first_not_of("0123456789abcdef")==s.npos;}
string Key(string s){if(s.size()<18||s[0]!='{'||s[17]!='}')return "";string k=s.substr(1,16);std::transform(k.begin(),k.end(),k.begin(),::tolower);return Guid(k)?k:string("");}
bool Integer(string s,int& n){try{size_t end;n=std::stoi(s,&end);return end==s.size();}catch(...){return false;}}
struct JsonSaveContext{Array<string> rows;void EnableTypeDiscriminator(bool){}void WriteValue(string,int){}void WriteValue(string,Array<string> v){rows=v;}};
struct Supplies{Array<string> m_Records={"default"},disk;bool authority=true,m_ReadOnly=false,fail=false;
bool Persist(JsonSaveContext json){if(fail)return false;disk=json.rows;return true;}
'''+methods+r'''
};
int main(){
string a="{AAAAAAAAAAAAAAAA}Prefabs/rifle.et",b="{BBBBBBBBBBBBBBBB}Prefabs/magazine.et";
Supplies s;assert(!s.Custom());assert(s.Set(a,2)&&s.Set(b,4)&&s.m_Records.size()==3);
assert(s.SetMode(true)&&s.Custom());assert(s.Set(a,3)&&s.m_Records.size()==3);
auto saved=s.m_Records;s.fail=true;assert(!s.SetMode(false)&&!s.Remove("aaaaaaaaaaaaaaaa")&&s.m_Records==saved);s.fail=false;
s.authority=false;assert(!s.Set(a,1)&&!s.SetMode(false)&&s.m_Records==saved);s.authority=true;
s.m_ReadOnly=true;assert(!s.Set(b,1)&&s.m_Records==saved);s.m_ReadOnly=false;
assert(!s.Set(a,-1)&&!s.Set(a,49)&&s.m_Records==saved);
assert(s.Set(a,48));assert(!s.Set(b,48));assert(s.Set(b,16));assert(!s.Set(b,17));
assert(s.SetMode(false)&&s.m_Records.size()==3);assert(s.SetMode(true)&&s.m_Records.size()==3);
assert(s.Remove("aaaaaaaaaaaaaaaa")&&s.m_Records.size()==2);assert(!s.Remove("aaaaaaaaaaaaaaaa"));
assert(s.Set(b,0)&&s.m_Records.size()==1&&s.Custom());
assert(!Supplies::Valid({})&&!Supplies::Valid({"invalid"}));
for(string bad:Array<string>{"", "missing-guid.et|1",a+"|0",a+"|-1",a+"|49",a+"|1.2",a+"|1junk",a+"|1|x"})assert(!Supplies::Valid({"custom",bad}));
assert(!Supplies::Valid({"custom",a+"|1",a+"|2"}));
for(int i=0;i<32;i++){char buf[128];std::snprintf(buf,sizeof(buf),"{%016x}Prefabs/item.et",i);assert(s.Set(buf,1));}
assert(!s.Set(a,1));assert(s.disk==s.m_Records);
}
''')
session=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_AdminSession.c'
assert body(session,'void Command(').index('if (!Authorize()) return;') < body(session,'void Command(').index('StartBackpackCommand(')
command=body(session,'protected bool StartBackpackCommand(')
assert command.index('catalog.Contains(parts[2], parts[3])') < command.index('supplies.Set(item.Prefab, count)')
stock=(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c').read_text()
assert 'spec.Kind == ERR_LocationKind.PRISON && !customStart' in stock
assert 'placement.Optional && GetGame().GetWorld().GetWorldTime() > m_Deadline' in stock
assert 'ERR_Actors.DeleteSpawned(placement.Item); placement.Skipped = true;' in stock
print('PASS: exact starting-stock edits, quantities/type/total limits, duplicate rejection, saved custom/default modes, authority/read-only/save-failure retention and authenticated catalog routing')