XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEWarframe

【WinUI】基于Warframe游戏本体的公开服务器以及安装器

公开
关注 0 Fork 0 Star 0
UTF-8
"""Copy audited event/reward data literals. Never evaluate upstream code.

All schedules, switches and mutations are implemented in C# WorldEventSettings.
This build-time importer fails on expressions it does not explicitly recognize.
"""
import json,re
from pathlib import Path

ROOT=Path(__file__).resolve().parents[1]
SRC=ROOT/'artifacts/openwf-frontend-audit-20260926/SpaceNinjaServer/src'
TOKEN=r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'|//[^\n]*|/\*[\s\S]*?\*/|\s+|[A-Za-z_$][\w$]*|[^\s]'
versions=json.loads((ROOT/'Server/data/world/gameToBuildVersion.json').read_text(encoding='utf-8-sig'))

def clean(s):
    return ''.join(t for t in re.findall(TOKEN,s) if not t.startswith('//') and not t.startswith('/*'))

def balanced(s,start):
    pairs={'{':'}','[':']','(':')'}; stack=[]; pos=start
    for token in re.findall(TOKEN,s[start:]):
        if token in pairs:stack.append(pairs[token])
        elif token in pairs.values():
            if not stack or stack.pop()!=token:raise ValueError('Unbalanced data literal')
            if not stack:return s[start:pos+len(token)]
        pos+=len(token)
    raise ValueError('Unclosed data literal')

def assignment(s,name):
    m=re.search(r'\bconst '+re.escape(name)+r'\b[^=]*=\s*',s)
    if not m:raise ValueError('Missing constant '+name)
    return balanced(s,m.end())

def literal(s,refs=None):
    s=clean(s)
    units={'second':1000,'minute':60000,'hour':3600000,'day':86400000,'week':604800000}
    s=re.sub(r'unixTimesInMs\.(\w+)\s*([*/])\s*([0-9.]+)',lambda m:str(units[m[1]]*float(m[3]) if m[2]=='*' else units[m[1]]/float(m[3])),s)
    s=re.sub(r'unixTimesInMs\.(\w+)',lambda m:str(units[m[1]]),s)
    s=re.sub(r'gameToBuildVersionInt\["([^"]+)"\]',lambda m:str(int(versions[m[1]].replace('.',''))),s)
    s=re.sub(r'toOid2\("([^"]+)",\s*buildVersion\)',lambda m:json.dumps({'$oid':m[1]}),s)
    s=re.sub(r'toMongoDate2\((\d+),\s*buildVersion\)',lambda m:json.dumps({'$date':{'$numberLong':m[1]}}),s)
    s=re.sub(r'changeLegacyTags\s*\?\s*tagsForOlderGoals\[\d+\]\s*:\s*("[^"]+")',r'\1',s)
    s=re.sub(r'\(?config\.worldState\.[A-Za-z]+ProgressOverride\s*\?\?\s*0\)?(?:\s*/\s*100)?','0',s)
    s=re.sub(r'\bcount\s*/\s*100','0',s)
    s=s.replace('!config.unfaithfulBugFixes?.giveBreedingGroundsRewardsAtSum','true')
    s=re.sub(r'defenseWavesPerRotation\s*\*\s*(\d+)',lambda m:json.dumps({'$defenseRotations':int(m[1])}),s)
    s=re.sub(r'\b(activation|expiry)\.toString\(\)',lambda m:json.dumps('@'+m[1]),s)
    tokens=re.findall(TOKEN,s);out=[]
    for i,t in enumerate(tokens):
        following=next((x for x in tokens[i+1:] if not x.isspace()),'')
        if t.startswith("'"):t=json.dumps(t[1:-1].replace("\\'","'").replace('\\\\','\\'))
        elif re.fullmatch(r'[A-Za-z_$][\w$]*',t):
            if following==':':t=json.dumps(t)
            elif refs and t in refs:t=json.dumps(refs[t])
        out.append(t)
    normalized=''.join(out)
    try:return json.loads(normalized)
    except json.JSONDecodeError as e:raise ValueError(normalized[max(0,e.pos-100):e.pos+160]) from e

def section(s,key):
    # This only selects the directly named optional switch, not expressions in a nested event.
    m=re.search(r'\bif\s*\(\s*\(?config\.worldState\?\.'+re.escape(key)+r'\b',s)
    if not m:raise ValueError('Missing switch '+key)
    cond=balanced(s,s.index('(',m.start())); start=m.start()+len(s[m.start():s.index('(',m.start())])+len(cond)
    return balanced(s,s.index('{',start))

def calls(s,field,refs=None):
    out=[]
    for m in re.finditer(r'worldState\.'+field+r'\.push\s*\(',s):
        args=balanced(s,m.end()-1)[1:-1]
        out.extend(literal('['+args+']',refs))
    return out

world=(SRC/'services/worldStateService.ts').read_text(encoding='utf-8')
result={'sourceCommit':'8a0d87a3760381927a855e6562fbce6a937f09dd', 'alerts':literal(assignment(world,'configAlerts')), 'events':{}, 'sales':{}}
for key,minimum in [('tennoLiveRelay','0.0.0'),('longShadow','18.22.1'),('hallowedFlame','26.0.0'),('scarletSpear','27.3.0'),('orphixVenom','29.6.8'),('bloodOfPerita','41.0.0'),('bellyOfTheBeast','36.0.0'),('operationAtramentum','42.0.0'),('eightClaw','39.0.0'),('qtccAlerts','0.0.0'),('snowdayShowdown','19.4.1'),('breedingGrounds','14.0.0')]:
    block=section(world,key)
    event={'minVersion':minimum}
    for field in ['Goals','Alerts','PVPAlternativeModes']:
        data=calls(block,field)
        if data:event[field]=data
    result['events'][key]=event

result['anniversary']=literal(assignment(world,'goalsByWeek'))
result['wolfHunt']=calls(section(world,'wolfHunt'),'Goals')
galleon=world[world.index('if (config.worldState?.galleonOfGhouls'):world.index('const firstNovemberWeekday')]
result['galleonOfGhouls']=calls(galleon,'Goals')
for key in ['hallowedNightmares','proxyRebellion','dogDaysOverride']:
    block=clean(section(world,key)); rewards=literal(assignment(block,'rewards')); variants=[]
    for year,choices in enumerate(rewards):
        sample=re.sub(r'rewards\[year\]\.slice\(0,\s*2\)',json.dumps(choices[:2]),block)
        sample=re.sub(r'rewards\[year\]\[(\d+)\]',lambda m:json.dumps(choices[int(m[1])] if int(m[1])<len(choices) else None),sample)
        sample=re.sub(r'"([a-f0-9]+)"\s*\+\s*year.toString\(16\)',lambda m:json.dumps(m[1]+format(year,'x')),sample)
        if key=='dogDaysOverride':
            sample=re.sub(r'_id:\s*\{[\s\S]*?\},', '_id: { $oid: "@dogDaysId" },',sample,count=1)
        data=calls(sample,'Goals')
        if key=='hallowedNightmares' and year==2:data=data[:1]
        variants.append(data)
    result[key]=variants
for key in ['naberusNightsOverride','thermiaFracturesOverride']:
    result[key]={f:calls(section(world,key),f,{'count':0}) for f in ['Goals','NodeOverrides','GlobalUpgrades']}
result['alertGeneratorConfig']=literal(assignment(world,'alertGeneratorConfig'))
lunar=section(world,'lunarNewYear')
repeating=literal(assignment(lunar,'reapeingItems')); stores=clean(assignment(lunar,'storeItems'))
while '...[' in stores:
    start=stores.index('...['); source=balanced(stores,start+3); end=start+3+len(source)
    sort=re.match(r'\s*\.sort\s*',stores[end:])
    if not sort:raise ValueError('Unknown lunar store expression')
    sort_args=balanced(stores,end+sort.end()); end+=sort.end()+len(sort_args)
    keys=re.findall(r'\.\.\.reapeingItems\["(\d+)"\]',source)
    if not keys or re.sub(r'\.\.\.reapeingItems\["\d+"\]|[\[\],\s]','',source):raise ValueError('Unknown lunar store spread')
    items=sorted([item for key in keys for item in repeating[key]],key=lambda item:item['TypeName'])
    stores=stores[:start]+','.join(json.dumps(item) for item in items)+stores[end:]
result['lunarNewYear']=literal(stores)
result['classic']={name:literal(assignment(world,name),{'undefined':None}) for name in ['alertStandardResources','alertAuras','alertHelmets','alertVaubanParts','alertWeapons','alertNightmareMods','alertOrokinBP','alertDescTexts']}
duration_start=world.index('[',world.index('const alertDurationMultipliers'))
result['classic']['durationMultipliers']=dict(literal(balanced(world,duration_start)))

flash=(SRC/'constants/flashSales.ts').read_text(encoding='utf-8')
for name in ['qqtcFlashSales','dogDaysFlashSales','saintPatrickDayFlashSales','prideMonthFlashSales','tennobaumFlashSales','naberusNightsFlashSales']:
    result['sales'][name]=literal(assignment(flash,name))

output=ROOT/'Server/data/editor/world-events.json'
output.write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf-8')
print({'alerts':len(result['alerts']),'events':len(result['events']),'sales':len(result['sales'])})