add: bili-toy-sdk cloud save support

This commit is contained in:
Vick Scarlet
2026-07-27 14:32:13 +08:00
parent 50411b82a0
commit 6fdbcea925
4 changed files with 549 additions and 453 deletions
+50
View File
@@ -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)
}
+70 -69
View File
@@ -1,28 +1,28 @@
class Character { class Character {
constructor(system) { constructor(system) {
this.#system = system; this.#system = system
} }
#system; #system
#characters; #characters
#characterPullCount; #characterPullCount
#rateableKnife; #rateableKnife
#rate; #rate
#pipe = []; #pipe = []
#uniqueWaTaShi; #uniqueWaTaShi
#propertyWeight; #propertyWeight
#talentWeight #talentWeight
initial({characters}) { initial({ characters }) {
this.#characters = characters; this.#characters = characters
const uniqueWaTaShi = localStorage.getItem('uniqueWaTaShi'); const uniqueWaTaShi = localStorage.getItem('uniqueWaTaShi')
if(uniqueWaTaShi != null || uniqueWaTaShi != 'undefined') if (uniqueWaTaShi != null || uniqueWaTaShi != 'undefined')
this.#uniqueWaTaShi = JSON.parse(uniqueWaTaShi); this.#uniqueWaTaShi = JSON.parse(uniqueWaTaShi)
return this.count; return this.count
} }
get count() { get count() {
return Object.keys(this.#characters).length; return Object.keys(this.#characters).length
} }
config({ config({
@@ -31,43 +31,47 @@ class Character {
propertyWeight, propertyWeight,
talentWeight, talentWeight,
} = {}) { } = {}) {
this.#characterPullCount = characterPullCount; this.#characterPullCount = characterPullCount
this.#rateableKnife = rateableKnife; this.#rateableKnife = rateableKnife
this.#propertyWeight = propertyWeight; this.#propertyWeight = propertyWeight
this.#talentWeight = talentWeight; this.#talentWeight = talentWeight
}
sync(data) {
if (!data) return JSON.stringify(this.#uniqueWaTaShi)
localStorage.setItem('uniqueWaTaShi', data)
this.#uniqueWaTaShi = JSON.parse(data)
} }
get #unique() { get #unique() {
if(this.#uniqueWaTaShi) { if (this.#uniqueWaTaShi) {
return this.#system.clone(this.#uniqueWaTaShi); return this.#system.clone(this.#uniqueWaTaShi)
} }
const now = Date.now(); const now = Date.now()
this.#pipe.push(now); this.#pipe.push(now)
if(this.#pipe.length < 10) return null; if (this.#pipe.length < 10) return null
const time = this.#pipe.shift(); const time = this.#pipe.shift()
if(now - time > 10000) return null; if (now - time > 10000) return null
return {unique: true, generate: false}; return { unique: true, generate: false }
} }
set #unique(data) { set #unique(data) {
this.#uniqueWaTaShi = this.#system.clone(data); this.#uniqueWaTaShi = this.#system.clone(data)
this.#uniqueWaTaShi.unique = true; this.#uniqueWaTaShi.unique = true
this.#uniqueWaTaShi.generate = true; this.#uniqueWaTaShi.generate = true
localStorage.setItem( const json = JSON.stringify(this.#uniqueWaTaShi)
'uniqueWaTaShi', localStorage.setItem('uniqueWaTaShi', json)
JSON.stringify(this.#uniqueWaTaShi)
);
} }
get #weightRandom() { get #weightRandom() {
return this.#system.function(this.#system.Function.UTIL).weightRandom; return this.#system.function(this.#system.Function.UTIL).weightRandom
} }
generateUnique() { generateUnique() {
if(this.#uniqueWaTaShi) return this.#unique; if (this.#uniqueWaTaShi) return this.#unique
const weightRandom = this.#weightRandom; const weightRandom = this.#weightRandom
const {CHR, INT, STR, MNY} = this.#system.PropertyTypes; const { CHR, INT, STR, MNY } = this.#system.PropertyTypes
this.#unique = { this.#unique = {
property: { property: {
@@ -81,7 +85,7 @@ class Character {
.random(weightRandom(this.#talentWeight)), .random(weightRandom(this.#talentWeight)),
} }
return this.#unique; return this.#unique
} }
random() { random() {
@@ -92,43 +96,40 @@ class Character {
} }
#rateable() { #rateable() {
if(!this.#rate) { if (!this.#rate) {
this.#rate = {}; this.#rate = {}
for(const id in this.#characters) { for (const id in this.#characters) {
this.#rate[id] = 1; this.#rate[id] = 1
} }
} }
const r = []; const r = []
const weightRandom = this.#weightRandom; const weightRandom = this.#weightRandom
new Array(this.#characterPullCount) new Array(this.#characterPullCount).fill(0).forEach(() => {
.fill(0) r.push(
.forEach(()=>{ weightRandom(
r.push( Object.keys(this.#rate)
weightRandom(Object .filter(id => !r.includes(id))
.keys(this.#rate) .map(id => [id, this.#rate[id]]),
.filter(id=>!r.includes(id)) ),
.map(id=>([id,this.#rate[id]])) )
) })
)
});
let min = Infinity; let min = Infinity
for(const id in this.#rate) { for (const id in this.#rate) {
if(r.includes(id)) { if (r.includes(id)) {
min = Math.min(min, this.#rate[id]); min = Math.min(min, this.#rate[id])
continue; continue
} }
min = Math.min(min, ++ this.#rate[id]); min = Math.min(min, ++this.#rate[id])
} }
if(min > this.#rateableKnife) { if (min > this.#rateableKnife) {
for(const id in this.#rate) { for (const id in this.#rate) {
this.#rate[id] -= this.#rateableKnife; 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; export default Character
+199 -162
View File
@@ -1,19 +1,19 @@
import * as util from '../functions/util.js'; import * as util from '../functions/util.js'
import * as fCondition from '../functions/condition.js'; import * as fCondition from '../functions/condition.js'
import Property from './property.js'; import Property from './property.js'
import Event from './event.js'; import Event from './event.js'
import Talent from './talent.js'; import Talent from './talent.js'
import Achievement from './achievement.js'; import Achievement from './achievement.js'
import Character from './character.js'; import Character from './character.js'
class Life { class Life {
constructor() { constructor() {
this.#property = new Property(this); this.#property = new Property(this)
this.#event = new Event(this); this.#event = new Event(this)
this.#talent = new Talent(this); this.#talent = new Talent(this)
this.#achievement = new Achievement(this); this.#achievement = new Achievement(this)
this.#character = new Character(this); this.#character = new Character(this)
} }
Module = { Module = {
@@ -29,18 +29,18 @@ class Life {
UTIL: 'UTIL', UTIL: 'UTIL',
} }
#property; #property
#event; #event
#talent; #talent
#achievement; #achievement
#character; #character
#triggerTalents; #triggerTalents
#defaultPropertyPoints; #defaultPropertyPoints
#talentSelectLimit; #talentSelectLimit
#propertyAllocateLimit; #propertyAllocateLimit
#defaultPropertys; #defaultPropertys
#specialThanks; #specialThanks
#initialData; #initialData
async initial(i18nLoad, commonLoad) { async initial(i18nLoad, commonLoad) {
const [age, talents, events, achievements, characters, specialThanks] = await Promise.all([ const [age, talents, events, achievements, characters, specialThanks] = await Promise.all([
@@ -50,16 +50,16 @@ class Life {
i18nLoad('achievement'), i18nLoad('achievement'),
i18nLoad('character'), i18nLoad('character'),
commonLoad('specialthanks'), commonLoad('specialthanks'),
]); ])
this.#specialThanks = specialThanks; this.#specialThanks = specialThanks
const total = { const total = {
[this.PropertyTypes.TACHV]: this.#achievement.initial({achievements}), [this.PropertyTypes.TACHV]: this.#achievement.initial({ achievements }),
[this.PropertyTypes.TEVT]: this.#event.initial({events}), [this.PropertyTypes.TEVT]: this.#event.initial({ events }),
[this.PropertyTypes.TTLT]: this.#talent.initial({talents}), [this.PropertyTypes.TTLT]: this.#talent.initial({ talents }),
}; }
this.#property.initial({age, total}); this.#property.initial({ age, total })
this.#character.initial({characters}); this.#character.initial({ characters })
} }
config({ config({
@@ -71,137 +71,146 @@ class Life {
propertyConfig, // config for property propertyConfig, // config for property
characterConfig, // config for character characterConfig, // config for character
} = {}) { } = {}) {
this.#defaultPropertyPoints = defaultPropertyPoints; this.#defaultPropertyPoints = defaultPropertyPoints
this.#talentSelectLimit = talentSelectLimit; this.#talentSelectLimit = talentSelectLimit
this.#propertyAllocateLimit = propertyAllocateLimit; this.#propertyAllocateLimit = propertyAllocateLimit
this.#defaultPropertys = defaultPropertys; this.#defaultPropertys = defaultPropertys
this.#talent.config(talentConfig); this.#talent.config(talentConfig)
this.#property.config(propertyConfig); this.#property.config(propertyConfig)
this.#character.config(characterConfig); this.#character.config(characterConfig)
} }
request(module) { request(module) {
switch (module) { switch (module) {
case this.Module.ACHIEVEMENT: return this.#achievement; case this.Module.ACHIEVEMENT: return this.#achievement
case this.Module.CHARACTER: return this.#character; case this.Module.CHARACTER: return this.#character
case this.Module.EVENT: return this.#event; case this.Module.EVENT: return this.#event
case this.Module.PROPERTY: return this.#property; case this.Module.PROPERTY: return this.#property
case this.Module.TALENT: return this.#talent; case this.Module.TALENT: return this.#talent
default: return null; default: return null
} }
} }
function(type) { function(type) {
switch (type) { switch (type) {
case this.Function.CONDITION: return fCondition; case this.Function.CONDITION: return fCondition
case this.Function.UTIL: return util; case this.Function.UTIL: return util
} }
} }
check(condition) { check(condition) {
return fCondition.checkCondition(this.#property,condition); return fCondition.checkCondition(this.#property, condition)
} }
clone(...args) { clone(...args) {
return util.clone(...args); return util.clone(...args)
} }
remake(talents) { remake(talents) {
this.#initialData = util.clone(this.#defaultPropertys); this.#initialData = util.clone(this.#defaultPropertys)
this.#initialData.TLT = util.clone(talents); this.#initialData.TLT = util.clone(talents)
this.#triggerTalents = {}; this.#triggerTalents = {}
return this.talentReplace(this.#initialData.TLT); return this.talentReplace(this.#initialData.TLT)
} }
start(allocation) { start(allocation) {
for(const key in allocation) { for (const key in allocation) {
this.#initialData[key] = util.clone(allocation[key]); this.#initialData[key] = util.clone(allocation[key])
} }
this.#property.restart(this.#initialData); this.#property.restart(this.#initialData)
this.doTalent() this.doTalent()
this.#property.restartLastStep(); this.#property.restartLastStep()
this.#achievement.achieve(this.AchievementOpportunity.START); this.#achievement.achieve(this.AchievementOpportunity.START)
} }
getPropertyPoints() { getPropertyPoints() {
return this.#defaultPropertyPoints + this.#talent.allocationAddition(this.#initialData.TLT); return (
this.#defaultPropertyPoints +
this.#talent.allocationAddition(this.#initialData.TLT)
)
} }
getTalentCurrentTriggerCount(talentId) { getTalentCurrentTriggerCount(talentId) {
return this.#triggerTalents[talentId] || 0; return this.#triggerTalents[talentId] || 0
} }
next() { next() {
const {age, event, talent} = this.#property.ageNext(); const { age, event, talent } = this.#property.ageNext()
const talentContent = this.doTalent(talent); const talentContent = this.doTalent(talent)
const eventContent = this.doEvent(this.random(event)); const eventContent = this.doEvent(this.random(event))
const isEnd = this.#property.isEnd(); const isEnd = this.#property.isEnd()
const content = [talentContent, eventContent].flat(); const content = [talentContent, eventContent].flat()
this.#achievement.achieve(this.AchievementOpportunity.TRAJECTORY); this.#achievement.achieve(this.AchievementOpportunity.TRAJECTORY)
return { age, content, isEnd }; return { age, content, isEnd }
} }
talentReplace(talents) { talentReplace(talents) {
const result = this.#talent.replace(talents); const result = this.#talent.replace(talents)
const contents = []; const contents = []
for(const id in result) { for (const id in result) {
talents.push(result[id]); talents.push(result[id])
const source = this.#talent.get(id); const source = this.#talent.get(id)
const target = this.#talent.get(result[id]); const target = this.#talent.get(result[id])
contents.push({ contents.push({
type: 'talentReplace', type: 'talentReplace',
source, target source,
}); target,
})
} }
return contents; return contents
} }
doTalent(talents) { doTalent(talents) {
if(talents) this.#property.change(this.PropertyTypes.TLT, talents); if (talents) this.#property.change(this.PropertyTypes.TLT, talents)
talents = this.#property.get(this.PropertyTypes.TLT) talents = this.#property
.filter(talentId => this.getTalentCurrentTriggerCount(talentId) < this.#talent.get(talentId).max_triggers); .get(this.PropertyTypes.TLT)
.filter(
talentId =>
this.getTalentCurrentTriggerCount(talentId) <
this.#talent.get(talentId).max_triggers,
)
const contents = []; const contents = []
for(const talentId of talents) { for (const talentId of talents) {
const result = this.#talent.do(talentId); const result = this.#talent.do(talentId)
if(!result) continue; if (!result) continue
this.#triggerTalents[talentId] = this.getTalentCurrentTriggerCount(talentId) + 1; this.#triggerTalents[talentId] = this.getTalentCurrentTriggerCount(talentId) + 1
const { effect, name, description, grade } = result; const { effect, name, description, grade } = result
contents.push({ contents.push({
type: this.PropertyTypes.TLT, type: this.PropertyTypes.TLT,
name, name,
grade, grade,
description: this.format(description), description: this.format(description),
}) })
if(!effect) continue; if (!effect) continue
this.#property.effect(effect); this.#property.effect(effect)
} }
return contents; return contents
} }
doEvent(eventId) { doEvent(eventId) {
const { effect, next, description, postEvent, grade } = this.#event.do(eventId); const { effect, next, description, postEvent, grade } = this.#event.do(eventId)
this.#property.change(this.PropertyTypes.EVT, eventId); this.#property.change(this.PropertyTypes.EVT, eventId)
this.#property.effect(effect); this.#property.effect(effect)
const content = { const content = {
type: this.PropertyTypes.EVT, type: this.PropertyTypes.EVT,
description: this.format(description), description: this.format(description),
postEvent: postEvent && this.format(postEvent), postEvent: postEvent && this.format(postEvent),
grade, grade,
} }
if(next) return [content, this.doEvent(next)].flat(); if (next) return [content, this.doEvent(next)].flat()
return [content]; return [content]
} }
random(events) { random(events) {
return util.weightRandom( return util.weightRandom(
events.filter( events.filter(([eventId]) =>
([eventId])=>this.#event.check(eventId, this.#property) this.#event.check(eventId, this.#property),
) ),
); )
} }
talentRandom() { talentRandom() {
@@ -210,118 +219,146 @@ class Life {
this.#getPropertys( this.#getPropertys(
this.PropertyTypes.TMS, this.PropertyTypes.TMS,
this.PropertyTypes.CACHV, this.PropertyTypes.CACHV,
) ),
); )
} }
characterRandom() { characterRandom() {
const characters = this.#character.random(); const characters = this.#character.random()
const replaceTalent = v=>v.talent=v.talent.map( const replaceTalent = v =>
id=>this.#talent.get(id) (v.talent = v.talent.map(id => this.#talent.get(id)))
); characters.normal.forEach(replaceTalent)
characters.normal.forEach(replaceTalent); if (characters.unique && characters.unique.talent)
if(characters.unique && characters.unique.talent) replaceTalent(characters.unique)
replaceTalent(characters.unique); return characters
return characters;
} }
talentExtend(talentId) { talentExtend(talentId) {
this.#property.set(this.PropertyTypes.EXT, talentId); this.#property.set(this.PropertyTypes.EXT, talentId)
} }
exclude(talents, exclusive) { exclude(talents, exclusive) {
return this.#talent.exclude(talents, exclusive); return this.#talent.exclude(talents, exclusive)
} }
generateUnique() { generateUnique() {
this.#character.generateUnique(); const unique = this.#character.generateUnique()
$$event('syncUnique', unique)
} }
#getJudges(...types) { #getJudges(...types) {
return util.getListValuesMap(types.flat(), key => this.#property.judge(key)); return util.getListValuesMap(types.flat(), key =>
this.#property.judge(key),
)
} }
#getPropertys(...types) { #getPropertys(...types) {
return util.getListValuesMap(types.flat(), key => this.#property.get(key)); return util.getListValuesMap(types.flat(), key =>
this.#property.get(key),
)
} }
format(discription) { 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) { #format(key) {
switch (key.slice(1, -1).trim().toLowerCase()) { switch (key.slice(1, -1).trim().toLowerCase()) {
case 'currentyear': return new Date().getFullYear() case 'currentyear':
case 'age': return this.#property.get(this.PropertyTypes.AGE) return new Date().getFullYear()
case 'charm': return this.#property.get(this.PropertyTypes.CHR) case 'age':
case 'intelligence': return this.#property.get(this.PropertyTypes.INT) return this.#property.get(this.PropertyTypes.AGE)
case 'strength': return this.#property.get(this.PropertyTypes.STR) case 'charm':
case 'money': return this.#property.get(this.PropertyTypes.MNY) return this.#property.get(this.PropertyTypes.CHR)
case 'spirit': return this.#property.get(this.PropertyTypes.SPR) case 'intelligence':
default: return key 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() { get lastExtendTalent() {
return this.#property.get(this.PropertyTypes.EXT); return this.#property.get(this.PropertyTypes.EXT)
} }
get summary() { 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, return this.#getJudges(
pt.HAGE, pt.HCHR, pt.HINT, pt.SUM,
pt.HSTR, pt.HMNY, pt.HSPR, pt.HAGE,
); pt.HCHR,
pt.HINT,
pt.HSTR,
pt.HMNY,
pt.HSPR,
)
} }
get statistics() { get statistics() {
const pt = this.PropertyTypes; const pt = this.PropertyTypes
return this.#getJudges( pt.TMS, return this.#getJudges(pt.TMS, pt.CACHV, pt.RTLT, pt.REVT)
pt.CACHV, pt.RTLT, pt.REVT,
);
} }
get achievements() { get achievements() {
const ticks = {}; const ticks = {}
this.#property this.#property
.get(this.PropertyTypes.ACHV) .get(this.PropertyTypes.ACHV)
.forEach(([id, tick]) => ticks[id] = tick); .forEach(([id, tick]) => (ticks[id] = tick))
return this return this.#achievement
.#achievement
.list(this.#property) .list(this.#property)
.sort(( .sort(
{id: a, grade: ag, hide: ah}, (
{id: b, grade: bg, hide: bh} { id: a, grade: ag, hide: ah },
)=>{ { id: b, grade: bg, hide: bh },
a = ticks[a]; ) => {
b = ticks[b]; a = ticks[a]
if(a&&b) return b - a; b = ticks[b]
if(!a&&!b) { if (a && b) return b - a
if(ah&&bh) return bg - ag; if (!a && !b) {
if(ah) return 1; if (ah && bh) return bg - ag
if(bh) return -1; if (ah) return 1
return bg - ag; if (bh) return -1
} return bg - ag
if(!a) return 1; }
if(!b) return -1; if (!a) return 1
}); if (!b) return -1
},
)
} }
get PropertyTypes() { return this.#property.TYPES; } get PropertyTypes() { return this.#property.TYPES }
get AchievementOpportunity() { return this.#achievement.Opportunity; } get AchievementOpportunity() { return this.#achievement.Opportunity }
get talentSelectLimit() { return this.#talentSelectLimit; } get talentSelectLimit() { return this.#talentSelectLimit }
get propertyAllocateLimit() { return util.clone(this.#propertyAllocateLimit); } get propertyAllocateLimit() { return util.clone(this.#propertyAllocateLimit) }
get propertys() { return this.#property.getPropertys(); } get propertys() { return this.#property.getPropertys() }
get times() { return this.#property.get(this.PropertyTypes.TMS) || 0; } get times() { return this.#property.get(this.PropertyTypes.TMS) || 0}
set times(v) { set times(v) {
this.#property.set(this.PropertyTypes.TMS, v); this.#property.set(this.PropertyTypes.TMS, v)
this.#achievement.achieve(this.AchievementOpportunity.END); this.#achievement.achieve(this.AchievementOpportunity.END)
} }
get specialThanks() { return this.#specialThanks; } get specialThanks() { return this.#specialThanks }
} }
export default Life; export default Life
+230 -222
View File
@@ -1,110 +1,107 @@
class Property { class Property {
constructor(system) { constructor(system) {
this.#system = system; this.#system = system
} }
TYPES = { TYPES = {
// 本局 // 本局
AGE: "AGE", // 年龄 age AGE AGE: 'AGE', // 年龄 age AGE
CHR: "CHR", // 颜值 charm CHR CHR: 'CHR', // 颜值 charm CHR
INT: "INT", // 智力 intelligence INT INT: 'INT', // 智力 intelligence INT
STR: "STR", // 体质 strength STR STR: 'STR', // 体质 strength STR
MNY: "MNY", // 家境 money MNY MNY: 'MNY', // 家境 money MNY
SPR: "SPR", // 快乐 spirit SPR SPR: 'SPR', // 快乐 spirit SPR
LIF: "LIF", // 生命 life LIFE LIF: 'LIF', // 生命 life LIFE
TLT: "TLT", // 天赋 talent TLT TLT: 'TLT', // 天赋 talent TLT
EVT: "EVT", // 事件 event EVT EVT: 'EVT', // 事件 event EVT
TMS: "TMS", // 次数 times TMS TMS: 'TMS', // 次数 times TMS
// Auto calc // Auto calc
LAGE: "LAGE", // 最低年龄 Low Age LAGE: 'LAGE', // 最低年龄 Low Age
HAGE: "HAGE", // 最高年龄 High Age HAGE: 'HAGE', // 最高年龄 High Age
LCHR: "LCHR", // 最低颜值 Low Charm LCHR: 'LCHR', // 最低颜值 Low Charm
HCHR: "HCHR", // 最高颜值 High Charm HCHR: 'HCHR', // 最高颜值 High Charm
LINT: "LINT", // 最低智力 Low Intelligence LINT: 'LINT', // 最低智力 Low Intelligence
HINT: "HINT", // 最高智力 High Intelligence HINT: 'HINT', // 最高智力 High Intelligence
LSTR: "LSTR", // 最低体质 Low Strength LSTR: 'LSTR', // 最低体质 Low Strength
HSTR: "HSTR", // 最高体质 High Strength HSTR: 'HSTR', // 最高体质 High Strength
LMNY: "LMNY", // 最低家境 Low Money LMNY: 'LMNY', // 最低家境 Low Money
HMNY: "HMNY", // 最高家境 High Money HMNY: 'HMNY', // 最高家境 High Money
LSPR: "LSPR", // 最低快乐 Low Spirit LSPR: 'LSPR', // 最低快乐 Low Spirit
HSPR: "HSPR", // 最高快乐 High Spirit HSPR: 'HSPR', // 最高快乐 High Spirit
SUM: "SUM", // 总评 summary SUM SUM: 'SUM', // 总评 summary SUM
EXT: "EXT", // 继承天赋 EXT: 'EXT', // 继承天赋
// 总计 // 总计
// Achievement Total // Achievement Total
ATLT: "ATLT", // 拥有过的天赋 Achieve Talent ATLT: 'ATLT', // 拥有过的天赋 Achieve Talent
AEVT: "AEVT", // 触发过的事件 Achieve Event AEVT: 'AEVT', // 触发过的事件 Achieve Event
ACHV: "ACHV", // 达成的成就 Achievement ACHV: 'ACHV', // 达成的成就 Achievement
CTLT: "CTLT", // 天赋选择数 Count Talent CTLT: 'CTLT', // 天赋选择数 Count Talent
CEVT: "CEVT", // 事件收集数 Count Event CEVT: 'CEVT', // 事件收集数 Count Event
CACHV: "CACHV", // 成就达成数 Count Achievement CACHV: 'CACHV', // 成就达成数 Count Achievement
// 总数 // 总数
TTLT: "TTLT", // 总天赋数 Total Talent TTLT: 'TTLT', // 总天赋数 Total Talent
TEVT: "TEVT", // 总事件数 Total Event TEVT: 'TEVT', // 总事件数 Total Event
TACHV: "TACHV", // 总成就数 Total Achievement TACHV: 'TACHV', // 总成就数 Total Achievement
// 比率 // 比率
REVT: "REVT", // 事件收集率 Rate Event REVT: 'REVT', // 事件收集率 Rate Event
RTLT: "RTLT", // 天赋选择率 Rate Talent RTLT: 'RTLT', // 天赋选择率 Rate Talent
RACHV: "RACHV", // 成就达成率 Rate Achievement RACHV: 'RACHV', // 成就达成率 Rate Achievement
// SPECIAL // SPECIAL
RDM: 'RDM', // 随机属性 random RDM RDM: 'RDM', // 随机属性 random RDM
}
};
// 特殊类型 // 特殊类型
SPECIAL = { SPECIAL = {
RDM: [ // 随机属性 random RDM RDM: [
// 随机属性 random RDM
this.TYPES.CHR, this.TYPES.CHR,
this.TYPES.INT, this.TYPES.INT,
this.TYPES.STR, this.TYPES.STR,
this.TYPES.MNY, this.TYPES.MNY,
this.TYPES.SPR, this.TYPES.SPR,
] ],
} }
#system; #system
#ageData; #ageData
#data = {}; #data = {}
#total; #total
#judge; #judge
#sync = new Set()
get #util() { get #util() {
return this.#system.function(this.#system.Function.UTIL); return this.#system.function(this.#system.Function.UTIL)
} }
initial({age, total}) { initial({ age, total }) {
this.#ageData = age; this.#ageData = age
for(const a in age) { for (const a in age) {
let { event, talent } = age[a]; let { event, talent } = age[a]
if(!Array.isArray(event)) if (!Array.isArray(event)) event = event?.split(',') || []
event = event?.split(',') || [];
event = event.map(v=>{ event = event.map(v => {
const value = `${v}`.split('*').map(n=>Number(n)); const value = `${v}`.split('*').map(Number)
if(value.length==1) value.push(1); if (value.length == 1) value.push(1)
return value; return value
}); })
if(!Array.isArray(talent)) if (!Array.isArray(talent)) talent = talent?.split(',') || []
talent = talent?.split(',') || []; talent = talent.map(Number)
age[a] = { event, talent }
talent = talent.map(v=>Number(v));
age[a] = { event, talent };
} }
this.#total = total; this.#total = total
} }
config({judge = {}}) { config({ judge = {} }) {
this.#judge = judge; this.#judge = judge
} }
restart(data) { restart(data) {
@@ -135,29 +132,28 @@ class Property {
[this.TYPES.HSTR]: -Infinity, [this.TYPES.HSTR]: -Infinity,
[this.TYPES.HMNY]: -Infinity, [this.TYPES.HMNY]: -Infinity,
[this.TYPES.HSPR]: -Infinity, [this.TYPES.HSPR]: -Infinity,
}; }
for(const key in data) for (const key in data) this.change(key, data[key])
this.change(key, data[key]);
} }
restartLastStep() { restartLastStep() {
this.#data[this.TYPES.LAGE] = this.get(this.TYPES.AGE); this.#data[this.TYPES.LAGE] = this.get(this.TYPES.AGE)
this.#data[this.TYPES.LCHR] = this.get(this.TYPES.CHR); this.#data[this.TYPES.LCHR] = this.get(this.TYPES.CHR)
this.#data[this.TYPES.LINT] = this.get(this.TYPES.INT); this.#data[this.TYPES.LINT] = this.get(this.TYPES.INT)
this.#data[this.TYPES.LSTR] = this.get(this.TYPES.STR); this.#data[this.TYPES.LSTR] = this.get(this.TYPES.STR)
this.#data[this.TYPES.LSPR] = this.get(this.TYPES.SPR); this.#data[this.TYPES.LSPR] = this.get(this.TYPES.SPR)
this.#data[this.TYPES.LMNY] = this.get(this.TYPES.MNY); this.#data[this.TYPES.LMNY] = this.get(this.TYPES.MNY)
this.#data[this.TYPES.HAGE] = this.get(this.TYPES.AGE); this.#data[this.TYPES.HAGE] = this.get(this.TYPES.AGE)
this.#data[this.TYPES.HCHR] = this.get(this.TYPES.CHR); this.#data[this.TYPES.HCHR] = this.get(this.TYPES.CHR)
this.#data[this.TYPES.HINT] = this.get(this.TYPES.INT); this.#data[this.TYPES.HINT] = this.get(this.TYPES.INT)
this.#data[this.TYPES.HSTR] = this.get(this.TYPES.STR); this.#data[this.TYPES.HSTR] = this.get(this.TYPES.STR)
this.#data[this.TYPES.HMNY] = this.get(this.TYPES.MNY); this.#data[this.TYPES.HMNY] = this.get(this.TYPES.MNY)
this.#data[this.TYPES.HSPR] = this.get(this.TYPES.SPR); this.#data[this.TYPES.HSPR] = this.get(this.TYPES.SPR)
} }
get(prop) { get(prop) {
const util = this.#util; const util = this.#util
switch(prop) { switch (prop) {
case this.TYPES.AGE: case this.TYPES.AGE:
case this.TYPES.CHR: case this.TYPES.CHR:
case this.TYPES.INT: case this.TYPES.INT:
@@ -167,89 +163,83 @@ class Property {
case this.TYPES.LIF: case this.TYPES.LIF:
case this.TYPES.TLT: case this.TYPES.TLT:
case this.TYPES.EVT: case this.TYPES.EVT:
return util.clone(this.#data[prop]); return util.clone(this.#data[prop])
case this.TYPES.LAGE: case this.TYPES.LAGE:
case this.TYPES.LCHR: case this.TYPES.LCHR:
case this.TYPES.LINT: case this.TYPES.LINT:
case this.TYPES.LSTR: case this.TYPES.LSTR:
case this.TYPES.LMNY: case this.TYPES.LMNY:
case this.TYPES.LSPR: case this.TYPES.LSPR:
return util.min( return util.min(this.#data[prop], this.get(this.fallback(prop)))
this.#data[prop],
this.get(this.fallback(prop))
);
case this.TYPES.HAGE: case this.TYPES.HAGE:
case this.TYPES.HCHR: case this.TYPES.HCHR:
case this.TYPES.HINT: case this.TYPES.HINT:
case this.TYPES.HSTR: case this.TYPES.HSTR:
case this.TYPES.HMNY: case this.TYPES.HMNY:
case this.TYPES.HSPR: case this.TYPES.HSPR:
return util.max( return util.max(this.#data[prop], this.get(this.fallback(prop)))
this.#data[prop], case this.TYPES.SUM: {
this.get(this.fallback(prop)) const HAGE = this.get(this.TYPES.HAGE)
); const HCHR = this.get(this.TYPES.HCHR)
case this.TYPES.SUM: const HINT = this.get(this.TYPES.HINT)
const HAGE = this.get(this.TYPES.HAGE); const HSTR = this.get(this.TYPES.HSTR)
const HCHR = this.get(this.TYPES.HCHR); const HMNY = this.get(this.TYPES.HMNY)
const HINT = this.get(this.TYPES.HINT); const HSPR = this.get(this.TYPES.HSPR)
const HSTR = this.get(this.TYPES.HSTR); return Math.floor(util.sum(HCHR, HINT, HSTR, HMNY, HSPR) * 2 + HAGE / 2)
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: case this.TYPES.TMS:
return this.lsget('times') || 0; return this.lsget('times') || 0
case this.TYPES.EXT: case this.TYPES.EXT:
return this.lsget('extendTalent') || null; return this.lsget('extendTalent') || null
case this.TYPES.ATLT: case this.TYPES.ATLT:
case this.TYPES.AEVT: case this.TYPES.AEVT:
case this.TYPES.ACHV: case this.TYPES.ACHV:
return this.lsget(prop) || []; return this.lsget(prop) || []
case this.TYPES.CTLT: case this.TYPES.CTLT:
case this.TYPES.CEVT: case this.TYPES.CEVT:
case this.TYPES.CACHV: case this.TYPES.CACHV:
return this.get( return this.get(this.fallback(prop)).length
this.fallback(prop)
).length;
case this.TYPES.TTLT: case this.TYPES.TTLT:
case this.TYPES.TEVT: case this.TYPES.TEVT:
case this.TYPES.TACHV: case this.TYPES.TACHV:
return this.#total[prop]; return this.#total[prop]
case this.TYPES.RTLT: case this.TYPES.RTLT:
case this.TYPES.REVT: case this.TYPES.REVT:
case this.TYPES.RACHV: case this.TYPES.RACHV: {
const fb = this.fallback(prop); const fb = this.fallback(prop)
return this.get(fb[0]) / this.get(fb[1]); return this.get(fb[0]) / this.get(fb[1])
default: return 0; }
default: return 0
} }
} }
fallback(prop) { fallback(prop) {
switch(prop) { switch (prop) {
case this.TYPES.LAGE: 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.LCHR:
case this.TYPES.HCHR: return this.TYPES.CHR; case this.TYPES.HCHR: return this.TYPES.CHR
case this.TYPES.LINT: 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.LSTR:
case this.TYPES.HSTR: return this.TYPES.STR; case this.TYPES.HSTR: return this.TYPES.STR
case this.TYPES.LMNY: 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.LSPR:
case this.TYPES.HSPR: return this.TYPES.SPR; case this.TYPES.HSPR: return this.TYPES.SPR
case this.TYPES.CTLT: return this.TYPES.ATLT; case this.TYPES.CTLT: return this.TYPES.ATLT
case this.TYPES.CEVT: return this.TYPES.AEVT; case this.TYPES.CEVT: return this.TYPES.AEVT
case this.TYPES.CACHV: return this.TYPES.ACHV; case this.TYPES.CACHV: return this.TYPES.ACHV
case this.TYPES.LIF: return this.TYPES.LIF; case this.TYPES.LIF: return this.TYPES.LIF
case this.TYPES.RTLT: return [this.TYPES.CTLT, this.TYPES.TTLT]; 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.REVT: return [this.TYPES.CEVT, this.TYPES.TEVT]
case this.TYPES.RACHV: return [this.TYPES.CACHV, this.TYPES.TACHV]; case this.TYPES.RACHV: return [this.TYPES.CACHV, this.TYPES.TACHV]
default: return; default: return
} }
} }
set(prop, value) { set(prop, value) {
switch(prop) { switch (prop) {
case this.TYPES.AGE: case this.TYPES.AGE:
case this.TYPES.CHR: case this.TYPES.CHR:
case this.TYPES.INT: case this.TYPES.INT:
@@ -259,16 +249,17 @@ class Property {
case this.TYPES.LIF: case this.TYPES.LIF:
case this.TYPES.TLT: case this.TYPES.TLT:
case this.TYPES.EVT: case this.TYPES.EVT:
this.hl(prop, this.#data[prop] = this.#system.clone(value)); this.#data[prop] = this.#system.clone(value)
this.achieve(prop, value); this.hl(prop, this.#data[prop])
return; this.achieve(prop, value)
case this.TYPES.TMS:
this.lsset('times', parseInt(value) || 0);
return;
case this.TYPES.EXT:
this.lsset('extendTalent', value);
return 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.STR]: this.get(this.TYPES.STR),
[this.TYPES.MNY]: this.get(this.TYPES.MNY), [this.TYPES.MNY]: this.get(this.TYPES.MNY),
[this.TYPES.SPR]: this.get(this.TYPES.SPR), [this.TYPES.SPR]: this.get(this.TYPES.SPR),
}); })
} }
change(prop, value) { change(prop, value) {
if(Array.isArray(value)) { if (Array.isArray(value)) {
for(const v of value) for (const v of value) this.change(prop, Number(v))
this.change(prop, Number(v)); return
return;
} }
switch(prop) { switch (prop) {
case this.TYPES.AGE: case this.TYPES.AGE:
case this.TYPES.CHR: case this.TYPES.CHR:
case this.TYPES.INT: case this.TYPES.INT:
@@ -297,128 +287,146 @@ class Property {
case this.TYPES.MNY: case this.TYPES.MNY:
case this.TYPES.SPR: case this.TYPES.SPR:
case this.TYPES.LIF: case this.TYPES.LIF:
this.hl(prop, this.#data[prop] += Number(value)); this.#data[prop] += Number(value)
return; this.hl(prop, this.#data[prop])
return
case this.TYPES.TLT: case this.TYPES.TLT:
case this.TYPES.EVT: case this.TYPES.EVT: {
const v = this.#data[prop]; const v = this.#data[prop]
if(value<0) { if (value < 0) {
const index = v.indexOf(value); const index = v.indexOf(value)
if(index!=-1) v.splice(index,1); if (index != -1) v.splice(index, 1)
} }
if(!v.includes(value)) v.push(value); if (!v.includes(value)) v.push(value)
this.achieve(prop, value); this.achieve(prop, value)
return; return
}
case this.TYPES.TMS: case this.TYPES.TMS:
this.set( this.set(prop, this.get(prop) + Number.parseInt(value))
prop, return
this.get(prop) + parseInt(value) default: return
);
return;
default: return;
} }
} }
hookSpecial(prop) { hookSpecial(prop) {
switch(prop) { if (prop === this.TYPES.RDM)
case this.TYPES.RDM: return this.#util.listRandom(this.SPECIAL.RDM)
return this.#util.listRandom(this.SPECIAL.RDM); return prop
default: return prop;
}
} }
effect(effects) { effect(effects) {
for(let prop in effects) for (let prop in effects)
this.change( this.change(this.hookSpecial(prop), Number(effects[prop]))
this.hookSpecial(prop),
Number(effects[prop])
);
} }
judge(prop) { judge(prop) {
const value = this.get(prop); const value = this.get(prop)
const d = this.#judge[prop]; const d = this.#judge[prop]
let length = d.length; 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--) { while (length--) {
const [min, grade, judge] = d[length]; const [min, grade, judge] = d[length]
if(!length || min==void 0 || value >= min) return {prop, value, judge, grade, progress: progress()}; if (!length || min == void 0 || value >= min)
return { prop, value, judge, grade, progress: progress() }
} }
} }
isEnd() { isEnd() {
return this.get(this.TYPES.LIF) < 1; return this.get(this.TYPES.LIF) < 1
} }
ageNext() { ageNext() {
this.change(this.TYPES.AGE, 1); this.change(this.TYPES.AGE, 1)
const age = this.get(this.TYPES.AGE); const age = this.get(this.TYPES.AGE)
const {event, talent} = this.getAgeData(age); const { event, talent } = this.getAgeData(age)
return {age, event, talent}; return { age, event, talent }
} }
getAgeData(age) { getAgeData(age) {
return this.#system.clone(this.#ageData[age]); return this.#system.clone(this.#ageData[age])
} }
hl(prop, value) { hl(prop, value) {
let keys; let keys
switch(prop) { switch (prop) {
case this.TYPES.AGE: keys = [this.TYPES.LAGE, this.TYPES.HAGE]; break; 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.CHR: keys = [this.TYPES.LCHR, this.TYPES.HCHR]; break
case this.TYPES.INT: keys = [this.TYPES.LINT, this.TYPES.HINT]; 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.STR: keys = [this.TYPES.LSTR, this.TYPES.HSTR]; break
case this.TYPES.MNY: keys = [this.TYPES.LMNY, this.TYPES.HMNY]; 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; case this.TYPES.SPR: keys = [this.TYPES.LSPR, this.TYPES.HSPR]; break
default: return; default: return
} }
const [l, h] = keys; const [l, h] = keys
this.#data[l] = this.#util.min(this.#data[l], value); this.#data[l] = this.#util.min(this.#data[l], value)
this.#data[h] = this.#util.max(this.#data[h], value); this.#data[h] = this.#util.max(this.#data[h], value)
} }
achieve(prop, newData) { achieve(prop, newData) {
let key; let key
switch(prop) { switch (prop) {
case this.TYPES.ACHV: case this.TYPES.ACHV: {
const lastData = this.lsget(prop); const lastData = this.lsget(prop)
this.lsset( this.lsset(
prop, prop,
(lastData || []).concat([[newData, Date.now()]]) (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()
) )
) 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) { lsget(key) {
const data = localStorage.getItem(key); const data = localStorage.getItem(key)
if(data === null || data === 'undefined') return; if (data === null || data === 'undefined') return
return JSON.parse(data); return JSON.parse(data)
} }
lsset(key, value) { lsset(key, value) {
localStorage.setItem( localStorage.setItem(key, JSON.stringify(value))
key, this.#sync.add(key)
JSON.stringify(value)
);
} }
} }
export default Property; export default Property