Life reopen simulator git
https://github.com/VickScarlet/lifeRestart
1. The process of painfully reading code
Under Windows 11 system, according to readme, start the game with a series of instructions beginning with npm
After entering the life simulation, use f10, and soon find it in life JS, he calls next() every year
next() { const {age, event, talent} = this.#property.ageNext(); const talentContent = this.doTalent(talent); const eventContent = this.doEvent(this.random(event)); const isEnd = this.#property.isEnd(); const content = [talentContent, eventContent].flat(); this.#achievement.achieve(this.AchievementOpportunity.TRAJECTORY); return { age, content, isEnd }; }
This is an instance of this class. Looking up, this is a life class
class Life { constructor() { this.#property = new Property(this); this.#event = new Event(this); this.#talent = new Talent(this); this.#achievement = new Achievement(this); this.#character = new Character(this); } Module = { PROPERTY: 'PROPERTY', TALENT: 'TALENT', EVENT: 'EVENT', ACHIEVEMENT: 'ACHIEVEMENT', CHARACTER: 'CHARACTER', } Function = { CONDITION: 'CONDITION', UTIL: 'UTIL', } #property; #event; #talent; #achievement; #character; #triggerTalents; #defaultPropertyPoints; #talentSelectLimit; #propertyAllocateLimit; #defaultPropertys; #specialThanks; #initialData; //Later strategy
#Property is the property of this class, and ageNext() is in property js
ageNext() { this.change(this.TYPES.AGE, 1); const age = this.get(this.TYPES.AGE); const {event, talent} = this.getAgeData(age); return {age, event, talent}; }
In this function, this is an instance of the Property class
class Property { constructor(system) { this.#system = system; } TYPES = { // This Council AGE: "AGE", // Age CHR: "CHR", // Beauty charm CHR INT: "INT", // intelligence INT STR: "STR", // Physical strength STR MNY: "MNY", // Family money MNY SPR: "SPR", // Happy spirit SPR LIF: "LIF", // life LIFE TLT: "TLT", // talent TLT EVT: "EVT", // event EVT TMS: "TMS", // times TMS // Auto calc LAGE: "LAGE", // Minimum age Low Age HAGE: "HAGE", // Highest age LCHR: "LCHR", // Lowest appearance value Low Charm HCHR: "HCHR", // Highest appearance value High Charm LINT: "LINT", // Minimum intelligence HINT: "HINT", // Highest intelligence LSTR: "LSTR", // Low Strength HSTR: "HSTR", // High Strength LMNY: "LMNY", // Low Money HMNY: "HMNY", // High Money LSPR: "LSPR", // Lowest spirit HSPR: "HSPR", // Highest spirit SUM: "SUM", // summary SUM EXT: "EXT", // Inherited talent // total // Achievement Total ATLT: "ATLT", // Have a talent achievement talent AEVT: "AEVT", // Triggered event achievement event ACHV: "ACHV", // Achievement s achieved CTLT: "CTLT", // Talent count CEVT: "CEVT", // Event collection Count Event CACHV: "CACHV", // Number of achievements // total TTLT: "TTLT", // Total Talent TEVT: "TEVT", // Total events TACHV: "TACHV", // Total Achievement // ratio REVT: "REVT", // Event collection rate RTLT: "RTLT", // Talent selection Rate Talent RACHV: "RACHV", // Rate Achievement // SPECIAL RDM: 'RDM', // Random attribute RDM }; // Special type SPECIAL = { RDM: [ // Random attribute RDM this.TYPES.CHR, this.TYPES.INT, this.TYPES.STR, this.TYPES.MNY, this.TYPES.SPR, ] } #system; #ageData; #data = {}; #total; #judge; //Later strategy
change() is a method of a class
change(prop, value) { if(Array.isArray(value)) { for(const v of value) this.change(prop, Number(v)); return; } switch(prop) { case this.TYPES.AGE: case this.TYPES.CHR: case this.TYPES.INT: case this.TYPES.STR: case this.TYPES.MNY: case this.TYPES.SPR: case this.TYPES.LIF: this.hl(prop, this.#data[prop] += Number(value)); return; case this.TYPES.TLT: case this.TYPES.EVT: const v = this.#data[prop]; if(value<0) { const index = v.indexOf(value); if(index!=-1) v.splice(index,1); } if(!v.includes(value)) v.push(value); this.achieve(prop, value); return; case this.TYPES.TMS: this.set( prop, this.get(prop) + parseInt(value) ); return; default: return; } }
If you enter a value as a vector, foreach the vector, and then use change for prop for each vector element, so that you can use a function to deal with 1-to-1 to deal with 1-to-1 pairs of vectors. That's great
Different attributes have different ways of adding. No matter how detailed they are, they won't be seen
Go back to ageNext(), and then get
get(prop) { const util = this.#util; switch(prop) { case this.TYPES.AGE: case this.TYPES.CHR: case this.TYPES.INT: case this.TYPES.STR: case this.TYPES.MNY: case this.TYPES.SPR: case this.TYPES.LIF: case this.TYPES.TLT: case this.TYPES.EVT: return util.clone(this.#data[prop]); case this.TYPES.LAGE: case this.TYPES.LCHR: case this.TYPES.LINT: case this.TYPES.LSTR: case this.TYPES.LMNY: case this.TYPES.LSPR: return util.min( this.#data[prop], this.get(this.fallback(prop)) ); case this.TYPES.HAGE: case this.TYPES.HCHR: case this.TYPES.HINT: case this.TYPES.HSTR: case this.TYPES.HMNY: case this.TYPES.HSPR: return util.max( this.#data[prop], this.get(this.fallback(prop)) ); case this.TYPES.SUM: const HAGE = this.get(this.TYPES.HAGE); const HCHR = this.get(this.TYPES.HCHR); const HINT = this.get(this.TYPES.HINT); const HSTR = this.get(this.TYPES.HSTR); const HMNY = this.get(this.TYPES.HMNY); const HSPR = this.get(this.TYPES.HSPR); return Math.floor(util.sum(HCHR, HINT, HSTR, HMNY, HSPR)*2 + HAGE/2); case this.TYPES.TMS: return this.lsget('times') || 0; case this.TYPES.EXT: return this.lsget('extendTalent') || null; case this.TYPES.ATLT: case this.TYPES.AEVT: case this.TYPES.ACHV: return this.lsget(prop) || []; case this.TYPES.CTLT: case this.TYPES.CEVT: case this.TYPES.CACHV: return this.get( this.fallback(prop) ).length; case this.TYPES.TTLT: case this.TYPES.TEVT: case this.TYPES.TACHV: return this.#total[prop]; case this.TYPES.RTLT: case this.TYPES.REVT: case this.TYPES.RACHV: const fb = this.fallback(prop); return this.get(fb[0]) / this.get(fb[1]); default: return 0; } }
Some attributes return clone and some return fallback. HMM... I don't know why it's not callback. Shouldn't it mean callback
clone() in util js
function clone(value) { switch(typeof value) { case 'object': if(Array.isArray(value)) return value.map(v=>clone(v)); const newObj = {}; for(const key in value) newObj[key] = clone(value[key]); return newObj; default: return value; } }
If the argument to be cloned is an object
If the argument is a vector, as usual, a vector argument is divided into vector element arguments by traversal
Then copy the key value pair of the argument of this object type. The value may be a vector, so you need to use a clone() to return the value
Then return the copied object
Oh... Is a function that can copy objects
fallback(prop) { switch(prop) { case this.TYPES.LAGE: case this.TYPES.HAGE: return this.TYPES.AGE; case this.TYPES.LCHR: case this.TYPES.HCHR: return this.TYPES.CHR; case this.TYPES.LINT: case this.TYPES.HINT: return this.TYPES.INT; case this.TYPES.LSTR: case this.TYPES.HSTR: return this.TYPES.STR; case this.TYPES.LMNY: case this.TYPES.HMNY: return this.TYPES.MNY; case this.TYPES.LSPR: case this.TYPES.HSPR: return this.TYPES.SPR; case this.TYPES.CTLT: return this.TYPES.ATLT; case this.TYPES.CEVT: return this.TYPES.AEVT; case this.TYPES.CACHV: return this.TYPES.ACHV; case this.TYPES.LIF: return this.TYPES.LIF; case this.TYPES.RTLT: return [this.TYPES.CTLT, this.TYPES.TTLT]; case this.TYPES.REVT: return [this.TYPES.CEVT, this.TYPES.TEVT]; case this.TYPES.RACHV: return [this.TYPES.CACHV, this.TYPES.TACHV]; default: return; } }
Originally, fallback means fallback... It should back up some properties? I don't understand. I don't read it
Go back to ageNext(), followed by getAgeData()
getAgeData(age) { return this.#system.clone(this.#ageData[age]); }
Then an ageData is used
I don't understand... Why is ageData initialized with = age, and then [age] can be used without any objects
I don't understand. Skip
Back to next(), the next line is doTalent()
doTalent(talents) { if(talents) this.#property.change(this.PropertyTypes.TLT, talents); talents = this.#property.get(this.PropertyTypes.TLT) .filter(talentId => this.getTalentCurrentTriggerCount(talentId) < this.#talent.get(talentId).max_triggers); const contents = []; for(const talentId of talents) { const result = this.#talent.do(talentId); if(!result) continue; this.#triggerTalents[talentId] = this.getTalentCurrentTriggerCount(talentId) + 1; const { effect, name, description, grade } = result; contents.push({ type: this.PropertyTypes.TLT, name, grade, description, }) if(!effect) continue; this.#property.effect(effect); } return contents; }
The first line is to use change() to change talents
case this.TYPES.TLT: case this.TYPES.EVT: const v = this.#data[prop]; if(value<0) { const index = v.indexOf(value); if(index!=-1) v.splice(index,1); } if(!v.includes(value)) v.push(value); this.achieve(prop, value); return;
This input is talent. Although I don't know what talent is, it should also be an object. Why can the object be less than 0... It's strange
Estimated to deal with anomalies? I don't know. Skip
The const in js is the underlying const, so you can modify the value pointed to. If this# If there is no talent in the data [prop], add it. HMM, it's reasonable
Then achieve()
achieve(prop, newData) { let key; switch(prop) { case this.TYPES.ACHV: const lastData = this.lsget(prop); this.lsset( prop, (lastData || []).concat([[newData, Date.now()]]) ); return; case this.TYPES.TLT: key = this.TYPES.ATLT; break; case this.TYPES.EVT: key = this.TYPES.AEVT; break; default: return; } const lastData = this.lsget(key) || []; this.lsset( key, Array.from( new Set( lastData .concat(newData||[]) .flat() ) ) ) }
This came in at this time PropertyTypes. TLT, get key = this TYPES. Atlt, pass in lsget()
lsget(key) { const data = localStorage.getItem(key); if(data === null || data === 'undefined') return; return JSON.parse(data); }
It seems that we are going to start reading the meter. First go back and have a look at ATLT
ATLT: "ATLT", // Have a talent achievement talent
He fetches the ATLT from the local storage, which should be saved during initialization
But when I set the breakpoint, I didn't see this ATLT... Embarrassed
Forget it, whatever
AEVT: "AEVT", // Triggered event achievement event ACHV: "ACHV", // Achievement s achieved
Then it parses the JSON string, nb
Well, I still haven't read my watch
Or is there a watch in the local storage?
I don't understand. Skip
Back to achieve
const lastData = this.lsget(key) || []; this.lsset( key, Array.from( new Set( lastData .concat(newData||[]) .flat() ) ) )
Use array From() passes the second argument to lsset()
The new Set is array From () is the only argument, so here is array The only job of from () is to convert this Set into an array
Set is the splicing of lastData and newData. When a value participates in a logical or operation, the result is true, and the first value that is true will be returned; If the result is false, the second false value is returned. [] is false, so an empty array will be returned when newData is false. Finally, flat() cancels the depth
In fact, the new data is transmitted to lsset() together with the old data
lsset(key, value) { localStorage.setItem( key, JSON.stringify(value) ); }
Convert to JSON and save to local storage
That is, the function of achievement () is to take the corresponding old data from the local and store it locally together with the new data
Maybe property The function of change() is to update the storage according to the attribute?
Go back to doTalent(), property Get() is passed into TLT, so lsget() is still used. I've seen it
Array. The filtering condition set by filter () is that the number of trigger s corresponding to this id is less than the maximum value
getTalentCurrentTriggerCount(talentId) { return this.#triggerTalents[talentId] || 0; }
It seems that a trigger will be set in one place. Skip it first
Wow, I'll let every talent work next... Maybe there's really game logic?
talent.do in talent js
do(talentId) { const { effect, condition, grade, name, description } = this.get(talentId); if(condition && !this.#system.check(condition)) return null; return { effect, grade, name, description }; }
get is to directly obtain the talents attribute. This attribute has initialization, um... Then skip
get(talentId) { const talent = this.#talents[talentId]; if(!talent) throw new Error(`[ERROR] No Talent[${talentId}]`); return this.#system.clone(talent); }
Back to talent do
system.check() returns true talent to talent Do returns true
class Talent { constructor(system) { this.#system = system; } #system; #talents; #talentPullCount; #talentRate; #additions;
I don't know what kind of system this is. It's numb
Set a breakpoint
You can't get there in one step... Forget it
If this talent has running results, put its trigger + 1, and then take out the effect to property effect()
effect(effects) { for(let prop in effects) this.change( this.hookSpecial(prop), Number(effects[prop]) ); }
For each item in the effect, the corresponding attribute and value should be extracted
hookSpecial(prop) { switch(prop) { case this.TYPES.RDM: return this.#util.listRandom(this.SPECIAL.RDM); default: return prop; } }
hookSpecial() is a special attribute special handling
function listRandom(list) { return list[Math.floor(Math.random() * list.length)]; }
listRandom() returns a random element in the list
// SPECIAL RDM: 'RDM', // Random attribute RDM
// Special type SPECIAL = { RDM: [ // Random attribute RDM this.TYPES.CHR, this.TYPES.INT, this.TYPES.STR, this.TYPES.MNY, this.TYPES.SPR, ] }
Random attributes are a list
That's actually property The function of effect () is to apply the attribute value in effect to change()
In fact, doTalent() is talent Apply after do() finds the effect
Return to next(). The next line is doEvent()
doEvent(eventId) { const { effect, next, description, postEvent, grade } = this.#event.do(eventId); this.#property.change(this.PropertyTypes.EVT, eventId); this.#property.effect(effect); const content = { type: this.PropertyTypes.EVT, description, postEvent, grade, } if(next) return [content, this.doEvent(next)].flat(); return [content]; }
Look at event Do() in event js
do(eventId) { const { effect, branch, event: description, postEvent, grade } = this.get(eventId); if(branch) for(const [cond, next] of branch) if(this.#system.check(cond)) return { effect, next, description, grade }; return { effect, postEvent, description, grade }; }
Take out so many things according to an id... I really want to understand that the logic lies in this get()
Then it is whether there is a branch. If there is a branch, check the conditions of the branch. If the conditions are met, it returns the next event pointed to by the branch. Otherwise, it returns the end of the event
Go back to doEvent(), if it is event Do() returns the next event, so do doEvent() recursion again for this event
Go back to next(), and then judge whether it is over
isEnd() { return this.get(this.TYPES.LIF) < 1; }
LIF: "LIF", // life LIFE
This is based on life
Then judge whether you have achieved success Achievement() in achievement JS
achieve(opportunity) { this.list() .filter(({isAchieved})=>!isAchieved) .filter(({opportunity: o})=>o==opportunity) .filter(({id})=>this.check(id, this.#prop)) .forEach(({id})=>{ this.#prop.achieve(this.#prop.TYPES.ACHV, id) $$event('achievement', this.get(id)) }); }
get(achievementId) { const achievement = this.#achievements[achievementId]; if(!achievement) throw new Error(`[ERROR] No Achievement[${achievementId}]`); return this.#system.clone(achievement); }
Unfinished
Have a chance
The id of the attribute cannot be found
property. Achievement () in JS
achieve(prop, newData) { let key; switch(prop) { case this.TYPES.ACHV: const lastData = this.lsget(prop); this.lsset( prop, (lastData || []).concat([[newData, Date.now()]]) ); return; case this.TYPES.TLT: key = this.TYPES.ATLT; break; case this.TYPES.EVT: key = this.TYPES.AEVT; break; default: return; } const lastData = this.lsget(key) || []; this.lsset( key, Array.from( new Set( lastData .concat(newData||[]) .flat() ) ) ) }
If it is a completed achievement, add the current time and return
If it is a talent or event, return the triggered talent or event
Return to other situations
Then add new data to the triggered talent or event and return
Well... Well, I don't understand
Ah... That's all about next()... I still don't understand
So I think the most important thing is these two sentences
const { effect, condition, grade, name, description } = this.get(talentId); const { effect, branch, event: description, postEvent, grade } = this.get(eventId);
get(talentId) { const talent = this.#talents[talentId]; if(!talent) throw new Error(`[ERROR] No Talent[${talentId}]`); return this.#system.clone(talent); } get(eventId) { const event = this.#events[eventId]; if(!event) throw new Error(`[ERROR] No Event[${eventId}]`); return this.#system.clone(event); }
But this get gets their own initialized properties... So it should also depend on how this thing is initialized
initial({talents}) { this.#talents = talents; const emt = this.#system.function(this.#system.Function.CONDITION).extractMaxTriggers; for(const id in talents) { const talent = talents[id]; talent.id= Number(id); talent.grade = Number(talent.grade); talent.max_triggers = emt(talent.condition); if(talent.replacement) { for(let key in talent.replacement) { const obj = {}; for(let value of talent.replacement[key]) { value = `${value}`.split('*'); obj[value[0]||0] = Number(value[1]) || 1; } talent.replacement[key] = obj; } } } return this.count; } initial({events}) { this.#events = events; for(const id in events) { const event = events[id]; if(!event.branch) continue; event.branch = event.branch.map(b=>{ b = b.split(':'); b[1] = Number(b[1]); return b; }); } return this.count; }
Then I feel that her initialization is not constructed from scratch. There is also an input value, which depends on what is input
Then you'll see that it's located in lief initial in JS
async initial(i18nLoad, commonLoad) { const [age, talents, events, achievements, characters, specialThanks] = await Promise.all([ i18nLoad('age'), i18nLoad('talents'), i18nLoad('events'), i18nLoad('achievement'), i18nLoad('character'), commonLoad('specialthanks'), ]); this.#specialThanks = specialThanks; const total = { [this.PropertyTypes.TACEV]: this.#achievement.initial({achievements}), [this.PropertyTypes.TEVT]: this.#event.initial({events}), [this.PropertyTypes.TTLT]: this.#talent.initial({talents}), }; this.#property.initial({age, total}); this.#character.initial({characters}); }
The value used for initialization is promise All(), where i18nload and commonload are the input functions. It depends on who called the initial
That's strange. I can't find anyone who called this initial
Single step debugging
At first, run index
const core = new Life(); const game = new App(); globalThis.core = core; globalThis.game = game;
Then the constructor of life
class Life { constructor() { this.#property = new Property(this); this.#event = new Event(this); this.#talent = new Talent(this); this.#achievement = new Achievement(this); this.#character = new Character(this); }
A series of
Finally
game.start(query);
Then I click to skip to the end of initial... Strange
Then you can only look at variables
Well, then I found it again
Ah, but I can't see what parameters are entered. Why
If you skip this, it's the only place you haven't seen
I just started to see this core. I thought it wasn't Life, so I didn't care
Later, I thought, well, since it's looking for references, it's really life initial()
await core.initial( dataSet=>Laya.promises.loader.load(`data/${this.#language}/${dataSet}.json`, null, Laya.Loader.JSON), dataSet=>Laya.promises.loader.load(`data/${dataSet}.json`, null, Laya.Loader.JSON), );
What I found on the Internet seems to be LAYA loader. There is no LAYA promises. loader. Yes, it's amazing
Laya.promises = { ...//Front strategy loader: { load: async function (url, progress, type) { return new Promise(function (resolve, reject) { try { Laya.loader.load(url, Laya.Handler.create(null, ret=>resolve(ret), null, true), progress, type); } catch (e) { reject(e); } }); } } };
Ah... It looks like a tool. I won't delve into it
Well, then go back to talent Initial() and event initial()
const emt = this.#system.function(this.#system.Function.CONDITION).extractMaxTriggers;
Here we use the system attribute, which is initialized in the constructor
class Talent { constructor(system) { this.#system = system; }
Talent is constructed in Life
class Life { constructor() { this.#property = new Property(this); this.#event = new Event(this); this.#talent = new Talent(this); this.#achievement = new Achievement(this); this.#character = new Character(this); }
So Talent's system is Life
Then life is called here Function
class Life { Function = { CONDITION: 'CONDITION', UTIL: 'UTIL', }
The last is a string CONDITION
The outer layer is life function
class Life { function(type) { switch (type) { case this.Function.CONDITION: return fCondition; case this.Function.UTIL: return util; } }
fCondition is called
import * as fCondition from '../functions/condition.js';
So I see condition JS, where
function extractMaxTriggers(condition) { // Assuming only age related talents can be triggered multiple times. const RE_AGE_CONDITION = /AGE\?\[([0-9\,]+)\]/; const match_object = RE_AGE_CONDITION.exec(condition); if (match_object == null) { // Not age related, single trigger. return 1; } const age_list = match_object[1].split(","); return age_list.length; }
/AGE\?\[([0-9 \,] +) \] / is a regular expression, age \ [([0-9 \,] +) \] is the content, \? Is an escaped question mark character, \ [is an escaped parenthesis character, ([0-9 \,] +) saves the search results of [0-9 \,] +, + modifier represents at least one, [] represents the range, and the range is numbers 0 to 9 and commas. Then ([0-9 \,] +) is at least one number 0 to 9 and commas. The result of the whole expression is age? [at least one number 0 to 9 and comma]
This is the age condition obtained, and then separated by commas, return the number of age conditions... I didn't understand it, so I jumped
Back to talent Initial(), now emt becomes this function
The entered talents is a large table. For each element in the table, re assign id, grade and max_triggers attribute
Next, this talent should be replaced by other talents
To event Initial (), handle the branch
Then there was no
In fact, it still depends on the json of talent and event
Maybe the last thing I should look for is how to parse the agreed string
Ah, finally, other people's trade secrets are confused
reflection
2. Summary
It seems that the only thing we can do now is to stroke it again
Every year, increase one year, first check the talent, then check the event, and finally judge the achievement
When checking talents, select those talents that do not reach the upper limit of use times and judge whether they meet the effective conditions of talents. If they can take effect, apply the effect of talents and add 1 to the use times of this talent
When checking an event, judge whether the event has a branch. If there is no branch, the event will take effect directly. If there is a branch, judge whether the effective conditions of the branch event are met. If so, return a branch when the event takes effect, and then recursively check the branch event
It seems that the only thing I can understand is this hhh