Fraser commited on
Commit
d871370
·
1 Parent(s): e8aa797
src/lib/components/Battle/PicletInfo.svelte CHANGED
@@ -8,8 +8,8 @@
8
  export let isPlayer: boolean;
9
 
10
  // Calculate real XP percentage using levelingService
11
- $: realXpPercentage = isPlayer ? getXpProgress(piclet.xp, piclet.level) : 0;
12
- $: nextLevelXp = isPlayer ? getXpForNextLevel(piclet.level) : 0;
13
 
14
  $: hpColor = hpPercentage > 0.5 ? '#4caf50' : hpPercentage > 0.2 ? '#ffc107' : '#f44336';
15
  $: displayHp = Math.ceil(piclet.currentHp);
 
8
  export let isPlayer: boolean;
9
 
10
  // Calculate real XP percentage using levelingService
11
+ $: realXpPercentage = isPlayer ? getXpProgress(piclet.xp, piclet.level, piclet.tier) : 0;
12
+ $: nextLevelXp = isPlayer ? getXpForNextLevel(piclet.level, piclet.tier) : 0;
13
 
14
  $: hpColor = hpPercentage > 0.5 ? '#4caf50' : hpPercentage > 0.2 ? '#ffc107' : '#f44336';
15
  $: displayHp = Math.ceil(piclet.currentHp);
src/lib/components/Pages/Battle.svelte CHANGED
@@ -7,7 +7,7 @@
7
  import { BattleEngine } from '$lib/battle-engine/BattleEngine';
8
  import type { BattleState, MoveAction } from '$lib/battle-engine/types';
9
  import { picletInstanceToBattleDefinition, battlePicletToInstance, stripBattlePrefix } from '$lib/utils/battleConversion';
10
- import { calculateBattleXp, processAllLevelUps, getXpProgress } from '$lib/services/levelingService';
11
  import { db } from '$lib/db/index';
12
  import { getEffectivenessText, getEffectivenessColor } from '$lib/types/picletTypes';
13
 
 
7
  import { BattleEngine } from '$lib/battle-engine/BattleEngine';
8
  import type { BattleState, MoveAction } from '$lib/battle-engine/types';
9
  import { picletInstanceToBattleDefinition, battlePicletToInstance, stripBattlePrefix } from '$lib/utils/battleConversion';
10
+ import { calculateBattleXp, processAllLevelUps } from '$lib/services/levelingService';
11
  import { db } from '$lib/db/index';
12
  import { getEffectivenessText, getEffectivenessColor } from '$lib/types/picletTypes';
13
 
src/lib/components/Piclets/PicletDetail.svelte CHANGED
@@ -8,6 +8,7 @@
8
  import AbilityDisplay from './AbilityDisplay.svelte';
9
  import MoveDisplay from './MoveDisplay.svelte';
10
  import { picletInstanceToBattleDefinition } from '$lib/utils/battleConversion';
 
11
 
12
  interface Props {
13
  instance: PicletInstance;
@@ -19,8 +20,15 @@
19
  let selectedTab = $state<'about' | 'abilities'>('about');
20
  let isSharing = $state(false);
21
 
 
 
 
22
  // Convert to battle definition to get enhanced ability data
23
- const battleDefinition = $derived(picletInstanceToBattleDefinition(instance));
 
 
 
 
24
 
25
  // Type-based styling
26
  const typeData = $derived(TYPE_DATA[instance.primaryType]);
@@ -93,7 +101,7 @@
93
  <path d="M19 12H5m0 0l7 7m-7-7l7-7"></path>
94
  </svg>
95
  </button>
96
- <h1 class="card-title">{instance.nickname || instance.typeId}</h1>
97
  <button
98
  class="share-button"
99
  onclick={handleShare}
@@ -114,8 +122,8 @@
114
  <div class="large-image-section">
115
  <div class="large-image-container">
116
  <img
117
- src={instance.imageData || instance.imageUrl}
118
- alt={instance.nickname || instance.typeId}
119
  class="large-piclet-image"
120
  />
121
  </div>
@@ -123,6 +131,28 @@
123
  </div>
124
  </div>
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  <!-- Tab Bar -->
127
  <div class="tab-bar" style="--type-color: {typeColor}">
128
  <button
@@ -153,23 +183,23 @@
153
  <div class="stats-list">
154
  <div class="stat-row">
155
  <span>Attack</span>
156
- <span class="stat-value">{instance.attack}</span>
157
  </div>
158
  <div class="stat-row">
159
  <span>Defense</span>
160
- <span class="stat-value">{instance.defense}</span>
161
  </div>
162
  <div class="stat-row">
163
  <span>Field Attack</span>
164
- <span class="stat-value">{instance.fieldAttack}</span>
165
  </div>
166
  <div class="stat-row">
167
  <span>Field Defense</span>
168
- <span class="stat-value">{instance.fieldDefense}</span>
169
  </div>
170
  <div class="stat-row">
171
  <span>Speed</span>
172
- <span class="stat-value">{instance.speed}</span>
173
  </div>
174
  </div>
175
 
@@ -178,11 +208,11 @@
178
  <div class="stat-summary">
179
  <div class="summary-item">
180
  <span class="summary-label">BST</span>
181
- <span class="summary-value">{instance.bst}</span>
182
  </div>
183
  <div class="summary-item">
184
  <span class="summary-label">Tier</span>
185
- <span class="summary-value">{instance.tier.toUpperCase()}</span>
186
  </div>
187
  </div>
188
  </div>
@@ -198,7 +228,7 @@
198
 
199
  <h3 class="section-heading">Moves</h3>
200
  <div class="moves-list">
201
- {#each instance.moves as move, index}
202
  <MoveDisplay
203
  {move}
204
  expanded={true}
@@ -496,6 +526,65 @@
496
  gap: 4px;
497
  }
498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  @media (min-width: 768px) {
500
  .detail-page {
501
  position: relative;
 
8
  import AbilityDisplay from './AbilityDisplay.svelte';
9
  import MoveDisplay from './MoveDisplay.svelte';
10
  import { picletInstanceToBattleDefinition } from '$lib/utils/battleConversion';
11
+ import { recalculatePicletStats, getXpProgress, getXpForNextLevel } from '$lib/services/levelingService';
12
 
13
  interface Props {
14
  instance: PicletInstance;
 
20
  let selectedTab = $state<'about' | 'abilities'>('about');
21
  let isSharing = $state(false);
22
 
23
+ // Ensure stats are up-to-date with current level and nature
24
+ const updatedInstance = $derived(recalculatePicletStats(instance));
25
+
26
  // Convert to battle definition to get enhanced ability data
27
+ const battleDefinition = $derived(picletInstanceToBattleDefinition(updatedInstance));
28
+
29
+ // XP and level calculations
30
+ const xpProgress = $derived(getXpProgress(updatedInstance.xp, updatedInstance.level, updatedInstance.tier));
31
+ const nextLevelXp = $derived(getXpForNextLevel(updatedInstance.level, updatedInstance.tier));
32
 
33
  // Type-based styling
34
  const typeData = $derived(TYPE_DATA[instance.primaryType]);
 
101
  <path d="M19 12H5m0 0l7 7m-7-7l7-7"></path>
102
  </svg>
103
  </button>
104
+ <h1 class="card-title">{updatedInstance.nickname || updatedInstance.typeId}</h1>
105
  <button
106
  class="share-button"
107
  onclick={handleShare}
 
122
  <div class="large-image-section">
123
  <div class="large-image-container">
124
  <img
125
+ src={updatedInstance.imageData || updatedInstance.imageUrl}
126
+ alt={updatedInstance.nickname || updatedInstance.typeId}
127
  class="large-piclet-image"
128
  />
129
  </div>
 
131
  </div>
132
  </div>
133
 
134
+ <!-- Level and XP Progress -->
135
+ <div class="level-xp-section">
136
+ <div class="level-info">
137
+ <span class="level-label">Level {updatedInstance.level}</span>
138
+ {#if updatedInstance.level < 100}
139
+ <span class="xp-label">{Math.floor(xpProgress)}% to next level</span>
140
+ {:else}
141
+ <span class="xp-label">MAX LEVEL</span>
142
+ {/if}
143
+ </div>
144
+
145
+ {#if updatedInstance.level < 100}
146
+ <div class="xp-progress-bar">
147
+ <div class="xp-progress-fill" style="width: {xpProgress}%"></div>
148
+ </div>
149
+ {/if}
150
+
151
+ <div class="hp-info">
152
+ <span class="hp-label">HP: {updatedInstance.currentHp}/{updatedInstance.maxHp}</span>
153
+ </div>
154
+ </div>
155
+
156
  <!-- Tab Bar -->
157
  <div class="tab-bar" style="--type-color: {typeColor}">
158
  <button
 
183
  <div class="stats-list">
184
  <div class="stat-row">
185
  <span>Attack</span>
186
+ <span class="stat-value">{updatedInstance.attack}</span>
187
  </div>
188
  <div class="stat-row">
189
  <span>Defense</span>
190
+ <span class="stat-value">{updatedInstance.defense}</span>
191
  </div>
192
  <div class="stat-row">
193
  <span>Field Attack</span>
194
+ <span class="stat-value">{updatedInstance.fieldAttack}</span>
195
  </div>
196
  <div class="stat-row">
197
  <span>Field Defense</span>
198
+ <span class="stat-value">{updatedInstance.fieldDefense}</span>
199
  </div>
200
  <div class="stat-row">
201
  <span>Speed</span>
202
+ <span class="stat-value">{updatedInstance.speed}</span>
203
  </div>
204
  </div>
205
 
 
208
  <div class="stat-summary">
209
  <div class="summary-item">
210
  <span class="summary-label">BST</span>
211
+ <span class="summary-value">{updatedInstance.bst}</span>
212
  </div>
213
  <div class="summary-item">
214
  <span class="summary-label">Tier</span>
215
+ <span class="summary-value">{updatedInstance.tier.toUpperCase()}</span>
216
  </div>
217
  </div>
218
  </div>
 
228
 
229
  <h3 class="section-heading">Moves</h3>
230
  <div class="moves-list">
231
+ {#each updatedInstance.moves as move, index}
232
  <MoveDisplay
233
  {move}
234
  expanded={true}
 
526
  gap: 4px;
527
  }
528
 
529
+ /* Level and XP Section */
530
+ .level-xp-section {
531
+ background: white;
532
+ margin: 0 16px 16px;
533
+ border-radius: 12px;
534
+ padding: 16px;
535
+ border: 0.5px solid #c6c6c8;
536
+ }
537
+
538
+ .level-info {
539
+ display: flex;
540
+ justify-content: space-between;
541
+ align-items: center;
542
+ margin-bottom: 12px;
543
+ }
544
+
545
+ .level-label {
546
+ font-size: 18px;
547
+ font-weight: 700;
548
+ color: #1a1a1a;
549
+ }
550
+
551
+ .xp-label {
552
+ font-size: 14px;
553
+ font-weight: 500;
554
+ color: #8e8e93;
555
+ }
556
+
557
+ .xp-progress-bar {
558
+ width: 100%;
559
+ height: 8px;
560
+ background: #e5e5ea;
561
+ border-radius: 4px;
562
+ overflow: hidden;
563
+ margin-bottom: 12px;
564
+ }
565
+
566
+ .xp-progress-fill {
567
+ height: 100%;
568
+ background: linear-gradient(90deg, #2196f3 0%, #21cbf3 100%);
569
+ border-radius: 4px;
570
+ transition: width 0.8s ease;
571
+ }
572
+
573
+ .hp-info {
574
+ display: flex;
575
+ justify-content: center;
576
+ }
577
+
578
+ .hp-label {
579
+ font-size: 14px;
580
+ font-weight: 600;
581
+ color: #495057;
582
+ background: rgba(76, 175, 80, 0.1);
583
+ border: 1px solid #4caf50;
584
+ border-radius: 16px;
585
+ padding: 4px 12px;
586
+ }
587
+
588
  @media (min-width: 768px) {
589
  .detail-page {
590
  position: relative;
src/lib/services/levelingService.ts CHANGED
@@ -36,10 +36,31 @@ export const NATURES = {
36
 
37
  export type NatureName = keyof typeof NATURES;
38
 
39
- // Experience requirements for Medium Fast growth rate (level³)
40
- const XP_REQUIREMENTS: number[] = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  for (let level = 1; level <= 100; level++) {
42
- XP_REQUIREMENTS[level] = level * level * level;
43
  }
44
 
45
  export interface LevelUpInfo {
@@ -110,29 +131,41 @@ export function getNatureModifiers(nature: string): NatureModifiers {
110
  /**
111
  * Get XP required to reach a specific level
112
  */
113
- export function getXpForLevel(level: number): number {
 
 
 
114
  if (level < 1 || level > 100) {
115
  throw new Error('Level must be between 1 and 100');
116
  }
117
- return XP_REQUIREMENTS[level];
 
 
 
118
  }
119
 
120
  /**
121
  * Get XP required for next level
122
  */
123
- export function getXpForNextLevel(currentLevel: number): number {
 
 
 
124
  if (currentLevel >= 100) return 0; // Max level
125
- return getXpForLevel(currentLevel + 1);
126
  }
127
 
128
  /**
129
  * Calculate XP progress percentage for current level
130
  */
131
- export function getXpProgress(currentXp: number, currentLevel: number): number {
 
 
 
132
  if (currentLevel >= 100) return 100;
133
 
134
- const currentLevelXp = getXpForLevel(currentLevel);
135
- const nextLevelXp = getXpForLevel(currentLevel + 1);
136
  const xpIntoLevel = currentXp - currentLevelXp;
137
  const xpNeededForLevel = nextLevelXp - currentLevelXp;
138
 
@@ -178,7 +211,7 @@ export function processLevelUp(instance: PicletInstance): {
178
  newInstance: PicletInstance;
179
  levelUpInfo: LevelUpInfo | null;
180
  } {
181
- const requiredXp = getXpForNextLevel(instance.level);
182
 
183
  // Check if level up is possible
184
  if (instance.level >= 100 || instance.xp < requiredXp) {
 
36
 
37
  export type NatureName = keyof typeof NATURES;
38
 
39
+ // Growth rate multipliers for different tiers
40
+ const TIER_XP_MULTIPLIERS = {
41
+ 'low': 0.8, // 20% less XP required (faster leveling)
42
+ 'medium': 1.0, // Base XP requirements
43
+ 'high': 1.4, // 40% more XP required (slower leveling)
44
+ 'legendary': 1.8 // 80% more XP required (much slower leveling)
45
+ } as const;
46
+
47
+ type TierType = keyof typeof TIER_XP_MULTIPLIERS;
48
+
49
+ /**
50
+ * Convert string tier to TierType, defaulting to 'medium' for unknown values
51
+ */
52
+ function normalizeTier(tier: string): TierType {
53
+ if (tier in TIER_XP_MULTIPLIERS) {
54
+ return tier as TierType;
55
+ }
56
+ return 'medium'; // Default fallback
57
+ }
58
+
59
+ // Base experience requirements for Medium Fast growth rate (level³)
60
+ // Other tiers will use multipliers of this base
61
+ const BASE_XP_REQUIREMENTS: number[] = [];
62
  for (let level = 1; level <= 100; level++) {
63
+ BASE_XP_REQUIREMENTS[level] = level * level * level;
64
  }
65
 
66
  export interface LevelUpInfo {
 
131
  /**
132
  * Get XP required to reach a specific level
133
  */
134
+ /**
135
+ * Get XP required for a specific level based on tier
136
+ */
137
+ export function getXpForLevel(level: number, tier: string = 'medium'): number {
138
  if (level < 1 || level > 100) {
139
  throw new Error('Level must be between 1 and 100');
140
  }
141
+ const normalizedTier = normalizeTier(tier);
142
+ const baseXp = BASE_XP_REQUIREMENTS[level];
143
+ const multiplier = TIER_XP_MULTIPLIERS[normalizedTier];
144
+ return Math.floor(baseXp * multiplier);
145
  }
146
 
147
  /**
148
  * Get XP required for next level
149
  */
150
+ /**
151
+ * Get XP required for next level based on tier
152
+ */
153
+ export function getXpForNextLevel(currentLevel: number, tier: string = 'medium'): number {
154
  if (currentLevel >= 100) return 0; // Max level
155
+ return getXpForLevel(currentLevel + 1, tier);
156
  }
157
 
158
  /**
159
  * Calculate XP progress percentage for current level
160
  */
161
+ /**
162
+ * Get XP progress percentage towards next level based on tier
163
+ */
164
+ export function getXpProgress(currentXp: number, currentLevel: number, tier: string = 'medium'): number {
165
  if (currentLevel >= 100) return 100;
166
 
167
+ const currentLevelXp = getXpForLevel(currentLevel, tier);
168
+ const nextLevelXp = getXpForLevel(currentLevel + 1, tier);
169
  const xpIntoLevel = currentXp - currentLevelXp;
170
  const xpNeededForLevel = nextLevelXp - currentLevelXp;
171
 
 
211
  newInstance: PicletInstance;
212
  levelUpInfo: LevelUpInfo | null;
213
  } {
214
+ const requiredXp = getXpForNextLevel(instance.level, instance.tier);
215
 
216
  // Check if level up is possible
217
  if (instance.level >= 100 || instance.xp < requiredXp) {