mirror of
https://github.com/System-End/scraps.git
synced 2026-04-19 23:22:54 +00:00
feat(shop): add displayRollCost and perRollMultiplier frontend support
- Add displayRollCost to ShopItem interface in stores - ShopItemModal and shop page prefer server-provided displayRollCost - Clean up as-any casts in ShopItemModal roll cost computation - Admin shop: add perRollMultiplier to item type and EV simulation - Remove unused modalEV derived (dead code, formEV used in template)
This commit is contained in:
parent
719b4cb04d
commit
7367dac120
4 changed files with 593 additions and 603 deletions
|
|
@ -50,15 +50,24 @@
|
|||
let showConfirmation = $state(false);
|
||||
let localHearted = $state(item.userHearted);
|
||||
let localHeartCount = $state(item.heartCount);
|
||||
let rollCost = $derived((() => {
|
||||
let baseCost: number;
|
||||
if (item.rollCostOverride != null && item.rollCostOverride > 0) {
|
||||
baseCost = item.rollCostOverride;
|
||||
} else {
|
||||
baseCost = Math.max(1, Math.round(item.price * (item.baseProbability / 100)));
|
||||
}
|
||||
return Math.round(baseCost * (1 + 0.05 * (item.rollCount || 0)));
|
||||
})());
|
||||
let rollCost = $derived(
|
||||
(() => {
|
||||
// Prefer server-provided displayRollCost when present (authoritative).
|
||||
// Fallback to local computation if the server value is missing for any reason.
|
||||
if (item.displayRollCost != null && Number.isFinite(item.displayRollCost)) {
|
||||
return item.displayRollCost;
|
||||
}
|
||||
let baseCost: number;
|
||||
if (item.rollCostOverride != null && item.rollCostOverride > 0) {
|
||||
baseCost = item.rollCostOverride;
|
||||
} else {
|
||||
baseCost = Math.max(1, Math.round(item.price * (item.baseProbability / 100)));
|
||||
}
|
||||
const perRoll = item.perRollMultiplier ?? 0.05;
|
||||
const rollCount = item.rollCount ?? 0;
|
||||
return Math.round(baseCost * (1 + perRoll * rollCount));
|
||||
})()
|
||||
);
|
||||
let canAfford = $derived($userScrapsStore >= rollCost);
|
||||
let alertMessage = $state<string | null>(null);
|
||||
let alertType = $state<'error' | 'info'>('info');
|
||||
|
|
@ -313,7 +322,7 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 min-h-[120px] rounded-lg border-2 border-black p-4">
|
||||
<div class="mb-6 min-h-30 rounded-lg border-2 border-black p-4">
|
||||
{#if activeTab === 'leaderboard'}
|
||||
{#if loadingLeaderboard}
|
||||
<p class="text-center text-gray-500">{$t.common.loading}</p>
|
||||
|
|
@ -404,7 +413,7 @@
|
|||
|
||||
{#if showConfirmation}
|
||||
<div
|
||||
class="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 p-4"
|
||||
class="fixed inset-0 z-60 flex items-center justify-center bg-black/50 p-4"
|
||||
onclick={(e) => e.target === e.currentTarget && (showConfirmation = false)}
|
||||
onkeydown={(e) => e.key === 'Escape' && (showConfirmation = false)}
|
||||
role="dialog"
|
||||
|
|
@ -443,7 +452,7 @@
|
|||
|
||||
{#if alertMessage}
|
||||
<div
|
||||
class="fixed inset-0 z-[70] flex items-center justify-center bg-black/50 p-4"
|
||||
class="fixed inset-0 z-70 flex items-center justify-center bg-black/50 p-4"
|
||||
onclick={(e) => e.target === e.currentTarget && (alertMessage = null)}
|
||||
onkeydown={(e) => e.key === 'Escape' && (alertMessage = null)}
|
||||
role="dialog"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export interface ShopItem {
|
|||
price: number;
|
||||
category: string;
|
||||
count: number;
|
||||
rollCount: number;
|
||||
heartCount: number;
|
||||
userHearted: boolean;
|
||||
baseProbability: number;
|
||||
|
|
@ -33,11 +34,12 @@ export interface ShopItem {
|
|||
costMultiplier: number;
|
||||
boostAmount: number;
|
||||
rollCostOverride: number | null;
|
||||
perRollMultiplier: number;
|
||||
userBoostPercent: number;
|
||||
upgradeCount: number;
|
||||
effectiveProbability: number;
|
||||
nextUpgradeCost: number | null;
|
||||
rollCount: number;
|
||||
displayRollCost?: number;
|
||||
}
|
||||
|
||||
export interface LeaderboardEntry {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -23,13 +23,19 @@
|
|||
}
|
||||
|
||||
function getItemRollCost(item: ShopItem): number {
|
||||
// Prefer the server-provided displayRollCost (authoritative) when available.
|
||||
// Fallback to a local computation that mirrors server logic exactly.
|
||||
if (item.displayRollCost != null && Number.isFinite(item.displayRollCost)) {
|
||||
return item.displayRollCost;
|
||||
}
|
||||
let baseCost: number;
|
||||
if (item.rollCostOverride != null && item.rollCostOverride > 0) {
|
||||
baseCost = item.rollCostOverride;
|
||||
} else {
|
||||
baseCost = Math.max(1, Math.round(item.price * (item.baseProbability / 100)));
|
||||
}
|
||||
return Math.round(baseCost * (1 + 0.05 * (item.rollCount || 0)));
|
||||
const perRoll = item.perRollMultiplier ?? 0.05;
|
||||
return Math.round(baseCost * (1 + perRoll * (item.rollCount || 0)));
|
||||
}
|
||||
|
||||
let selectedCategories = $state<Set<string>>(new Set());
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue