@rpg-engine/long-bow 0.8.18 → 0.8.19

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.
@@ -18,190 +18,230 @@ interface INPCDetailsProps {
18
18
  itemsAtlasIMG: string;
19
19
  entitiesAtlasJSON: Record<string, any>;
20
20
  entitiesAtlasIMG: string;
21
+ iconAtlasIMG?: any;
22
+ iconAtlasJSON?: any;
21
23
  onBack: () => void;
22
24
  }
23
25
 
24
26
  const ITEMS_PER_PAGE = 5;
25
27
 
26
- export const InformationCenterNPCDetails: React.FC<INPCDetailsProps> = ({
27
- npc,
28
- itemsAtlasJSON,
29
- itemsAtlasIMG,
30
- entitiesAtlasJSON,
31
- entitiesAtlasIMG,
32
- onBack,
33
- }) => {
34
- const isMobile = isMobileOrTablet();
35
- const [lootSearchQuery, setLootSearchQuery] = useState('');
36
- const [currentLootPage, setCurrentLootPage] = useState(1);
37
-
38
- const formatText = (text: string | RangeTypes | MovementSpeed): string => {
39
- if (typeof text === 'number') {
40
- return text.toString();
41
- }
42
- return text
43
- .toString()
44
- .replace(/([A-Z])/g, ' $1')
45
- .trim()
46
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
47
- .replace(/\s+/g, ' ');
48
- };
49
-
50
- const formatRarity = (rarity: number): string => {
51
- switch (rarity) {
52
- case 0.5:
53
- return 'Very Rare';
54
- case 1:
55
- return 'Rare';
56
- case 10:
57
- return 'Uncommon';
58
- case 15:
59
- return 'Semi Common';
60
- case 20:
61
- return 'Common';
62
- case 35:
63
- return 'Very Common';
64
- default:
65
- return 'Unknown';
66
- }
67
- };
68
-
69
- const filteredLoots =
70
- npc.loots?.filter(loot =>
71
- formatText(loot.itemBlueprintKey)
72
- .toLowerCase()
73
- .includes(lootSearchQuery.toLowerCase())
74
- ) || [];
75
-
76
- const totalLootPages = Math.ceil(filteredLoots.length / ITEMS_PER_PAGE);
77
- const paginatedLoots = filteredLoots.slice(
78
- (currentLootPage - 1) * ITEMS_PER_PAGE,
79
- currentLootPage * ITEMS_PER_PAGE
80
- );
81
-
82
- return (
83
- <BaseInformationDetails
84
- name={npc.name}
85
- spriteKey={npc.key}
86
- atlasJSON={entitiesAtlasJSON}
87
- atlasIMG={entitiesAtlasIMG}
88
- onBack={onBack}
89
- >
90
- <InfoSection>
91
- <InfoItem>
92
- <Label>Type:</Label>
93
- <Value>{formatText(npc.subType)}</Value>
94
- </InfoItem>
95
- <InfoItem>
96
- <Label>Alignment:</Label>
97
- <Value>{formatText(npc.alignment)}</Value>
98
- </InfoItem>
99
- <InfoItem>
100
- <Label>Attack Type:</Label>
101
- <Value>{formatText(npc.attackType)}</Value>
102
- </InfoItem>
103
- <InfoItem>
104
- <Label>Range:</Label>
105
- <Value>{formatText(npc.maxRangeAttack)}</Value>
106
- </InfoItem>
107
- <InfoItem>
108
- <Label>Speed:</Label>
109
- <Value>{formatText(npc.speed)}</Value>
110
- </InfoItem>
111
- </InfoSection>
112
-
113
- <StyledCollapsible title="Stats" defaultOpen={!isMobile}>
114
- <StatGrid>
115
- <StatItem>HP: {npc.baseHealth}</StatItem>
116
- <StatItem>Level: {npc.skills.level}</StatItem>
117
- {npc.skills.strength?.level && (
118
- <StatItem>Strength: {npc.skills.strength.level}</StatItem>
119
- )}
120
- {npc.skills.dexterity?.level && (
121
- <StatItem>Dexterity: {npc.skills.dexterity.level}</StatItem>
122
- )}
123
- {npc.skills.resistance?.level && (
124
- <StatItem>Resistance: {npc.skills.resistance.level}</StatItem>
125
- )}
126
- </StatGrid>
127
- </StyledCollapsible>
128
-
129
- {npc.loots && npc.loots.length > 0 && (
130
- <StyledCollapsible title="Loot" defaultOpen={!isMobile}>
131
- <LootSearchContainer>
132
- <StyledSearchBar
133
- value={lootSearchQuery}
134
- onChange={setLootSearchQuery}
135
- placeholder="Search loot..."
136
- />
137
- </LootSearchContainer>
138
- <LootGrid>
139
- {paginatedLoots.map((loot, index) => (
140
- <LootItem key={index}>
141
- <SpriteFromAtlas
142
- atlasJSON={itemsAtlasJSON}
143
- atlasIMG={itemsAtlasIMG}
144
- spriteKey={loot.itemBlueprintKey}
145
- width={24}
146
- height={24}
147
- imgScale={1}
148
- />
149
- <LootDetails>
150
- <LootName>{formatText(loot.itemBlueprintKey)}</LootName>
151
- <LootInfo>
152
- <LootChance>{formatRarity(loot.chance)}</LootChance>
153
- {loot.quantityRange && (
154
- <LootQuantity>
155
- x{loot.quantityRange[0]}-{loot.quantityRange[1]}
156
- </LootQuantity>
157
- )}
158
- </LootInfo>
159
- </LootDetails>
160
- </LootItem>
161
- ))}
162
- </LootGrid>
163
- {filteredLoots.length > ITEMS_PER_PAGE && (
164
- <PaginationContainer>
165
- <StyledPagination
166
- currentPage={currentLootPage}
167
- totalPages={totalLootPages}
168
- onPageChange={setCurrentLootPage}
169
- />
170
- </PaginationContainer>
171
- )}
172
- </StyledCollapsible>
173
- )}
174
-
175
- {npc.entityEffects && npc.entityEffects.length > 0 && (
176
- <StyledCollapsible title="Effects" defaultOpen={!isMobile}>
177
- <EffectsList>
178
- {npc.entityEffects.map((effect, index) => (
179
- <EffectItem key={index}>{formatText(effect)}</EffectItem>
180
- ))}
181
- </EffectsList>
182
- </StyledCollapsible>
183
- )}
184
-
185
- {npc.areaSpells && npc.areaSpells.length > 0 && (
186
- <StyledCollapsible title="Spells" defaultOpen={!isMobile}>
187
- <SpellsList>
188
- {npc.areaSpells.map((spell, index) => (
189
- <SpellItem key={index}>
190
- <SpellName>{formatText(spell.spellKey)}</SpellName>
191
- <SpellDetails>
192
- Power: <SpellValue>{formatText(spell.power)}</SpellValue>
193
- <Separator>•</Separator>
194
- Chance: <SpellValue>{spell.probability}%</SpellValue>
195
- </SpellDetails>
196
- </SpellItem>
197
- ))}
198
- </SpellsList>
199
- </StyledCollapsible>
200
- )}
201
- </BaseInformationDetails>
202
- );
28
+ const formatItemName = (itemPath: string): string => {
29
+ const cleanText =
30
+ itemPath
31
+ .split('/')
32
+ .pop()
33
+ ?.split('.')
34
+ .shift() || '';
35
+
36
+ return cleanText
37
+ .split('-')
38
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
39
+ .join(' ');
203
40
  };
204
41
 
42
+ export const InformationCenterNPCDetails: React.FC<INPCDetailsProps> = ({
43
+ npc,
44
+ itemsAtlasJSON,
45
+ itemsAtlasIMG,
46
+ entitiesAtlasJSON,
47
+ entitiesAtlasIMG,
48
+ iconAtlasIMG,
49
+ iconAtlasJSON,
50
+ onBack,
51
+ }) => {
52
+ const isMobile = isMobileOrTablet();
53
+ const [lootSearchQuery, setLootSearchQuery] = useState('');
54
+ const [currentLootPage, setCurrentLootPage] = useState(1);
55
+
56
+ const formatText = (
57
+ text: string | RangeTypes | MovementSpeed
58
+ ): string => {
59
+ if (typeof text === 'number') {
60
+ return text.toString();
61
+ }
62
+
63
+ return text
64
+ .toString()
65
+ .replace(/([A-Z])/g, ' $1')
66
+ .trim()
67
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
68
+ .replace(/\s+/g, ' ');
69
+ };
70
+
71
+ const formatRarity = (rarity: number): string => {
72
+ switch (rarity) {
73
+ case 0.5:
74
+ return 'Very Rare';
75
+ case 1:
76
+ return 'Rare';
77
+ case 10:
78
+ return 'Uncommon';
79
+ case 15:
80
+ return 'Semi Common';
81
+ case 20:
82
+ return 'Common';
83
+ case 35:
84
+ return 'Very Common';
85
+ default:
86
+ return 'Unknown';
87
+ }
88
+ };
89
+
90
+ const filteredLoots =
91
+ npc.loots?.filter(loot =>
92
+ formatItemName(loot.itemBlueprintKey)
93
+ .toLowerCase()
94
+ .includes(lootSearchQuery.toLowerCase())
95
+ ) || [];
96
+
97
+ const totalLootPages = Math.ceil(
98
+ filteredLoots.length / ITEMS_PER_PAGE
99
+ );
100
+ const paginatedLoots = filteredLoots.slice(
101
+ (currentLootPage - 1) * ITEMS_PER_PAGE,
102
+ currentLootPage * ITEMS_PER_PAGE
103
+ );
104
+
105
+ return (
106
+ <BaseInformationDetails
107
+ name={npc.name}
108
+ spriteKey={npc.key}
109
+ atlasJSON={entitiesAtlasJSON}
110
+ atlasIMG={entitiesAtlasIMG}
111
+ onBack={onBack}
112
+ >
113
+ <InfoSection>
114
+ <InfoItem>
115
+ <Label>Type:</Label>
116
+ <Value>{formatText(npc.subType)}</Value>
117
+ </InfoItem>
118
+ <InfoItem>
119
+ <Label>Alignment:</Label>
120
+ <Value>{formatText(npc.alignment)}</Value>
121
+ </InfoItem>
122
+ <InfoItem>
123
+ <Label>Attack Type:</Label>
124
+ <Value>{formatText(npc.attackType)}</Value>
125
+ </InfoItem>
126
+ <InfoItem>
127
+ <Label>Range:</Label>
128
+ <Value>{formatText(npc.maxRangeAttack)}</Value>
129
+ </InfoItem>
130
+ <InfoItem>
131
+ <Label>Speed:</Label>
132
+ <Value>{formatText(npc.speed)}</Value>
133
+ </InfoItem>
134
+ </InfoSection>
135
+
136
+ <StyledCollapsible title="Stats" defaultOpen={!isMobile}>
137
+ <StatGrid>
138
+ <StatItem>HP: {npc.baseHealth}</StatItem>
139
+ <StatItem>Level: {npc.skills.level}</StatItem>
140
+ {npc.skills.strength?.level && (
141
+ <StatItem>Strength: {npc.skills.strength.level}</StatItem>
142
+ )}
143
+ {npc.skills.dexterity?.level && (
144
+ <StatItem>Dexterity: {npc.skills.dexterity.level}</StatItem>
145
+ )}
146
+ {npc.skills.resistance?.level && (
147
+ <StatItem>
148
+ Resistance: {npc.skills.resistance.level}
149
+ </StatItem>
150
+ )}
151
+ </StatGrid>
152
+ </StyledCollapsible>
153
+
154
+ {npc.loots && npc.loots.length > 0 && (
155
+ <StyledCollapsible title="Loot" defaultOpen={!isMobile}>
156
+ <LootSearchContainer>
157
+ <StyledSearchBar
158
+ value={lootSearchQuery}
159
+ onChange={setLootSearchQuery}
160
+ placeholder="Search loot..."
161
+ />
162
+ </LootSearchContainer>
163
+ <LootGrid>
164
+ {paginatedLoots.map((loot, index) => (
165
+ <LootItem key={index}>
166
+ <SpriteFromAtlas
167
+ atlasJSON={itemsAtlasJSON}
168
+ atlasIMG={itemsAtlasIMG}
169
+ spriteKey={loot.itemBlueprintKey}
170
+ width={24}
171
+ height={24}
172
+ imgScale={1}
173
+ />
174
+ <LootDetails>
175
+ <LootName>
176
+ {formatItemName(loot.itemBlueprintKey)}
177
+ </LootName>
178
+ <LootInfo>
179
+ <LootChance>{formatRarity(loot.chance)}</LootChance>
180
+ {loot.quantityRange && (
181
+ <LootQuantity>
182
+ x{loot.quantityRange[0]}-{loot.quantityRange[1]}
183
+ </LootQuantity>
184
+ )}
185
+ </LootInfo>
186
+ </LootDetails>
187
+ </LootItem>
188
+ ))}
189
+ </LootGrid>
190
+ {filteredLoots.length > ITEMS_PER_PAGE && (
191
+ <PaginationContainer>
192
+ <StyledPagination
193
+ currentPage={currentLootPage}
194
+ totalPages={totalLootPages}
195
+ onPageChange={setCurrentLootPage}
196
+ />
197
+ </PaginationContainer>
198
+ )}
199
+ </StyledCollapsible>
200
+ )}
201
+
202
+ {npc.entityEffects && npc.entityEffects.length > 0 && (
203
+ <StyledCollapsible title="Effects" defaultOpen={!isMobile}>
204
+ <EffectsList>
205
+ {npc.entityEffects.map((effect, index) => (
206
+ <EffectItem key={index}>{formatText(effect)}</EffectItem>
207
+ ))}
208
+ </EffectsList>
209
+ </StyledCollapsible>
210
+ )}
211
+
212
+ {npc.areaSpells && npc.areaSpells.length > 0 && (
213
+ <StyledCollapsible title="Spells" defaultOpen={!isMobile}>
214
+ <SpellsList>
215
+ {npc.areaSpells.map((spell, index) => (
216
+ <SpellItem key={index}>
217
+ <SpellIconContainer>
218
+ <SpriteFromAtlas
219
+ atlasJSON={iconAtlasJSON}
220
+ atlasIMG={iconAtlasIMG}
221
+ spriteKey={spell.texturePath || spell.spellKey}
222
+ width={24}
223
+ height={24}
224
+ imgScale={1}
225
+ />
226
+ </SpellIconContainer>
227
+ <SpellContent>
228
+ <SpellName>{formatText(spell.spellKey)}</SpellName>
229
+ <SpellDetails>
230
+ Power:{' '}
231
+ <SpellValue>{formatText(spell.power)}</SpellValue>
232
+ <Separator>•</Separator>
233
+ Chance: <SpellValue>{spell.probability}%</SpellValue>
234
+ </SpellDetails>
235
+ </SpellContent>
236
+ </SpellItem>
237
+ ))}
238
+ </SpellsList>
239
+ </StyledCollapsible>
240
+ )}
241
+ </BaseInformationDetails>
242
+ );
243
+ };
244
+
205
245
  const InfoSection = styled.div`
206
246
  display: grid;
207
247
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
@@ -271,13 +311,27 @@ const SpellsList = styled.div`
271
311
  padding: 12px;
272
312
  `;
273
313
 
274
- const SpellItem = styled.div`
314
+ const SpellIconContainer = styled.div`
315
+ display: flex;
316
+ padding-right: 30px;
317
+ padding-bottom: 30px;
318
+ `;
319
+
320
+ const SpellContent = styled.div`
275
321
  display: flex;
276
322
  flex-direction: column;
277
323
  gap: 4px;
324
+ flex: 1;
325
+ `;
326
+
327
+ const SpellItem = styled.div`
328
+ display: flex;
329
+ gap: 8px;
278
330
  background: rgba(255, 255, 255, 0.05);
279
331
  padding: 8px;
332
+ padding-left: 10px;
280
333
  border-radius: 4px;
334
+ align-items: center;
281
335
  `;
282
336
 
283
337
  const SpellName = styled.div`
@@ -30,7 +30,7 @@ interface IPaginatedContentProps<T> {
30
30
 
31
31
  export const PaginatedContent = <T extends unknown>({
32
32
  items,
33
- itemsPerPage = 5,
33
+ itemsPerPage = 8,
34
34
  renderItem,
35
35
  emptyMessage = 'No items found',
36
36
  className,
@@ -44,27 +44,29 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
44
44
  entityEffects: [EntityEffectBlueprint.Burning],
45
45
  areaSpells: [
46
46
  {
47
- spellKey: 'ThunderStorm',
47
+ spellKey: 'VampiricStorm',
48
+ texturePath: 'spell-icons/vampiric-storm.png',
48
49
  probability: 35,
49
50
  power: 'UltraHigh',
50
51
  },
51
52
  {
52
- spellKey: 'LightningBreath',
53
+ spellKey: 'ArrowCreationSpell',
54
+ texturePath: 'spell-icons/arrow-creation-spell.png',
53
55
  probability: 45,
54
56
  power: 'High',
55
57
  },
56
58
  ],
57
59
  loots: [
58
60
  {
59
- itemBlueprintKey: 'DragonScale',
61
+ itemBlueprintKey: 'maces/dragonscale-cleaver-club.png',
60
62
  chance: LootProbability.Uncommon,
61
63
  },
62
64
  {
63
- itemBlueprintKey: 'ThunderStaff',
65
+ itemBlueprintKey: 'staffs/thunder-bolt-staff.png',
64
66
  chance: LootProbability.Rare,
65
67
  },
66
68
  {
67
- itemBlueprintKey: 'DragonEssence',
69
+ itemBlueprintKey: 'crafting-resources/dragon-tooth.png',
68
70
  chance: LootProbability.Uncommon,
69
71
  quantityRange: [1, 3],
70
72
  },
@@ -93,15 +95,16 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
93
95
  spellKey: 'WarCry',
94
96
  probability: 25,
95
97
  power: 'Medium',
98
+ texturePath: ''
96
99
  },
97
100
  ],
98
101
  loots: [
99
102
  {
100
- itemBlueprintKey: 'OrcishAxe',
103
+ itemBlueprintKey: 'crafting-resources/leather.png',
101
104
  chance: LootProbability.Uncommon,
102
105
  },
103
106
  {
104
- itemBlueprintKey: 'HealingHerb',
107
+ itemBlueprintKey: 'crafting-resources/herb.png',
105
108
  chance: LootProbability.Common,
106
109
  quantityRange: [1, 4],
107
110
  },
@@ -133,6 +136,7 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
133
136
  spellKey: 'BoneShards',
134
137
  probability: 20,
135
138
  power: 'Medium',
139
+ texturePath: ''
136
140
  },
137
141
  ],
138
142
  loots: [
@@ -204,7 +208,8 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
204
208
  ],
205
209
  areaSpells: [
206
210
  {
207
- spellKey: 'FrostBite',
211
+ spellKey: 'VampiricStorm',
212
+ texturePath: 'spell-icons/vampiric-storm.png',
208
213
  probability: 15,
209
214
  power: 'Medium',
210
215
  },
@@ -248,11 +253,13 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
248
253
  spellKey: 'DeathNova',
249
254
  probability: 30,
250
255
  power: 'VeryHigh',
256
+ texturePath: ''
251
257
  },
252
258
  {
253
259
  spellKey: 'SoulDrain',
254
260
  probability: 25,
255
261
  power: 'High',
262
+ texturePath: ''
256
263
  },
257
264
  ],
258
265
  loots: [
@@ -297,6 +304,7 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
297
304
  spellKey: 'BoulderThrow',
298
305
  probability: 40,
299
306
  power: 'High',
307
+ texturePath: ''
300
308
  },
301
309
  ],
302
310
  loots: [
@@ -341,6 +349,7 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
341
349
  spellKey: 'ToxicCloud',
342
350
  probability: 20,
343
351
  power: 'Medium',
352
+ texturePath: ''
344
353
  },
345
354
  ],
346
355
  loots: [
@@ -5,8 +5,11 @@ import { IInformationCenterItem } from '../../../components/InformationCenter/In
5
5
  import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
6
6
  import entitiesAtlasJSON from '../../../mocks/atlas/entities/entities.json';
7
7
  import entitiesAtlasIMG from '../../../mocks/atlas/entities/entities.png';
8
+ import iconsAtlasJSON from '../../../mocks/atlas/icons/icons.json';
9
+ import iconsAtlasIMG from '../../../mocks/atlas/icons/icons.png';
8
10
  import itemsAtlasJSON from '../../../mocks/atlas/items/items.json';
9
11
  import itemsAtlasIMG from '../../../mocks/atlas/items/items.png';
12
+
10
13
  import { mockBestiaryItems, mockFaqItems, mockItems, mockTutorials } from '../../../mocks/informationCenter.mocks';
11
14
 
12
15
 
@@ -27,6 +30,8 @@ const Template: Story = args => (
27
30
  itemsAtlasIMG={itemsAtlasIMG}
28
31
  entitiesAtlasJSON={entitiesAtlasJSON}
29
32
  entitiesAtlasIMG={entitiesAtlasIMG}
33
+ iconsAtlasJSON={iconsAtlasJSON}
34
+ iconsAtlasIMG={iconsAtlasIMG}
30
35
  faqItems={mockFaqItems}
31
36
  bestiaryItems={mockBestiaryItems}
32
37
  videoGuides={mockTutorials}