138 lines
5.3 KiB
JavaScript
138 lines
5.3 KiB
JavaScript
import * as e from './common.mjs';
|
|
import {
|
|
fixpoint
|
|
} from './trait_effects.mjs';
|
|
const CONDITION=/\b(after|each|every|when|whenever|upon|until|at the (beginning|start|end)|in this run|lasting|if|per|subsequent|next)\b/i;
|
|
const COUNT='(#\\d|\\d+|a|an|one|Obtains?)';
|
|
const GRANTS=[['item', new RegExp(COUNT+' "?<gridfightinfo type=item id=(\\d+)', 'g')], ['role', new RegExp(COUNT+' "?<gridfightinfo type=role id=(\\d+)', 'g')], ['gold', new RegExp(COUNT+' Gold Coin', 'g')], ['refresh', new RegExp(COUNT+' free refresh', 'g')], ['cost_role', new RegExp(COUNT+' random (?:1-star )?(\\d)-cost character', 'g')], ['basic', new RegExp(COUNT+' (?:random basic equipment|Random Basic Weapon)', 'gi')], ['emblem', new RegExp(COUNT+' random Emblem', 'g')]];
|
|
export const _fixpoint=fixpoint;
|
|
function count(token, params){
|
|
if(token.startsWith('#')){
|
|
const i=Number(token.slice(1))-1;
|
|
if(i>=params.length)return null;
|
|
const v=params[i];
|
|
return v>0&&Math.abs(v-Math.round(v))<1e-6?Math.round(v):null;
|
|
}
|
|
return ['a', 'an', 'one', 'Obtain', 'Obtains'].includes(token)?1:Number(token);
|
|
}
|
|
export function parse_grants(text, params){
|
|
text=text.replace(/<\/?unbreak>|<color=[^>]*>|<\/color>/g, '').replaceAll('[i]', '');
|
|
let grants=[],
|
|
complete=true;
|
|
for(const sentence of text.split(/(?<=[.!])"?\s+(?=[A-Z])/)){
|
|
if(!/^(Immediately )?Obtains?\b/.test(sentence)||CONDITION.test(sentence.replace(/<[^>]*>/g, ''))){
|
|
complete=false;
|
|
continue;
|
|
}
|
|
const found=[];
|
|
for(const [kind, pattern]of GRANTS)for(const m of sentence.matchAll(pattern)){
|
|
const n=count(m[1], params);
|
|
if(n===null)return [null, false];
|
|
const g={
|
|
kind,
|
|
count:n
|
|
};
|
|
if(['item', 'role'].includes(kind))g.id=Number(m[2]);
|
|
if(kind==='cost_role')g.cost=Number(m[2]);
|
|
found.push([m.index, g]);
|
|
}
|
|
if(!found.length)return [null, false];
|
|
grants.push(...found.sort((a, b)=>a[0]-b[0]).map(([, g])=>g));
|
|
}
|
|
return [grants, complete];
|
|
}
|
|
export function build(raw, season){
|
|
const buffs=new Map(raw.GridFightPortalBuffConfigRow.map(r=>[r.ID, r])),
|
|
portals=[];
|
|
for(const link of raw.GridFightSeasonPortalConfigRow){
|
|
if(link.SeasonID!==season)continue;
|
|
const row=buffs.get(link.PortalID);
|
|
if(!row)e.fail('season portal '+link.PortalID+' has no PortalBuff row');
|
|
portals.push({
|
|
id:row.ID,
|
|
oc:!!(row.IsOCEffective??0),
|
|
title_hash:row.PortalBuffTitle.hash,
|
|
json_path:row.JsonPath??''
|
|
});
|
|
}
|
|
portals.sort((a, b)=>a.id-b.id);
|
|
if(!portals.length||!e.uniqueValues(portals, p=>p.id))e.fail('missing or duplicate season portals');
|
|
return {
|
|
season_id:season,
|
|
portals
|
|
};
|
|
}
|
|
const PINNED={
|
|
211701:[{
|
|
kind:'item',
|
|
count:1,
|
|
id:355100302
|
|
}, {
|
|
kind:'role',
|
|
count:1,
|
|
id:1501
|
|
}],
|
|
100301:[{
|
|
kind:'refresh',
|
|
count:11
|
|
}]
|
|
};
|
|
export function check_grants(augment, grants, params, item_ids){
|
|
const granted=new Set(grants.filter(g=>g.kind==='item').map(g=>g.id));
|
|
for(const v of params)if(Math.abs(v-Math.round(v))<1e-6&&item_ids.has(Math.round(v))&&!granted.has(Math.round(v)))e.fail(`strategy ${augment}: item param ${Math.trunc(v)} is not granted`);
|
|
if(PINNED[augment]&&e.json(grants)!==e.json(PINNED[augment]))e.fail('strategy '+augment+': grants differ from pinned');
|
|
}
|
|
export function build_augments(raw, season, texts=null){
|
|
const rows=new Map(raw.GridFightAugmentConfigRow.map(r=>[r.ID, r])),
|
|
item_ids=new Set(raw.GridFightItemConfigRow.map(r=>r.ID)),
|
|
augments=[];
|
|
for(const link of raw.GridFightSeasonAugmentConfigRow){
|
|
if(link.SeasonID!==season)continue;
|
|
const row=rows.get(link.AugmentID);
|
|
if(!row)e.fail('season augment '+link.AugmentID+' has no Augment row');
|
|
const a={
|
|
id:row.ID,
|
|
quality:row.Quality,
|
|
chapters:row.ChapterLimitList??[],
|
|
oc:!!(row.IsOCEffective??0)
|
|
};
|
|
augments.push(a);
|
|
const text=texts?.[row.ID];
|
|
if(text){
|
|
const params=(row.EffectParamList??[]).map(p=>fixpoint(p.wire)),
|
|
[grants, complete]=parse_grants(text, params);
|
|
if(grants?.length){
|
|
check_grants(row.ID, grants, params, item_ids);
|
|
a.grants=grants;
|
|
a.grants_complete=complete;
|
|
}
|
|
}
|
|
}
|
|
augments.sort((a, b)=>a.id-b.id);
|
|
if(!augments.length||!e.uniqueValues(augments, a=>a.id))e.fail('missing or duplicate season augments');
|
|
return {
|
|
season_id:season,
|
|
augments
|
|
};
|
|
}
|
|
export function main(){
|
|
const a=e.cli([], {
|
|
items:e.resource('items.raw.json'),
|
|
season:2,
|
|
output:e.resource('portals.json'),
|
|
augments:e.resource('augments.json'),
|
|
catalog:e.catalogDefault
|
|
}),
|
|
raw=e.loadJSON(a.items),
|
|
data=build(raw, a.season);
|
|
let texts=null;
|
|
if(e.fs.existsSync(a.catalog))texts=Object.fromEntries(e.loadJSON(a.catalog).tables.GridFightAugmentConfigRow.map(r=>[r.ID, r.HexDesc?.text??'']));
|
|
const augments=build_augments(raw, a.season, texts);
|
|
e.atomic_json(a.output, data, 1, true);
|
|
e.atomic_json(a.augments, augments, 1, true);
|
|
console.log(`${a.output}: ${data.portals.length} portals for season ${a.season}`);
|
|
const granting=augments.augments.filter(a=>a.grants);
|
|
console.log(`${a.augments}: ${augments.augments.length} augments for season ${a.season}, ${granting.length} with immediate grants (${granting.filter(a=>!a.grants_complete).length} partial)`);
|
|
}
|
|
e.runMain(import.meta.url, main);
|