@rpg-engine/long-bow 0.8.207 → 0.8.209

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.207",
3
+ "version": "0.8.209",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -84,7 +84,7 @@
84
84
  "dependencies": {
85
85
  "@capacitor/core": "^6.1.0",
86
86
  "@rollup/plugin-image": "^2.1.1",
87
- "@rpg-engine/shared": "^0.10.109",
87
+ "@rpg-engine/shared": "^0.10.115",
88
88
  "dayjs": "^1.11.2",
89
89
  "font-awesome": "^4.7.0",
90
90
  "fs-extra": "^10.1.0",
@@ -27,6 +27,7 @@ export interface IBuyOrderDetailsModalProps {
27
27
  atlasIMG: any;
28
28
  enableHotkeys?: () => void;
29
29
  disableHotkeys?: () => void;
30
+ scale?: number;
30
31
  }
31
32
 
32
33
  const scaleIn = keyframes`
@@ -57,6 +58,7 @@ export const BuyOrderDetailsModal: React.FC<IBuyOrderDetailsModalProps> = ({
57
58
  atlasIMG,
58
59
  enableHotkeys,
59
60
  disableHotkeys,
61
+ scale,
60
62
  }) => {
61
63
  if (!isOpen || !blueprint) return null;
62
64
 
@@ -73,7 +75,7 @@ export const BuyOrderDetailsModal: React.FC<IBuyOrderDetailsModalProps> = ({
73
75
  return (
74
76
  <ModalPortal>
75
77
  <Overlay onPointerDown={handleClose} />
76
- <ModalContainer>
78
+ <ModalContainer $scale={scale}>
77
79
  <ModalContent
78
80
  onClick={stopPropagation as React.MouseEventHandler}
79
81
  onPointerDown={stopPropagation as React.PointerEventHandler}
@@ -162,7 +164,7 @@ const Overlay = styled.div`
162
164
  z-index: 1000;
163
165
  `;
164
166
 
165
- const ModalContainer = styled.div`
167
+ const ModalContainer = styled.div<{ $scale?: number }>`
166
168
  position: fixed;
167
169
  inset: 0;
168
170
  display: flex;
@@ -170,6 +172,7 @@ const ModalContainer = styled.div`
170
172
  justify-content: center;
171
173
  z-index: 1001;
172
174
  pointer-events: none;
175
+ ${({ $scale }) => $scale !== undefined && $scale !== 1 ? `transform: scale(${$scale});` : ''}
173
176
  `;
174
177
 
175
178
  const ModalContent = styled.div`
@@ -35,6 +35,7 @@ export interface IBuyOrderPanelProps {
35
35
  onCancelBuyOrder: (buyOrderId: string) => void;
36
36
  enableHotkeys?: () => void;
37
37
  disableHotkeys?: () => void;
38
+ scale?: number;
38
39
  }
39
40
 
40
41
  const BUY_ORDERS_PER_PAGE = 5;
@@ -60,6 +61,7 @@ export const BuyOrderPanel: React.FC<IBuyOrderPanelProps> = (props) => {
60
61
  onCancelBuyOrder,
61
62
  enableHotkeys,
62
63
  disableHotkeys,
64
+ scale,
63
65
  } = props;
64
66
 
65
67
  // Local blueprint display: cleared immediately on Place Request so the
@@ -111,6 +113,7 @@ export const BuyOrderPanel: React.FC<IBuyOrderPanelProps> = (props) => {
111
113
  atlasIMG={atlasIMG}
112
114
  enableHotkeys={enableHotkeys}
113
115
  disableHotkeys={disableHotkeys}
116
+ scale={scale}
114
117
  />
115
118
  )}
116
119
 
@@ -5,6 +5,7 @@ import { FaTimes } from 'react-icons/fa';
5
5
  import styled, { keyframes } from 'styled-components';
6
6
  import ModalPortal from '../Abstractions/ModalPortal';
7
7
  import { ConfirmModal } from '../ConfirmModal';
8
+ import { gemColors } from '../Item/Inventory/ItemGem';
8
9
  import { CTAButton } from '../shared/CTAButton/CTAButton';
9
10
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
10
11
 
@@ -34,6 +35,12 @@ const RARITY_COLORS: Record<string, string> = {
34
35
  const rarityColor = (rarity?: string) =>
35
36
  RARITY_COLORS[(rarity ?? '').toLowerCase()] ?? RARITY_COLORS.common;
36
37
 
38
+ const rarityGlowColor = (rarity?: string): string | null => {
39
+ const key = (rarity ?? '').toLowerCase();
40
+ if (!key || key === 'common') return null;
41
+ return RARITY_COLORS[key] ?? null;
42
+ };
43
+
37
44
  const formatEquipmentSlot = (slot?: string) => {
38
45
  if (!slot) return 'Unknown';
39
46
 
@@ -86,9 +93,9 @@ export const CharacterDetailModal: React.FC<ICharacterDetailModalProps> = ({
86
93
  if (!isOpen || !listing) return null;
87
94
 
88
95
  const snap = listing.characterSnapshot;
89
- const topSkills = Object.entries(snap.skills ?? {})
90
- .sort(([, a], [, b]) => b - a)
91
- .slice(0, 8);
96
+ const topSkills = Object.entries(snap.skills ?? {}).sort(
97
+ ([, a], [, b]) => b - a
98
+ );
92
99
 
93
100
  return (
94
101
  <ModalPortal>
@@ -121,83 +128,101 @@ export const CharacterDetailModal: React.FC<ICharacterDetailModalProps> = ({
121
128
  </CloseButton>
122
129
  </Header>
123
130
 
124
- <HeroSection>
125
- <SpriteContainer>
126
- <SpriteFromAtlas
127
- atlasIMG={characterAtlasIMG}
128
- atlasJSON={characterAtlasJSON}
129
- spriteKey={`${snap.textureKey}/down/standing/0.png`}
130
- imgScale={4}
131
- height={96}
132
- width={96}
133
- centered
134
- />
135
- </SpriteContainer>
136
- <HeroInfo>
137
- <CharacterName>{snap.name || 'Unknown'}</CharacterName>
138
- <CharacterClass>
139
- Lv.{snap.level} · {snap.class}
140
- </CharacterClass>
141
- <CharacterOrigin>
142
- {snap.race} · {snap.faction}
143
- </CharacterOrigin>
144
- <ModeBadge $hardcore={snap.mode?.toLowerCase() === 'hardcore'}>
145
- {snap.mode || 'Standard'}
146
- </ModeBadge>
147
- </HeroInfo>
148
- </HeroSection>
149
-
150
- <Divider />
151
-
152
- <MetaColumns>
153
- {topSkills.length > 0 && (
154
- <Section>
155
- <SectionTitle>Skills</SectionTitle>
156
- <SkillsList>
157
- {topSkills.map(([name, value]) => (
158
- <SkillRow key={name}>
159
- <SkillName>{name}</SkillName>
160
- <SkillValue>{value}</SkillValue>
161
- </SkillRow>
162
- ))}
163
- </SkillsList>
164
- </Section>
165
- )}
166
-
167
- {snap.equipment?.length > 0 && (
168
- <Section>
169
- <SectionTitle>Equipment</SectionTitle>
170
- <EquipmentList>
171
- {snap.equipment.map((eq, i) => (
172
- <EquipmentRow key={i}>
173
- <EquipmentSprite>
174
- <SpriteFromAtlas
175
- atlasIMG={atlasIMG}
176
- atlasJSON={atlasJSON}
177
- spriteKey={eq.itemKey}
178
- imgScale={2}
179
- width={32}
180
- height={32}
181
- centered
182
- />
183
- </EquipmentSprite>
184
- <EquipMeta>
185
- <EquipName>{eq.itemName}</EquipName>
186
- <EquipDetails>
187
- <EquipSlot>{formatEquipmentSlot(eq.slot)}</EquipSlot>
188
- <RarityBadge $rarity={eq.rarity}>
189
- {eq.rarity || 'Common'}
190
- </RarityBadge>
191
- </EquipDetails>
192
- </EquipMeta>
193
- </EquipmentRow>
194
- ))}
195
- </EquipmentList>
196
- </Section>
197
- )}
198
- </MetaColumns>
199
-
200
- <Divider />
131
+ <ScrollableBody>
132
+ <HeroSection>
133
+ <SpriteContainer>
134
+ <SpriteFromAtlas
135
+ atlasIMG={characterAtlasIMG}
136
+ atlasJSON={characterAtlasJSON}
137
+ spriteKey={`${snap.textureKey}/down/standing/0.png`}
138
+ imgScale={4}
139
+ height={96}
140
+ width={96}
141
+ centered
142
+ />
143
+ </SpriteContainer>
144
+ <HeroInfo>
145
+ <CharacterName>{snap.name || 'Unknown'}</CharacterName>
146
+ <CharacterClass>
147
+ Lv.{snap.level} · {snap.class}
148
+ </CharacterClass>
149
+ <CharacterOrigin>
150
+ {snap.race} · {snap.faction}
151
+ </CharacterOrigin>
152
+ <ModeBadge $hardcore={snap.mode?.toLowerCase() === 'hardcore'}>
153
+ {snap.mode || 'Standard'}
154
+ </ModeBadge>
155
+ {snap.gold != null && snap.gold > 0 && (
156
+ <GoldRow>
157
+ <GoldLabel>GOLD</GoldLabel>
158
+ <GoldAmount>{snap.gold.toLocaleString()}</GoldAmount>
159
+ </GoldRow>
160
+ )}
161
+ </HeroInfo>
162
+ </HeroSection>
163
+
164
+ <Divider />
165
+
166
+ <MetaColumns>
167
+ {topSkills.length > 0 && (
168
+ <Section>
169
+ <SectionTitle>Skills</SectionTitle>
170
+ <SkillsList>
171
+ {topSkills.map(([name, value]) => (
172
+ <SkillRow key={name}>
173
+ <SkillName>{name}</SkillName>
174
+ <SkillValue>{value}</SkillValue>
175
+ </SkillRow>
176
+ ))}
177
+ </SkillsList>
178
+ </Section>
179
+ )}
180
+
181
+ {snap.equipment?.length > 0 && (
182
+ <Section>
183
+ <SectionTitle>Equipment</SectionTitle>
184
+ <EquipmentList>
185
+ {snap.equipment.map((eq, i) => (
186
+ <EquipmentRow key={i}>
187
+ <EquipmentSprite $rarity={eq.rarity}>
188
+ <SpriteFromAtlas
189
+ atlasIMG={atlasIMG}
190
+ atlasJSON={atlasJSON}
191
+ spriteKey={eq.itemKey}
192
+ imgScale={2}
193
+ width={32}
194
+ height={32}
195
+ centered
196
+ />
197
+ {eq.attachedGems && eq.attachedGems.length > 0 && (
198
+ <GemRow>
199
+ {eq.attachedGems.map((gem, gi) => (
200
+ <GemDot
201
+ key={gi}
202
+ $color={gemColors[gem.key] ?? '#fff'}
203
+ />
204
+ ))}
205
+ </GemRow>
206
+ )}
207
+ </EquipmentSprite>
208
+ <EquipMeta>
209
+ <EquipName>{eq.itemName}</EquipName>
210
+ <EquipDetails>
211
+ <EquipSlot>{formatEquipmentSlot(eq.slot)}</EquipSlot>
212
+ <RarityBadge $rarity={eq.rarity}>
213
+ {eq.rarity || 'Common'}
214
+ </RarityBadge>
215
+ </EquipDetails>
216
+ </EquipMeta>
217
+ </EquipmentRow>
218
+ ))}
219
+ </EquipmentList>
220
+ </Section>
221
+ )}
222
+ </MetaColumns>
223
+ </ScrollableBody>
224
+
225
+ <FooterDivider />
201
226
 
202
227
  <Footer>
203
228
  <SellerInfo>Listed by {listing.listedByCharacterName}</SellerInfo>
@@ -257,29 +282,16 @@ const ModalContent = styled.div`
257
282
  background: #1a1a2e;
258
283
  border: 2px solid #f59e0b;
259
284
  border-radius: 8px;
260
- padding: 20px 24px;
285
+ padding: 20px 24px 16px;
261
286
  width: 580px;
262
287
  max-width: 96%;
263
288
  max-height: 85dvh;
264
289
  display: flex;
265
290
  flex-direction: column;
266
291
  gap: 14px;
267
- overflow-y: auto;
268
- overflow-x: hidden;
292
+ overflow: hidden;
269
293
  pointer-events: auto;
270
294
  animation: ${scaleIn} 0.15s ease-out;
271
-
272
- &::-webkit-scrollbar {
273
- width: 6px;
274
- }
275
- &::-webkit-scrollbar-track {
276
- background: rgba(0, 0, 0, 0.2);
277
- border-radius: 4px;
278
- }
279
- &::-webkit-scrollbar-thumb {
280
- background: rgba(245, 158, 11, 0.3);
281
- border-radius: 4px;
282
- }
283
295
  `;
284
296
 
285
297
  const Header = styled.div`
@@ -377,6 +389,30 @@ const Divider = styled.hr`
377
389
  flex-shrink: 0;
378
390
  `;
379
391
 
392
+ const FooterDivider = styled(Divider)``;
393
+
394
+ const ScrollableBody = styled.div`
395
+ flex: 1;
396
+ overflow-y: auto;
397
+ overflow-x: hidden;
398
+ display: flex;
399
+ flex-direction: column;
400
+ gap: 14px;
401
+ min-height: 0;
402
+
403
+ &::-webkit-scrollbar {
404
+ width: 6px;
405
+ }
406
+ &::-webkit-scrollbar-track {
407
+ background: rgba(0, 0, 0, 0.2);
408
+ border-radius: 4px;
409
+ }
410
+ &::-webkit-scrollbar-thumb {
411
+ background: rgba(245, 158, 11, 0.3);
412
+ border-radius: 4px;
413
+ }
414
+ `;
415
+
380
416
  const Section = styled.div`
381
417
  display: flex;
382
418
  flex-direction: column;
@@ -445,13 +481,66 @@ const EquipmentRow = styled.div`
445
481
  min-width: 0;
446
482
  `;
447
483
 
448
- const EquipmentSprite = styled.div`
484
+ const EquipmentSprite = styled.div<{ $rarity?: string }>`
485
+ position: relative;
449
486
  display: flex;
450
487
  align-items: center;
451
488
  justify-content: center;
452
489
  width: 32px;
453
490
  height: 32px;
454
491
  flex-shrink: 0;
492
+ border-radius: 3px;
493
+ ${({ $rarity }) => {
494
+ const color = rarityGlowColor($rarity);
495
+ return color
496
+ ? `box-shadow: 0 0 4px 3px ${color} inset, 0 0 6px 2px ${color};`
497
+ : '';
498
+ }}
499
+ `;
500
+
501
+ const GemRow = styled.div`
502
+ position: absolute;
503
+ bottom: -1px;
504
+ left: 0;
505
+ display: flex;
506
+ gap: 1px;
507
+ pointer-events: none;
508
+ `;
509
+
510
+ const GemDot = styled.div<{ $color: string }>`
511
+ width: 5px;
512
+ height: 5px;
513
+ border-radius: 1px;
514
+ transform: rotate(45deg);
515
+ background: radial-gradient(
516
+ circle at 30% 30%,
517
+ rgba(255, 255, 255, 0.8),
518
+ transparent 40%
519
+ ),
520
+ linear-gradient(45deg, ${({ $color }) => $color}, rgba(255, 255, 255, 0.2));
521
+ border: 1px solid rgba(0, 0, 0, 0.6);
522
+ box-shadow: 0 0 3px ${({ $color }) => $color};
523
+ `;
524
+
525
+ const GoldRow = styled.div`
526
+ display: flex;
527
+ align-items: center;
528
+ gap: 4px;
529
+ margin-top: 2px;
530
+ `;
531
+
532
+ const GoldLabel = styled.span`
533
+ font-family: 'Press Start 2P', cursive !important;
534
+ font-size: 0.35rem !important;
535
+ color: #6b7280 !important;
536
+ text-transform: uppercase;
537
+ letter-spacing: 0.5px;
538
+ `;
539
+
540
+ const GoldAmount = styled.span`
541
+ font-family: 'Press Start 2P', cursive !important;
542
+ font-size: 0.38rem !important;
543
+ color: #fde68a !important;
455
544
  `;
456
545
 
457
546
  const EquipMeta = styled.div`
@@ -136,6 +136,7 @@ export interface IMarketPlaceProps {
136
136
  characterListingsLoading?: boolean;
137
137
  characterNameFilter?: string;
138
138
  onCharacterNameFilterChange?: (name: string) => void;
139
+ fullScreen?: boolean;
139
140
  }
140
141
 
141
142
  type ActiveTab = 'marketplace' | 'characters' | 'sell' | 'buy-orders' | 'history' | 'wallet' | 'settings';
@@ -200,6 +201,7 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
200
201
  onCharacterNameFilterChange,
201
202
  characterAtlasIMG,
202
203
  characterAtlasJSON,
204
+ fullScreen = false,
203
205
  } = props;
204
206
 
205
207
  const [activeTab, setActiveTab] = useState<ActiveTab>('marketplace');
@@ -233,6 +235,7 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
233
235
  onCloseButton={() => {
234
236
  if (onClose) onClose();
235
237
  }}
238
+ isFullScreen={fullScreen}
236
239
  width="920px"
237
240
  cancelDrag="#MarketContainer, .rpgui-dropdown-imp, input, .empty-slot, button"
238
241
  scale={scale}
@@ -375,6 +378,7 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
375
378
  onCancelBuyOrder={onCancelBuyOrder ?? (() => {})}
376
379
  enableHotkeys={props.enableHotkeys}
377
380
  disableHotkeys={props.disableHotkeys}
381
+ scale={scale}
378
382
  />
379
383
  <BlueprintSearchModal
380
384
  isOpen={isBlueprintSearchOpen}
@@ -4,7 +4,7 @@ import { Gift } from 'pixelarticons/react/Gift';
4
4
  import { Package } from 'pixelarticons/react/Package';
5
5
  import { Wallet } from 'pixelarticons/react/Wallet';
6
6
  import React, { ReactNode, useMemo, useState } from 'react';
7
- import { FaHistory, FaShoppingCart, FaTicketAlt, FaWallet } from 'react-icons/fa';
7
+ import { FaHistory, FaShoppingCart, FaTicketAlt, FaUsers, FaWallet } from 'react-icons/fa';
8
8
  import styled from 'styled-components';
9
9
  import { uiColors } from '../../constants/uiColors';
10
10
  import { DraggableContainer } from '../DraggableContainer';
@@ -22,7 +22,7 @@ import { StoreRedeemSection } from './sections/StoreRedeemSection';
22
22
  import { StoreItemDetails } from './StoreItemDetails';
23
23
 
24
24
  // Define TabId union type for tab identifiers
25
- type TabId = 'premium' | 'packs' | 'items' | 'wallet' | 'history' | 'redeem';
25
+ type TabId = 'premium' | 'packs' | 'items' | 'characters' | 'wallet' | 'history' | 'redeem';
26
26
 
27
27
  // Define IStoreProps locally as a workaround
28
28
  export interface IStoreProps {
@@ -44,8 +44,11 @@ export interface IStoreProps {
44
44
  defaultActiveTab?: TabId;
45
45
  textInputItemKeys?: string[];
46
46
  customPacksContent?: React.ReactNode;
47
+ customCharactersContent?: React.ReactNode;
47
48
  customWalletContent?: React.ReactNode;
48
49
  customHistoryContent?: React.ReactNode;
50
+ /** When true the store renders full-screen (useful on mobile). */
51
+ fullScreen?: boolean;
49
52
  packsBadge?: string;
50
53
  featuredItems?: IFeaturedItem[];
51
54
  onQuickBuy?: (item: IProductBlueprint, quantity?: number) => void;
@@ -103,8 +106,10 @@ export const Store: React.FC<IStoreProps> = ({
103
106
  defaultActiveTab,
104
107
  textInputItemKeys = [],
105
108
  customPacksContent,
109
+ customCharactersContent,
106
110
  customWalletContent,
107
111
  customHistoryContent,
112
+ fullScreen = false,
108
113
  packsTabLabel = 'Packs',
109
114
  packsBadge,
110
115
  featuredItems,
@@ -241,6 +246,7 @@ export const Store: React.FC<IStoreProps> = ({
241
246
  // Build tabs dynamically based on props
242
247
  const tabIds: TabId[] = [
243
248
  ...(tabOrder ?? defaultTabOrder),
249
+ ...(customCharactersContent ? ['characters' as TabId] : []),
244
250
  ...(onRedeem ? ['redeem' as TabId] : []),
245
251
  ...((onShowWallet || customWalletContent) ? ['wallet' as TabId] : []),
246
252
  ...((onShowHistory || customHistoryContent) ? ['history' as TabId] : [])
@@ -347,6 +353,12 @@ export const Store: React.FC<IStoreProps> = ({
347
353
  />
348
354
  ),
349
355
  },
356
+ characters: {
357
+ id: 'characters',
358
+ title: 'Characters',
359
+ icon: <FaUsers size={16} />,
360
+ content: customCharactersContent ?? null,
361
+ },
350
362
  redeem: {
351
363
  id: 'redeem',
352
364
  title: 'Redeem',
@@ -390,6 +402,7 @@ export const Store: React.FC<IStoreProps> = ({
390
402
  height="auto"
391
403
  type={RPGUIContainerTypes.Framed}
392
404
  cancelDrag="[class*='Store__Container'], [class*='CartView'], [class*='StoreItemDetails'], .close-button"
405
+ isFullScreen={fullScreen}
393
406
  >
394
407
  {isCollectingMetadata && currentMetadataItem && currentMetadataItem.metadataType ? (
395
408
  <MetadataCollector