From 6fdbcea925c8feec529d561acea3f74f7f9a1896 Mon Sep 17 00:00:00 2001 From: Vick Scarlet Date: Mon, 27 Jul 2026 14:32:13 +0800 Subject: [PATCH] add: bili-toy-sdk cloud save support --- src/features/bili-toy.js | 50 +++++ src/modules/character.js | 139 ++++++------ src/modules/life.js | 361 +++++++++++++++++-------------- src/modules/property.js | 452 ++++++++++++++++++++------------------- 4 files changed, 549 insertions(+), 453 deletions(-) create mode 100644 src/features/bili-toy.js diff --git a/src/features/bili-toy.js b/src/features/bili-toy.js new file mode 100644 index 0000000..cef98b2 --- /dev/null +++ b/src/features/bili-toy.js @@ -0,0 +1,50 @@ +async function loadSDK(sdk) { + const script = document.createElement('script') + script.src = sdk + script.async = true + document.body.appendChild(script) + return new Promise((resolve, reject) => { + script.onload = () => { + resolve() + } + script.onerror = () => { + reject(new Error('Failed to load SDK')) + } + }) +} + +export default async function init(options) { + try { + await loadSDK(options.sdk) + } catch (error) { + console.error('Failed to load Bili Toy SDK:', error) + return + } + if (!(await toy.isSupport('getCloudStorage'))) return + if (!(await toy.isSupport('setCloudStorage'))) return + if (!(await toy.isSupport('removeCloudStorage'))) return + syncUnique() + syncProps() +} + +async function syncUnique() { + if (core.sync('unique')) return + const data = await toy.getCloudStorage(['unique']) + if (data?.unique) { + core.sync('unique', data?.unique) + } else { + $$on('syncUnique', unique => toy.setCloudStorage({ unique })) + } +} + +async function syncProps() { + const sync = async () => { + const changed = core.sync('props') + if (!changed) return + const cloudData = await toy.getCloudStorage(['props']) + const props = core.sync('props', cloudData?.props ?? '{}') + await toy.setCloudStorage({ props }) + } + await sync() + setInterval(sync, 10000) +} diff --git a/src/modules/character.js b/src/modules/character.js index 643a11e..6c4e305 100644 --- a/src/modules/character.js +++ b/src/modules/character.js @@ -1,28 +1,28 @@ class Character { constructor(system) { - this.#system = system; + this.#system = system } - #system; - #characters; - #characterPullCount; - #rateableKnife; - #rate; - #pipe = []; - #uniqueWaTaShi; - #propertyWeight; + #system + #characters + #characterPullCount + #rateableKnife + #rate + #pipe = [] + #uniqueWaTaShi + #propertyWeight #talentWeight - initial({characters}) { - this.#characters = characters; - const uniqueWaTaShi = localStorage.getItem('uniqueWaTaShi'); - if(uniqueWaTaShi != null || uniqueWaTaShi != 'undefined') - this.#uniqueWaTaShi = JSON.parse(uniqueWaTaShi); - return this.count; + initial({ characters }) { + this.#characters = characters + const uniqueWaTaShi = localStorage.getItem('uniqueWaTaShi') + if (uniqueWaTaShi != null || uniqueWaTaShi != 'undefined') + this.#uniqueWaTaShi = JSON.parse(uniqueWaTaShi) + return this.count } get count() { - return Object.keys(this.#characters).length; + return Object.keys(this.#characters).length } config({ @@ -31,43 +31,47 @@ class Character { propertyWeight, talentWeight, } = {}) { - this.#characterPullCount = characterPullCount; - this.#rateableKnife = rateableKnife; - this.#propertyWeight = propertyWeight; - this.#talentWeight = talentWeight; + this.#characterPullCount = characterPullCount + this.#rateableKnife = rateableKnife + this.#propertyWeight = propertyWeight + this.#talentWeight = talentWeight + } + + sync(data) { + if (!data) return JSON.stringify(this.#uniqueWaTaShi) + localStorage.setItem('uniqueWaTaShi', data) + this.#uniqueWaTaShi = JSON.parse(data) } get #unique() { - if(this.#uniqueWaTaShi) { - return this.#system.clone(this.#uniqueWaTaShi); + if (this.#uniqueWaTaShi) { + return this.#system.clone(this.#uniqueWaTaShi) } - const now = Date.now(); - this.#pipe.push(now); - if(this.#pipe.length < 10) return null; - const time = this.#pipe.shift(); - if(now - time > 10000) return null; - return {unique: true, generate: false}; + const now = Date.now() + this.#pipe.push(now) + if (this.#pipe.length < 10) return null + const time = this.#pipe.shift() + if (now - time > 10000) return null + return { unique: true, generate: false } } set #unique(data) { - this.#uniqueWaTaShi = this.#system.clone(data); - this.#uniqueWaTaShi.unique = true; - this.#uniqueWaTaShi.generate = true; - localStorage.setItem( - 'uniqueWaTaShi', - JSON.stringify(this.#uniqueWaTaShi) - ); + this.#uniqueWaTaShi = this.#system.clone(data) + this.#uniqueWaTaShi.unique = true + this.#uniqueWaTaShi.generate = true + const json = JSON.stringify(this.#uniqueWaTaShi) + localStorage.setItem('uniqueWaTaShi', json) } get #weightRandom() { - return this.#system.function(this.#system.Function.UTIL).weightRandom; + return this.#system.function(this.#system.Function.UTIL).weightRandom } generateUnique() { - if(this.#uniqueWaTaShi) return this.#unique; - const weightRandom = this.#weightRandom; - const {CHR, INT, STR, MNY} = this.#system.PropertyTypes; + if (this.#uniqueWaTaShi) return this.#unique + const weightRandom = this.#weightRandom + const { CHR, INT, STR, MNY } = this.#system.PropertyTypes this.#unique = { property: { @@ -81,7 +85,7 @@ class Character { .random(weightRandom(this.#talentWeight)), } - return this.#unique; + return this.#unique } random() { @@ -92,43 +96,40 @@ class Character { } #rateable() { - if(!this.#rate) { - this.#rate = {}; - for(const id in this.#characters) { - this.#rate[id] = 1; + if (!this.#rate) { + this.#rate = {} + for (const id in this.#characters) { + this.#rate[id] = 1 } } - const r = []; - const weightRandom = this.#weightRandom; - new Array(this.#characterPullCount) - .fill(0) - .forEach(()=>{ - r.push( - weightRandom(Object - .keys(this.#rate) - .filter(id=>!r.includes(id)) - .map(id=>([id,this.#rate[id]])) - ) - ) - }); + const r = [] + const weightRandom = this.#weightRandom + new Array(this.#characterPullCount).fill(0).forEach(() => { + r.push( + weightRandom( + Object.keys(this.#rate) + .filter(id => !r.includes(id)) + .map(id => [id, this.#rate[id]]), + ), + ) + }) - let min = Infinity; - for(const id in this.#rate) { - if(r.includes(id)) { - min = Math.min(min, this.#rate[id]); - continue; + let min = Infinity + for (const id in this.#rate) { + if (r.includes(id)) { + min = Math.min(min, this.#rate[id]) + continue } - min = Math.min(min, ++ this.#rate[id]); + min = Math.min(min, ++this.#rate[id]) } - if(min > this.#rateableKnife) { - for(const id in this.#rate) { - this.#rate[id] -= this.#rateableKnife; + if (min > this.#rateableKnife) { + for (const id in this.#rate) { + this.#rate[id] -= this.#rateableKnife } } - return r.map(id=>this.#system.clone(this.#characters[id])); + return r.map(id => this.#system.clone(this.#characters[id])) } - } -export default Character; \ No newline at end of file +export default Character diff --git a/src/modules/life.js b/src/modules/life.js index 4f2cedf..cbfc934 100644 --- a/src/modules/life.js +++ b/src/modules/life.js @@ -1,19 +1,19 @@ -import * as util from '../functions/util.js'; -import * as fCondition from '../functions/condition.js'; +import * as util from '../functions/util.js' +import * as fCondition from '../functions/condition.js' -import Property from './property.js'; -import Event from './event.js'; -import Talent from './talent.js'; -import Achievement from './achievement.js'; -import Character from './character.js'; +import Property from './property.js' +import Event from './event.js' +import Talent from './talent.js' +import Achievement from './achievement.js' +import Character from './character.js' 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); + 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 = { @@ -29,18 +29,18 @@ class Life { UTIL: 'UTIL', } - #property; - #event; - #talent; - #achievement; - #character; - #triggerTalents; - #defaultPropertyPoints; - #talentSelectLimit; - #propertyAllocateLimit; - #defaultPropertys; - #specialThanks; - #initialData; + #property + #event + #talent + #achievement + #character + #triggerTalents + #defaultPropertyPoints + #talentSelectLimit + #propertyAllocateLimit + #defaultPropertys + #specialThanks + #initialData async initial(i18nLoad, commonLoad) { const [age, talents, events, achievements, characters, specialThanks] = await Promise.all([ @@ -50,16 +50,16 @@ class Life { i18nLoad('achievement'), i18nLoad('character'), commonLoad('specialthanks'), - ]); - this.#specialThanks = specialThanks; + ]) + this.#specialThanks = specialThanks const total = { - [this.PropertyTypes.TACHV]: 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}); + [this.PropertyTypes.TACHV]: 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 }) } config({ @@ -71,137 +71,146 @@ class Life { propertyConfig, // config for property characterConfig, // config for character } = {}) { - this.#defaultPropertyPoints = defaultPropertyPoints; - this.#talentSelectLimit = talentSelectLimit; - this.#propertyAllocateLimit = propertyAllocateLimit; - this.#defaultPropertys = defaultPropertys; - this.#talent.config(talentConfig); - this.#property.config(propertyConfig); - this.#character.config(characterConfig); + this.#defaultPropertyPoints = defaultPropertyPoints + this.#talentSelectLimit = talentSelectLimit + this.#propertyAllocateLimit = propertyAllocateLimit + this.#defaultPropertys = defaultPropertys + this.#talent.config(talentConfig) + this.#property.config(propertyConfig) + this.#character.config(characterConfig) } request(module) { switch (module) { - case this.Module.ACHIEVEMENT: return this.#achievement; - case this.Module.CHARACTER: return this.#character; - case this.Module.EVENT: return this.#event; - case this.Module.PROPERTY: return this.#property; - case this.Module.TALENT: return this.#talent; - default: return null; + case this.Module.ACHIEVEMENT: return this.#achievement + case this.Module.CHARACTER: return this.#character + case this.Module.EVENT: return this.#event + case this.Module.PROPERTY: return this.#property + case this.Module.TALENT: return this.#talent + default: return null } } function(type) { switch (type) { - case this.Function.CONDITION: return fCondition; - case this.Function.UTIL: return util; + case this.Function.CONDITION: return fCondition + case this.Function.UTIL: return util } } check(condition) { - return fCondition.checkCondition(this.#property,condition); + return fCondition.checkCondition(this.#property, condition) } clone(...args) { - return util.clone(...args); + return util.clone(...args) } remake(talents) { - this.#initialData = util.clone(this.#defaultPropertys); - this.#initialData.TLT = util.clone(talents); - this.#triggerTalents = {}; - return this.talentReplace(this.#initialData.TLT); + this.#initialData = util.clone(this.#defaultPropertys) + this.#initialData.TLT = util.clone(talents) + this.#triggerTalents = {} + return this.talentReplace(this.#initialData.TLT) } start(allocation) { - for(const key in allocation) { - this.#initialData[key] = util.clone(allocation[key]); + for (const key in allocation) { + this.#initialData[key] = util.clone(allocation[key]) } - this.#property.restart(this.#initialData); + this.#property.restart(this.#initialData) this.doTalent() - this.#property.restartLastStep(); - this.#achievement.achieve(this.AchievementOpportunity.START); + this.#property.restartLastStep() + this.#achievement.achieve(this.AchievementOpportunity.START) } getPropertyPoints() { - return this.#defaultPropertyPoints + this.#talent.allocationAddition(this.#initialData.TLT); + return ( + this.#defaultPropertyPoints + + this.#talent.allocationAddition(this.#initialData.TLT) + ) } getTalentCurrentTriggerCount(talentId) { - return this.#triggerTalents[talentId] || 0; + return this.#triggerTalents[talentId] || 0 } next() { - const {age, event, talent} = this.#property.ageNext(); + const { age, event, talent } = this.#property.ageNext() - const talentContent = this.doTalent(talent); - const eventContent = this.doEvent(this.random(event)); + const talentContent = this.doTalent(talent) + const eventContent = this.doEvent(this.random(event)) - const isEnd = this.#property.isEnd(); + const isEnd = this.#property.isEnd() - const content = [talentContent, eventContent].flat(); - this.#achievement.achieve(this.AchievementOpportunity.TRAJECTORY); - return { age, content, isEnd }; + const content = [talentContent, eventContent].flat() + this.#achievement.achieve(this.AchievementOpportunity.TRAJECTORY) + return { age, content, isEnd } } talentReplace(talents) { - const result = this.#talent.replace(talents); - const contents = []; - for(const id in result) { - talents.push(result[id]); - const source = this.#talent.get(id); - const target = this.#talent.get(result[id]); + const result = this.#talent.replace(talents) + const contents = [] + for (const id in result) { + talents.push(result[id]) + const source = this.#talent.get(id) + const target = this.#talent.get(result[id]) contents.push({ type: 'talentReplace', - source, target - }); + source, + target, + }) } - return contents; + return contents } 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); + 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; + 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: this.format(description), }) - if(!effect) continue; - this.#property.effect(effect); + if (!effect) continue + this.#property.effect(effect) } - return contents; + return contents } doEvent(eventId) { - const { effect, next, description, postEvent, grade } = this.#event.do(eventId); - this.#property.change(this.PropertyTypes.EVT, eventId); - this.#property.effect(effect); + 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: this.format(description), postEvent: postEvent && this.format(postEvent), grade, } - if(next) return [content, this.doEvent(next)].flat(); - return [content]; + if (next) return [content, this.doEvent(next)].flat() + return [content] } random(events) { return util.weightRandom( - events.filter( - ([eventId])=>this.#event.check(eventId, this.#property) - ) - ); + events.filter(([eventId]) => + this.#event.check(eventId, this.#property), + ), + ) } talentRandom() { @@ -210,118 +219,146 @@ class Life { this.#getPropertys( this.PropertyTypes.TMS, this.PropertyTypes.CACHV, - ) - ); + ), + ) } characterRandom() { - const characters = this.#character.random(); - const replaceTalent = v=>v.talent=v.talent.map( - id=>this.#talent.get(id) - ); - characters.normal.forEach(replaceTalent); - if(characters.unique && characters.unique.talent) - replaceTalent(characters.unique); - return characters; + const characters = this.#character.random() + const replaceTalent = v => + (v.talent = v.talent.map(id => this.#talent.get(id))) + characters.normal.forEach(replaceTalent) + if (characters.unique && characters.unique.talent) + replaceTalent(characters.unique) + return characters } talentExtend(talentId) { - this.#property.set(this.PropertyTypes.EXT, talentId); + this.#property.set(this.PropertyTypes.EXT, talentId) } exclude(talents, exclusive) { - return this.#talent.exclude(talents, exclusive); + return this.#talent.exclude(talents, exclusive) } generateUnique() { - this.#character.generateUnique(); + const unique = this.#character.generateUnique() + $$event('syncUnique', unique) } #getJudges(...types) { - return util.getListValuesMap(types.flat(), key => this.#property.judge(key)); + return util.getListValuesMap(types.flat(), key => + this.#property.judge(key), + ) } #getPropertys(...types) { - return util.getListValuesMap(types.flat(), key => this.#property.get(key)); + return util.getListValuesMap(types.flat(), key => + this.#property.get(key), + ) } format(discription) { - return `${discription}`.replaceAll(/\{\s*[0-9a-zA-Z_-]+\s*?\}/g, (match) => this.#format(match)); + return `${discription}`.replaceAll( + /\{\s*[0-9a-zA-Z_-]+\s*?\}/g, + match => this.#format(match), + ) } #format(key) { switch (key.slice(1, -1).trim().toLowerCase()) { - case 'currentyear': return new Date().getFullYear() - case 'age': return this.#property.get(this.PropertyTypes.AGE) - case 'charm': return this.#property.get(this.PropertyTypes.CHR) - case 'intelligence': return this.#property.get(this.PropertyTypes.INT) - case 'strength': return this.#property.get(this.PropertyTypes.STR) - case 'money': return this.#property.get(this.PropertyTypes.MNY) - case 'spirit': return this.#property.get(this.PropertyTypes.SPR) - default: return key + case 'currentyear': + return new Date().getFullYear() + case 'age': + return this.#property.get(this.PropertyTypes.AGE) + case 'charm': + return this.#property.get(this.PropertyTypes.CHR) + case 'intelligence': + return this.#property.get(this.PropertyTypes.INT) + case 'strength': + return this.#property.get(this.PropertyTypes.STR) + case 'money': + return this.#property.get(this.PropertyTypes.MNY) + case 'spirit': + return this.#property.get(this.PropertyTypes.SPR) + default: + return key + } + } + + sync(tag, data) { + switch (tag) { + case 'unique': + return this.#character.sync(data) + case 'props': + return this.#property.sync(data) } } get lastExtendTalent() { - return this.#property.get(this.PropertyTypes.EXT); + return this.#property.get(this.PropertyTypes.EXT) } get summary() { - this.#achievement.achieve(this.AchievementOpportunity.SUMMARY); + this.#achievement.achieve(this.AchievementOpportunity.SUMMARY) - const pt = this.PropertyTypes; + const pt = this.PropertyTypes - return this.#getJudges(pt.SUM, - pt.HAGE, pt.HCHR, pt.HINT, - pt.HSTR, pt.HMNY, pt.HSPR, - ); + return this.#getJudges( + pt.SUM, + pt.HAGE, + pt.HCHR, + pt.HINT, + pt.HSTR, + pt.HMNY, + pt.HSPR, + ) } get statistics() { - const pt = this.PropertyTypes; + const pt = this.PropertyTypes - return this.#getJudges( pt.TMS, - pt.CACHV, pt.RTLT, pt.REVT, - ); + return this.#getJudges(pt.TMS, pt.CACHV, pt.RTLT, pt.REVT) } get achievements() { - const ticks = {}; + const ticks = {} this.#property .get(this.PropertyTypes.ACHV) - .forEach(([id, tick]) => ticks[id] = tick); - return this - .#achievement + .forEach(([id, tick]) => (ticks[id] = tick)) + return this.#achievement .list(this.#property) - .sort(( - {id: a, grade: ag, hide: ah}, - {id: b, grade: bg, hide: bh} - )=>{ - a = ticks[a]; - b = ticks[b]; - if(a&&b) return b - a; - if(!a&&!b) { - if(ah&&bh) return bg - ag; - if(ah) return 1; - if(bh) return -1; - return bg - ag; - } - if(!a) return 1; - if(!b) return -1; - }); + .sort( + ( + { id: a, grade: ag, hide: ah }, + { id: b, grade: bg, hide: bh }, + ) => { + a = ticks[a] + b = ticks[b] + if (a && b) return b - a + if (!a && !b) { + if (ah && bh) return bg - ag + if (ah) return 1 + if (bh) return -1 + return bg - ag + } + if (!a) return 1 + if (!b) return -1 + }, + ) } - get PropertyTypes() { return this.#property.TYPES; } - get AchievementOpportunity() { return this.#achievement.Opportunity; } - get talentSelectLimit() { return this.#talentSelectLimit; } - get propertyAllocateLimit() { return util.clone(this.#propertyAllocateLimit); } + get PropertyTypes() { return this.#property.TYPES } + get AchievementOpportunity() { return this.#achievement.Opportunity } + get talentSelectLimit() { return this.#talentSelectLimit } + get propertyAllocateLimit() { return util.clone(this.#propertyAllocateLimit) } - get propertys() { return this.#property.getPropertys(); } - get times() { return this.#property.get(this.PropertyTypes.TMS) || 0; } + get propertys() { return this.#property.getPropertys() } + get times() { return this.#property.get(this.PropertyTypes.TMS) || 0} set times(v) { - this.#property.set(this.PropertyTypes.TMS, v); - this.#achievement.achieve(this.AchievementOpportunity.END); + this.#property.set(this.PropertyTypes.TMS, v) + this.#achievement.achieve(this.AchievementOpportunity.END) } - get specialThanks() { return this.#specialThanks; } + get specialThanks() { return this.#specialThanks } } -export default Life; \ No newline at end of file +export default Life diff --git a/src/modules/property.js b/src/modules/property.js index 4135248..f452abb 100644 --- a/src/modules/property.js +++ b/src/modules/property.js @@ -1,110 +1,107 @@ class Property { constructor(system) { - this.#system = 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 + 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 + 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 + SUM: 'SUM', // 总评 summary SUM - EXT: "EXT", // 继承天赋 + EXT: 'EXT', // 继承天赋 // 总计 // Achievement Total - ATLT: "ATLT", // 拥有过的天赋 Achieve Talent - AEVT: "AEVT", // 触发过的事件 Achieve Event - ACHV: "ACHV", // 达成的成就 Achievement + ATLT: 'ATLT', // 拥有过的天赋 Achieve Talent + AEVT: 'AEVT', // 触发过的事件 Achieve Event + ACHV: 'ACHV', // 达成的成就 Achievement - CTLT: "CTLT", // 天赋选择数 Count Talent - CEVT: "CEVT", // 事件收集数 Count Event - CACHV: "CACHV", // 成就达成数 Count 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 + TTLT: 'TTLT', // 总天赋数 Total Talent + TEVT: 'TEVT', // 总事件数 Total Event + TACHV: 'TACHV', // 总成就数 Total Achievement // 比率 - REVT: "REVT", // 事件收集率 Rate Event - RTLT: "RTLT", // 天赋选择率 Rate Talent - RACHV: "RACHV", // 成就达成率 Rate Achievement + REVT: 'REVT', // 事件收集率 Rate Event + RTLT: 'RTLT', // 天赋选择率 Rate Talent + RACHV: 'RACHV', // 成就达成率 Rate Achievement // SPECIAL RDM: 'RDM', // 随机属性 random RDM - - }; + } // 特殊类型 SPECIAL = { - RDM: [ // 随机属性 random RDM + RDM: [ + // 随机属性 random RDM this.TYPES.CHR, this.TYPES.INT, this.TYPES.STR, this.TYPES.MNY, this.TYPES.SPR, - ] + ], } - #system; - #ageData; - #data = {}; - #total; - #judge; + #system + #ageData + #data = {} + #total + #judge + #sync = new Set() get #util() { - return this.#system.function(this.#system.Function.UTIL); + return this.#system.function(this.#system.Function.UTIL) } - initial({age, total}) { - this.#ageData = age; - for(const a in age) { - let { event, talent } = age[a]; - if(!Array.isArray(event)) - event = event?.split(',') || []; + initial({ age, total }) { + this.#ageData = age + for (const a in age) { + let { event, talent } = age[a] + if (!Array.isArray(event)) event = event?.split(',') || [] - event = event.map(v=>{ - const value = `${v}`.split('*').map(n=>Number(n)); - if(value.length==1) value.push(1); - return value; - }); + event = event.map(v => { + const value = `${v}`.split('*').map(Number) + if (value.length == 1) value.push(1) + return value + }) - if(!Array.isArray(talent)) - talent = talent?.split(',') || []; - - talent = talent.map(v=>Number(v)); - - age[a] = { event, talent }; + if (!Array.isArray(talent)) talent = talent?.split(',') || [] + talent = talent.map(Number) + age[a] = { event, talent } } - this.#total = total; + this.#total = total } - config({judge = {}}) { - this.#judge = judge; + config({ judge = {} }) { + this.#judge = judge } restart(data) { @@ -135,29 +132,28 @@ class Property { [this.TYPES.HSTR]: -Infinity, [this.TYPES.HMNY]: -Infinity, [this.TYPES.HSPR]: -Infinity, - }; - for(const key in data) - this.change(key, data[key]); + } + for (const key in data) this.change(key, data[key]) } restartLastStep() { - this.#data[this.TYPES.LAGE] = this.get(this.TYPES.AGE); - this.#data[this.TYPES.LCHR] = this.get(this.TYPES.CHR); - this.#data[this.TYPES.LINT] = this.get(this.TYPES.INT); - this.#data[this.TYPES.LSTR] = this.get(this.TYPES.STR); - this.#data[this.TYPES.LSPR] = this.get(this.TYPES.SPR); - this.#data[this.TYPES.LMNY] = this.get(this.TYPES.MNY); - this.#data[this.TYPES.HAGE] = this.get(this.TYPES.AGE); - this.#data[this.TYPES.HCHR] = this.get(this.TYPES.CHR); - this.#data[this.TYPES.HINT] = this.get(this.TYPES.INT); - this.#data[this.TYPES.HSTR] = this.get(this.TYPES.STR); - this.#data[this.TYPES.HMNY] = this.get(this.TYPES.MNY); - this.#data[this.TYPES.HSPR] = this.get(this.TYPES.SPR); + this.#data[this.TYPES.LAGE] = this.get(this.TYPES.AGE) + this.#data[this.TYPES.LCHR] = this.get(this.TYPES.CHR) + this.#data[this.TYPES.LINT] = this.get(this.TYPES.INT) + this.#data[this.TYPES.LSTR] = this.get(this.TYPES.STR) + this.#data[this.TYPES.LSPR] = this.get(this.TYPES.SPR) + this.#data[this.TYPES.LMNY] = this.get(this.TYPES.MNY) + this.#data[this.TYPES.HAGE] = this.get(this.TYPES.AGE) + this.#data[this.TYPES.HCHR] = this.get(this.TYPES.CHR) + this.#data[this.TYPES.HINT] = this.get(this.TYPES.INT) + this.#data[this.TYPES.HSTR] = this.get(this.TYPES.STR) + this.#data[this.TYPES.HMNY] = this.get(this.TYPES.MNY) + this.#data[this.TYPES.HSPR] = this.get(this.TYPES.SPR) } get(prop) { - const util = this.#util; - switch(prop) { + const util = this.#util + switch (prop) { case this.TYPES.AGE: case this.TYPES.CHR: case this.TYPES.INT: @@ -167,89 +163,83 @@ class Property { case this.TYPES.LIF: case this.TYPES.TLT: case this.TYPES.EVT: - return util.clone(this.#data[prop]); + 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)) - ); + 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); + 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; + return this.lsget('times') || 0 case this.TYPES.EXT: - return this.lsget('extendTalent') || null; + return this.lsget('extendTalent') || null case this.TYPES.ATLT: case this.TYPES.AEVT: case this.TYPES.ACHV: - return this.lsget(prop) || []; + return this.lsget(prop) || [] case this.TYPES.CTLT: case this.TYPES.CEVT: case this.TYPES.CACHV: - return this.get( - this.fallback(prop) - ).length; + return this.get(this.fallback(prop)).length case this.TYPES.TTLT: case this.TYPES.TEVT: case this.TYPES.TACHV: - return this.#total[prop]; + 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; + case this.TYPES.RACHV: { + const fb = this.fallback(prop) + return this.get(fb[0]) / this.get(fb[1]) + } + default: return 0 } } fallback(prop) { - switch(prop) { + switch (prop) { case this.TYPES.LAGE: - case this.TYPES.HAGE: return this.TYPES.AGE; + case this.TYPES.HAGE: return this.TYPES.AGE case this.TYPES.LCHR: - case this.TYPES.HCHR: return this.TYPES.CHR; + case this.TYPES.HCHR: return this.TYPES.CHR case this.TYPES.LINT: - case this.TYPES.HINT: return this.TYPES.INT; + case this.TYPES.HINT: return this.TYPES.INT case this.TYPES.LSTR: - case this.TYPES.HSTR: return this.TYPES.STR; + case this.TYPES.HSTR: return this.TYPES.STR case this.TYPES.LMNY: - case this.TYPES.HMNY: return this.TYPES.MNY; + 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; + 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 } } set(prop, value) { - switch(prop) { + switch (prop) { case this.TYPES.AGE: case this.TYPES.CHR: case this.TYPES.INT: @@ -259,16 +249,17 @@ class Property { case this.TYPES.LIF: case this.TYPES.TLT: case this.TYPES.EVT: - this.hl(prop, this.#data[prop] = this.#system.clone(value)); - this.achieve(prop, value); - return; - case this.TYPES.TMS: - this.lsset('times', parseInt(value) || 0); - return; - case this.TYPES.EXT: - this.lsset('extendTalent', value); + this.#data[prop] = this.#system.clone(value) + this.hl(prop, this.#data[prop]) + this.achieve(prop, value) return - default: return; + case this.TYPES.TMS: + this.lsset('times', Number.parseInt(value) || 0) + return + case this.TYPES.EXT: + this.lsset('extendTalent', value) + return + default: return } } @@ -280,16 +271,15 @@ class Property { [this.TYPES.STR]: this.get(this.TYPES.STR), [this.TYPES.MNY]: this.get(this.TYPES.MNY), [this.TYPES.SPR]: this.get(this.TYPES.SPR), - }); + }) } change(prop, value) { - if(Array.isArray(value)) { - for(const v of value) - this.change(prop, Number(v)); - return; + if (Array.isArray(value)) { + for (const v of value) this.change(prop, Number(v)) + return } - switch(prop) { + switch (prop) { case this.TYPES.AGE: case this.TYPES.CHR: case this.TYPES.INT: @@ -297,128 +287,146 @@ class Property { case this.TYPES.MNY: case this.TYPES.SPR: case this.TYPES.LIF: - this.hl(prop, this.#data[prop] += Number(value)); - return; + this.#data[prop] += Number(value) + this.hl(prop, this.#data[prop]) + 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); + 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; + 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; + this.set(prop, this.get(prop) + Number.parseInt(value)) + return + default: return } } hookSpecial(prop) { - switch(prop) { - case this.TYPES.RDM: - return this.#util.listRandom(this.SPECIAL.RDM); - default: return prop; - } + if (prop === this.TYPES.RDM) + return this.#util.listRandom(this.SPECIAL.RDM) + return prop } effect(effects) { - for(let prop in effects) - this.change( - this.hookSpecial(prop), - Number(effects[prop]) - ); + for (let prop in effects) + this.change(this.hookSpecial(prop), Number(effects[prop])) } judge(prop) { - const value = this.get(prop); + const value = this.get(prop) - const d = this.#judge[prop]; - let length = d.length; + const d = this.#judge[prop] + let length = d.length - const progress = () => Math.max(Math.min(value, 10), 0) / 10; + const progress = () => Math.max(Math.min(value, 10), 0) / 10 - while(length--) { - const [min, grade, judge] = d[length]; - if(!length || min==void 0 || value >= min) return {prop, value, judge, grade, progress: progress()}; + while (length--) { + const [min, grade, judge] = d[length] + if (!length || min == void 0 || value >= min) + return { prop, value, judge, grade, progress: progress() } } } isEnd() { - return this.get(this.TYPES.LIF) < 1; + return this.get(this.TYPES.LIF) < 1 } 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.change(this.TYPES.AGE, 1) + const age = this.get(this.TYPES.AGE) + const { event, talent } = this.getAgeData(age) + return { age, event, talent } } getAgeData(age) { - return this.#system.clone(this.#ageData[age]); + return this.#system.clone(this.#ageData[age]) } hl(prop, value) { - let keys; - switch(prop) { - case this.TYPES.AGE: keys = [this.TYPES.LAGE, this.TYPES.HAGE]; break; - case this.TYPES.CHR: keys = [this.TYPES.LCHR, this.TYPES.HCHR]; break; - case this.TYPES.INT: keys = [this.TYPES.LINT, this.TYPES.HINT]; break; - case this.TYPES.STR: keys = [this.TYPES.LSTR, this.TYPES.HSTR]; break; - case this.TYPES.MNY: keys = [this.TYPES.LMNY, this.TYPES.HMNY]; break; - case this.TYPES.SPR: keys = [this.TYPES.LSPR, this.TYPES.HSPR]; break; - default: return; + let keys + switch (prop) { + case this.TYPES.AGE: keys = [this.TYPES.LAGE, this.TYPES.HAGE]; break + case this.TYPES.CHR: keys = [this.TYPES.LCHR, this.TYPES.HCHR]; break + case this.TYPES.INT: keys = [this.TYPES.LINT, this.TYPES.HINT]; break + case this.TYPES.STR: keys = [this.TYPES.LSTR, this.TYPES.HSTR]; break + case this.TYPES.MNY: keys = [this.TYPES.LMNY, this.TYPES.HMNY]; break + case this.TYPES.SPR: keys = [this.TYPES.LSPR, this.TYPES.HSPR]; break + default: return } - const [l, h] = keys; - this.#data[l] = this.#util.min(this.#data[l], value); - this.#data[h] = this.#util.max(this.#data[h], value); + const [l, h] = keys + this.#data[l] = this.#util.min(this.#data[l], value) + this.#data[h] = this.#util.max(this.#data[h], value) } achieve(prop, newData) { - let key; - switch(prop) { - case this.TYPES.ACHV: - const lastData = this.lsget(prop); + 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() + (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()))) + } + + #syncMerge(key, cloud) { + const local = this.lsget(key) + if (!cloud) return local + if (!local) { + localStorage.setItem(key, JSON.stringify(cloud)) + return cloud + } + if (Array.isArray(cloud)) { + const merged = Array.from(new Set(cloud.concat(local))) + localStorage.setItem(key, JSON.stringify(merged)) + return merged + } + if (Number.isNumber(cloud)) { + const merged = Math.max(cloud, local) + localStorage.setItem(key, JSON.stringify(merged)) + return merged + } + return local + } + + sync(cloudData) { + if (!cloudData) return this.#sync.size > 0 + cloudData = JSON.parse(cloudData) + const syncData = Object.fromEntries( + Array.from( + this.#sync.union(new Set(Object.keys(cloudData))), + key => [key, this.#syncMerge(key, cloudData[key])], + ), ) + this.#sync.clear() + return JSON.stringify(syncData) } lsget(key) { - const data = localStorage.getItem(key); - if(data === null || data === 'undefined') return; - return JSON.parse(data); + const data = localStorage.getItem(key) + if (data === null || data === 'undefined') return + return JSON.parse(data) } lsset(key, value) { - localStorage.setItem( - key, - JSON.stringify(value) - ); + localStorage.setItem(key, JSON.stringify(value)) + this.#sync.add(key) } } -export default Property; \ No newline at end of file +export default Property