dsh-piggy 0.25.0
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/CHANGELOG.md +1348 -0
- package/LICENSE +32 -0
- package/README.md +322 -0
- package/THIRD-PARTY.md +31 -0
- package/assets/elder.svg +35 -0
- package/assets/pig-king-bathe.svg +38 -0
- package/assets/pig-king-eat.svg +45 -0
- package/assets/pig-king-pet.svg +45 -0
- package/assets/pig-king-play.svg +45 -0
- package/assets/pig-king-relaxed.svg +56 -0
- package/assets/pig-king-study.svg +45 -0
- package/assets/pig-king-trip.svg +45 -0
- package/assets/pig-king-work.svg +45 -0
- package/assets/pig-king.svg +56 -0
- package/assets/piglet.svg +30 -0
- package/client.js +3438 -0
- package/commands.js +252 -0
- package/cordis.patch.yml +15 -0
- package/core.js +11 -0
- package/data.js +10 -0
- package/index.js +68 -0
- package/package.json +72 -0
- package/packages/pet-core/package.json +21 -0
- package/packages/pet-core/src/core/activity.js +80 -0
- package/packages/pet-core/src/core/care.js +160 -0
- package/packages/pet-core/src/core/clock.js +122 -0
- package/packages/pet-core/src/core/constants.js +109 -0
- package/packages/pet-core/src/core/daily.js +204 -0
- package/packages/pet-core/src/core/diary.js +108 -0
- package/packages/pet-core/src/core/drops.js +82 -0
- package/packages/pet-core/src/core/effects.js +84 -0
- package/packages/pet-core/src/core/egg.js +120 -0
- package/packages/pet-core/src/core/evolution.js +90 -0
- package/packages/pet-core/src/core/growth.js +110 -0
- package/packages/pet-core/src/core/illness.js +235 -0
- package/packages/pet-core/src/core/interests.js +55 -0
- package/packages/pet-core/src/core/inventory.js +177 -0
- package/packages/pet-core/src/core/lines.js +157 -0
- package/packages/pet-core/src/core/migrate.js +226 -0
- package/packages/pet-core/src/core/profile.js +102 -0
- package/packages/pet-core/src/core/random.js +79 -0
- package/packages/pet-core/src/core/school.js +81 -0
- package/packages/pet-core/src/core/settlement.js +308 -0
- package/packages/pet-core/src/core/state.js +210 -0
- package/packages/pet-core/src/core/travel.js +58 -0
- package/packages/pet-core/src/core/upgrades.js +193 -0
- package/packages/pet-core/src/core/views.js +55 -0
- package/packages/pet-core/src/core/work.js +50 -0
- package/packages/pet-core/src/core.js +95 -0
- package/packages/pet-core/src/data/daily.js +121 -0
- package/packages/pet-core/src/data/drops.js +34 -0
- package/packages/pet-core/src/data/evolution.js +41 -0
- package/packages/pet-core/src/data/growth.js +72 -0
- package/packages/pet-core/src/data/illness.js +188 -0
- package/packages/pet-core/src/data/interests.js +39 -0
- package/packages/pet-core/src/data/jobs.js +150 -0
- package/packages/pet-core/src/data/life.js +88 -0
- package/packages/pet-core/src/data/lines.js +198 -0
- package/packages/pet-core/src/data/minutes.js +23 -0
- package/packages/pet-core/src/data/profile.js +60 -0
- package/packages/pet-core/src/data/school.js +99 -0
- package/packages/pet-core/src/data/shop.js +142 -0
- package/packages/pet-core/src/data/traits.js +42 -0
- package/packages/pet-core/src/data/travel.js +87 -0
- package/packages/pet-core/src/data.js +52 -0
- package/packages/pet-core/src/index.js +12 -0
- package/render.js +357 -0
- package/routes.js +201 -0
- package/scripts/build-client.mjs +100 -0
- package/scripts/typecheck.mjs +122 -0
- package/snapshot.js +368 -0
- package/src/client/art.js +29 -0
- package/src/client/constants.js +88 -0
- package/src/client/css-base.js +225 -0
- package/src/client/css-card.js +63 -0
- package/src/client/css-tabs.js +250 -0
- package/src/client/css-tiles.js +89 -0
- package/src/client/dom.js +33 -0
- package/src/client/effects.js +159 -0
- package/src/client/format.js +27 -0
- package/src/client/index.js +393 -0
- package/src/client/io.js +100 -0
- package/src/client/layout.js +103 -0
- package/src/client/normalize.js +352 -0
- package/src/client/panel.js +319 -0
- package/src/client/scene.js +113 -0
- package/src/client/storage.js +19 -0
- package/src/client/styles.js +13 -0
- package/src/client/tabs/bag.js +203 -0
- package/src/client/tabs/card.js +110 -0
- package/src/client/tabs/crown.js +59 -0
- package/src/client/tabs/dev.js +101 -0
- package/src/client/tabs/home.js +72 -0
- package/src/client/tabs/shop.js +78 -0
- package/src/client/tabs/status.js +223 -0
- package/src/client/tabs/study.js +128 -0
- package/src/client/tabs/travel.js +89 -0
- package/src/client/tabs/update.js +164 -0
- package/src/client/tabs/work.js +106 -0
- package/src/client/values.js +11 -0
- package/src/client/widgets.js +126 -0
- package/store/api.js +260 -0
- package/store/state-file.js +129 -0
- package/store.js +96 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* 面板渲染:开关面板、切页签、把快照画到 DOM 上
|
|
4
|
+
*
|
|
5
|
+
* 只通过 ctx 读写外壳的状态与元素(getter/setter 转发),不直接碰全局。
|
|
6
|
+
* @module dsh-piggy/client/panel
|
|
7
|
+
*/
|
|
8
|
+
import { syncPigArt } from './art.js'
|
|
9
|
+
import { DEV_TAB, OPEN_KEY, TABS, UPDATE_TAB } from './constants.js'
|
|
10
|
+
import { button, el } from './dom.js'
|
|
11
|
+
import { normalize } from './normalize.js'
|
|
12
|
+
import { writeStore } from './storage.js'
|
|
13
|
+
import { CSS } from './styles.js'
|
|
14
|
+
import { renderBagTab } from './tabs/bag.js'
|
|
15
|
+
import { renderCardTab } from './tabs/card.js'
|
|
16
|
+
import { renderCrownTab } from './tabs/crown.js'
|
|
17
|
+
import { appHeader, renderHome } from './tabs/home.js'
|
|
18
|
+
import { renderDevTab } from './tabs/dev.js'
|
|
19
|
+
import { renderUpdateTab, updatesBridge } from './tabs/update.js'
|
|
20
|
+
import { renderShopTab } from './tabs/shop.js'
|
|
21
|
+
import { renderStatusTab } from './tabs/status.js'
|
|
22
|
+
import { renderStudyTab } from './tabs/study.js'
|
|
23
|
+
import { renderTravelTab } from './tabs/travel.js'
|
|
24
|
+
import { renderWorkTab } from './tabs/work.js'
|
|
25
|
+
import { str } from './values.js'
|
|
26
|
+
|
|
27
|
+
/** News that gets through 免打扰. */
|
|
28
|
+
var URGENT_KINDS = ['sick', 'worse', 'death', 'cured', 'revived']
|
|
29
|
+
|
|
30
|
+
export function createPanel(ctx) {
|
|
31
|
+
var AWAY_LINE = {
|
|
32
|
+
work: '在忙',
|
|
33
|
+
study: '在念书',
|
|
34
|
+
trip: '在路上',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setOpen(next) {
|
|
38
|
+
ctx.isOpen = next
|
|
39
|
+
ctx.host.setAttribute('data-open', next ? 'true' : 'false')
|
|
40
|
+
// Collapsed must be the pig and *nothing else*. One switch hides the
|
|
41
|
+
// whole panel now that the pig is not inside it — and driving visibility
|
|
42
|
+
// from the DOM rather than only from CSS makes it something a test can
|
|
43
|
+
// actually assert.
|
|
44
|
+
ctx.card.hidden = !next
|
|
45
|
+
// The hud rides with the panel: a bare pig in the corner should not have
|
|
46
|
+
// a name and a coin count floating beside it.
|
|
47
|
+
ctx.hud.hidden = !next
|
|
48
|
+
if (!next) ctx.bubble.hidden = true
|
|
49
|
+
writeStore(OPEN_KEY, next ? 'true' : 'false')
|
|
50
|
+
if (next) {
|
|
51
|
+
renderContent()
|
|
52
|
+
ctx.fitPanel()
|
|
53
|
+
} else {
|
|
54
|
+
// Back to the default anchor so the next open starts from a clean
|
|
55
|
+
// slate. `auto` (not '') keeps the stylesheet's bottom from re-applying
|
|
56
|
+
// alongside a stale top.
|
|
57
|
+
ctx.card.style.right = ''
|
|
58
|
+
ctx.card.style.top = 'auto'
|
|
59
|
+
ctx.card.style.bottom = ''
|
|
60
|
+
ctx.card.style.maxHeight = ''
|
|
61
|
+
ctx.card.style.maxWidth = ''
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function select(next) {
|
|
66
|
+
ctx.tab = next
|
|
67
|
+
ctx.picker = null
|
|
68
|
+
// Opening a tile tab always starts at its top layer.
|
|
69
|
+
if (next in ctx.drill) { ctx.drill[next] = null; ctx.drill.pick = null }
|
|
70
|
+
renderContent()
|
|
71
|
+
for (var k in ctx.icons) ctx.icons[k].setAttribute('data-active', k === ctx.tab ? 'true' : 'false')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Repaint the panel body, keeping the reader's place.
|
|
76
|
+
*
|
|
77
|
+
* The body is rebuilt from scratch on every poll; without saving the
|
|
78
|
+
* offset, a long shelf jumped back to the top every four seconds.
|
|
79
|
+
*/
|
|
80
|
+
function renderContent() {
|
|
81
|
+
var scrollTop = ctx.content.scrollTop
|
|
82
|
+
paintContent()
|
|
83
|
+
ctx.content.scrollTop = scrollTop
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function paintContent() {
|
|
87
|
+
ctx.content.textContent = ''
|
|
88
|
+
for (var k = 0; k < TABS.length; k += 1) {
|
|
89
|
+
ctx.icons[TABS[k].key].setAttribute('data-active', TABS[k].key === ctx.tab ? 'true' : 'false')
|
|
90
|
+
}
|
|
91
|
+
if (ctx.host.getAttribute('data-open') !== 'true') return
|
|
92
|
+
|
|
93
|
+
// Only the host-version warning sits above every tab; the pig's own
|
|
94
|
+
// banners (away / sick / gone) live on the status tab (B9).
|
|
95
|
+
if (ctx.view.legacy) {
|
|
96
|
+
var legacy = el('div', 'dp-alert dp-legacy')
|
|
97
|
+
legacy.appendChild(el('b', null, '⚠️ 宿主是旧版本'))
|
|
98
|
+
legacy.appendChild(el('div', null, '金币、健康、打工、商店这些是新增的,重启 dsh(不是刷新页面)之后才会出现。'))
|
|
99
|
+
ctx.content.appendChild(legacy)
|
|
100
|
+
}
|
|
101
|
+
if (ctx.view.pig === null) {
|
|
102
|
+
ctx.content.appendChild(el('div', 'dp-empty', '门口放着一个纸盒,里面窸窸窣窣 📦'))
|
|
103
|
+
var grid = el('div', 'dp-actions')
|
|
104
|
+
var hatch = button('dp-btn dp-btn-wide', { 'data-action': 'hatch' }, function () { ctx.send('hatch') })
|
|
105
|
+
hatch.appendChild(el('span', null, '🥚'))
|
|
106
|
+
hatch.appendChild(el('span', null, '拆开纸盒'))
|
|
107
|
+
grid.appendChild(hatch)
|
|
108
|
+
ctx.content.appendChild(grid)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// B9: the home screen first; every app gets a 「‹」 back to it on its
|
|
113
|
+
// top layer (inside a category the app's own 「‹」 goes up a layer).
|
|
114
|
+
var apps = TABS.concat(updatesBridge() !== null ? [UPDATE_TAB] : [], ctx.devMode ? [DEV_TAB] : [])
|
|
115
|
+
if (ctx.tab === 'home') {
|
|
116
|
+
renderHome(ctx, apps)
|
|
117
|
+
ctx.fitPanel()
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
var drilled = ctx.tab in ctx.drill && ctx.drill[ctx.tab] !== null
|
|
121
|
+
var app = apps.find(function (entry) { return entry.key === ctx.tab })
|
|
122
|
+
if (app !== undefined && !drilled) appHeader(ctx, app, ctx.tab === 'shop' ? '🪙 ' + ctx.view.pig.coins : '')
|
|
123
|
+
|
|
124
|
+
if (ctx.tab === 'status') renderStatusTab(ctx)
|
|
125
|
+
else if (ctx.tab === 'card') renderCardTab(ctx)
|
|
126
|
+
else if (ctx.tab === 'crown') renderCrownTab(ctx)
|
|
127
|
+
else if (ctx.tab === 'study') renderStudyTab(ctx)
|
|
128
|
+
else if (ctx.tab === 'work') renderWorkTab(ctx)
|
|
129
|
+
else if (ctx.tab === 'shop') renderShopTab(ctx)
|
|
130
|
+
else if (ctx.tab === 'travel') renderTravelTab(ctx)
|
|
131
|
+
else if (ctx.tab === 'dev') renderDevTab(ctx)
|
|
132
|
+
else if (ctx.tab === 'update') renderUpdateTab(ctx)
|
|
133
|
+
else renderBagTab(ctx)
|
|
134
|
+
|
|
135
|
+
// Every tab is a different height, so the fit is recomputed after each
|
|
136
|
+
// render rather than only on open.
|
|
137
|
+
ctx.fitPanel()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function render(next) {
|
|
141
|
+
ctx.view = normalize(next)
|
|
142
|
+
// Never leave the study tab parked on a stage the pig cannot attend —
|
|
143
|
+
// but only until the user picks one themselves: after that the poll
|
|
144
|
+
// must not yank their choice away.
|
|
145
|
+
var stageEntry = null
|
|
146
|
+
var firstOpen = null
|
|
147
|
+
for (var s = 0; s < ctx.view.stages.length; s += 1) {
|
|
148
|
+
var entry = ctx.view.stages[s]
|
|
149
|
+
if (entry.unlocked !== false && firstOpen === null) firstOpen = entry.key
|
|
150
|
+
if (entry.key === ctx.stage) stageEntry = entry
|
|
151
|
+
}
|
|
152
|
+
// The 兴趣 button is not a stage; leave it selected.
|
|
153
|
+
if (!ctx.stagePicked && firstOpen !== null && (stageEntry === null || stageEntry.unlocked === false)) ctx.stage = firstOpen
|
|
154
|
+
ctx.host.setAttribute('data-dead', ctx.view.dead ? 'true' : 'false')
|
|
155
|
+
ctx.host.setAttribute('data-open', ctx.isOpen ? 'true' : 'false')
|
|
156
|
+
ctx.host.setAttribute('data-dev', ctx.devMode ? 'true' : 'false')
|
|
157
|
+
// Drives both the prop and the pig's own activity animation.
|
|
158
|
+
ctx.host.setAttribute('data-away', ctx.view.activity === null ? 'false' : ctx.view.activity.kind)
|
|
159
|
+
if (ctx.view.activity === null) {
|
|
160
|
+
ctx.work.hidden = true
|
|
161
|
+
} else {
|
|
162
|
+
ctx.work.hidden = false
|
|
163
|
+
ctx.prop.textContent = ctx.view.activity.emoji
|
|
164
|
+
ctx.progressFill.style.width = ctx.view.activity.progress + '%'
|
|
165
|
+
ctx.work.setAttribute('data-kind', ctx.view.activity.kind)
|
|
166
|
+
ctx.work.title = (AWAY_LINE[ctx.view.activity.kind] ?? '在外面') + ':' + ctx.view.activity.label
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (ctx.view.hatched !== true) {
|
|
170
|
+
ctx.pigArt.hidden = true
|
|
171
|
+
ctx.pigArt.removeAttribute('src')
|
|
172
|
+
ctx.pigEmoji.hidden = false
|
|
173
|
+
ctx.pigEmoji.textContent = ctx.view.boxStage.emoji
|
|
174
|
+
ctx.pig.removeAttribute('data-art')
|
|
175
|
+
ctx.pig.setAttribute('data-mood', 'box')
|
|
176
|
+
// Size comes from the host so the box and the pig can never drift.
|
|
177
|
+
ctx.host.style.setProperty('--pig-size', ctx.view.boxStage.size + 'px')
|
|
178
|
+
ctx.soul.hidden = true
|
|
179
|
+
ctx.host.setAttribute('data-soul', 'false')
|
|
180
|
+
ctx.host.setAttribute('data-faded', 'false')
|
|
181
|
+
ctx.host.setAttribute('data-unhatched', 'true')
|
|
182
|
+
ctx.pokeHint.hidden = false
|
|
183
|
+
ctx.hudName.textContent = '一个' + ctx.view.boxStage.label
|
|
184
|
+
ctx.hudCoins.textContent = '点开拆开它'
|
|
185
|
+
ctx.hudHealth.textContent = ''
|
|
186
|
+
ctx.lastStage = null
|
|
187
|
+
} else {
|
|
188
|
+
const pigStage = ctx.view.pig.stage
|
|
189
|
+
// A drawn stage shows its sprite; everything else is the emoji.
|
|
190
|
+
if (pigStage.art !== null) {
|
|
191
|
+
ctx.pigArt.hidden = false
|
|
192
|
+
ctx.pigEmoji.hidden = true
|
|
193
|
+
ctx.pig.setAttribute('data-art', pigStage.art)
|
|
194
|
+
ctx.pig.setAttribute('data-art-actions', pigStage.actionArt ? 'true' : 'false')
|
|
195
|
+
ctx.host.setAttribute('data-art-actions', pigStage.actionArt ? 'true' : 'false')
|
|
196
|
+
ctx.pig.setAttribute('data-activity', ctx.view.activity === null ? '' : ctx.view.activity.kind)
|
|
197
|
+
syncPigArt(ctx.pig, ctx.pigArt)
|
|
198
|
+
} else {
|
|
199
|
+
ctx.pigArt.hidden = true
|
|
200
|
+
ctx.pigArt.removeAttribute('src')
|
|
201
|
+
ctx.pigEmoji.hidden = false
|
|
202
|
+
ctx.pigEmoji.textContent = pigStage.emoji
|
|
203
|
+
ctx.pig.removeAttribute('data-art')
|
|
204
|
+
ctx.pig.removeAttribute('data-art-actions')
|
|
205
|
+
ctx.host.removeAttribute('data-art-actions')
|
|
206
|
+
}
|
|
207
|
+
// Literally grows up: the stage carries its own size.
|
|
208
|
+
ctx.host.style.setProperty('--pig-size', pigStage.size + 'px')
|
|
209
|
+
ctx.pig.setAttribute('data-mood', ctx.view.pig.mood)
|
|
210
|
+
ctx.host.setAttribute('data-soul', ctx.view.pig.soul ? 'true' : 'false')
|
|
211
|
+
// Old age reads as a faded coat, since every stage is the same pig.
|
|
212
|
+
ctx.host.setAttribute('data-faded', pigStage.faded ? 'true' : 'false')
|
|
213
|
+
ctx.host.setAttribute('data-unhatched', 'false')
|
|
214
|
+
ctx.pokeHint.hidden = true
|
|
215
|
+
ctx.soul.hidden = ctx.view.pig.soul !== true
|
|
216
|
+
ctx.pig.setAttribute('data-stage', pigStage.key)
|
|
217
|
+
// 装扮挂在猪身上(见 .dp-slot),名字牌上不再重复一遍。
|
|
218
|
+
ctx.dressSlots.textContent = ''
|
|
219
|
+
for (var wd = 0; wd < ctx.view.dress.length; wd += 1) {
|
|
220
|
+
var piece = ctx.view.dress[wd]
|
|
221
|
+
if (!piece.worn || piece.slot === '') continue
|
|
222
|
+
// 加冕后的样子自带王冠披风,盖住的位置不挂装扮(东西还在背包里)。
|
|
223
|
+
if (pigStage.hides.indexOf(piece.slot) >= 0) continue
|
|
224
|
+
var node = el('span', 'dp-slot', piece.emoji)
|
|
225
|
+
node.setAttribute('data-slot', piece.slot)
|
|
226
|
+
ctx.dressSlots.appendChild(node)
|
|
227
|
+
}
|
|
228
|
+
ctx.hudName.textContent = ctx.view.pig.name
|
|
229
|
+
+ (ctx.view.pig.sex !== null ? ' ' + ctx.view.pig.sex.symbol : '')
|
|
230
|
+
+ ' Lv.' + ctx.view.pig.level.level
|
|
231
|
+
+ ' · ' + pigStage.label
|
|
232
|
+
+ (ctx.view.pig.ageLabel ? ' · ' + ctx.view.pig.ageLabel : '')
|
|
233
|
+
+ (ctx.view.pig.ageForced ? ' 🔧' : '')
|
|
234
|
+
ctx.hudCoins.textContent = '🪙 ' + ctx.view.pig.coins
|
|
235
|
+
ctx.hudHealth.textContent = '💚 ' + ctx.view.pig.health + '/' + ctx.view.maxHealth
|
|
236
|
+
// Growing up is announced with the same flourish a level-up used to get.
|
|
237
|
+
if (ctx.lastStage !== null && pigStage.key !== ctx.lastStage) {
|
|
238
|
+
ctx.react('levelup', 950)
|
|
239
|
+
ctx.burst(['✨', '🎉', '⭐'], 4)
|
|
240
|
+
ctx.showBubble('我长大啦!' + pigStage.emoji, 2600)
|
|
241
|
+
}
|
|
242
|
+
ctx.lastStage = pigStage.key
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 猪头上的日常提示:能签到就先显示签到,否则显示礼包。
|
|
246
|
+
// 点一下直接领;点击不再冒泡到场景,免得同时被当成摸猪/拖动。
|
|
247
|
+
var daily = ctx.view.daily
|
|
248
|
+
var dailyAction = daily.canSignIn ? 'signIn' : (daily.unclaimed > 0 ? 'openGift' : null)
|
|
249
|
+
ctx.dailyHint.hidden = dailyAction === null || ctx.view.pig === null
|
|
250
|
+
if (dailyAction !== null) {
|
|
251
|
+
ctx.dailyHint.textContent = dailyAction === 'signIn' ? '📅' : '🎁'
|
|
252
|
+
ctx.dailyHint.title = dailyAction === 'signIn'
|
|
253
|
+
? '签到第 ' + daily.signInDay + '/' + daily.cycle + ' 天'
|
|
254
|
+
: '有 ' + daily.unclaimed + ' 个在线礼包'
|
|
255
|
+
// 动作放在 data-action 上,监听只在外壳里注册一次(见 index.js),
|
|
256
|
+
// 免得每 4 秒重绘都往上挂一个 listener。
|
|
257
|
+
ctx.dailyHint.setAttribute('data-action', dailyAction)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Alerts on the icon bar itself, so a collapsed pig still warns.
|
|
261
|
+
// The study icon lights when there is a course to take right now:
|
|
262
|
+
// idle, the stage on screen is unlocked, and it still has a subject.
|
|
263
|
+
var studyStage = null
|
|
264
|
+
for (var st = 0; st < ctx.view.stages.length; st += 1) {
|
|
265
|
+
if (ctx.view.stages[st].key === ctx.stage) studyStage = ctx.view.stages[st]
|
|
266
|
+
}
|
|
267
|
+
var studyOpen = ctx.view.canGoOut && (studyStage === null || studyStage.unlocked !== false)
|
|
268
|
+
// B4: no stage ladder any more, so "a course to take" is simply one the
|
|
269
|
+
// pig can afford (older hosts still send stages, handled above).
|
|
270
|
+
var hasCourse = studyOpen && ctx.view.subjects.some(function (subject) {
|
|
271
|
+
var onStage = studyStage === null || studyStage.subjects.length === 0 || studyStage.subjects.indexOf(subject.key) >= 0
|
|
272
|
+
return onStage && subject.affordable
|
|
273
|
+
})
|
|
274
|
+
ctx.icons.study.setAttribute('data-alert', hasCourse ? 'true' : 'false')
|
|
275
|
+
ctx.icons.shop.setAttribute('data-alert', ctx.view.pig !== null && ctx.view.pig.illness !== null ? 'true' : 'false')
|
|
276
|
+
// 加冕: a form is waiting to be picked.
|
|
277
|
+
ctx.icons.crown.setAttribute('data-alert', ctx.view.forms !== null && ctx.view.forms.forms.some(function (f) { return f.ready }) ? 'true' : 'false')
|
|
278
|
+
ctx.icons.travel.setAttribute('data-alert', ctx.view.pig !== null && ctx.view.pig.coins >= 400 ? 'true' : 'false')
|
|
279
|
+
|
|
280
|
+
for (var i = 0; i < ctx.view.pending.length; i += 1) {
|
|
281
|
+
var event = ctx.view.pending[i]
|
|
282
|
+
// Messages carry an id that only goes up; two from the same instant
|
|
283
|
+
// ("病情加重" then "走了") used to collapse into the first one.
|
|
284
|
+
// Hosts older than the id still dedupe on the timestamp.
|
|
285
|
+
if (event.id > 0 ? event.id <= ctx.lastPendingId : event.at <= ctx.lastPendingAt) continue
|
|
286
|
+
if (event.id > 0) ctx.lastPendingId = event.id
|
|
287
|
+
ctx.lastPendingAt = Math.max(ctx.lastPendingAt, event.at)
|
|
288
|
+
if (event.kind === 'line') {
|
|
289
|
+
showPigLine(event)
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
// 免打扰: routine news stays quiet; illness and death still speak.
|
|
293
|
+
if (ctx.view.dialogue.quiet && URGENT_KINDS.indexOf(event.kind) < 0) continue
|
|
294
|
+
ctx.toast(str(event.text, '猪有新消息'))
|
|
295
|
+
if (event.kind === 'coronation') { ctx.react('levelup', 950); ctx.burst(['👑', '✨'], 3) }
|
|
296
|
+
else if (event.kind === 'levelup') { ctx.react('levelup', 950); ctx.burst(['✨', '🎉'], 3) }
|
|
297
|
+
else if (event.kind === 'cured') { ctx.react('cure', 900); ctx.burst(['💚', '✨'], 3) }
|
|
298
|
+
else if (event.kind === 'death') ctx.react('refuse', 700)
|
|
299
|
+
else if (event.kind === 'work') { ctx.react('away', 900); ctx.burst(['🪙', '💰'], 3) }
|
|
300
|
+
else if (event.kind === 'study') { ctx.react('away', 900); ctx.burst(['📚', '✨'], 3) }
|
|
301
|
+
else if (event.kind === 'trip') { ctx.react('away', 900); ctx.burst(['🧳', '🎁'], 3) }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Typing a new name: a repaint would drop the input and its focus.
|
|
305
|
+
if ((ctx.ownerEdit !== null || ctx.pigNameEdit !== null) && ctx.tab === 'status') return
|
|
306
|
+
if (ctx.cardEdit !== null && ctx.tab === 'card') return
|
|
307
|
+
renderContent()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** A line from the pig goes in its bubble, with the owner's reply buttons under it. */
|
|
311
|
+
function showPigLine(event) {
|
|
312
|
+
var lineId = event.id
|
|
313
|
+
ctx.showLine(event.text, event.replies, function (index) {
|
|
314
|
+
ctx.send('reply', { line: lineId, index: index })
|
|
315
|
+
})
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return { setOpen, select, renderContent, render }
|
|
319
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* 场景搭建:注入样式与字体,建出猪、气泡、进度条、图标栏与面板骨架。
|
|
4
|
+
*
|
|
5
|
+
* 只创建 DOM,不绑定事件、不读状态(见 docs/CONVENTIONS.md);
|
|
6
|
+
* 事件与渲染由 shell 负责。
|
|
7
|
+
* @module dsh-piggy/client/scene
|
|
8
|
+
*/
|
|
9
|
+
import { MOUNTED } from './constants.js'
|
|
10
|
+
import { el } from './dom.js'
|
|
11
|
+
import { CSS } from './styles.js'
|
|
12
|
+
|
|
13
|
+
/** Build the widget once and hand every element back to the shell. */
|
|
14
|
+
export function createScene() {
|
|
15
|
+
var font = document.createElement('link')
|
|
16
|
+
font.rel = 'stylesheet'
|
|
17
|
+
font.href = 'https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900'
|
|
18
|
+
+ '&family=Noto+Sans+SC:wght@400;500;700&display=swap'
|
|
19
|
+
document.head.appendChild(font)
|
|
20
|
+
|
|
21
|
+
var style = document.createElement('style')
|
|
22
|
+
style.textContent = CSS
|
|
23
|
+
document.head.appendChild(style)
|
|
24
|
+
|
|
25
|
+
var host = document.createElement('div')
|
|
26
|
+
host.setAttribute(MOUNTED, '')
|
|
27
|
+
|
|
28
|
+
var card = el('div', 'dp-card')
|
|
29
|
+
// The pig lives beside the panel, not inside it, so it stays transparent
|
|
30
|
+
// and unmoved when the panel opens.
|
|
31
|
+
var scene = el('div', 'dp-scene')
|
|
32
|
+
|
|
33
|
+
var hud = el('div', 'dp-hud')
|
|
34
|
+
var hudName = el('div', null, '猪猪')
|
|
35
|
+
var hudCoins = el('div', null, '🪙 0')
|
|
36
|
+
var hudHealth = el('div', null, '💚 5/5')
|
|
37
|
+
hud.appendChild(hudName)
|
|
38
|
+
hud.appendChild(hudCoins)
|
|
39
|
+
hud.appendChild(hudHealth)
|
|
40
|
+
scene.appendChild(hud)
|
|
41
|
+
|
|
42
|
+
var bubble = el('div', 'dp-bubble', '')
|
|
43
|
+
bubble.hidden = true
|
|
44
|
+
scene.appendChild(bubble)
|
|
45
|
+
|
|
46
|
+
// What the pig is doing while it is out: a prop to work/read/travel with,
|
|
47
|
+
// and a line showing how far through it is. Sits to the pig's left, so the
|
|
48
|
+
// pig itself never shifts when it appears.
|
|
49
|
+
var work = el('div', 'dp-work')
|
|
50
|
+
var prop = el('span', 'dp-prop', '💼')
|
|
51
|
+
var progressWrap = el('div', 'dp-progress')
|
|
52
|
+
var progressFill = document.createElement('i')
|
|
53
|
+
progressWrap.appendChild(progressFill)
|
|
54
|
+
work.appendChild(prop)
|
|
55
|
+
work.appendChild(progressWrap)
|
|
56
|
+
work.hidden = true
|
|
57
|
+
scene.appendChild(work)
|
|
58
|
+
|
|
59
|
+
// Shown while the box is still shut, so it reads as something to poke
|
|
60
|
+
// rather than a decorative cardboard box sitting in the corner.
|
|
61
|
+
var pokeHint = el('div', 'dp-poke-hint')
|
|
62
|
+
pokeHint.appendChild(el('span', null, '👆'))
|
|
63
|
+
pokeHint.appendChild(el('span', null, '戳三下'))
|
|
64
|
+
pokeHint.hidden = true
|
|
65
|
+
scene.appendChild(pokeHint)
|
|
66
|
+
|
|
67
|
+
// 签到 / 在线礼包的小气泡,位置在猪头上,样式在 css-tabs.js。
|
|
68
|
+
var dailyHint = el('button', 'dp-daily')
|
|
69
|
+
dailyHint.hidden = true
|
|
70
|
+
scene.appendChild(dailyHint)
|
|
71
|
+
|
|
72
|
+
var soul = el('span', 'dp-soul', '👻')
|
|
73
|
+
soul.hidden = true
|
|
74
|
+
scene.appendChild(soul)
|
|
75
|
+
|
|
76
|
+
// Drawn stages (the piglet, the elder pig) use an <img>; the rest fall
|
|
77
|
+
// back to the emoji. Both live in the pig box so the layout never cares.
|
|
78
|
+
var pigArt = document.createElement('img')
|
|
79
|
+
pigArt.className = 'dp-pig-img'
|
|
80
|
+
pigArt.alt = ''
|
|
81
|
+
pigArt.hidden = true
|
|
82
|
+
var pigEmoji = el('span', 'dp-pig-emoji', '🐖')
|
|
83
|
+
var pig = el('div', 'dp-pig')
|
|
84
|
+
pig.appendChild(pigArt)
|
|
85
|
+
pig.appendChild(pigEmoji)
|
|
86
|
+
// 装扮点位:每个点位挂一件,位置全在 CSS 里(.dp-slot[data-slot=…])。
|
|
87
|
+
var dressSlots = el('div', 'dp-dress')
|
|
88
|
+
pig.appendChild(dressSlots)
|
|
89
|
+
scene.appendChild(pig)
|
|
90
|
+
// Right-click is not discoverable on its own, so the native tooltip says so.
|
|
91
|
+
scene.title = '左键摸摸 · 右键打开面板 · 拖动可移动'
|
|
92
|
+
|
|
93
|
+
var bar = el('div', 'dp-bar')
|
|
94
|
+
|
|
95
|
+
var content = el('div', 'dp-content')
|
|
96
|
+
|
|
97
|
+
// Panel first, pig second: as flex siblings in a bottom-anchored column,
|
|
98
|
+
// the pig ends up at a fixed screen position whether the panel is open or
|
|
99
|
+
// not, and the panel can only ever grow upwards from it.
|
|
100
|
+
card.appendChild(content)
|
|
101
|
+
card.appendChild(bar)
|
|
102
|
+
host.appendChild(card)
|
|
103
|
+
host.appendChild(scene)
|
|
104
|
+
if (document.body !== null && document.body !== undefined) {
|
|
105
|
+
document.body.appendChild(host)
|
|
106
|
+
} else {
|
|
107
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
108
|
+
try { document.body.appendChild(host) } catch (error) { /* shell not ready */ }
|
|
109
|
+
}, { once: true })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { font, style, host, card, scene, hud, hudName, hudCoins, hudHealth, bubble, work, prop, progressWrap, progressFill, pokeHint, dailyHint, soul, pigArt, pigEmoji, pig, dressSlots, bar, content }
|
|
113
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* 面板自己的小存储:展开状态、位置与开发者模式开关。
|
|
4
|
+
*
|
|
5
|
+
* 隐私模式下 localStorage 会抛异常,所以读写都要兜住。
|
|
6
|
+
* @module dsh-piggy/client/storage
|
|
7
|
+
*/
|
|
8
|
+
export function readStore(key) {
|
|
9
|
+
try {
|
|
10
|
+
var value = window.localStorage.getItem(key)
|
|
11
|
+
// Keys were 'dsh-pig:*' before the rename; read those once so nothing resets.
|
|
12
|
+
return value !== null ? value : window.localStorage.getItem(key.replace(/^dsh-piggy:/, 'dsh-pig:'))
|
|
13
|
+
} catch (error) { return null }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export function writeStore(key, value) {
|
|
18
|
+
try { window.localStorage.setItem(key, value) } catch (error) { /* private mode */ }
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* 样式入口:基础样式 + 页签样式拼成一张表。
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-piggy/client/styles
|
|
6
|
+
*/
|
|
7
|
+
import { CSS_BASE } from './css-base.js'
|
|
8
|
+
import { CSS_TABS } from './css-tabs.js'
|
|
9
|
+
import { CSS_TILES } from './css-tiles.js'
|
|
10
|
+
import { CSS_CARD } from './css-card.js'
|
|
11
|
+
|
|
12
|
+
/** The whole stylesheet, in the order it must be applied. */
|
|
13
|
+
export const CSS = CSS_BASE + CSS_TABS + CSS_TILES + CSS_CARD
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* 背包页签(B8:动森手机式方块,跟商店一个样子)。
|
|
4
|
+
*
|
|
5
|
+
* 第一层:食物 / 洗浴 / 玩具 / 药品 / 复活 / 装扮 / 日记 / 纪念品,右上角写件数,空的变灰。
|
|
6
|
+
* 第二层:道具点一下就用;装扮点一下穿上或脱下;日记和纪念品点一下,下面显示全文 / 故事。
|
|
7
|
+
* @module dsh-piggy/client/tabs/bag
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { KIND_ORDER } from '../constants.js'
|
|
11
|
+
import { button, el } from '../dom.js'
|
|
12
|
+
import { num } from '../values.js'
|
|
13
|
+
import { drillHeader, drillTo, tile, tileGrid } from '../widgets.js'
|
|
14
|
+
import { SHELF_COLOR, shelfParts } from './shop.js'
|
|
15
|
+
|
|
16
|
+
/** The consumable shelves, in shop order; 装扮 is its own category below. */
|
|
17
|
+
var CONSUMABLES = KIND_ORDER.filter(function (kind) { return kind !== 'dress' })
|
|
18
|
+
|
|
19
|
+
/** The non-shelf categories: what they are called and how they look. */
|
|
20
|
+
var EXTRA = {
|
|
21
|
+
dress: { emoji: '👕', label: '装扮', color: 'pink' },
|
|
22
|
+
diary: { emoji: '📔', label: '日记', color: 'brown' },
|
|
23
|
+
souvenir: { emoji: '🎁', label: '纪念品', color: 'blue' },
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 「2026-10-01」 → 「10-01」: the tile only has room for the month and day. */
|
|
27
|
+
function shortDay(day) {
|
|
28
|
+
return day.length >= 10 ? day.slice(5) : day
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function renderBagTab(ui) {
|
|
32
|
+
var open = ui.drill.bag
|
|
33
|
+
if (open === 'dress') renderDress(ui)
|
|
34
|
+
else if (open === 'diary') renderDiary(ui)
|
|
35
|
+
else if (open === 'souvenir') renderSouvenirs(ui)
|
|
36
|
+
else if (open !== null && CONSUMABLES.indexOf(open) >= 0) renderItems(ui, open)
|
|
37
|
+
else renderCategories(ui)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** What the pig owns of one shelf, with counts. */
|
|
41
|
+
function ownedOf(ui, kind) {
|
|
42
|
+
return ui.view.shop.filter(function (item) {
|
|
43
|
+
return item.kind === kind && num(ui.view.inventory[item.key], 0) > 0
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderCategories(ui) {
|
|
48
|
+
var grid = tileGrid()
|
|
49
|
+
for (var k = 0; k < CONSUMABLES.length; k += 1) {
|
|
50
|
+
(function (kind) {
|
|
51
|
+
var items = ownedOf(ui, kind)
|
|
52
|
+
var count = items.reduce(function (sum, item) { return sum + num(ui.view.inventory[item.key], 0) }, 0)
|
|
53
|
+
var parts = shelfParts(kind)
|
|
54
|
+
grid.appendChild(tile({
|
|
55
|
+
emoji: parts[0], label: parts[1], color: SHELF_COLOR[kind] ?? 'blue',
|
|
56
|
+
badge: count > 0 ? String(count) : '', dim: count === 0,
|
|
57
|
+
tag: items.some(function (item) { return item.needed }) ? '需要' : '',
|
|
58
|
+
data: { 'data-bag': kind },
|
|
59
|
+
onPick: function () { drillTo(ui, 'bag', kind) },
|
|
60
|
+
}))
|
|
61
|
+
})(CONSUMABLES[k])
|
|
62
|
+
}
|
|
63
|
+
var counts = {
|
|
64
|
+
dress: ui.view.dress.filter(function (item) { return item.owned }).length,
|
|
65
|
+
diary: ui.view.diary.length,
|
|
66
|
+
souvenir: ui.view.pig.souvenirs.length,
|
|
67
|
+
}
|
|
68
|
+
for (var key in EXTRA) {
|
|
69
|
+
(function (category) {
|
|
70
|
+
var spec = EXTRA[category]
|
|
71
|
+
grid.appendChild(tile({
|
|
72
|
+
emoji: spec.emoji, label: spec.label, color: spec.color,
|
|
73
|
+
badge: counts[category] > 0 ? String(counts[category]) : '', dim: counts[category] === 0,
|
|
74
|
+
data: { 'data-bag': category },
|
|
75
|
+
onPick: function () { drillTo(ui, 'bag', category) },
|
|
76
|
+
}))
|
|
77
|
+
})(key)
|
|
78
|
+
}
|
|
79
|
+
ui.content.appendChild(grid)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A consumable shelf: tap an item to use it. */
|
|
83
|
+
function renderItems(ui, kind) {
|
|
84
|
+
var parts = shelfParts(kind)
|
|
85
|
+
drillHeader(ui, 'bag', parts[0] + ' ' + parts[1], '点一下就用')
|
|
86
|
+
var items = ownedOf(ui, kind)
|
|
87
|
+
if (items.length === 0) {
|
|
88
|
+
ui.content.appendChild(el('div', 'dp-empty', '空的'))
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
var grid = tileGrid()
|
|
92
|
+
for (var i = 0; i < items.length; i += 1) {
|
|
93
|
+
(function (item) {
|
|
94
|
+
grid.appendChild(tile({
|
|
95
|
+
emoji: item.emoji, label: item.label, color: SHELF_COLOR[kind] ?? 'blue', soft: true,
|
|
96
|
+
badge: '×' + num(ui.view.inventory[item.key], 0), tag: item.needed ? '需要' : '',
|
|
97
|
+
data: { 'data-use': item.key },
|
|
98
|
+
onPick: function () { ui.send('use', { item: item.key }) },
|
|
99
|
+
}))
|
|
100
|
+
})(items[i])
|
|
101
|
+
}
|
|
102
|
+
ui.content.appendChild(grid)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 家当: tap to put it on or take it off; one piece per slot. */
|
|
106
|
+
function renderDress(ui) {
|
|
107
|
+
var owned = ui.view.dress.filter(function (item) { return item.owned })
|
|
108
|
+
var worn = owned.filter(function (item) { return item.worn }).length
|
|
109
|
+
drillHeader(ui, 'bag', '👕 装扮', worn + ' 件穿着')
|
|
110
|
+
if (owned.length === 0) {
|
|
111
|
+
ui.content.appendChild(el('div', 'dp-empty', '还没有装扮'))
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
var grid = tileGrid()
|
|
115
|
+
for (var i = 0; i < owned.length; i += 1) {
|
|
116
|
+
(function (item) {
|
|
117
|
+
grid.appendChild(tile({
|
|
118
|
+
emoji: item.emoji, label: item.label, color: EXTRA.dress.color, soft: true,
|
|
119
|
+
note: item.slotLabel, tag: item.worn ? '穿着' : '', active: item.worn,
|
|
120
|
+
data: { 'data-wear': item.key },
|
|
121
|
+
onPick: function () { ui.send('wear', { item: item.key, on: !item.worn }) },
|
|
122
|
+
}))
|
|
123
|
+
})(owned[i])
|
|
124
|
+
}
|
|
125
|
+
ui.content.appendChild(grid)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 日记: one tile per day, newest first; the picked day's page opens below. */
|
|
129
|
+
function renderDiary(ui) {
|
|
130
|
+
drillHeader(ui, 'bag', '📔 日记', ui.view.diary.length + ' 篇')
|
|
131
|
+
if (ui.view.diary.length === 0) {
|
|
132
|
+
ui.content.appendChild(el('div', 'dp-empty', '还没有日记'))
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
var grid = tileGrid()
|
|
136
|
+
var picked = null
|
|
137
|
+
for (var d = 0; d < ui.view.diary.length; d += 1) {
|
|
138
|
+
(function (entry) {
|
|
139
|
+
var active = ui.drill.pick === entry.day
|
|
140
|
+
if (active) picked = entry
|
|
141
|
+
grid.appendChild(tile({
|
|
142
|
+
emoji: '📔', label: shortDay(entry.day), color: EXTRA.diary.color, soft: true, active: active,
|
|
143
|
+
data: { 'data-diary': entry.day },
|
|
144
|
+
onPick: function () {
|
|
145
|
+
ui.drill.pick = active ? null : entry.day
|
|
146
|
+
ui.renderContent()
|
|
147
|
+
},
|
|
148
|
+
}))
|
|
149
|
+
})(ui.view.diary[d])
|
|
150
|
+
}
|
|
151
|
+
ui.content.appendChild(grid)
|
|
152
|
+
if (picked !== null) {
|
|
153
|
+
var page = el('div', 'dp-pick dp-tile-card dp-diary-page')
|
|
154
|
+
page.appendChild(el('div', 'dp-pick-head', picked.day))
|
|
155
|
+
page.appendChild(el('div', null, picked.text))
|
|
156
|
+
ui.content.appendChild(page)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** 纪念品: tap one for its story, and sell it from there. */
|
|
161
|
+
function renderSouvenirs(ui) {
|
|
162
|
+
var list = ui.view.pig.souvenirs
|
|
163
|
+
drillHeader(ui, 'bag', '🎁 纪念品', list.length + ' 件')
|
|
164
|
+
if (list.length === 0) {
|
|
165
|
+
ui.content.appendChild(el('div', 'dp-empty', '收藏册还空着'))
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
var grid = tileGrid()
|
|
169
|
+
var picked = null
|
|
170
|
+
for (var s = 0; s < list.length; s += 1) {
|
|
171
|
+
(function (entry, index) {
|
|
172
|
+
var id = entry.key + '#' + index
|
|
173
|
+
var active = ui.drill.pick === id
|
|
174
|
+
if (active) picked = entry
|
|
175
|
+
grid.appendChild(tile({
|
|
176
|
+
emoji: entry.emoji, label: entry.label, color: EXTRA.souvenir.color, soft: true, active: active,
|
|
177
|
+
note: entry.rarityEmoji + entry.rarityLabel,
|
|
178
|
+
data: { 'data-souvenir': id },
|
|
179
|
+
onPick: function () {
|
|
180
|
+
ui.drill.pick = active ? null : id
|
|
181
|
+
ui.renderContent()
|
|
182
|
+
},
|
|
183
|
+
}))
|
|
184
|
+
})(list[s], s)
|
|
185
|
+
}
|
|
186
|
+
ui.content.appendChild(grid)
|
|
187
|
+
if (picked === null) return
|
|
188
|
+
var story = el('div', 'dp-pick dp-tile-card')
|
|
189
|
+
story.appendChild(el('div', 'dp-pick-head', picked.emoji + ' ' + picked.label
|
|
190
|
+
+ (picked.fromLabel === '' ? '' : ' · ' + picked.fromLabel)))
|
|
191
|
+
story.appendChild(el('div', null, picked.story === '' ? '(旧版本带回来的,没有故事)' : '「' + picked.story + '」'))
|
|
192
|
+
if (picked.price > 0) {
|
|
193
|
+
var sold = picked
|
|
194
|
+
var sell = button('dp-mini', { 'data-sell': sold.key }, function () {
|
|
195
|
+
ui.drill.pick = null
|
|
196
|
+
ui.send('sell', { souvenir: sold.key })
|
|
197
|
+
})
|
|
198
|
+
sell.textContent = '卖掉 +' + sold.price + ' 🪙'
|
|
199
|
+
sell.style.marginTop = '6px'
|
|
200
|
+
story.appendChild(sell)
|
|
201
|
+
}
|
|
202
|
+
ui.content.appendChild(story)
|
|
203
|
+
}
|