mirror of
https://github.com/VickScarlet/lifeRestart.git
synced 2026-08-29 10:04:43 +08:00
add: achievement, game
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
import { expect, test, describe } from 'bun:test'
|
||||||
|
import { createState } from './state'
|
||||||
|
import { trigger } from './achievement'
|
||||||
|
import { enableMapSet } from 'immer'
|
||||||
|
enableMapSet()
|
||||||
|
|
||||||
|
describe('Achievement', () => {
|
||||||
|
const profile = {
|
||||||
|
times: 0,
|
||||||
|
talents: new Set([]),
|
||||||
|
achievements: new Set([]),
|
||||||
|
events: new Set([]),
|
||||||
|
}
|
||||||
|
const allocation = {
|
||||||
|
charm: 5,
|
||||||
|
intelligence: 5,
|
||||||
|
strength: 5,
|
||||||
|
money: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
test('trigger [times:500]', () => {
|
||||||
|
const p = { ...profile, times: 500 }
|
||||||
|
const result = trigger('END', createState(allocation, []), p)
|
||||||
|
expect(result.state.achievements).toContain(101) // 既视感
|
||||||
|
expect(result.state.achievements).toContain(102) // 孟婆愁
|
||||||
|
expect(result.state.achievements).toContain(103) // 所有人都是我
|
||||||
|
expect(result.state.achievements).toContain(104) // Rewrite
|
||||||
|
expect(result.triggers).toContain(101)
|
||||||
|
expect(result.triggers).toContain(102)
|
||||||
|
expect(result.triggers).toContain(103)
|
||||||
|
expect(result.triggers).toContain(104)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type {
|
||||||
|
Achievement,
|
||||||
|
AchievementOpportunity,
|
||||||
|
} from '@remake/data/achievement'
|
||||||
|
import achievements from '@remake/data/achievement'
|
||||||
|
import type { GameState, ProfileState } from '@/state'
|
||||||
|
import { createFlatState } from './state'
|
||||||
|
import { check } from '@remake/condition'
|
||||||
|
import { produce } from 'immer'
|
||||||
|
import type { TriggerResult } from '@/game'
|
||||||
|
|
||||||
|
const OpportunityMap = Map.groupBy(
|
||||||
|
achievements.keys(),
|
||||||
|
a => achievements.get(a)!.opportunity,
|
||||||
|
)
|
||||||
|
|
||||||
|
export function trigger(
|
||||||
|
opportunity: AchievementOpportunity,
|
||||||
|
state: GameState,
|
||||||
|
profile: ProfileState,
|
||||||
|
): TriggerResult<Achievement['id']> {
|
||||||
|
const flatState = createFlatState(state, profile)
|
||||||
|
const triggers = OpportunityMap.get(opportunity)!.filter(a => {
|
||||||
|
if (state.achievements.has(a)) return false
|
||||||
|
if (profile.achievements.has(a)) return false
|
||||||
|
const { condition } = achievements.get(a)!
|
||||||
|
return check(flatState, condition)
|
||||||
|
})
|
||||||
|
const newState = produce(state, draft => {
|
||||||
|
draft.achievements = new Set([...draft.achievements, ...triggers])
|
||||||
|
})
|
||||||
|
return { state: newState, triggers }
|
||||||
|
}
|
||||||
@@ -26,13 +26,13 @@ describe('Event', () => {
|
|||||||
)
|
)
|
||||||
expect(result.state.life).toBe(1)
|
expect(result.state.life).toBe(1)
|
||||||
expect(result.state.events).toEqual(new Set([10003, 10004]))
|
expect(result.state.events).toEqual(new Set([10003, 10004]))
|
||||||
expect(result.events).toEqual([10003, 10004])
|
expect(result.triggers).toEqual([10003, 10004])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('branch 10003:1 [死了]', () => {
|
test('branch 10003:1 [死了]', () => {
|
||||||
const result = trigger(10003, createState(allocation, []), profile)
|
const result = trigger(10003, createState(allocation, []), profile)
|
||||||
expect(result.state.life).toBe(0)
|
expect(result.state.life).toBe(0)
|
||||||
expect(result.state.events).toEqual(new Set([10003, 10000]))
|
expect(result.state.events).toEqual(new Set([10003, 10000]))
|
||||||
expect(result.events).toEqual([10003, 10000])
|
expect(result.triggers).toEqual([10003, 10000])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+20
-11
@@ -3,22 +3,31 @@ import events from '@remake/data/event'
|
|||||||
import type { Properties } from '@/state'
|
import type { Properties } from '@/state'
|
||||||
import type { GameState, ProfileState } from '@/state'
|
import type { GameState, ProfileState } from '@/state'
|
||||||
import { propsEffect, createFlatState } from '@/state'
|
import { propsEffect, createFlatState } from '@/state'
|
||||||
import { check } from '@remake/condition'
|
import { check as checkCondition } from '@remake/condition'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
|
import type { TriggerResult } from '@/game'
|
||||||
|
|
||||||
export interface TriggerResult {
|
export function check(
|
||||||
state: GameState
|
event: Event['id'],
|
||||||
events: Event['id'][]
|
state: GameState,
|
||||||
|
profile: ProfileState,
|
||||||
|
) {
|
||||||
|
const { include, exclude, NoRandom } = events.get(event)!
|
||||||
|
if (NoRandom) return false
|
||||||
|
const flatState = createFlatState(state, profile)
|
||||||
|
if (exclude && checkCondition(flatState, exclude)) return false
|
||||||
|
if (include) return checkCondition(flatState, include)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function trigger(
|
export function trigger(
|
||||||
eventId: number,
|
event: number,
|
||||||
state: GameState,
|
state: GameState,
|
||||||
profile: ProfileState,
|
profile: ProfileState,
|
||||||
): TriggerResult {
|
): TriggerResult<Event['id']> {
|
||||||
const { effect, branch } = events.get(eventId)!
|
const { effect, branch } = events.get(event)!
|
||||||
const newState = produce(state, draft => {
|
const newState = produce(state, draft => {
|
||||||
draft.events.add(eventId)
|
draft.events.add(event)
|
||||||
if (!effect) return
|
if (!effect) return
|
||||||
if (effect.LIF) draft.life += effect.LIF
|
if (effect.LIF) draft.life += effect.LIF
|
||||||
const pe = {} as Partial<Properties>
|
const pe = {} as Partial<Properties>
|
||||||
@@ -32,14 +41,14 @@ export function trigger(
|
|||||||
const flatState = createFlatState(newState, profile)
|
const flatState = createFlatState(newState, profile)
|
||||||
if (branch) {
|
if (branch) {
|
||||||
for (const { condition, event } of branch) {
|
for (const { condition, event } of branch) {
|
||||||
if (check(flatState, condition)) {
|
if (checkCondition(flatState, condition)) {
|
||||||
const result = trigger(event, newState, profile)
|
const result = trigger(event, newState, profile)
|
||||||
return {
|
return {
|
||||||
state: result.state,
|
state: result.state,
|
||||||
events: [eventId, ...result.events],
|
triggers: [event, ...result.triggers],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { state: newState, events: [eventId] }
|
return { state: newState, triggers: [event] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { expect, test, describe } from 'bun:test'
|
||||||
|
import { createState, propsEffect, summary as stateSummary } from './state'
|
||||||
|
import { talentsPicked, start, next, summary, end } from './game'
|
||||||
|
import { produce } from 'immer'
|
||||||
|
import { enableMapSet } from 'immer'
|
||||||
|
enableMapSet()
|
||||||
|
|
||||||
|
describe('Achievement', () => {
|
||||||
|
const profile = {
|
||||||
|
times: 0,
|
||||||
|
talents: new Set([]),
|
||||||
|
achievements: new Set([]),
|
||||||
|
events: new Set([]),
|
||||||
|
}
|
||||||
|
const allocation = {
|
||||||
|
charm: 5,
|
||||||
|
intelligence: 5,
|
||||||
|
strength: 5,
|
||||||
|
money: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
test('talentsPicked', () => {
|
||||||
|
// 挑战者、阴间福袋、轮盘赌
|
||||||
|
const result = talentsPicked([1122, 1145, 1146])
|
||||||
|
expect(result.talents.chains.size).toBeGreaterThan(1)
|
||||||
|
expect(result.additionalPoints.source).toContainEqual({
|
||||||
|
talent: 1122,
|
||||||
|
points: -20,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('start', () => {
|
||||||
|
const result = start(profile, allocation, [1001, 1111, 1130])
|
||||||
|
expect(result.state.achievements).toContain(228) // 只通一窍
|
||||||
|
})
|
||||||
|
|
||||||
|
test('next', () => {
|
||||||
|
// 白色胶囊
|
||||||
|
const state = produce(
|
||||||
|
createState(allocation, [1001, 1002, 1103]),
|
||||||
|
draft => {
|
||||||
|
draft.props = propsEffect(draft.props, { age: 10 })
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const result = next(state, profile)
|
||||||
|
console.debug(result)
|
||||||
|
expect(result.state.props.current.age).toBe(10) // 十岁
|
||||||
|
expect(result.state.events).toContain(10588) // 无事发生
|
||||||
|
})
|
||||||
|
|
||||||
|
test('summary', () => {
|
||||||
|
const state = produce(
|
||||||
|
createState(allocation, [1001, 1111, 1130]),
|
||||||
|
draft => {
|
||||||
|
draft.props = propsEffect(draft.props, {
|
||||||
|
charm: 1000,
|
||||||
|
intelligence: 1000,
|
||||||
|
strength: 1000,
|
||||||
|
money: 1000,
|
||||||
|
spirit: 1000,
|
||||||
|
age: 501,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const result = summary(state, profile)
|
||||||
|
const s = stateSummary(state)
|
||||||
|
expect(s).toBeGreaterThan(10000) // 总评大于10000
|
||||||
|
expect(result.state.achievements).toContain(245) // 传奇人物
|
||||||
|
expect(result.state.achievements).toContain(246) // 不可思议
|
||||||
|
expect(result.state.achievements).toContain(247) // 大能者
|
||||||
|
expect(result.state.achievements).toContain(248) // 无上存在
|
||||||
|
})
|
||||||
|
|
||||||
|
test('end', () => {
|
||||||
|
const p = { ...profile, times: 500, talents: new Set([1002, 1003]) }
|
||||||
|
const state = produce(
|
||||||
|
createState(allocation, [1001, 1111, 1130]),
|
||||||
|
draft => {
|
||||||
|
draft.props = propsEffect(draft.props, {
|
||||||
|
charm: 1000,
|
||||||
|
intelligence: 1000,
|
||||||
|
strength: 1000,
|
||||||
|
money: 1000,
|
||||||
|
spirit: 1000,
|
||||||
|
age: 501,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const result = end(state, p)
|
||||||
|
expect(result.profile.achievements).toContain(101) // 既视感
|
||||||
|
expect(result.profile.achievements).toContain(102) // 孟婆愁
|
||||||
|
expect(result.profile.achievements).toContain(103) // 所有人都是我
|
||||||
|
expect(result.profile.achievements).toContain(104) // Rewrite
|
||||||
|
expect(result.achievements).toContain(101)
|
||||||
|
expect(result.achievements).toContain(102)
|
||||||
|
expect(result.achievements).toContain(103)
|
||||||
|
expect(result.achievements).toContain(104)
|
||||||
|
expect(result.profile.talents).toContain(1001)
|
||||||
|
expect(result.profile.talents).toContain(1002)
|
||||||
|
expect(result.profile.talents).toContain(1003)
|
||||||
|
expect(result.profile.talents).toContain(1111)
|
||||||
|
expect(result.profile.talents).toContain(1130)
|
||||||
|
})
|
||||||
|
})
|
||||||
+116
-2
@@ -1,5 +1,119 @@
|
|||||||
import type { GameState, ProfileState } from '@/state'
|
import ages from '@remake/data/age'
|
||||||
import { enableMapSet } from 'immer'
|
import { propsEffect } from '@/state'
|
||||||
|
import { enableMapSet, produce } from 'immer'
|
||||||
enableMapSet()
|
enableMapSet()
|
||||||
|
|
||||||
|
export type * as achievement from '@/achievement'
|
||||||
|
export type * as event from '@/event'
|
||||||
|
export type * as talent from '@/talent'
|
||||||
|
export type * as state from '@/state'
|
||||||
|
import type { GameState, ProfileState } from '@/state'
|
||||||
export type { GameState, ProfileState }
|
export type { GameState, ProfileState }
|
||||||
|
export interface TriggerResult<T> {
|
||||||
|
state: GameState
|
||||||
|
triggers: T[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export { pull } from '@/talent'
|
||||||
|
// export { summary, nextProfile } from '@/state'
|
||||||
|
|
||||||
|
import { replacement, additionalPoints } from '@/talent'
|
||||||
|
import type { ReplacementResult, AdditionalPoints } from '@/talent'
|
||||||
|
|
||||||
|
export interface TalentsPickedResult {
|
||||||
|
talents: ReplacementResult
|
||||||
|
additionalPoints: AdditionalPoints
|
||||||
|
}
|
||||||
|
export function talentsPicked(talents: Iterable<Talent['id']>) {
|
||||||
|
const r = replacement(talents)
|
||||||
|
const ap = additionalPoints(r.talents)
|
||||||
|
return { talents: r, additionalPoints: ap }
|
||||||
|
}
|
||||||
|
|
||||||
|
import { createState } from '@/state'
|
||||||
|
|
||||||
|
export interface StartResult {
|
||||||
|
state: GameState
|
||||||
|
achievements: Achievement['id'][]
|
||||||
|
}
|
||||||
|
export function start(
|
||||||
|
profile: ProfileState,
|
||||||
|
...args: Parameters<typeof createState>
|
||||||
|
): StartResult {
|
||||||
|
const state = createState(...args)
|
||||||
|
const ar = achievementTrigger('START', state, profile)
|
||||||
|
return { state: ar.state, achievements: ar.triggers }
|
||||||
|
}
|
||||||
|
|
||||||
|
import type { Achievement } from '@remake/data/achievement'
|
||||||
|
import type { Event } from '@remake/data/event'
|
||||||
|
import type { Talent } from '@remake/data/talent'
|
||||||
|
import { trigger as achievementTrigger } from '@/achievement'
|
||||||
|
import { trigger as eventTrigger } from '@/event'
|
||||||
|
import { trigger as talentTrigger } from '@/talent'
|
||||||
|
import { check as eventCheck } from '@/event'
|
||||||
|
import { pickWeight } from '@remake/vitex'
|
||||||
|
|
||||||
|
export interface NextResult {
|
||||||
|
state: GameState
|
||||||
|
age: number
|
||||||
|
achievements: Achievement['id'][]
|
||||||
|
events: Event['id'][]
|
||||||
|
talents: Talent['id'][]
|
||||||
|
end: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function next(state: GameState, profile: ProfileState): NextResult {
|
||||||
|
let s = produce(state, draft => {
|
||||||
|
draft.props = propsEffect(state.props, { age: 1 })
|
||||||
|
})
|
||||||
|
const age = s.props.current.age
|
||||||
|
const tr = talentTrigger(s, profile)
|
||||||
|
const events = ages
|
||||||
|
.get(age)!
|
||||||
|
.event.filter(([e]) => eventCheck(e, tr.state, profile))
|
||||||
|
const event = pickWeight(events)!
|
||||||
|
const er = eventTrigger(event, tr.state, profile)
|
||||||
|
const ar = achievementTrigger('TRAJECTORY', er.state, profile)
|
||||||
|
const end = ar.state.life < 1
|
||||||
|
return {
|
||||||
|
state: ar.state,
|
||||||
|
age,
|
||||||
|
achievements: ar.triggers,
|
||||||
|
events: er.triggers,
|
||||||
|
talents: tr.triggers,
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import { summary as stateSummary } from '@/state'
|
||||||
|
|
||||||
|
export interface SummaryResult {
|
||||||
|
state: GameState
|
||||||
|
summary: number
|
||||||
|
achievements: Achievement['id'][]
|
||||||
|
}
|
||||||
|
export function summary(
|
||||||
|
state: GameState,
|
||||||
|
profile: ProfileState,
|
||||||
|
): SummaryResult {
|
||||||
|
const ar = achievementTrigger('SUMMARY', state, profile)
|
||||||
|
const s = stateSummary(ar.state)
|
||||||
|
return { state: ar.state, summary: s, achievements: ar.triggers }
|
||||||
|
}
|
||||||
|
|
||||||
|
import { nextProfile } from '@/state'
|
||||||
|
export interface EndResult {
|
||||||
|
profile: ProfileState
|
||||||
|
achievements: Achievement['id'][]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function end(
|
||||||
|
state: GameState,
|
||||||
|
profile: ProfileState,
|
||||||
|
lockedTalent?: Talent['id'],
|
||||||
|
) {
|
||||||
|
const ar = achievementTrigger('END', state, profile)
|
||||||
|
const p = nextProfile(profile, ar.state, lockedTalent)
|
||||||
|
return { profile: p, achievements: ar.triggers }
|
||||||
|
}
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ describe('State', () => {
|
|||||||
const k = key as keyof Properties
|
const k = key as keyof Properties
|
||||||
const c = props.current[k]
|
const c = props.current[k]
|
||||||
const be = c + effect[k]
|
const be = c + effect[k]
|
||||||
expect(c).toBe(be)
|
expect(effected.current[k]).toBe(be)
|
||||||
expect(effected.highest[k]).toBe(Math.max(c, be))
|
expect(effected.highest[k]).toBe(Math.max(c, be))
|
||||||
expect(effected.lowest[k]).toBe(Math.min(c, be))
|
expect(effected.lowest[k]).toBe(Math.min(c, be))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,13 @@ export function createState(
|
|||||||
talentTriggers: new Map(),
|
talentTriggers: new Map(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function summary({ props }: GameState) {
|
||||||
|
const { age, ...others } = props.highest
|
||||||
|
const s = sum(Object.values(others))
|
||||||
|
return Math.floor(s * 2 + age / 2)
|
||||||
|
}
|
||||||
|
|
||||||
export interface FlatState {
|
export interface FlatState {
|
||||||
AGE: GameState['props']['current']['age']
|
AGE: GameState['props']['current']['age']
|
||||||
CHR: GameState['props']['current']['charm']
|
CHR: GameState['props']['current']['charm']
|
||||||
@@ -131,11 +138,7 @@ const FlatMappers = {
|
|||||||
AEVT: state => state.profile.events,
|
AEVT: state => state.profile.events,
|
||||||
ATLT: state => state.profile.talents,
|
ATLT: state => state.profile.talents,
|
||||||
AACH: state => state.profile.achievements,
|
AACH: state => state.profile.achievements,
|
||||||
SUM: ({ game: { props } }) => {
|
SUM: ({ game }) => summary(game),
|
||||||
const { age, ...others } = props.highest
|
|
||||||
const s = sum(Object.values(others))
|
|
||||||
return Math.floor(s * 2 + age / 2)
|
|
||||||
},
|
|
||||||
} as { [Key in FlatStateKey]: FlatMapper<Key> }
|
} as { [Key in FlatStateKey]: FlatMapper<Key> }
|
||||||
|
|
||||||
export const SupportedFlatStateKeys = new Set(Object.keys(FlatMappers))
|
export const SupportedFlatStateKeys = new Set(Object.keys(FlatMappers))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test, describe } from 'bun:test'
|
import { expect, test, describe } from 'bun:test'
|
||||||
import type { ProfileState } from './state'
|
import type { ProfileState } from './state'
|
||||||
import type { TalentPullOptions } from './talent'
|
import type { PullOptions } from './talent'
|
||||||
import { pull, exclude, additionalPoints, replacement } from './talent'
|
import { pull, exclude, additionalPoints, replacement } from './talent'
|
||||||
import { createState } from './state'
|
import { createState } from './state'
|
||||||
import talents from '@remake/data/talent'
|
import talents from '@remake/data/talent'
|
||||||
@@ -27,7 +27,7 @@ describe('Talent', () => {
|
|||||||
]),
|
]),
|
||||||
additions: {},
|
additions: {},
|
||||||
},
|
},
|
||||||
} as TalentPullOptions
|
} as PullOptions
|
||||||
|
|
||||||
test('pull external', () => {
|
test('pull external', () => {
|
||||||
const p = produce(profile, draft => {
|
const p = produce(profile, draft => {
|
||||||
@@ -71,13 +71,10 @@ describe('Talent', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const allo = { charm: 5, intelligence: 5, strength: 5, money: 5 }
|
|
||||||
test('replacement talent [阴间福袋]', () => {
|
test('replacement talent [阴间福袋]', () => {
|
||||||
const talent = 1145
|
const talent = 1145
|
||||||
const state = createState(allo, [talent])
|
const result = replacement([talent])
|
||||||
const result = replacement(state)
|
expect(result.talents.size).toBeGreaterThan(1)
|
||||||
expect(result.changed).toBe(true)
|
|
||||||
expect(result.state.talents.size).toBeGreaterThan(1)
|
|
||||||
expect(result.chains.get(talent)).toBeDefined()
|
expect(result.chains.get(talent)).toBeDefined()
|
||||||
const source = talents.get(talent)!.replacement!.talent!
|
const source = talents.get(talent)!.replacement!.talent!
|
||||||
const replaced = result.chains.get(talent)!.at(0)!
|
const replaced = result.chains.get(talent)!.at(0)!
|
||||||
@@ -86,10 +83,8 @@ describe('Talent', () => {
|
|||||||
|
|
||||||
test('replacement grade [橙色转盘]', () => {
|
test('replacement grade [橙色转盘]', () => {
|
||||||
const talent = 1144
|
const talent = 1144
|
||||||
const state = createState(allo, [talent])
|
const result = replacement([talent])
|
||||||
const result = replacement(state)
|
expect(result.talents.size).toBeGreaterThan(1)
|
||||||
expect(result.changed).toBe(true)
|
|
||||||
expect(result.state.talents.size).toBeGreaterThan(1)
|
|
||||||
expect(result.chains.get(talent)).toBeDefined()
|
expect(result.chains.get(talent)).toBeDefined()
|
||||||
const source = talents.get(talent)!.replacement!.grade!
|
const source = talents.get(talent)!.replacement!.grade!
|
||||||
const replaced = result.chains.get(talent)!.at(0)!
|
const replaced = result.chains.get(talent)!.at(0)!
|
||||||
|
|||||||
+32
-41
@@ -6,6 +6,7 @@ import { propsEffect, createFlatState } from './state'
|
|||||||
import { check } from '@remake/condition'
|
import { check } from '@remake/condition'
|
||||||
import { pick, pickWeight } from '@remake/vitex'
|
import { pick, pickWeight } from '@remake/vitex'
|
||||||
import { produce } from 'immer'
|
import { produce } from 'immer'
|
||||||
|
import type { TriggerResult } from '@/game'
|
||||||
|
|
||||||
const Grades: TalentGrade[] = [0, 1, 2, 3] as const
|
const Grades: TalentGrade[] = [0, 1, 2, 3] as const
|
||||||
const GradeMap = Map.groupBy(
|
const GradeMap = Map.groupBy(
|
||||||
@@ -19,23 +20,23 @@ export function get(talent: number) {
|
|||||||
return talents.get(talent)
|
return talents.get(talent)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TalentPullRate = Map<TalentGrade, number>
|
export type PullRate = Map<TalentGrade, number>
|
||||||
export type TalentPullRateAddition = {
|
export type PullRateAddition = {
|
||||||
mode: 'add' | 'multiply'
|
mode: 'add' | 'multiply'
|
||||||
value: number
|
value: number
|
||||||
}
|
}
|
||||||
export type TalentPullRateAdditions = {
|
export type PullRateAdditions = {
|
||||||
[key in keyof ProfileState]?: (
|
[key in keyof ProfileState]?: (
|
||||||
value: ProfileState[key],
|
value: ProfileState[key],
|
||||||
) => Map<TalentGrade, TalentPullRateAddition>
|
) => Map<TalentGrade, PullRateAddition>
|
||||||
}
|
}
|
||||||
export interface TalentRateOptions {
|
export interface RateOptions {
|
||||||
base: TalentPullRate
|
base: PullRate
|
||||||
additions: TalentPullRateAdditions
|
additions: PullRateAdditions
|
||||||
}
|
}
|
||||||
const AddidtionInit = Grades.map(g => [g, 1]) as [TalentGrade, number][]
|
const AddidtionInit = Grades.map(g => [g, 1]) as [TalentGrade, number][]
|
||||||
function talentRateWithAddition(
|
function talentRateWithAddition(
|
||||||
{ base, additions }: TalentRateOptions,
|
{ base, additions }: RateOptions,
|
||||||
profile: ProfileState,
|
profile: ProfileState,
|
||||||
) {
|
) {
|
||||||
const rate = new Map(AddidtionInit)
|
const rate = new Map(AddidtionInit)
|
||||||
@@ -58,12 +59,12 @@ function talentRateWithAddition(
|
|||||||
return rate
|
return rate
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TalentPullOptions {
|
export interface PullOptions {
|
||||||
count: number
|
count: number
|
||||||
rate: TalentRateOptions
|
rate: RateOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pull(options: TalentPullOptions, profile: ProfileState) {
|
export function pull(options: PullOptions, profile: ProfileState) {
|
||||||
const rate = Array.from(
|
const rate = Array.from(
|
||||||
talentRateWithAddition(options.rate, profile).entries(),
|
talentRateWithAddition(options.rate, profile).entries(),
|
||||||
)
|
)
|
||||||
@@ -99,12 +100,6 @@ export function exclude(talent: Talent['id'], list: Iterable<Talent['id']>) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TalentReplacementResult {
|
|
||||||
state: GameState
|
|
||||||
chains: Map<Talent['id'], Talent['id'][]>
|
|
||||||
changed: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function chainReplace(t: Talent['id'], ts: Set<Talent['id']>) {
|
function chainReplace(t: Talent['id'], ts: Set<Talent['id']>) {
|
||||||
const { replacement: r } = talents.get(t)!
|
const { replacement: r } = talents.get(t)!
|
||||||
if (!r) return null
|
if (!r) return null
|
||||||
@@ -126,22 +121,21 @@ function chainReplace(t: Talent['id'], ts: Set<Talent['id']>) {
|
|||||||
return [picked]
|
return [picked]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replacement(state: GameState): TalentReplacementResult {
|
export interface ReplacementResult {
|
||||||
const chains = new Map() as TalentReplacementResult['chains']
|
talents: Set<Talent['id']>
|
||||||
const newTalents = new Set(state.talents)
|
chains: Map<Talent['id'], Talent['id'][]>
|
||||||
let changed = false
|
}
|
||||||
for (const talent of state.talents) {
|
|
||||||
const chain = chainReplace(talent, newTalents)
|
export function replacement(list: Iterable<Talent['id']>): ReplacementResult {
|
||||||
|
const set = new Set(list)
|
||||||
|
const chains = new Map() as ReplacementResult['chains']
|
||||||
|
for (const talent of list) {
|
||||||
|
const chain = chainReplace(talent, set)
|
||||||
if (!chain) continue
|
if (!chain) continue
|
||||||
changed = true
|
|
||||||
chains.set(talent, chain)
|
chains.set(talent, chain)
|
||||||
chain.forEach(t => newTalents.add(t))
|
chain.forEach(t => set.add(t))
|
||||||
}
|
}
|
||||||
if (!changed) return { state, chains, changed }
|
return { talents: set, chains }
|
||||||
const newState = produce(state, draft => {
|
|
||||||
draft.talents = newTalents
|
|
||||||
})
|
|
||||||
return { state: newState, chains, changed }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdditionalPoint {
|
export interface AdditionalPoint {
|
||||||
@@ -154,7 +148,9 @@ export interface AdditionalPoints {
|
|||||||
points: number
|
points: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function additionalPoints(list: Talent['id'][]) {
|
export function additionalPoints(
|
||||||
|
list: Iterable<Talent['id']>,
|
||||||
|
): AdditionalPoints {
|
||||||
const source: AdditionalPoint[] = []
|
const source: AdditionalPoint[] = []
|
||||||
let total = 0
|
let total = 0
|
||||||
for (const id of list) {
|
for (const id of list) {
|
||||||
@@ -166,23 +162,18 @@ export function additionalPoints(list: Talent['id'][]) {
|
|||||||
return { source, points: total }
|
return { source, points: total }
|
||||||
}
|
}
|
||||||
|
|
||||||
const TalentEffectRandomProperties = [
|
const EffectRandomProperties = [
|
||||||
'money',
|
'money',
|
||||||
'strength',
|
'strength',
|
||||||
'intelligence',
|
'intelligence',
|
||||||
'charm',
|
'charm',
|
||||||
'spirit',
|
'spirit',
|
||||||
] as (keyof Properties)[]
|
] as (keyof Properties)[]
|
||||||
export interface TalentTriggerResult {
|
|
||||||
state: GameState
|
|
||||||
triggers: Talent['id'][]
|
|
||||||
changed: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trigger(
|
export function trigger(
|
||||||
state: GameState,
|
state: GameState,
|
||||||
profile: ProfileState,
|
profile: ProfileState,
|
||||||
): TalentTriggerResult {
|
): TriggerResult<Talent['id']> {
|
||||||
const flatState = createFlatState(state, profile)
|
const flatState = createFlatState(state, profile)
|
||||||
const triggers: Talent['id'][] = []
|
const triggers: Talent['id'][] = []
|
||||||
for (const talent of state.talents) {
|
for (const talent of state.talents) {
|
||||||
@@ -191,7 +182,7 @@ export function trigger(
|
|||||||
if (condition && !check(flatState, condition)) continue
|
if (condition && !check(flatState, condition)) continue
|
||||||
triggers.push(talent)
|
triggers.push(talent)
|
||||||
}
|
}
|
||||||
if (triggers.length === 0) return { state, triggers, changed: false }
|
if (triggers.length === 0) return { state, triggers }
|
||||||
const newState = produce(state, draft => {
|
const newState = produce(state, draft => {
|
||||||
for (const talent of triggers) {
|
for (const talent of triggers) {
|
||||||
const times = draft.talentTriggers.get(talent) ?? 0
|
const times = draft.talentTriggers.get(talent) ?? 0
|
||||||
@@ -205,11 +196,11 @@ export function trigger(
|
|||||||
if (effect.MNY) pe.money = effect.MNY
|
if (effect.MNY) pe.money = effect.MNY
|
||||||
if (effect.SPR) pe.spirit = effect.SPR
|
if (effect.SPR) pe.spirit = effect.SPR
|
||||||
if (effect.RND) {
|
if (effect.RND) {
|
||||||
const key = pick(TalentEffectRandomProperties)!
|
const key = pick(EffectRandomProperties)!
|
||||||
pe[key] = (pe[key] ?? 0) || effect.RND
|
pe[key] = (pe[key] ?? 0) || effect.RND
|
||||||
}
|
}
|
||||||
draft.props = propsEffect(draft.props, pe)
|
draft.props = propsEffect(draft.props, pe)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return { state: newState, triggers, changed: true }
|
return { state: newState, triggers }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user