xp show
Browse files
src/lib/components/Battle/PicletInfo.svelte
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<script lang="ts">
|
2 |
import type { PicletInstance } from '$lib/db/schema';
|
3 |
-
import { getXpProgress,
|
4 |
|
5 |
export let piclet: PicletInstance;
|
6 |
export let hpPercentage: number;
|
@@ -9,7 +9,7 @@
|
|
9 |
|
10 |
// Calculate real XP percentage using levelingService
|
11 |
$: realXpPercentage = isPlayer ? getXpProgress(piclet.xp, piclet.level, piclet.tier) : 0;
|
12 |
-
$:
|
13 |
|
14 |
$: hpColor = hpPercentage > 0.5 ? '#4caf50' : hpPercentage > 0.2 ? '#ffc107' : '#f44336';
|
15 |
$: displayHp = Math.ceil(piclet.currentHp);
|
@@ -55,20 +55,10 @@
|
|
55 |
<div class="xp-bar">
|
56 |
<div
|
57 |
class="xp-fill"
|
58 |
-
style="width: {
|
59 |
></div>
|
60 |
</div>
|
61 |
|
62 |
-
<!-- XP Progress Text (Player only) -->
|
63 |
-
{#if piclet.level < 100}
|
64 |
-
<div class="xp-text">
|
65 |
-
<span class="xp-progress">{Math.floor(realXpPercentage)}% to next level</span>
|
66 |
-
</div>
|
67 |
-
{:else}
|
68 |
-
<div class="xp-text">
|
69 |
-
<span class="xp-progress">MAX LEVEL</span>
|
70 |
-
</div>
|
71 |
-
{/if}
|
72 |
{/if}
|
73 |
</div>
|
74 |
|
@@ -182,7 +172,7 @@
|
|
182 |
.xp-fill {
|
183 |
height: 100%;
|
184 |
background: #2196f3;
|
185 |
-
transition: width
|
186 |
}
|
187 |
|
188 |
/* XP Text */
|
@@ -193,6 +183,7 @@
|
|
193 |
|
194 |
.xp-progress {
|
195 |
font-weight: 500;
|
|
|
196 |
}
|
197 |
|
198 |
/* Triangle Pointer */
|
|
|
1 |
<script lang="ts">
|
2 |
import type { PicletInstance } from '$lib/db/schema';
|
3 |
+
import { getXpProgress, getXpTowardsNextLevel } from '$lib/services/levelingService';
|
4 |
|
5 |
export let piclet: PicletInstance;
|
6 |
export let hpPercentage: number;
|
|
|
9 |
|
10 |
// Calculate real XP percentage using levelingService
|
11 |
$: realXpPercentage = isPlayer ? getXpProgress(piclet.xp, piclet.level, piclet.tier) : 0;
|
12 |
+
$: xpTowardsNext = isPlayer ? getXpTowardsNextLevel(piclet.xp, piclet.level, piclet.tier) : { current: 0, needed: 0, percentage: 0 };
|
13 |
|
14 |
$: hpColor = hpPercentage > 0.5 ? '#4caf50' : hpPercentage > 0.2 ? '#ffc107' : '#f44336';
|
15 |
$: displayHp = Math.ceil(piclet.currentHp);
|
|
|
55 |
<div class="xp-bar">
|
56 |
<div
|
57 |
class="xp-fill"
|
58 |
+
style="width: {xpTowardsNext.percentage}%"
|
59 |
></div>
|
60 |
</div>
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
{/if}
|
63 |
</div>
|
64 |
|
|
|
172 |
.xp-fill {
|
173 |
height: 100%;
|
174 |
background: #2196f3;
|
175 |
+
transition: width 1.2s ease-out;
|
176 |
}
|
177 |
|
178 |
/* XP Text */
|
|
|
183 |
|
184 |
.xp-progress {
|
185 |
font-weight: 500;
|
186 |
+
transition: all 0.3s ease;
|
187 |
}
|
188 |
|
189 |
/* Triangle Pointer */
|
src/lib/components/Pages/Battle.svelte
CHANGED
@@ -365,52 +365,53 @@
|
|
365 |
// Calculate XP gained from defeating the enemy
|
366 |
const xpGained = calculateBattleXp(currentEnemyPiclet, 1);
|
367 |
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
const { newInstance, levelUpInfo } = processAllLevelUps(updatedPlayerPiclet);
|
376 |
-
|
377 |
-
// Save updated Piclet to database
|
378 |
-
if (newInstance.id) {
|
379 |
-
await db.picletInstances.update(newInstance.id, newInstance);
|
380 |
-
}
|
381 |
-
|
382 |
-
// Update local state
|
383 |
-
currentPlayerPiclet = newInstance;
|
384 |
-
|
385 |
-
// Prepare battle results for display
|
386 |
-
battleResults = {
|
387 |
-
victory: true,
|
388 |
-
xpGained,
|
389 |
-
levelUps: levelUpInfo,
|
390 |
-
newLevel: newInstance.level
|
391 |
-
};
|
392 |
-
|
393 |
-
// Show battle results screen
|
394 |
-
if (xpGained > 0 || levelUpInfo.length > 0) {
|
395 |
-
battleResultsVisible = true;
|
396 |
|
397 |
-
//
|
398 |
-
|
399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
onBattleEnd(true);
|
401 |
-
}
|
402 |
} else {
|
403 |
onBattleEnd(true);
|
404 |
}
|
405 |
} else {
|
406 |
// Player lost - no XP gained
|
407 |
-
battleResults = {
|
408 |
-
victory: false,
|
409 |
-
xpGained: 0,
|
410 |
-
levelUps: [],
|
411 |
-
newLevel: currentPlayerPiclet.level
|
412 |
-
};
|
413 |
-
|
414 |
onBattleEnd(false);
|
415 |
}
|
416 |
}
|
@@ -464,11 +465,6 @@
|
|
464 |
<div class="battle-results-card">
|
465 |
<h2>{battleResults.victory ? 'Victory!' : 'Defeat!'}</h2>
|
466 |
|
467 |
-
{#if battleResults.victory && battleResults.xpGained > 0}
|
468 |
-
<div class="xp-gain">
|
469 |
-
<p><strong>{currentPlayerPiclet.nickname}</strong> gained <strong>{battleResults.xpGained} XP</strong>!</p>
|
470 |
-
</div>
|
471 |
-
{/if}
|
472 |
|
473 |
{#if battleResults.levelUps.length > 0}
|
474 |
{#each battleResults.levelUps as levelUp}
|
@@ -592,19 +588,6 @@
|
|
592 |
color: #1a1a1a;
|
593 |
}
|
594 |
|
595 |
-
.xp-gain {
|
596 |
-
background: #e3f2fd;
|
597 |
-
border-radius: 8px;
|
598 |
-
padding: 1rem;
|
599 |
-
margin: 1rem 0;
|
600 |
-
border: 2px solid #2196f3;
|
601 |
-
}
|
602 |
-
|
603 |
-
.xp-gain p {
|
604 |
-
margin: 0;
|
605 |
-
color: #1565c0;
|
606 |
-
font-size: 1.1rem;
|
607 |
-
}
|
608 |
|
609 |
.level-up {
|
610 |
background: linear-gradient(135deg, #fff3e0 0%, #ffcc02 100%);
|
|
|
365 |
// Calculate XP gained from defeating the enemy
|
366 |
const xpGained = calculateBattleXp(currentEnemyPiclet, 1);
|
367 |
|
368 |
+
if (xpGained > 0) {
|
369 |
+
// Animate XP gain by updating UI first
|
370 |
+
const updatedPlayerPiclet = {
|
371 |
+
...currentPlayerPiclet,
|
372 |
+
xp: currentPlayerPiclet.xp + xpGained
|
373 |
+
};
|
374 |
+
currentPlayerPiclet = updatedPlayerPiclet;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
|
376 |
+
// Wait a moment for XP bar animation
|
377 |
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
378 |
+
|
379 |
+
// Process any level ups
|
380 |
+
const { newInstance, levelUpInfo } = processAllLevelUps(updatedPlayerPiclet);
|
381 |
+
|
382 |
+
// Save updated Piclet to database
|
383 |
+
if (newInstance.id) {
|
384 |
+
await db.picletInstances.update(newInstance.id, newInstance);
|
385 |
+
}
|
386 |
+
|
387 |
+
// Update local state with final leveled instance
|
388 |
+
currentPlayerPiclet = newInstance;
|
389 |
+
|
390 |
+
// Show level up results if any occurred
|
391 |
+
if (levelUpInfo.length > 0) {
|
392 |
+
battleResults = {
|
393 |
+
victory: true,
|
394 |
+
xpGained,
|
395 |
+
levelUps: levelUpInfo,
|
396 |
+
newLevel: newInstance.level
|
397 |
+
};
|
398 |
+
|
399 |
+
battleResultsVisible = true;
|
400 |
+
|
401 |
+
// Auto-dismiss after showing level up
|
402 |
+
setTimeout(() => {
|
403 |
+
battleResultsVisible = false;
|
404 |
+
onBattleEnd(true);
|
405 |
+
}, 4000);
|
406 |
+
} else {
|
407 |
+
// No level up, just end battle
|
408 |
onBattleEnd(true);
|
409 |
+
}
|
410 |
} else {
|
411 |
onBattleEnd(true);
|
412 |
}
|
413 |
} else {
|
414 |
// Player lost - no XP gained
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
415 |
onBattleEnd(false);
|
416 |
}
|
417 |
}
|
|
|
465 |
<div class="battle-results-card">
|
466 |
<h2>{battleResults.victory ? 'Victory!' : 'Defeat!'}</h2>
|
467 |
|
|
|
|
|
|
|
|
|
|
|
468 |
|
469 |
{#if battleResults.levelUps.length > 0}
|
470 |
{#each battleResults.levelUps as levelUp}
|
|
|
588 |
color: #1a1a1a;
|
589 |
}
|
590 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
591 |
|
592 |
.level-up {
|
593 |
background: linear-gradient(135deg, #fff3e0 0%, #ffcc02 100%);
|
src/lib/components/Piclets/PicletDetail.svelte
CHANGED
@@ -8,7 +8,7 @@
|
|
8 |
import AbilityDisplay from './AbilityDisplay.svelte';
|
9 |
import MoveDisplay from './MoveDisplay.svelte';
|
10 |
import { picletInstanceToBattleDefinition } from '$lib/utils/battleConversion';
|
11 |
-
import { recalculatePicletStats, getXpProgress,
|
12 |
|
13 |
interface Props {
|
14 |
instance: PicletInstance;
|
@@ -28,7 +28,7 @@
|
|
28 |
|
29 |
// XP and level calculations
|
30 |
const xpProgress = $derived(getXpProgress(updatedInstance.xp, updatedInstance.level, updatedInstance.tier));
|
31 |
-
const
|
32 |
|
33 |
// Type-based styling
|
34 |
const typeData = $derived(TYPE_DATA[instance.primaryType]);
|
@@ -136,7 +136,7 @@
|
|
136 |
<div class="level-info">
|
137 |
<span class="level-label">Level {updatedInstance.level}</span>
|
138 |
{#if updatedInstance.level < 100}
|
139 |
-
<span class="xp-label">{
|
140 |
{:else}
|
141 |
<span class="xp-label">MAX LEVEL</span>
|
142 |
{/if}
|
@@ -144,7 +144,7 @@
|
|
144 |
|
145 |
{#if updatedInstance.level < 100}
|
146 |
<div class="xp-progress-bar">
|
147 |
-
<div class="xp-progress-fill" style="width: {
|
148 |
</div>
|
149 |
{/if}
|
150 |
|
|
|
8 |
import AbilityDisplay from './AbilityDisplay.svelte';
|
9 |
import MoveDisplay from './MoveDisplay.svelte';
|
10 |
import { picletInstanceToBattleDefinition } from '$lib/utils/battleConversion';
|
11 |
+
import { recalculatePicletStats, getXpProgress, getXpTowardsNextLevel } from '$lib/services/levelingService';
|
12 |
|
13 |
interface Props {
|
14 |
instance: PicletInstance;
|
|
|
28 |
|
29 |
// XP and level calculations
|
30 |
const xpProgress = $derived(getXpProgress(updatedInstance.xp, updatedInstance.level, updatedInstance.tier));
|
31 |
+
const xpTowardsNext = $derived(getXpTowardsNextLevel(updatedInstance.xp, updatedInstance.level, updatedInstance.tier));
|
32 |
|
33 |
// Type-based styling
|
34 |
const typeData = $derived(TYPE_DATA[instance.primaryType]);
|
|
|
136 |
<div class="level-info">
|
137 |
<span class="level-label">Level {updatedInstance.level}</span>
|
138 |
{#if updatedInstance.level < 100}
|
139 |
+
<span class="xp-label">{xpTowardsNext.current}/{xpTowardsNext.needed} to next level</span>
|
140 |
{:else}
|
141 |
<span class="xp-label">MAX LEVEL</span>
|
142 |
{/if}
|
|
|
144 |
|
145 |
{#if updatedInstance.level < 100}
|
146 |
<div class="xp-progress-bar">
|
147 |
+
<div class="xp-progress-fill" style="width: {xpTowardsNext.percentage}%"></div>
|
148 |
</div>
|
149 |
{/if}
|
150 |
|
src/lib/services/levelingService.ts
CHANGED
@@ -172,6 +172,31 @@ export function getXpProgress(currentXp: number, currentLevel: number, tier: str
|
|
172 |
return Math.min(100, Math.max(0, (xpIntoLevel / xpNeededForLevel) * 100));
|
173 |
}
|
174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
175 |
/**
|
176 |
* Recalculate all stats for a Piclet based on current level and nature
|
177 |
*/
|
|
|
172 |
return Math.min(100, Math.max(0, (xpIntoLevel / xpNeededForLevel) * 100));
|
173 |
}
|
174 |
|
175 |
+
/**
|
176 |
+
* Get current XP towards next level in X/Y format
|
177 |
+
*/
|
178 |
+
export function getXpTowardsNextLevel(currentXp: number, currentLevel: number, tier: string = 'medium'): {
|
179 |
+
current: number;
|
180 |
+
needed: number;
|
181 |
+
percentage: number;
|
182 |
+
} {
|
183 |
+
if (currentLevel >= 100) {
|
184 |
+
return { current: 0, needed: 0, percentage: 100 };
|
185 |
+
}
|
186 |
+
|
187 |
+
const currentLevelXp = getXpForLevel(currentLevel, tier);
|
188 |
+
const nextLevelXp = getXpForLevel(currentLevel + 1, tier);
|
189 |
+
const xpIntoLevel = Math.max(0, currentXp - currentLevelXp);
|
190 |
+
const xpNeededForLevel = nextLevelXp - currentLevelXp;
|
191 |
+
const percentage = Math.min(100, Math.max(0, (xpIntoLevel / xpNeededForLevel) * 100));
|
192 |
+
|
193 |
+
return {
|
194 |
+
current: xpIntoLevel,
|
195 |
+
needed: xpNeededForLevel,
|
196 |
+
percentage
|
197 |
+
};
|
198 |
+
}
|
199 |
+
|
200 |
/**
|
201 |
* Recalculate all stats for a Piclet based on current level and nature
|
202 |
*/
|