feedback1 WB: special-refresh offers drawn as what they hold (凯瑟琳's three items as item cards)
Player report #6: 凯瑟琳's 定向投放 offer after a level-up showed three nameless operator cards (the raw item ids) under the promotion reward's 晋升奖励 header. The shop bar now draws each offer slot by its kind (item slots as free item cards) and names the offer: m.private shop.rewardOffer carries source ('merge' | 'special') and label (the strategy's effect name, the item's name or the 特质's operator — effectsMeta.offerLabel); the reminder pill says <label>待选择. Same path for 娜仁图亚, 寻呼模块, 信标 and 松果.
This commit is contained in:
1 parent
5e6fe19a70
commit
49c0ebcf4d
10 files changed
+427
-39
No files matched your search
@@ -538,6 +538,25 @@ export function completesMerge(priv, slot, { getChess = () => null, getItem = ()
|
||||
return need > 1 && copies + 1 >= need;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the shop bar names a free pick-one offer (m.private shop.rewardOffer): the promotion reward (`source` 'merge')
|
||||
* reads 晋升奖励 / PROMOTION; any other offer — a strategy's special refresh (凯瑟琳 定向投放, 娜仁图亚 见者有份), an item
|
||||
* (寻呼模块, 信标) or a 特质 (松果) — reads its `label` with the refresh icon (player report #6 after 0.1.0: 凯瑟琳's three
|
||||
* items showed up as nameless operator cards under 晋升奖励). `items` = the offer holds items (cards drawn as item cards).
|
||||
* @param {any} offer
|
||||
* @returns {{ title: string, micro: string, sub: string, icon: string, items: boolean, pill: string }}
|
||||
*/
|
||||
export function offerHeader(offer) {
|
||||
const slots = isObj(offer) && Array.isArray(offer.slots) ? offer.slots : [];
|
||||
const items = slots.some((s) => isObj(s) && s.kind === 'item');
|
||||
const label = isObj(offer) && typeof offer.label === 'string' && offer.label ? offer.label : null;
|
||||
if (!items && (!isObj(offer) || offer.source === 'merge' || offer.source == null) && !label) {
|
||||
return { title: '晋升奖励', micro: 'PROMOTION', sub: '免费选择 1 名', icon: 'crown', items: false, pill: '晋升奖励待选择' };
|
||||
}
|
||||
const title = label || (items ? '装备补给' : '特殊招募');
|
||||
return { title, micro: 'SPECIAL', sub: items ? '免费选择 1 件' : '免费选择 1 名', icon: 'refresh', items, pill: `${title}待选择` };
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a shop action is unavailable (null when available).
|
||||
* @param {'buy'|'reward'|'refresh'|'freeze'|'levelUp'|'ready'} kind
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Merge reward reminder (research 00 §3): the reward cards themselves live in the shop bar (shopBar.js RewardCards —
|
||||
// after a promotion they replace the bar's operator cards, like the original). When the player puts the pick off
|
||||
// (稍后选择) this pill above the bar brings the cards back; it disappears once the server clears `shop.rewardOffer`
|
||||
// (picked, or the round ended).
|
||||
// Pick-one offer reminder (research 00 §3): the offer's cards themselves live in the shop bar (shopBar.js RewardCards —
|
||||
// after a promotion, or a special refresh such as 凯瑟琳's 定向投放, they replace the bar's operator cards, like the
|
||||
// original). When the player puts the pick off (稍后选择) this pill above the bar brings the cards back; it names the
|
||||
// offer like the bar's header (gameLogic.offerHeader: 晋升奖励待选择 / 定向投放待选择 …, player report #6 after 0.1.0) and
|
||||
// disappears once the server clears `shop.rewardOffer` (picked, or the round ended).
|
||||
|
||||
import { html, Icon, TierChip } from './components.js';
|
||||
import { offerHeader } from './gameLogic.js';
|
||||
|
||||
/**
|
||||
* @param {{ priv:any, minimized:boolean, onMinimize:(m:boolean)=>void }} props
|
||||
@@ -11,7 +13,8 @@ import { html, Icon, TierChip } from './components.js';
|
||||
export function RewardOverlay({ priv, minimized, onMinimize }) {
|
||||
const offer = priv?.shop?.rewardOffer;
|
||||
if (!offer || !Array.isArray(offer.slots) || !offer.slots.length || !minimized) return null;
|
||||
const head = offerHeader(offer);
|
||||
return html`<button type="button" class="rewardpill" onClick=${() => onMinimize(false)}>
|
||||
<${Icon} name="crown" /><span>晋升奖励待选择</span><${TierChip} tier=${offer.tier || 1} size="sm" />
|
||||
<${Icon} name=${head.icon} /><span>${head.pill}</span>${Number.isInteger(offer.tier) ? html`<${TierChip} tier=${offer.tier} size="sm" />` : null}
|
||||
</button>`;
|
||||
}
|
||||
+28
-18
@@ -11,7 +11,9 @@
|
||||
// detail); a card that cannot be bought right now (the bar is not editable: ready, round start) opens its detail. The
|
||||
// D / R / F keys stay one-press shortcuts. There is no drag-to-sell zone: selling is the 出售 +N button of a tapped
|
||||
// unit's underframe (ui/underframe.js). After a promotion the operator cards are replaced by
|
||||
// the 晋升奖励 cards (3 free operators, pick 1 — also two taps) until picked or put off (稍后 → rewardOverlay.js pill).
|
||||
// the 晋升奖励 cards (3 free operators, pick 1 — also two taps) until picked or put off (稍后 → rewardOverlay.js pill);
|
||||
// a special refresh (凯瑟琳 定向投放's 3 items, 娜仁图亚, 寻呼模块, 信标, 松果) takes the same place under its own name, an
|
||||
// item slot drawn as an item card (player report #6 after 0.1.0).
|
||||
// Every operator card shows the skill it will fight with — the player's 干员调配 loadout (m.private.loadout, DESIGN
|
||||
// §16): the skill icon above the name, mint-framed with 已调配 in its title when it is not the default skill (and the
|
||||
// module type of an elite card, with its official type icon when the local-client art has it).
|
||||
@@ -19,7 +21,7 @@
|
||||
import { useEffect, useState } from '../../vendor/hooks.module.js';
|
||||
import { html, Icon, HexBadge, TierChip, Tooltip, MicroLabel } from './components.js';
|
||||
import { Img, BondGlyph, CoinGlyph, GIcon, RichText } from './gameComponents.js';
|
||||
import { priceTone, mergeProgress, mergeTarget, shopBlockReason, chessLoadout } from './gameLogic.js';
|
||||
import { priceTone, mergeProgress, mergeTarget, shopBlockReason, chessLoadout, offerHeader } from './gameLogic.js';
|
||||
import { chessPortraitUrl, itemIconUrl, profIconUrl, uiUrl, skillIconUrl, skillRecordIconUrl, moduleTypeIconUrl } from './assetUrls.js';
|
||||
import { data } from '../data.js';
|
||||
|
||||
@@ -120,7 +122,7 @@ function SkillBadge({ chess, lo }) {
|
||||
* @param {{ slot:any, idx:number, frozen?:boolean, reason?:string|null, armed?:boolean, onTap?:(idx:number)=>void,
|
||||
* onBuy:Function, onDetail:Function }} props
|
||||
*/
|
||||
export function ItemCard({ slot, idx, frozen = false, reason = null, armed = false, onTap = null, onBuy, onDetail }) {
|
||||
export function ItemCard({ slot, idx, frozen = false, reason = null, free = false, armed = false, onTap = null, onBuy, onDetail }) {
|
||||
const it = data.lookup('items', slot.id);
|
||||
const m = data.get('assets');
|
||||
const disabled = !!reason;
|
||||
@@ -132,7 +134,7 @@ export function ItemCard({ slot, idx, frozen = false, reason = null, armed = fal
|
||||
<span class="scard__bg" aria-hidden="true"></span>
|
||||
<span class="scard__top">
|
||||
<${TierChip} tier=${it?.tier ?? 1} size="md" />
|
||||
<${PriceHex} slot=${slot} poor=${reason === '资金不足'} />
|
||||
<${PriceHex} slot=${slot} free=${free} poor=${reason === '资金不足'} />
|
||||
</span>
|
||||
<span class="scard__itemart"><${Img} src=${itemIconUrl(m, it)} fallback=${html`<${GIcon} name="bolt" />`} /></span>
|
||||
<span class="scard__body">
|
||||
@@ -140,7 +142,7 @@ export function ItemCard({ slot, idx, frozen = false, reason = null, armed = fal
|
||||
<span class="scard__idesc"><${RichText} text=${it?.descRaw || it?.desc || ''} /></span>
|
||||
</span>
|
||||
${frozen ? html`<span class="scard__ice" aria-hidden="true"><${Icon} name="snow" /></span>` : null}
|
||||
${armed ? html`<${ArmedTag} reason=${reason} />` : null}
|
||||
${armed ? html`<${ArmedTag} reason=${reason} free=${free} />` : null}
|
||||
</button>`;
|
||||
return reason && reason !== '已售出' && !armed ? html`<${Tooltip} text=${reason} block=${true} class="scard-wrap">${card}<//>` : card;
|
||||
}
|
||||
@@ -210,24 +212,32 @@ export function useTwoTap({ editable, keys }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Promotion reward (research 00 §3): after a merge the operator cards of the bar are replaced by 3 free operators of
|
||||
* tier min(level+1, 6); pick 1 (g.reward idx) or put it off (稍后选择 → the normal shop + a reminder pill).
|
||||
* A free pick-one offer in place of the bar's operator cards (pick 1 — g.reward idx — or put it off: 稍后选择 → the
|
||||
* normal shop + a reminder pill): the promotion reward (research 00 §3: 3 free operators of tier min(level+1, 6)) and
|
||||
* the special refreshes of strategies, items and 特质 — e.g. 凯瑟琳 【定向投放】 "在调度中心刷新随机3件装备,可以选择并获得
|
||||
* 其中1件". Each slot is drawn by its kind: an item slot is an item card (icon, name, tier, description, FREE) — player
|
||||
* report #6 after 0.1.0, the items used to be drawn as nameless operator cards — and the header names the offer
|
||||
* (gameLogic.offerHeader: 晋升奖励 / the strategy's effect name …).
|
||||
*/
|
||||
function RewardCards({ offer, priv, editable, onPick, onDetail, onLater, armed, onTap }) {
|
||||
return html`<div class="shopbar__reward" role="group" aria-label="晋升奖励">
|
||||
export function RewardCards({ offer, priv, editable, onPick, onDetail, onLater, armed, onTap }) {
|
||||
const head = offerHeader(offer);
|
||||
return html`<div class=${cx('shopbar__reward', head.items && 'is-items')} role="group" aria-label=${head.title}>
|
||||
<div class="rwtag">
|
||||
<${Icon} name="crown" class="rwtag__icon" />
|
||||
<b class="rwtag__title">晋升奖励</b>
|
||||
<span class="rwtag__micro">PROMOTION</span>
|
||||
<span class="rwtag__sub">免费选择 1 名</span>
|
||||
<${Icon} name=${head.icon} class="rwtag__icon" />
|
||||
<b class="rwtag__title">${head.title}</b>
|
||||
<span class="rwtag__micro">${head.micro}</span>
|
||||
<span class="rwtag__sub">${head.sub}</span>
|
||||
<button type="button" class="rwtag__later" onClick=${onLater} title="稍后选择(回合结束后消失)"><${Icon} name="minus" />稍后</button>
|
||||
</div>
|
||||
<div class="shopbar__rwcards">
|
||||
${offer.slots.map((s, i) => (s && !s.sold
|
||||
? html`<${ChessCard} key=${`rw${i}:${s.id}`} slot=${{ ...s, price: 0 }} idx=${i} priv=${priv} free=${true}
|
||||
armed=${armed === armKey('r', i, s)} onTap=${(idx) => onTap('r', idx, s, 'chess', shopBlockReason('reward', { priv, editable, slot: s, ...LOOKUPS }), onPick)}
|
||||
reason=${shopBlockReason('reward', { priv, editable, slot: s, ...LOOKUPS })} onBuy=${onPick} onDetail=${onDetail} />`
|
||||
: html`<div key=${`rw${i}`} class="scard scard--sold"><span class="scard__soldtxt"><span>已选择</span></span></div>`))}
|
||||
${offer.slots.map((s, i) => {
|
||||
if (!s || s.sold) return html`<div key=${`rw${i}`} class=${cx('scard', 'scard--sold', s && s.kind === 'item' && 'scard--item')}><span class="scard__soldtxt"><span>已选择</span></span></div>`;
|
||||
const kind = s.kind === 'item' ? 'item' : 'chess';
|
||||
const reason = shopBlockReason('reward', { priv, editable, slot: s, ...LOOKUPS });
|
||||
const props = { slot: { ...s, price: 0 }, idx: i, free: true, reason, armed: armed === armKey('r', i, s), onBuy: onPick, onDetail,
|
||||
onTap: (idx) => onTap('r', idx, s, kind, reason, onPick) };
|
||||
return kind === 'item' ? html`<${ItemCard} key=${`rw${i}:${s.id}`} ...${props} />` : html`<${ChessCard} key=${`rw${i}:${s.id}`} priv=${priv} ...${props} />`;
|
||||
})}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user