人生重开模拟器 git
https://github.com/VickScarlet/lifeRestart
1.表
他的表很好懂,或者说其实看了表之后都不用强求看代码了hhh策划狂喜
天赋:
额外天赋,替换,互斥
事件:
属性是否大于小于或者是否有某天赋
年龄:
有没有一种可能
人生重来模拟器
是把每一年可能遇到的事件都枚举出来的
成就:
属性是否大于小于某一值,是否有某一事件
2.痛苦阅读代码的过程
windows11 系统下,根据 readme,使用 npm 开头的系列指令启动游戏
进入人生模拟之后使用 f10,然后很快发现在 life.js 中,他每过一年就是调用一次 next()
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
是这个类的实例自己,往上看,这是一个 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);
}
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;
//后略
#property
是这个类的属性,
ageNext()
在 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};
}
这个函数中,
this
是
Property
类的实例
class Property {
constructor(system) {
this.#system = system;
}
TYPES = {
// 本局
AGE: "AGE", // 年龄 age AGE
CHR: "CHR", // 颜值 charm CHR
INT: "INT", // 智力 intelligence INT
STR: "STR", // 体质 strength STR
MNY: "MNY", // 家境 money MNY
SPR: "SPR", // 快乐 spirit SPR
LIF: "LIF", // 生命 life LIFE
TLT: "TLT", // 天赋 talent TLT
EVT: "EVT", // 事件 event EVT
TMS: "TMS", // 次数 times TMS
// Auto calc
LAGE: "LAGE", // 最低年龄 Low Age
HAGE: "HAGE", // 最高年龄 High Age
LCHR: "LCHR", // 最低颜值 Low Charm
HCHR: "HCHR", // 最高颜值 High Charm
LINT: "LINT", // 最低智力 Low Intelligence
HINT: "HINT", // 最高智力 High Intelligence
LSTR: "LSTR", // 最低体质 Low Strength
HSTR: "HSTR", // 最高体质 High Strength
LMNY: "LMNY", // 最低家境 Low Money
HMNY: "HMNY", // 最高家境 High Money
LSPR: "LSPR", // 最低快乐 Low Spirit
HSPR: "HSPR", // 最高快乐 High Spirit
SUM: "SUM", // 总评 summary SUM
EXT: "EXT", // 继承天赋
// 总计
// Achievement Total
ATLT: "ATLT", // 拥有过的天赋 Achieve Talent
AEVT: "AEVT", // 触发过的事件 Achieve Event
ACHV: "ACHV", // 达成的成就 Achievement
CTLT: "CTLT", // 天赋选择数 Count Talent
CEVT: "CEVT", // 事件收集数 Count Event
CACHV: "CACHV", // 成就达成数 Count Achievement
// 总数
TTLT: "TTLT", // 总天赋数 Total Talent
TEVT: "TEVT", // 总事件数 Total Event
TACHV: "TACHV", // 总成就数 Total Achievement
// 比率
REVT: "REVT", // 事件收集率 Rate Event
RTLT: "RTLT", // 天赋选择率 Rate Talent
RACHV: "RACHV", // 成就达成率 Rate Achievement
// SPECIAL
RDM: 'RDM', // 随机属性 random RDM
};
// 特殊类型
SPECIAL = {
RDM: [ // 随机属性 random RDM
this.TYPES.CHR,
this.TYPES.INT,
this.TYPES.STR,
this.TYPES.MNY,
this.TYPES.SPR,
]
}
#system;
#ageData;
#data = {};
#total;
#judge;
//后略
change()
是类的方法
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;
}
}
如果输入了一个 value 是向量,就 foreach 这个向量,然后对每一个向量元素都对 prop 使用 change,这样就可以使用一个处理 1 对 1 的函数处理 1 对向量了,好厉害
不同的属性有不同的加方式,再细的就不看了
回到
ageNext()
,接下来是
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;
}
}
有的属性是返回
clone
,有的是返回
fallback
,嗯……不知道为什么不是
callback
,难道不应该是回调的意思吗
clone()
在 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;
}
}
如果是要克隆的实参是对象的话
如果这个实参是向量,照惯例,通过遍历,将一个向量实参拆成向量元素实参
然后复制这个对象类型的实参的键值对,值可能是一个向量,所以还要用一个
clone()
返回值
然后返回这个复制出来的对象
哦……就是一个可以复制对象的函数
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;
}
}
原来 fallback 是回退的意思……那应该是它备份了一些属性……?不懂,不看了
回到
ageNext()
,接下来是
getAgeData()
getAgeData(age) {
return this.#system.clone(this.#ageData[age]);
}
然后又用到一个
ageData
我不理解啊……为什么
ageData
初始化是
= age
,之后就可以使用
[age]
,又没有什么对象
不懂,跳过
回到
next()
,下一行是
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;
}
第一行要使用
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;
这输入的是
talent
,虽然还不知道
talent
是什么,但是应该也是对象把……为啥对象会可能小于 0……太奇怪了
估计是为了处理异常现象……?不知道,跳过
js 中的 const 是底层 const,所以可以修改指向的值。如果
this.#data[prop]
中没有这个
talent
,就把它加进去,嗯,合理
然后
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.PropertyTypes.TLT
,得到
key = this.TYPES.ATLT
,传入
lsget()
lsget(key) {
const data = localStorage.getItem(key);
if(data === null || data === 'undefined') return;
return JSON.parse(data);
}
似乎是要开始读表了,先回去看一下 ATLT
ATLT: "ATLT", // 拥有过的天赋 Achieve Talent
他从本地储存取出这个 ATLT,那应该是在初始化的时候存好了
但是我设断点看的时候一直没有出现这个 ATLT……尴尬了
算了,不管这个
AEVT: "AEVT", // 触发过的事件 Achieve Event
ACHV: "ACHV", // 达成的成就 Achievement
然后就是解析 JSON 字符串,nb
好吧,那还是没有读表
或者说难道本地储存里面就放着表?
不懂,跳过
回到
achieve()
,到了
const lastData = this.lsget(key) || [];
this.lsset(
key,
Array.from(
new Set(
lastData
.concat(newData||[])
.flat()
)
)
)
使用
Array.from()
向
lsset()
传入第二个实参
新建的 Set 是
Array.from()
唯一一个实参,因此这里
Array.from()
的唯一工作是把这个 Set 转换成数组
Set 之中是
lastData
和
newData
拼接。当数值参与逻辑或运算时,结果为true,会返回第一个为真的值;如果结果为false,会返回第二个为假的值。[] 为假值,因此 newData 为假时会返回空数组。最后
flat()
取消深度
那其实就是新数据旧数据一起传给
lsset()
lsset(key, value) {
localStorage.setItem(
key,
JSON.stringify(value)
);
}
转化成 JSON 存到本地储存
那就是
achieve()
的作用就是从本地取了相应的旧数据再和新数据一起存入本地
那或许
property.change()
的作用就是根据属性更新储存?
回到
doTalent()
,
property.get()
传入
TLT
,那就还是用的
lsget()
,已经看过了
Array.filter()
设置的过滤条件是,这个 id 对应的 trigger 数量小于最大值
getTalentCurrentTriggerCount(talentId) {
return this.#triggerTalents[talentId] || 0;
}
看来应该是有一个地方会设置 trigger,先跳过
哇哦,接下来会让每一个天赋工作……或许是真正要有游戏逻辑了?
talent.do
在 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
是直接获得
talents
属性,这个属性有初始化,嗯……那就跳过
get(talentId) {
const talent = this.#talents[talentId];
if(!talent) throw new Error(`[ERROR] No Talent[${talentId}]`);
return this.#system.clone(talent);
}
回到
talent.do
system.check()
返回真才让
talent.do
返回真
class Talent {
constructor(system) {
this.#system = system;
}
#system;
#talents;
#talentPullCount;
#talentRate;
#additions;
不知道这个
system
是什么类的,麻了
设个断点看看
单步走不到那里……算了
如果这个天赋有运行结果,就把它的 trigger + 1,然后拿出
effect
给
property.effect()
effect(effects) {
for(let prop in effects)
this.change(
this.hookSpecial(prop),
Number(effects[prop])
);
}
对
effect
中的每一项都是要提取对应的属性和数值
hookSpecial(prop) {
switch(prop) {
case this.TYPES.RDM:
return this.#util.listRandom(this.SPECIAL.RDM);
default: return prop;
}
}
hookSpecial()
是特殊属性特殊处理
function listRandom(list) {
return list[Math.floor(Math.random() * list.length)];
}
listRandom()
是返回列表中随机一个元素
// SPECIAL
RDM: 'RDM', // 随机属性 random RDM
// 特殊类型
SPECIAL = {
RDM: [ // 随机属性 random RDM
this.TYPES.CHR,
this.TYPES.INT,
this.TYPES.STR,
this.TYPES.MNY,
this.TYPES.SPR,
]
}
随机属性是个列表
那其实
property.effect()
的作用就是把
effect
中的属性-数值 应用到
change()
中
那其实
doTalent()
就是
talent.do()
找到了
effect
之后应用
回到
next()
。下一行是
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];
}
再看
event.do()
,在 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 };
}
根据一个 id 就取出这么多东西……我好想悟了,难道逻辑就在于这个
this.get()
然后就是是否有分支,如果有分支,就查看分支的条件,如果满足条件就返回分支指向的下一个事件,否则返回事件的结尾
回到
doEvent()
,如果是
event.do()
返回了下一个事件,那么就对这个事件再来一次
doEvent()
,递归
回到
next()
,接下来判断是否结束
isEnd() {
return this.get(this.TYPES.LIF) < 1;
}
LIF: "LIF", // 生命 life LIFE
这是根据寿命判断的
接下来再判断是否达成成就,
achievement.achieve()
在 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);
}
没有完成的
有机会的
id 找得到 属性的
property.js 中的
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()
)
)
)
}
如果是已完成的成就就添加当前时间,返回
如果是天赋或事件就返回已触发的天赋或事件
其他情况返回
然后已触发的天赋或事件添加上新数据返回
嗯……好吧有点没懂
啊……这就是
next()
的全部内容了……我还是没懂啊
所以感觉最主要的还是这两句
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);
}
但是这个
get
拿到的是他们自己的初始化好的属性……所以应该还要看这个东西是怎么初始化的
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;
}
然后就感觉她这个初始化也不是从头构造的,还有一个输入的值,还要看到底输入了啥
然后就看到位于 lief.js 中的
initial
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});
}
用于初始化的值是
Promise.all()
来的,其中
i18nLoad
,
commonLoad
是输入的函数,那还要看这个
initial
是谁调用的
好怪,找不到谁调用了这个
initial
单步调试一下
一开始是运行 index
const core = new Life();
const game = new App();
globalThis.core = core;
globalThis.game = game;
然后 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);
}
一系列的
最后是
game.start(query);
然后我一按跳过就到了
initial
的最后了……怪
然后就只能看看变量了
好吧,后来又找到了
诶,但是看不到输入了什么参数,为啥呢……
那如果跳过这个,那就是唯一一个还没看的地方了
我刚开始看到这个
core
我还以为不是
Life
类 就没管
后面想到,诶,既然是查找引用那就确实是
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),
);
网上搜到的好像都是
Laya.loader.load
没有
Laya.promises.loader.load
的,好神奇
Laya.promises = {
...//前略
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);
}
});
}
}
};
啊……看上去就是个工具,不深究了
好吧,那就回到
Talent.initial()
和
Event.initial()
const emt = this.#system.function(this.#system.Function.CONDITION).extractMaxTriggers;
这里用到了
system
属性,这个属性是在构造函数中被初始化的
class Talent {
constructor(system) {
this.#system = system;
}
而
Talent
是在
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);
}
所以
Talent
的
system
就是
Life
那这里调用的就是
Life.Function
class Life {
Function = {
CONDITION: 'CONDITION',
UTIL: 'UTIL',
}
最后是个字符串
CONDITION
外层是
Life.function
class Life {
function(type) {
switch (type) {
case this.Function.CONDITION: return fCondition;
case this.Function.UTIL: return util;
}
}
调用的是
fCondition
import * as fCondition from '../functions/condition.js';
于是看到 condition.js,其中
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\,]+)\]/
是正则表达式,
AGE\?\[([0-9\,]+)\]
是内容,
\?
是转义成问号字符,
\[
是转义成括号字符,
([0-9\,]+)
保存
[0-9\,]+
的搜索结果,
+
修饰代表至少一个,
[]
表示范围,范围是 数字 0 到 9 及逗号,那么
([0-9\,]+)
就是至少一个数字 0 到 9 及逗号,整个是表达式的结果是
AGE?[至少一个数字 0 到 9 及逗号]
这是获得的年龄的条件,然后以逗号分隔,返回年龄条件个数……没看懂,跳了
回到
Talent.initial()
,现在
emt
变成了这个函数
输入的
talents
是一个大表,对表中的每一个元素,重新赋
id
,
grade
,
max_triggers
属性
接下来看这个天赋应该被其他天赋置换
到
Event.initial()
,处理一下分支
然后就没有了……
那其实还是要看 talent 和 event 的 json 了
或许我最后应该找的是怎么解析他们约定好的字符串的地方
啊,最后就是别人的商业秘密了,混淆了
思考
3.总结
那看来现在唯一能做的就是再捋一捋了
每一年,增加一岁,先检查一次天赋,再检查一次事件,最后判断一次成就
检查天赋时,选择那些没有达到使用次数上限的天赋,判断是否满足天赋的生效条件,如果能生效,应用天赋的效果,给该天赋的使用次数加 1
检查事件时,判断事件是否有分支,没有分支则事件直接生效,有分支则判断是否满足分支事件的生效条件,满足则在本事件生效时也返回一个分支,然后递归检查这个分支事件
好像唯一看懂的代码就这些hhh