@rpg-engine/long-bow 0.8.202 → 0.8.205

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.
Files changed (45) hide show
  1. package/dist/components/Item/Inventory/ErrorBoundary.d.ts +2 -0
  2. package/dist/components/Marketplace/BuyOrderPanel.d.ts +2 -2
  3. package/dist/components/Marketplace/BuyOrderRows.d.ts +4 -4
  4. package/dist/components/Marketplace/HistoryPanel.d.ts +2 -2
  5. package/dist/components/Marketplace/MarketplaceRows.d.ts +2 -0
  6. package/dist/components/Tutorial/TutorialStepper.d.ts +2 -1
  7. package/dist/components/shared/MMORPGNumber.d.ts +6 -0
  8. package/dist/long-bow.cjs.development.js +794 -34640
  9. package/dist/long-bow.cjs.development.js.map +1 -1
  10. package/dist/long-bow.cjs.production.min.js +1 -1
  11. package/dist/long-bow.cjs.production.min.js.map +1 -1
  12. package/dist/long-bow.esm.js +795 -34641
  13. package/dist/long-bow.esm.js.map +1 -1
  14. package/dist/utils/numberUtils.d.ts +7 -0
  15. package/package.json +1 -1
  16. package/src/components/DCWallet/DCWalletContent.tsx +5 -3
  17. package/src/components/Item/Inventory/ErrorBoundary.tsx +25 -8
  18. package/src/components/Item/Inventory/ItemPropertyColorSelector.tsx +3 -1
  19. package/src/components/Marketplace/BuyOrderPanel.tsx +2 -2
  20. package/src/components/Marketplace/BuyOrderRows.tsx +13 -7
  21. package/src/components/Marketplace/CharacterDetailModal.tsx +99 -17
  22. package/src/components/Marketplace/CharacterListingModal.tsx +2 -1
  23. package/src/components/Marketplace/GroupedRowContainer.tsx +10 -10
  24. package/src/components/Marketplace/HistoryPanel.tsx +5 -5
  25. package/src/components/Marketplace/MarketplaceBuyModal.tsx +4 -4
  26. package/src/components/Marketplace/MarketplaceRows.tsx +13 -7
  27. package/src/components/Marketplace/__test__/CharacterDetailModal.spec.tsx +137 -0
  28. package/src/components/Store/CartView.tsx +2 -1
  29. package/src/components/Store/FeaturedBanner.tsx +1 -0
  30. package/src/components/Store/PaymentMethodModal.tsx +11 -3
  31. package/src/components/Store/StoreCharacterSkinRow.tsx +9 -3
  32. package/src/components/Store/StoreItemDetails.tsx +9 -1
  33. package/src/components/Store/StoreItemRow.tsx +13 -3
  34. package/src/components/Store/sections/StorePacksSection.tsx +7 -0
  35. package/src/components/Store/sections/StoreRedeemSection.tsx +2 -1
  36. package/src/components/Tutorial/TutorialStepper.tsx +16 -3
  37. package/src/components/shared/DCRateStrip.tsx +3 -2
  38. package/src/components/shared/MMORPGNumber.tsx +22 -0
  39. package/src/stories/Features/marketplace/CharacterDetailModal.stories.tsx +69 -15
  40. package/src/stories/Features/marketplace/CharacterMarketplace.stories.tsx +105 -11
  41. package/src/stories/Features/store/Store.stories.tsx +125 -35
  42. package/src/stories/Features/trading/Marketplace.stories.tsx +8 -4
  43. package/src/utils/__test__/atlasUtils.spec.ts +15 -0
  44. package/src/utils/atlasUtils.ts +78 -0
  45. package/src/utils/numberUtils.ts +31 -0
@@ -37,8 +37,24 @@ const mockCharacterListings: ICharacterListing[] = [
37
37
  mode: 'Hardcore',
38
38
  skills: { sword: 10, shield: 8 },
39
39
  equipment: [
40
- { slot: 'weapon', itemName: 'Broad Sword', itemKey: 'items/broad-sword', rarity: 'Common' },
41
- { slot: 'armor', itemName: 'Steel Armor', itemKey: 'items/steel-armor', rarity: 'Rare' },
40
+ {
41
+ slot: 'rightHand',
42
+ itemName: 'Broad Sword',
43
+ itemKey: 'broad-sword',
44
+ rarity: 'Common',
45
+ },
46
+ {
47
+ slot: 'armor',
48
+ itemName: 'Jacket',
49
+ itemKey: 'jacket',
50
+ rarity: 'Rare',
51
+ },
52
+ {
53
+ slot: 'head',
54
+ itemName: 'Iron Helmet',
55
+ itemKey: 'iron-helmet',
56
+ rarity: 'Uncommon',
57
+ },
42
58
  ],
43
59
  textureKey: 'black-knight',
44
60
  },
@@ -61,7 +77,24 @@ const mockCharacterListings: ICharacterListing[] = [
61
77
  mode: 'Standard',
62
78
  skills: { fireball: 15, frostbolt: 12 },
63
79
  equipment: [
64
- { slot: 'weapon', itemName: 'Fire Staff', itemKey: 'items/fire-staff', rarity: 'Epic' },
80
+ {
81
+ slot: 'rightHand',
82
+ itemName: 'Fire Staff',
83
+ itemKey: 'fire-staff',
84
+ rarity: 'Epic',
85
+ },
86
+ {
87
+ slot: 'armor',
88
+ itemName: 'Mystic Cape',
89
+ itemKey: 'mystic-cape',
90
+ rarity: 'Legendary',
91
+ },
92
+ {
93
+ slot: 'neck',
94
+ itemName: 'Bandana',
95
+ itemKey: 'bandana',
96
+ rarity: 'Rare',
97
+ },
65
98
  ],
66
99
  textureKey: 'pink-mage-1',
67
100
  },
@@ -84,7 +117,18 @@ const mockCharacterListings: ICharacterListing[] = [
84
117
  mode: 'Hardcore',
85
118
  skills: { stealth: 10, backstab: 12 },
86
119
  equipment: [
87
- { slot: 'weapon', itemName: 'Dagger', itemKey: 'items/dagger', rarity: 'Uncommon' },
120
+ {
121
+ slot: 'rightHand',
122
+ itemName: 'Dagger',
123
+ itemKey: 'dagger',
124
+ rarity: 'Uncommon',
125
+ },
126
+ {
127
+ slot: 'neck',
128
+ itemName: 'Bandana',
129
+ itemKey: 'bandana',
130
+ rarity: 'Common',
131
+ },
88
132
  ],
89
133
  textureKey: 'redhair-girl-1',
90
134
  },
@@ -107,8 +151,24 @@ const mockCharacterListings: ICharacterListing[] = [
107
151
  mode: 'Standard',
108
152
  skills: { holy: 20, shield: 15 },
109
153
  equipment: [
110
- { slot: 'weapon', itemName: 'Holy Sword', itemKey: 'items/holy-sword', rarity: 'Legendary' },
111
- { slot: 'armor', itemName: 'Divine Armor', itemKey: 'items/divine-armor', rarity: 'Epic' },
154
+ {
155
+ slot: 'rightHand',
156
+ itemName: 'Angelic Sword',
157
+ itemKey: 'angelic-sword',
158
+ rarity: 'Legendary',
159
+ },
160
+ {
161
+ slot: 'armor',
162
+ itemName: 'Leather Jacket',
163
+ itemKey: 'leather-jacket',
164
+ rarity: 'Epic',
165
+ },
166
+ {
167
+ slot: 'head',
168
+ itemName: 'Iron Helmet',
169
+ itemKey: 'iron-helmet',
170
+ rarity: 'Rare',
171
+ },
112
172
  ],
113
173
  textureKey: 'dragon-knight',
114
174
  },
@@ -131,7 +191,18 @@ const mockCharacterListings: ICharacterListing[] = [
131
191
  mode: 'Standard',
132
192
  skills: { archery: 12, tracking: 8 },
133
193
  equipment: [
134
- { slot: 'weapon', itemName: 'Longbow', itemKey: 'items/longbow', rarity: 'Rare' },
194
+ {
195
+ slot: 'rightHand',
196
+ itemName: 'Redwood Longbow',
197
+ itemKey: 'redwood-longbow',
198
+ rarity: 'Rare',
199
+ },
200
+ {
201
+ slot: 'head',
202
+ itemName: 'Hunters Cap',
203
+ itemKey: 'hunters-cap',
204
+ rarity: 'Uncommon',
205
+ },
135
206
  ],
136
207
  textureKey: 'pink-hair-girl-1',
137
208
  },
@@ -157,7 +228,18 @@ const mockMyCharacterListings: ICharacterListing[] = [
157
228
  mode: 'Standard',
158
229
  skills: { sword: 18, shield: 16 },
159
230
  equipment: [
160
- { slot: 'weapon', itemName: 'Epic Sword', itemKey: 'items/epic-sword', rarity: 'Epic' },
231
+ {
232
+ slot: 'rightHand',
233
+ itemName: 'Knights Sword',
234
+ itemKey: 'knights-sword',
235
+ rarity: 'Epic',
236
+ },
237
+ {
238
+ slot: 'armor',
239
+ itemName: 'Leather Jacket',
240
+ itemKey: 'leather-jacket',
241
+ rarity: 'Rare',
242
+ },
161
243
  ],
162
244
  textureKey: 'black-knight',
163
245
  },
@@ -180,7 +262,18 @@ const mockMyCharacterListings: ICharacterListing[] = [
180
262
  mode: 'Standard',
181
263
  skills: { fireball: 8 },
182
264
  equipment: [
183
- { slot: 'weapon', itemName: 'Basic Staff', itemKey: 'items/staff', rarity: 'Common' },
265
+ {
266
+ slot: 'rightHand',
267
+ itemName: 'Wooden Staff',
268
+ itemKey: 'wooden-staff',
269
+ rarity: 'Common',
270
+ },
271
+ {
272
+ slot: 'neck',
273
+ itemName: 'Bandana',
274
+ itemKey: 'bandana',
275
+ rarity: 'Common',
276
+ },
184
277
  ],
185
278
  textureKey: 'red-mage-1',
186
279
  },
@@ -277,7 +370,7 @@ CharacterBrowseEmpty.storyName = 'Empty State';
277
370
  export const CharacterBrowseFiltered: Story = () => (
278
371
  <RPGUIRoot>
279
372
  <CharacterMarketplacePanel
280
- characterListings={mockCharacterListings.filter(l =>
373
+ characterListings={mockCharacterListings.filter((l) =>
281
374
  l.characterSnapshot.name.toLowerCase().includes('warrior')
282
375
  )}
283
376
  totalCount={2}
@@ -359,7 +452,8 @@ export const MyCharacterListingsNoEligible: Story = () => (
359
452
  </RPGUIRoot>
360
453
  );
361
454
 
362
- MyCharacterListingsNoEligible.storyName = 'My Listings - No eligible chars to list';
455
+ MyCharacterListingsNoEligible.storyName =
456
+ 'My Listings - No eligible chars to list';
363
457
 
364
458
  export const CharacterListingPending: Story = () => (
365
459
  <RPGUIRoot>
@@ -155,23 +155,99 @@ const characterSkinItems: IProductBlueprint[] = [
155
155
  }
156
156
  ];
157
157
 
158
- // Create duplicated items once with unique keys
159
- const duplicatedItems: IProductBlueprint[] = [
158
+ // Showcase items for large number formatting
159
+ const largePriceItems: IProductBlueprint[] = [
160
+ {
161
+ key: 'mega-booster',
162
+ name: 'Mega Booster',
163
+ description: 'A massive boost for high-end players.',
164
+ price: 15.00,
165
+ currency: PaymentCurrency.USD,
166
+ texturePath: 'items/angelic_sword_1.png',
167
+ textureAtlas: 'items',
168
+ textureKey: 'items/angelic_sword_1.png',
169
+ type: PurchaseType.Item,
170
+ dcPrice: 150000, // 150k
171
+ onPurchase: async () => { },
172
+ itemType: ItemType.Other,
173
+ itemSubType: ItemSubType.Other,
174
+ rarity: ItemRarities.Rare,
175
+ weight: 0,
176
+ isStackable: true,
177
+ maxStackSize: 99,
178
+ isUsable: true,
179
+ },
180
+ {
181
+ key: 'galactic-throne',
182
+ name: 'Galactic Throne',
183
+ description: 'The ultimate symbol of wealth.',
184
+ price: 99.99,
185
+ currency: PaymentCurrency.USD,
186
+ texturePath: 'items/throne.png',
187
+ textureAtlas: 'items',
188
+ textureKey: 'items/throne.png',
189
+ type: PurchaseType.Item,
190
+ dcPrice: 2500000, // 2.5M
191
+ onPurchase: async () => { },
192
+ itemType: ItemType.Other,
193
+ itemSubType: ItemSubType.Other,
194
+ rarity: ItemRarities.Epic,
195
+ weight: 10,
196
+ isStackable: false,
197
+ maxStackSize: 1,
198
+ isUsable: true,
199
+ },
200
+ {
201
+ key: 'godly-artifact',
202
+ name: 'Godly Artifact',
203
+ description: 'Relic from ancient times.',
204
+ price: 1000.00,
205
+ currency: PaymentCurrency.USD,
206
+ texturePath: 'items/staff_1.png',
207
+ textureAtlas: 'items',
208
+ textureKey: 'items/staff_1.png',
209
+ type: PurchaseType.Item,
210
+ dcPrice: 1600000000, // 1.6B
211
+ onPurchase: async () => { },
212
+ itemType: ItemType.Other,
213
+ itemSubType: ItemSubType.Other,
214
+ rarity: ItemRarities.Legendary,
215
+ weight: 0,
216
+ isStackable: false,
217
+ maxStackSize: 1,
218
+ isUsable: true,
219
+ }
220
+ ];
221
+
222
+ // Create Social Crystal item
223
+ const socialCrystalItem: IProductBlueprint = {
224
+ key: 'social-crystal',
225
+ name: 'Social Crystal',
226
+ description: 'A mysterious crystal that resonates with social energy. Use it to unlock community rewards!',
227
+ price: 4.99,
228
+ currency: PaymentCurrency.USD,
229
+ texturePath: 'books/soul-crystal.png',
230
+ textureAtlas: 'items',
231
+ textureKey: 'books/soul-crystal.png',
232
+ type: PurchaseType.Item,
233
+ dcPrice: 250,
234
+ onPurchase: async () => { },
235
+ itemType: ItemType.Other,
236
+ itemSubType: ItemSubType.Other,
237
+ rarity: ItemRarities.Rare,
238
+ weight: 0,
239
+ isStackable: true,
240
+ maxStackSize: 99,
241
+ isUsable: true,
242
+ };
243
+
244
+ // Cleaned up items list without "junk" duplicates
245
+ const allStoreItems: IProductBlueprint[] = [
160
246
  ...storeItems,
161
247
  characterNameChangeItem,
162
248
  ...characterSkinItems,
163
- ...storeItems.map((item, index) => ({
164
- ...item,
165
- key: `copy1-${item.key}-${index}`,
166
- })),
167
- ...storeItems.map((item, index) => ({
168
- ...item,
169
- key: `copy2-${item.key}-${index}`,
170
- })),
171
- ...storeItems.map((item, index) => ({
172
- ...item,
173
- key: `copy3-${item.key}-${index}`,
174
- })),
249
+ socialCrystalItem,
250
+ ...largePriceItems,
175
251
  ];
176
252
 
177
253
  // Mock packs data
@@ -227,7 +303,7 @@ const mockPacks: IItemPack[] = [
227
303
  export const Default: Story = {
228
304
  render: () => (
229
305
  <Store
230
- items={duplicatedItems}
306
+ items={allStoreItems}
231
307
  packs={mockPacks}
232
308
  userAccountType={UserAccountTypes.Free}
233
309
  onPurchase={(purchase: Partial<IPurchase>) => {
@@ -235,7 +311,7 @@ export const Default: Story = {
235
311
  return Promise.resolve(true);
236
312
  }}
237
313
  customWalletContent={<DCWalletContent
238
- dcBalance={1200}
314
+ dcBalance={150000000} // 150M
239
315
  historyData={{ transactions: [], totalPages: 1, currentPage: 1 }}
240
316
  historyLoading={false}
241
317
  onRequestHistory={() => {}}
@@ -274,7 +350,9 @@ export const Default: Story = {
274
350
  'original-greater-life-potion-0': { originalPrice: 15.00 },
275
351
  'character-name-change': { originalPrice: 15.00 },
276
352
  'skin-character-customization': { originalPrice: 20.00 },
277
- 'starter-pack': { originalPrice: 9.99 }
353
+ 'starter-pack': { originalPrice: 9.99 },
354
+ 'mega-booster': { originalPrice: 200000 },
355
+ 'godly-artifact': { originalPrice: 2500000000 }
278
356
  }}
279
357
  />
280
358
  ),
@@ -284,7 +362,7 @@ export const Default: Story = {
284
362
  export const BRL: Story = {
285
363
  render: () => (
286
364
  <Store
287
- items={duplicatedItems}
365
+ items={allStoreItems}
288
366
  packs={mockPacks}
289
367
  userAccountType={UserAccountTypes.Free}
290
368
  onPurchase={(purchase: Partial<IPurchase>) => {
@@ -292,7 +370,7 @@ export const BRL: Story = {
292
370
  return Promise.resolve(true);
293
371
  }}
294
372
  customWalletContent={<DCWalletContent
295
- dcBalance={1200}
373
+ dcBalance={150000000} // 150M
296
374
  historyData={{ transactions: [], totalPages: 1, currentPage: 1 }}
297
375
  historyLoading={false}
298
376
  onRequestHistory={() => {}}
@@ -331,7 +409,9 @@ export const BRL: Story = {
331
409
  'original-greater-life-potion-0': { originalPrice: 15.00 },
332
410
  'character-name-change': { originalPrice: 15.00 },
333
411
  'skin-character-customization': { originalPrice: 20.00 },
334
- 'starter-pack': { originalPrice: 9.99 }
412
+ 'starter-pack': { originalPrice: 9.99 },
413
+ 'mega-booster': { originalPrice: 200000 },
414
+ 'godly-artifact': { originalPrice: 2500000000 }
335
415
  }}
336
416
  />
337
417
  ),
@@ -341,7 +421,7 @@ export const BRL: Story = {
341
421
  export const EUR: Story = {
342
422
  render: () => (
343
423
  <Store
344
- items={duplicatedItems}
424
+ items={allStoreItems}
345
425
  packs={mockPacks}
346
426
  userAccountType={UserAccountTypes.Free}
347
427
  onPurchase={(purchase: Partial<IPurchase>) => {
@@ -349,7 +429,7 @@ export const EUR: Story = {
349
429
  return Promise.resolve(true);
350
430
  }}
351
431
  customWalletContent={<DCWalletContent
352
- dcBalance={1200}
432
+ dcBalance={150000000} // 150M
353
433
  historyData={{ transactions: [], totalPages: 1, currentPage: 1 }}
354
434
  historyLoading={false}
355
435
  onRequestHistory={() => {}}
@@ -388,7 +468,9 @@ export const EUR: Story = {
388
468
  'original-greater-life-potion-0': { originalPrice: 15.00 },
389
469
  'character-name-change': { originalPrice: 15.00 },
390
470
  'skin-character-customization': { originalPrice: 20.00 },
391
- 'starter-pack': { originalPrice: 9.99 }
471
+ 'starter-pack': { originalPrice: 9.99 },
472
+ 'mega-booster': { originalPrice: 200000 },
473
+ 'godly-artifact': { originalPrice: 2500000000 }
392
474
  }}
393
475
  />
394
476
  ),
@@ -398,7 +480,7 @@ export const EUR: Story = {
398
480
  export const GBP: Story = {
399
481
  render: () => (
400
482
  <Store
401
- items={duplicatedItems}
483
+ items={allStoreItems}
402
484
  packs={mockPacks}
403
485
  userAccountType={UserAccountTypes.Free}
404
486
  onPurchase={(purchase: Partial<IPurchase>) => {
@@ -406,7 +488,7 @@ export const GBP: Story = {
406
488
  return Promise.resolve(true);
407
489
  }}
408
490
  customWalletContent={<DCWalletContent
409
- dcBalance={1200}
491
+ dcBalance={150000000} // 150M
410
492
  historyData={{ transactions: [], totalPages: 1, currentPage: 1 }}
411
493
  historyLoading={false}
412
494
  onRequestHistory={() => {}}
@@ -445,7 +527,9 @@ export const GBP: Story = {
445
527
  'original-greater-life-potion-0': { originalPrice: 15.00 },
446
528
  'character-name-change': { originalPrice: 15.00 },
447
529
  'skin-character-customization': { originalPrice: 20.00 },
448
- 'starter-pack': { originalPrice: 9.99 }
530
+ 'starter-pack': { originalPrice: 9.99 },
531
+ 'mega-booster': { originalPrice: 200000 },
532
+ 'godly-artifact': { originalPrice: 2500000000 }
449
533
  }}
450
534
  />
451
535
  ),
@@ -455,7 +539,7 @@ export const GBP: Story = {
455
539
  export const JPY: Story = {
456
540
  render: () => (
457
541
  <Store
458
- items={duplicatedItems}
542
+ items={allStoreItems}
459
543
  packs={mockPacks}
460
544
  userAccountType={UserAccountTypes.Free}
461
545
  onPurchase={(purchase: Partial<IPurchase>) => {
@@ -463,7 +547,7 @@ export const JPY: Story = {
463
547
  return Promise.resolve(true);
464
548
  }}
465
549
  customWalletContent={<DCWalletContent
466
- dcBalance={1200}
550
+ dcBalance={150000000} // 150M
467
551
  historyData={{ transactions: [], totalPages: 1, currentPage: 1 }}
468
552
  historyLoading={false}
469
553
  onRequestHistory={() => {}}
@@ -502,7 +586,9 @@ export const JPY: Story = {
502
586
  'original-greater-life-potion-0': { originalPrice: 15.00 },
503
587
  'character-name-change': { originalPrice: 15.00 },
504
588
  'skin-character-customization': { originalPrice: 20.00 },
505
- 'starter-pack': { originalPrice: 9.99 }
589
+ 'starter-pack': { originalPrice: 9.99 },
590
+ 'mega-booster': { originalPrice: 200000 },
591
+ 'godly-artifact': { originalPrice: 2500000000 }
506
592
  }}
507
593
  />
508
594
  ),
@@ -512,7 +598,7 @@ export const JPY: Story = {
512
598
  export const INR: Story = {
513
599
  render: () => (
514
600
  <Store
515
- items={duplicatedItems}
601
+ items={allStoreItems}
516
602
  packs={mockPacks}
517
603
  userAccountType={UserAccountTypes.Free}
518
604
  onPurchase={(purchase: Partial<IPurchase>) => {
@@ -520,7 +606,7 @@ export const INR: Story = {
520
606
  return Promise.resolve(true);
521
607
  }}
522
608
  customWalletContent={<DCWalletContent
523
- dcBalance={1200}
609
+ dcBalance={150000000} // 150M
524
610
  historyData={{ transactions: [], totalPages: 1, currentPage: 1 }}
525
611
  historyLoading={false}
526
612
  onRequestHistory={() => {}}
@@ -559,7 +645,9 @@ export const INR: Story = {
559
645
  'original-greater-life-potion-0': { originalPrice: 15.00 },
560
646
  'character-name-change': { originalPrice: 15.00 },
561
647
  'skin-character-customization': { originalPrice: 20.00 },
562
- 'starter-pack': { originalPrice: 9.99 }
648
+ 'starter-pack': { originalPrice: 9.99 },
649
+ 'mega-booster': { originalPrice: 200000 },
650
+ 'godly-artifact': { originalPrice: 2500000000 }
563
651
  }}
564
652
  />
565
653
  ),
@@ -568,7 +656,7 @@ export const INR: Story = {
568
656
  export const WithRedeemTab: Story = {
569
657
  render: () => (
570
658
  <Store
571
- items={duplicatedItems}
659
+ items={allStoreItems}
572
660
  packs={mockPacks}
573
661
  userAccountType={UserAccountTypes.Free}
574
662
  onPurchase={(purchase: Partial<IPurchase>) => {
@@ -583,7 +671,7 @@ export const WithRedeemTab: Story = {
583
671
  return { success: false, error: 'Invalid voucher code.' };
584
672
  }}
585
673
  customWalletContent={<DCWalletContent
586
- dcBalance={1200}
674
+ dcBalance={150000000} // 150M
587
675
  historyData={{ transactions: [], totalPages: 1, currentPage: 1 }}
588
676
  historyLoading={false}
589
677
  onRequestHistory={() => {}}
@@ -621,7 +709,9 @@ export const WithRedeemTab: Story = {
621
709
  'original-greater-life-potion-0': { originalPrice: 15.00 },
622
710
  'character-name-change': { originalPrice: 15.00 },
623
711
  'skin-character-customization': { originalPrice: 20.00 },
624
- 'starter-pack': { originalPrice: 9.99 }
712
+ 'starter-pack': { originalPrice: 9.99 },
713
+ 'mega-booster': { originalPrice: 200000 },
714
+ 'godly-artifact': { originalPrice: 2500000000 }
625
715
  }}
626
716
  />
627
717
  ),
@@ -42,6 +42,9 @@ const mockYourBuyOrders: IMarketplaceBuyOrderItem[] = [
42
42
  { _id: 'bo-1', owner: 'player-1', itemBlueprintKey: 'items/abyssal-edge', itemRarity: 'Rare', maxPrice: 500, escrowedGold: 500, fee: 25, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(3), updatedAt: daysAgo(3) },
43
43
  { _id: 'bo-2', owner: 'player-1', itemBlueprintKey: 'items/angelic-sword', maxPrice: 2000, escrowedGold: 2000, fee: 100, status: MarketplaceBuyOrderStatus.Fulfilled, fulfilledBy: 'DarkKnight42', createdAt: daysAgo(14), updatedAt: daysAgo(1) },
44
44
  { _id: 'bo-3', owner: 'player-1', itemBlueprintKey: 'items/leather-armor', maxPrice: 300, escrowedGold: 0, fee: 15, status: MarketplaceBuyOrderStatus.Expired, createdAt: daysAgo(35), updatedAt: daysAgo(7) },
45
+ { _id: 'bo-mega', owner: 'player-1', itemBlueprintKey: 'items/dragon-scale', maxPrice: 2500000, escrowedGold: 2500000, fee: 125000, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
46
+ { _id: 'bo-godly', owner: 'player-1', itemBlueprintKey: 'items/phoenix-feather', maxPrice: 1500000000, escrowedGold: 1500000000, fee: 7500000, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(0), updatedAt: daysAgo(0) },
47
+ { _id: 'bo-bugfix', owner: 'player-1', itemBlueprintKey: 'missing-blueprint-key', itemRarity: 'Epic', maxPrice: 150000, escrowedGold: 150000, fee: 7500, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2), itemTexturePath: 'swords/angelic-sword.png' } as any,
45
48
  ];
46
49
 
47
50
  const ITEM_KEYS = [
@@ -58,11 +61,12 @@ const RARITIES = ['Common', 'Uncommon', 'Rare', 'Epic', 'Legendary'] as const;
58
61
  const allMockOpenBuyOrders: IMarketplaceBuyOrderItem[] = Array.from({ length: 100 }, (_, i) => ({
59
62
  _id: `obo-${i + 1}`,
60
63
  owner: `player-${(i % 20) + 2}`,
61
- itemBlueprintKey: ITEM_KEYS[i % ITEM_KEYS.length],
64
+ itemBlueprintKey: i === 0 ? 'missing-key-for-test' : ITEM_KEYS[i % ITEM_KEYS.length],
62
65
  itemRarity: RARITIES[i % RARITIES.length] as any,
63
- maxPrice: Math.round((100 + i * 47) / 10) * 10,
64
- escrowedGold: Math.round((100 + i * 47) / 10) * 10,
65
- fee: Math.round((100 + i * 47) / 10) * 10 * 0.05,
66
+ ...(i === 0 ? { itemTexturePath: 'potions/greater-life-potion.png' } : {}),
67
+ maxPrice: i === 0 ? 3000000 : Math.round((100 + i * 47) / 10) * 10,
68
+ escrowedGold: i === 0 ? 3000000 : Math.round((100 + i * 47) / 10) * 10,
69
+ fee: i === 0 ? 150000 : Math.round((100 + i * 47) / 10) * 10 * 0.05,
66
70
  status: MarketplaceBuyOrderStatus.Active,
67
71
  createdAt: daysAgo(i % 30),
68
72
  updatedAt: daysAgo(i % 30),
@@ -33,6 +33,21 @@ describe('resolveAtlasSpriteKey', () => {
33
33
  expect(resolveAtlasSpriteKey(atlasJSON, 'silver-arrow')).toBe(
34
34
  'ranged-weapons/silvermoon-arrow.png'
35
35
  );
36
+ expect(resolveAtlasSpriteKey(atlasJSON, 'social-crystal')).toBe(
37
+ 'crafting-resources/tile049.png'
38
+ );
39
+ expect(resolveAtlasSpriteKey(atlasJSON, 'bloodroot-blossom-flower')).toBe(
40
+ 'crafting-resources/bloodroot-blossom.png'
41
+ );
42
+ expect(resolveAtlasSpriteKey(atlasJSON, 'sunspire-lotus-flower')).toBe(
43
+ 'crafting-resources/sunspire-lotus.png'
44
+ );
45
+ expect(resolveAtlasSpriteKey(atlasJSON, 'mana-shield-tome')).toBe(
46
+ 'spell-tomes/spell-tome-blue-4.png'
47
+ );
48
+ expect(resolveAtlasSpriteKey(atlasJSON, 'entangling-roots-tome')).toBe(
49
+ 'spell-tomes/spell-tome-green-2.png'
50
+ );
36
51
  });
37
52
 
38
53
  it('returns null when no atlas sprite can be resolved', () => {
@@ -3,6 +3,84 @@ export const NO_IMAGE_SPRITE_KEY = 'others/no-image.png';
3
3
  const atlasSpriteAliases = new Map<string, string>([
4
4
  ['mysticstaff', 'staffs/mystic-lightning-staff.png'],
5
5
  ['silverarrow', 'ranged-weapons/silvermoon-arrow.png'],
6
+ // Runtime blueprint keys whose atlas filenames do not match their item ids.
7
+ ['arcaneexplosiontome', 'spell-tomes/spell-tome-special-blue-2.png'],
8
+ ['arrowcreationspelltome', 'spell-tomes/spell-tome-gray-4.png'],
9
+ ['arrowstormtome', 'spell-tomes/spell-tome-gray-5.png'],
10
+ ['berserkerbloodthirsttome', 'spell-tomes/spell-tome-special-red-3.png'],
11
+ ['berserkerexecutiontome', 'spell-tomes/spell-tome-brown-5.png'],
12
+ ['berserkerfrenzytome', 'spell-tomes/spell-tome-special-red-2.png'],
13
+ ['berserkerragetome', 'spell-tomes/spell-tome-red-6.png'],
14
+ ['blankrunecreationspelltome', 'spell-tomes/spell-tome-gray-4.png'],
15
+ ['bleedingedgetome', 'spell-tomes/spell-tome-brown-4.png'],
16
+ ['blizzardtome', 'spell-tomes/spell-tome-special-blue-1.png'],
17
+ ['bloodrootblossomflower', 'crafting-resources/bloodroot-blossom.png'],
18
+ ['boltcreationspelltome', 'spell-tomes/spell-tome-blue-6.png'],
19
+ ['bombcreationtome', 'spell-tomes/spell-tome-red-4.png'],
20
+ ['chefsdelighttome', 'spell-tomes/spell-tome-brown-2.png'],
21
+ ['cleavingstomptome', 'spell-tomes/spell-tome-brown-6.png'],
22
+ ['corruptionbolttome', 'spell-tomes/spell-tome-dark-1.png'],
23
+ ['corruptionrunecreationspelltome', 'spell-tomes/spell-tome-dark-3.png'],
24
+ ['corruptionwavetome', 'spell-tomes/spell-tome-dark-2.png'],
25
+ ['crimsonarrowcreationtome', 'spell-tomes/spell-tome-red-6.png'],
26
+ ['curseofweaknesstome', 'spell-tomes/spell-tome-dark-6.png'],
27
+ ['darkrunecreationspelltome', 'spell-tomes/spell-tome-dark-3.png'],
28
+ ['dispelmagictome', 'spell-tomes/spell-tome-blue-4.png'],
29
+ ['druidshapeshifttome', 'spell-tomes/spell-tome-special-green-2.png'],
30
+ ['druidsilencetome', 'spell-tomes/spell-tome-green-3.png'],
31
+ ['duskwispherbflower', 'crafting-resources/duskwisp-herb.png'],
32
+ ['dwarfstoneformtome', 'spell-tomes/spell-tome-brown-5.png'],
33
+ ['elvenarrowtome', 'spell-tomes/spell-tome-green-5.png'],
34
+ ['emeraldarrowcreationtome', 'spell-tomes/spell-tome-green-4.png'],
35
+ ['energyboltrunecreationspelltome', 'spell-tomes/spell-tome-blue-6.png'],
36
+ ['energywavetome', 'spell-tomes/spell-tome-blue-5.png'],
37
+ ['entanglingrootstome', 'spell-tomes/spell-tome-green-2.png'],
38
+ ['fireboltcreationspelltome', 'spell-tomes/spell-tome-red-2.png'],
39
+ ['fireboltrunecreationspelltome', 'spell-tomes/spell-tome-red-4.png'],
40
+ ['firebolttome', 'spell-tomes/spell-tome-red-1.png'],
41
+ ['firerunecreationspelltome', 'spell-tomes/spell-tome-red-3.png'],
42
+ ['firestormtome', 'spell-tomes/spell-tome-special-red-1.png'],
43
+ ['focusswifttome', 'spell-tomes/spell-tome-gray-3.png'],
44
+ ['focustome', 'spell-tomes/spell-tome-gray-2.png'],
45
+ ['foodcreationspelltome', 'spell-tomes/spell-tome-brown-1.png'],
46
+ ['fortifydefensetome', 'spell-tomes/spell-tome-brown-5.png'],
47
+ ['frostarrowcreationtome', 'spell-tomes/spell-tome-blue-2.png'],
48
+ ['frostbolttome', 'spell-tomes/spell-tome-blue-1.png'],
49
+ ['greaterhealingspelltome', 'spell-tomes/spell-tome-special-yellow-2.png'],
50
+ ['healingrunecreationspelltome', 'spell-tomes/spell-tome-gray-1.png'],
51
+ ['hunterexecutiontome', 'spell-tomes/spell-tome-special-gray-2.png'],
52
+ ['hunterquickfiretome', 'spell-tomes/spell-tome-gray-6.png'],
53
+ ['ironwilltome', 'spell-tomes/spell-tome-special-yellow-1.png'],
54
+ ['magicshurikentome', 'spell-tomes/spell-tome-blue-7.png'],
55
+ ['manadraintome', 'spell-tomes/spell-tome-blue-3.png'],
56
+ ['manashieldtome', 'spell-tomes/spell-tome-blue-4.png'],
57
+ ['masshealingtome', 'spell-tomes/spell-tome-special-yellow-3.png'],
58
+ ['minotaurbullstrengthtome', 'spell-tomes/spell-tome-brown-6.png'],
59
+ ['naturesrevengetome', 'spell-tomes/spell-tome-special-green-1.png'],
60
+ ['pickpockettome', 'spell-tomes/spell-tome-gray-3.png'],
61
+ ['poisonarrowcreationspelltome', 'spell-tomes/spell-tome-green-5.png'],
62
+ ['poisonrunecreationspelltome', 'spell-tomes/spell-tome-green-6.png'],
63
+ ['powerstriketome', 'spell-tomes/spell-tome-brown-1.png'],
64
+ ['rogueexecutiontome', 'spell-tomes/spell-tome-special-gray-1.png'],
65
+ ['roguestealthspelltome', 'spell-tomes/spell-tome-gray-2.png'],
66
+ ['selfhastespelltome', 'spell-tomes/spell-tome-gray-1.png'],
67
+ ['selfhealingspelltome', 'spell-tomes/spell-tome-special-yellow-1.png'],
68
+ ['shieldbashtome', 'spell-tomes/spell-tome-brown-2.png'],
69
+ ['smallwoodensticks', 'crafting-resources/small-wood-sticks.png'],
70
+ ['socialcrystal', 'crafting-resources/tile049.png'],
71
+ ['spelldivineprotectiontome', 'spell-tomes/spell-tome-special-yellow-1.png'],
72
+ ['spelleagleeyestome', 'spell-tomes/spell-tome-gray-7.png'],
73
+ ['spellphysicalshieldtome', 'spell-tomes/spell-tome-brown-3.png'],
74
+ ['spellpolymorphtome', 'spell-tomes/spell-tome-special-blue-2.png'],
75
+ ['sunspirelotusflower', 'crafting-resources/sunspire-lotus.png'],
76
+ ['teleporttome', 'spell-tomes/spell-tome-special-blue-1.png'],
77
+ ['thunderrunecreationspelltome', 'spell-tomes/spell-tome-blue-7.png'],
78
+ ['vampiricstormtome', 'spell-tomes/spell-tome-dark-4.png'],
79
+ ['veilofundeathtome', 'spell-tomes/spell-tome-dark-5.png'],
80
+ ['vinegrasptome', 'spell-tomes/spell-tome-green-1.png'],
81
+ ['warriorexecutiontome', 'spell-tomes/spell-tome-special-brown-1.png'],
82
+ ['warriorstuntargettome', 'spell-tomes/spell-tome-brown-3.png'],
83
+ ['wildfirevolleytome', 'spell-tomes/spell-tome-red-5.png'],
6
84
  ]);
7
85
 
8
86
  const atlasBaseNameLookupCache = new WeakMap<object, Map<string, string>>();
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Formats a number using modern gaming standards (k for thousands, M for millions, B for billions).
3
+ * @param num The number to format
4
+ * @param useKNotation Whether to use the k/M/B notation
5
+ * @returns Formatted string
6
+ */
7
+ export const formatMMORPGNumber = (num: number, useKNotation: boolean = true): string => {
8
+ if (!useKNotation || num < 1000) {
9
+ return num.toLocaleString();
10
+ }
11
+
12
+ // Billions (B)
13
+ if (num >= 1000000000) {
14
+ const value = num / 1000000000;
15
+ return value.toFixed(value % 1 === 0 ? 0 : 1) + 'B';
16
+ }
17
+
18
+ // Millions (M)
19
+ if (num >= 1000000) {
20
+ const value = num / 1000000;
21
+ return value.toFixed(value % 1 === 0 ? 0 : 1) + 'M';
22
+ }
23
+
24
+ // Thousands (k)
25
+ if (num >= 1000) {
26
+ const value = num / 1000;
27
+ return value.toFixed(value % 1 === 0 ? 0 : 1) + 'k';
28
+ }
29
+
30
+ return num.toLocaleString();
31
+ };