@linxin666/dsh-pet 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +17 -3
- package/README.zh.md +17 -3
- package/assets/ouo-neko/pet.json +31 -0
- package/assets/ouo-neko/previews/failed.gif +0 -0
- package/assets/ouo-neko/previews/idle.gif +0 -0
- package/assets/ouo-neko/previews/jumping.gif +0 -0
- package/assets/ouo-neko/previews/review.gif +0 -0
- package/assets/ouo-neko/previews/running-left.gif +0 -0
- package/assets/ouo-neko/previews/running-right.gif +0 -0
- package/assets/ouo-neko/previews/running.gif +0 -0
- package/assets/ouo-neko/previews/waiting.gif +0 -0
- package/assets/ouo-neko/previews/waving.gif +0 -0
- package/assets/ouo-neko/spritesheet.webp +0 -0
- package/contracts/pet-manifest-v2.schema.json +102 -1
- package/lib/client.js +759 -26
- package/lib/client.js.map +1 -1
- package/lib/index.js +1224 -22
- package/lib/types/client/PetDockEntry.d.ts +3 -0
- package/lib/types/client/PetDockEntry.d.ts.map +1 -1
- package/lib/types/client/PetDockEntry.js +18 -2
- package/lib/types/client/PetSprite.d.ts +23 -0
- package/lib/types/client/PetSprite.d.ts.map +1 -1
- package/lib/types/client/PetSprite.js +34 -29
- package/lib/types/client/PluginSettingsCard.d.ts +2 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +2 -2
- package/lib/types/client/drag-stream.d.ts +19 -0
- package/lib/types/client/drag-stream.d.ts.map +1 -0
- package/lib/types/client/drag-stream.js +26 -0
- package/lib/types/client/gameplay-hud.d.ts +50 -0
- package/lib/types/client/gameplay-hud.d.ts.map +1 -0
- package/lib/types/client/gameplay-hud.js +274 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +12 -0
- package/lib/types/client/locales.d.ts +34 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +36 -0
- package/lib/types/client/pet-store.d.ts +7 -1
- package/lib/types/client/pet-store.d.ts.map +1 -1
- package/lib/types/client/pet-store.js +4 -0
- package/lib/types/client/renderers/Frames2dVisualMount.d.ts +29 -0
- package/lib/types/client/renderers/Frames2dVisualMount.d.ts.map +1 -0
- package/lib/types/client/renderers/Frames2dVisualMount.js +83 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts +6 -0
- package/lib/types/client/renderers/PetRendererSwitch.d.ts.map +1 -1
- package/lib/types/client/renderers/PetRendererSwitch.js +14 -1
- package/lib/types/client/renderers/frames2d.d.ts +36 -0
- package/lib/types/client/renderers/frames2d.d.ts.map +1 -0
- package/lib/types/client/renderers/frames2d.js +196 -0
- package/lib/types/defaults.d.ts +6 -0
- package/lib/types/defaults.d.ts.map +1 -0
- package/lib/types/defaults.js +5 -0
- package/lib/types/dsh-home.d.ts.map +1 -1
- package/lib/types/dsh-home.js +9 -3
- package/lib/types/gameplay.d.ts +182 -0
- package/lib/types/gameplay.d.ts.map +1 -0
- package/lib/types/gameplay.js +618 -0
- package/lib/types/ledger.d.ts +14 -0
- package/lib/types/ledger.d.ts.map +1 -1
- package/lib/types/ledger.js +30 -0
- package/lib/types/manifest-v2.d.ts +38 -1
- package/lib/types/manifest-v2.d.ts.map +1 -1
- package/lib/types/manifest-v2.js +182 -2
- package/lib/types/persist.d.ts +4 -4
- package/lib/types/persist.d.ts.map +1 -1
- package/lib/types/persist.js +41 -4
- package/lib/types/registry.d.ts +29 -0
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/registry.js +170 -1
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +22 -0
- package/lib/types/service.d.ts +56 -0
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +181 -0
- package/package.json +5 -2
- package/src/client/PetDockEntry.test.tsx +6 -0
- package/src/client/PetDockEntry.tsx +32 -1
- package/src/client/PetSprite.test.tsx +36 -6
- package/src/client/PetSprite.tsx +42 -21
- package/src/client/PluginSettingsCard.tsx +4 -2
- package/src/client/drag-stream.ts +35 -0
- package/src/client/gameplay-hud.test.tsx +338 -0
- package/src/client/gameplay-hud.tsx +403 -0
- package/src/client/index.ts +17 -1
- package/src/client/locales.ts +36 -0
- package/src/client/pet-store.ts +10 -1
- package/src/client/pet.module.css +192 -29
- package/src/client/renderers/Frames2dVisualMount.tsx +111 -0
- package/src/client/renderers/PetRendererSwitch.tsx +28 -1
- package/src/client/renderers/frames2d.test.ts +137 -0
- package/src/client/renderers/frames2d.ts +216 -0
- package/src/client/settings-card.module.css +5 -0
- package/src/defaults.ts +7 -0
- package/src/dsh-home.ts +9 -3
- package/src/gameplay.test.ts +239 -0
- package/src/gameplay.ts +692 -0
- package/src/ledger.test.ts +16 -0
- package/src/ledger.ts +31 -0
- package/src/manifest-v2.test.ts +130 -1
- package/src/manifest-v2.ts +181 -2
- package/src/persist.test.ts +3 -0
- package/src/persist.ts +43 -6
- package/src/registry.test.ts +168 -1
- package/src/registry.ts +199 -1
- package/src/routes.ts +19 -0
- package/src/service.ts +208 -0
package/src/service.ts
CHANGED
|
@@ -56,6 +56,19 @@ import {
|
|
|
56
56
|
type PetStateInput,
|
|
57
57
|
type PetStateSnapshot,
|
|
58
58
|
} from './state.ts'
|
|
59
|
+
import {
|
|
60
|
+
applyGameplayEffects,
|
|
61
|
+
drawLotteryTier,
|
|
62
|
+
initialGameplayState,
|
|
63
|
+
rollTouchBranch,
|
|
64
|
+
rollWorkOutcome,
|
|
65
|
+
settleGameplay,
|
|
66
|
+
type PetGameplayManifest,
|
|
67
|
+
type PetGameplayState,
|
|
68
|
+
} from './gameplay.ts'
|
|
69
|
+
|
|
70
|
+
/** The unified gameplay currency: the shared treat (小鱼干) ledger. */
|
|
71
|
+
const GAMEPLAY_TREATS_CURRENCY = 'treats'
|
|
59
72
|
|
|
60
73
|
/** Plugin configuration. */
|
|
61
74
|
export interface PetConfig {
|
|
@@ -176,11 +189,40 @@ export interface PetStateView {
|
|
|
176
189
|
* browser half renders no ornament.
|
|
177
190
|
*/
|
|
178
191
|
decoration?: DecorationView
|
|
192
|
+
/**
|
|
193
|
+
* The selected pet's gameplay view (miku-pet generalization), present
|
|
194
|
+
* when the pet declares a gameplay block. Read-only projection: polling
|
|
195
|
+
* settles the view in memory but never writes pet.json.
|
|
196
|
+
*/
|
|
197
|
+
gameplay?: PetGameplayStateView
|
|
179
198
|
}
|
|
180
199
|
|
|
181
200
|
/** Result of `pet.interact`. */
|
|
182
201
|
export type PetInteractResult = LedgerInteractionResult
|
|
183
202
|
|
|
203
|
+
/** The gameplay slice of the state view (dynamic state only; defs ride the pet definition). */
|
|
204
|
+
export interface PetGameplayStateView {
|
|
205
|
+
/** Stat values rounded for display. */
|
|
206
|
+
stats: Record<string, number>
|
|
207
|
+
mode: 'work' | 'sleep' | null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Result of the gameplay verbs (touch / setMode / workTick / buy). */
|
|
211
|
+
export interface PetGameplayVerbResult {
|
|
212
|
+
ok: boolean
|
|
213
|
+
error?: string
|
|
214
|
+
/** Touch: whether a branch hit, plus its presentation. */
|
|
215
|
+
hit?: boolean
|
|
216
|
+
state?: string
|
|
217
|
+
stateMs?: number
|
|
218
|
+
phrase?: string
|
|
219
|
+
/** Work tick outcome. */
|
|
220
|
+
outcome?: 'success' | 'fail'
|
|
221
|
+
/** Shop purchase: the drawn prize, when the item was a lottery. */
|
|
222
|
+
prize?: { amount: number; currency: string }
|
|
223
|
+
view?: PetGameplayStateView
|
|
224
|
+
}
|
|
225
|
+
|
|
184
226
|
declare module '@deepseek-ai/cordis' {
|
|
185
227
|
interface Context {
|
|
186
228
|
pet: PetService
|
|
@@ -483,6 +525,162 @@ export class PetService extends Service {
|
|
|
483
525
|
return result
|
|
484
526
|
}
|
|
485
527
|
|
|
528
|
+
/* ---------------------------------------------------------------- *
|
|
529
|
+
* Gameplay verbs (miku-pet generalization). The manifest block lives
|
|
530
|
+
* on the registry entry; dynamic state persists per pet id. Verbs
|
|
531
|
+
* settle and persist; the state view projects without writing.
|
|
532
|
+
* ---------------------------------------------------------------- */
|
|
533
|
+
|
|
534
|
+
/** The active pet's gameplay block, if it declares one. */
|
|
535
|
+
private gameplayDef(): PetGameplayManifest | undefined {
|
|
536
|
+
return this.activeEntry().gameplay
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** The persisted (or fresh) gameplay state of the selected pet. */
|
|
540
|
+
private gameplayState(def: PetGameplayManifest, now: number): { petId: string; state: PetGameplayState } {
|
|
541
|
+
const petId = this.selectedPetId()
|
|
542
|
+
const stored = this.ledger.snapshot.gameplay[petId]
|
|
543
|
+
return {
|
|
544
|
+
petId,
|
|
545
|
+
state: stored === undefined
|
|
546
|
+
? initialGameplayState(def, now)
|
|
547
|
+
: { stats: { ...stored.stats }, currencies: { ...stored.currencies }, mode: stored.mode, settledAt: stored.settledAt },
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** Display view of one gameplay state (rounded stats; treats ride the shared treat ledger). */
|
|
552
|
+
private gameplayViewOf(state: PetGameplayState): PetGameplayStateView {
|
|
553
|
+
const stats: Record<string, number> = {}
|
|
554
|
+
for (const [name, value] of Object.entries(state.stats)) stats[name] = Math.round(value)
|
|
555
|
+
return { stats, mode: state.mode }
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Move gameplay 'treats' currency (the unified post-wallet currency) from
|
|
560
|
+
* the engine's settle work area into the shared treat ledger, capped by
|
|
561
|
+
* the stock cap. The engine keeps its generic currency record for settle
|
|
562
|
+
* math; this drain is the only bridge to the wallet-free economy.
|
|
563
|
+
*/
|
|
564
|
+
private drainGameplayTreats(state: PetGameplayState): void {
|
|
565
|
+
const pending = Math.floor(state.currencies[GAMEPLAY_TREATS_CURRENCY] ?? 0)
|
|
566
|
+
delete state.currencies[GAMEPLAY_TREATS_CURRENCY]
|
|
567
|
+
if (pending > 0) this.ledger.grantTreats(pending)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** Persist the mutated gameplay state of one verb call. */
|
|
571
|
+
private commitGameplay(petId: string, state: PetGameplayState): void {
|
|
572
|
+
this.ledger.setGameplay(petId, state)
|
|
573
|
+
if (this.ledger.takeDirty()) this.flush()
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* RPC: a touch on the pet. 'zone' names a touch zone (roll a branch);
|
|
578
|
+
* omitted means a plain click while a touch animation holds (clickBoost).
|
|
579
|
+
*/
|
|
580
|
+
async gameplayTouch(zone?: string): Promise<PetGameplayVerbResult> {
|
|
581
|
+
const def = this.gameplayDef()
|
|
582
|
+
if (def === undefined) return { ok: false, error: 'no-gameplay' }
|
|
583
|
+
const now = Date.now()
|
|
584
|
+
const { petId, state } = this.gameplayState(def, now)
|
|
585
|
+
settleGameplay(state, def, now, { sessionActive: this.machine.render().sessionActive })
|
|
586
|
+
if (zone === undefined) {
|
|
587
|
+
const boost = def.touch?.clickBoost
|
|
588
|
+
if (boost === undefined) return { ok: false, error: 'no-touch' }
|
|
589
|
+
const amount = boost.min + Math.floor(Math.random() * (boost.max - boost.min + 1))
|
|
590
|
+
if (amount > 0) applyGameplayEffects(state, def, [{ stat: boost.stat, amount }])
|
|
591
|
+
this.drainGameplayTreats(state)
|
|
592
|
+
this.commitGameplay(petId, state)
|
|
593
|
+
return { ok: true, hit: false, view: this.gameplayViewOf(state) }
|
|
594
|
+
}
|
|
595
|
+
const target = def.touch?.zones.find(entry => entry.name === zone)
|
|
596
|
+
if (target === undefined) return { ok: false, error: 'unknown-zone' }
|
|
597
|
+
const branch = rollTouchBranch(target, Math.random)
|
|
598
|
+
if (branch === undefined) {
|
|
599
|
+
this.drainGameplayTreats(state)
|
|
600
|
+
this.commitGameplay(petId, state)
|
|
601
|
+
return { ok: true, hit: false, view: this.gameplayViewOf(state) }
|
|
602
|
+
}
|
|
603
|
+
if (branch.effects !== undefined) applyGameplayEffects(state, def, branch.effects)
|
|
604
|
+
const phrase = branch.phrases !== undefined && branch.phrases.length > 0
|
|
605
|
+
? branch.phrases[Math.floor(Math.random() * branch.phrases.length)]
|
|
606
|
+
: undefined
|
|
607
|
+
this.drainGameplayTreats(state)
|
|
608
|
+
this.commitGameplay(petId, state)
|
|
609
|
+
return {
|
|
610
|
+
ok: true,
|
|
611
|
+
hit: true,
|
|
612
|
+
...(branch.state === undefined ? {} : { state: branch.state }),
|
|
613
|
+
...(branch.stateMs === undefined ? {} : { stateMs: branch.stateMs }),
|
|
614
|
+
...(phrase === undefined ? {} : { phrase }),
|
|
615
|
+
view: this.gameplayViewOf(state),
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** RPC: enter or leave a gameplay mode ('work' | 'sleep' | null). */
|
|
620
|
+
async gameplaySetMode(mode: 'work' | 'sleep' | null): Promise<PetGameplayVerbResult> {
|
|
621
|
+
const def = this.gameplayDef()
|
|
622
|
+
if (def === undefined) return { ok: false, error: 'no-gameplay' }
|
|
623
|
+
if (mode === 'work' && def.work === undefined) return { ok: false, error: 'no-work' }
|
|
624
|
+
if (mode === 'sleep' && def.sleep === undefined) return { ok: false, error: 'no-sleep' }
|
|
625
|
+
const now = Date.now()
|
|
626
|
+
const { petId, state } = this.gameplayState(def, now)
|
|
627
|
+
settleGameplay(state, def, now, { sessionActive: this.machine.render().sessionActive })
|
|
628
|
+
state.mode = mode
|
|
629
|
+
this.drainGameplayTreats(state)
|
|
630
|
+
this.commitGameplay(petId, state)
|
|
631
|
+
return { ok: true, view: this.gameplayViewOf(state) }
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** RPC: one work-round adjudication (only while the work mode holds). */
|
|
635
|
+
async gameplayWorkTick(): Promise<PetGameplayVerbResult> {
|
|
636
|
+
const def = this.gameplayDef()
|
|
637
|
+
if (def?.work === undefined) return { ok: false, error: 'no-work' }
|
|
638
|
+
const now = Date.now()
|
|
639
|
+
const { petId, state } = this.gameplayState(def, now)
|
|
640
|
+
if (state.mode !== 'work') return { ok: false, error: 'not-working' }
|
|
641
|
+
settleGameplay(state, def, now, { sessionActive: this.machine.render().sessionActive })
|
|
642
|
+
const outcome = rollWorkOutcome(def.work, Math.random)
|
|
643
|
+
const effects = outcome === 'success' ? def.work.success?.effects : def.work.fail?.effects
|
|
644
|
+
if (effects !== undefined) applyGameplayEffects(state, def, effects)
|
|
645
|
+
this.drainGameplayTreats(state)
|
|
646
|
+
this.commitGameplay(petId, state)
|
|
647
|
+
return { ok: true, outcome, view: this.gameplayViewOf(state) }
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** RPC: buy one shop item (effects, currency swap, or a lottery draw). */
|
|
651
|
+
async gameplayBuy(itemId: string): Promise<PetGameplayVerbResult> {
|
|
652
|
+
const def = this.gameplayDef()
|
|
653
|
+
if (def === undefined) return { ok: false, error: 'no-gameplay' }
|
|
654
|
+
const item = def.shop?.items.find(entry => entry.id === itemId)
|
|
655
|
+
if (item === undefined) return { ok: false, error: 'unknown-item' }
|
|
656
|
+
const now = Date.now()
|
|
657
|
+
const { petId, state } = this.gameplayState(def, now)
|
|
658
|
+
settleGameplay(state, def, now, { sessionActive: this.machine.render().sessionActive })
|
|
659
|
+
// The unified currency is the shared treat ledger (wallet removed): shop
|
|
660
|
+
// prices and prizes ride the same balance the panel shows and feeding
|
|
661
|
+
// consumes, capped by the stock cap.
|
|
662
|
+
const treats = item.currency === GAMEPLAY_TREATS_CURRENCY
|
|
663
|
+
const balance = treats ? this.ledger.snapshot.treats.treats : (state.currencies[item.currency] ?? 0)
|
|
664
|
+
if (balance < item.price) {
|
|
665
|
+
return { ok: false, error: 'insufficient-funds', view: this.gameplayViewOf(state) }
|
|
666
|
+
}
|
|
667
|
+
if (treats) this.ledger.spendTreats(item.price)
|
|
668
|
+
else state.currencies[item.currency] = balance - item.price
|
|
669
|
+
if (item.effects !== undefined) applyGameplayEffects(state, def, item.effects)
|
|
670
|
+
let prize: PetGameplayVerbResult['prize']
|
|
671
|
+
if (item.lottery !== undefined) {
|
|
672
|
+
if (item.lottery.effects !== undefined) applyGameplayEffects(state, def, item.lottery.effects)
|
|
673
|
+
const tier = drawLotteryTier(item.lottery, Math.random)
|
|
674
|
+
const prizeCurrency = tier.currency ?? item.lottery.currency ?? item.currency
|
|
675
|
+
if (prizeCurrency === GAMEPLAY_TREATS_CURRENCY) this.ledger.grantTreats(tier.prize)
|
|
676
|
+
else state.currencies[prizeCurrency] = (state.currencies[prizeCurrency] ?? 0) + tier.prize
|
|
677
|
+
prize = { amount: tier.prize, currency: prizeCurrency }
|
|
678
|
+
}
|
|
679
|
+
this.drainGameplayTreats(state)
|
|
680
|
+
this.commitGameplay(petId, state)
|
|
681
|
+
return { ok: true, ...(prize === undefined ? {} : { prize }), view: this.gameplayViewOf(state) }
|
|
682
|
+
}
|
|
683
|
+
|
|
486
684
|
/** RPC: show or hide the pet. */
|
|
487
685
|
async setVisible(visible: boolean): Promise<{ ok: true; display: PetDisplayConfig }> {
|
|
488
686
|
this.ledger.setDisplay({ ...this.ledger.snapshot.display, visible })
|
|
@@ -597,6 +795,15 @@ export class PetService extends Service {
|
|
|
597
795
|
? whisper.text
|
|
598
796
|
: undefined
|
|
599
797
|
const decoration = this.activeDecoration()
|
|
798
|
+
// Gameplay view: a read-only projection. The settle runs on a copy, so
|
|
799
|
+
// polling never writes pet.json (verbs settle and persist).
|
|
800
|
+
const gameplayDef = this.gameplayDef()
|
|
801
|
+
let gameplay: PetGameplayStateView | undefined
|
|
802
|
+
if (gameplayDef !== undefined) {
|
|
803
|
+
const { state } = this.gameplayState(gameplayDef, Date.now())
|
|
804
|
+
settleGameplay(state, gameplayDef, Date.now(), { sessionActive: snapshot.sessionActive })
|
|
805
|
+
gameplay = this.gameplayViewOf(state)
|
|
806
|
+
}
|
|
600
807
|
// Read-only: the ledger settles on economic events only, never on a read,
|
|
601
808
|
// so polling the state cannot trigger pet.json writes.
|
|
602
809
|
return {
|
|
@@ -619,6 +826,7 @@ export class PetService extends Service {
|
|
|
619
826
|
stocked: this.ledger.snapshot.treats.treats,
|
|
620
827
|
max: this.ledger.treatMax,
|
|
621
828
|
},
|
|
829
|
+
...(gameplay === undefined ? {} : { gameplay }),
|
|
622
830
|
}
|
|
623
831
|
}
|
|
624
832
|
|