@rpg-engine/long-bow 0.8.19 → 0.8.20

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,229 +18,189 @@ interface INPCDetailsProps {
18
18
  itemsAtlasIMG: string;
19
19
  entitiesAtlasJSON: Record<string, any>;
20
20
  entitiesAtlasIMG: string;
21
- iconAtlasIMG?: any;
22
- iconAtlasJSON?: any;
23
21
  onBack: () => void;
24
22
  }
25
23
 
26
24
  const ITEMS_PER_PAGE = 5;
27
25
 
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(' ');
40
- };
41
-
42
26
  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
- };
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
+ );
203
+ };
244
204
 
245
205
  const InfoSection = styled.div`
246
206
  display: grid;
@@ -311,27 +271,13 @@ const SpellsList = styled.div`
311
271
  padding: 12px;
312
272
  `;
313
273
 
314
- const SpellIconContainer = styled.div`
315
- display: flex;
316
- padding-right: 30px;
317
- padding-bottom: 30px;
318
- `;
319
-
320
- const SpellContent = styled.div`
274
+ const SpellItem = styled.div`
321
275
  display: flex;
322
276
  flex-direction: column;
323
277
  gap: 4px;
324
- flex: 1;
325
- `;
326
-
327
- const SpellItem = styled.div`
328
- display: flex;
329
- gap: 8px;
330
278
  background: rgba(255, 255, 255, 0.05);
331
279
  padding: 8px;
332
- padding-left: 10px;
333
280
  border-radius: 4px;
334
- align-items: center;
335
281
  `;
336
282
 
337
283
  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 = 8,
33
+ itemsPerPage = 5,
34
34
  renderItem,
35
35
  emptyMessage = 'No items found',
36
36
  className,
@@ -44,29 +44,27 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
44
44
  entityEffects: [EntityEffectBlueprint.Burning],
45
45
  areaSpells: [
46
46
  {
47
- spellKey: 'VampiricStorm',
48
- texturePath: 'spell-icons/vampiric-storm.png',
47
+ spellKey: 'ThunderStorm',
49
48
  probability: 35,
50
49
  power: 'UltraHigh',
51
50
  },
52
51
  {
53
- spellKey: 'ArrowCreationSpell',
54
- texturePath: 'spell-icons/arrow-creation-spell.png',
52
+ spellKey: 'LightningBreath',
55
53
  probability: 45,
56
54
  power: 'High',
57
55
  },
58
56
  ],
59
57
  loots: [
60
58
  {
61
- itemBlueprintKey: 'maces/dragonscale-cleaver-club.png',
59
+ itemBlueprintKey: 'DragonScale',
62
60
  chance: LootProbability.Uncommon,
63
61
  },
64
62
  {
65
- itemBlueprintKey: 'staffs/thunder-bolt-staff.png',
63
+ itemBlueprintKey: 'ThunderStaff',
66
64
  chance: LootProbability.Rare,
67
65
  },
68
66
  {
69
- itemBlueprintKey: 'crafting-resources/dragon-tooth.png',
67
+ itemBlueprintKey: 'DragonEssence',
70
68
  chance: LootProbability.Uncommon,
71
69
  quantityRange: [1, 3],
72
70
  },
@@ -95,16 +93,15 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
95
93
  spellKey: 'WarCry',
96
94
  probability: 25,
97
95
  power: 'Medium',
98
- texturePath: ''
99
96
  },
100
97
  ],
101
98
  loots: [
102
99
  {
103
- itemBlueprintKey: 'crafting-resources/leather.png',
100
+ itemBlueprintKey: 'OrcishAxe',
104
101
  chance: LootProbability.Uncommon,
105
102
  },
106
103
  {
107
- itemBlueprintKey: 'crafting-resources/herb.png',
104
+ itemBlueprintKey: 'HealingHerb',
108
105
  chance: LootProbability.Common,
109
106
  quantityRange: [1, 4],
110
107
  },
@@ -136,7 +133,6 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
136
133
  spellKey: 'BoneShards',
137
134
  probability: 20,
138
135
  power: 'Medium',
139
- texturePath: ''
140
136
  },
141
137
  ],
142
138
  loots: [
@@ -208,8 +204,7 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
208
204
  ],
209
205
  areaSpells: [
210
206
  {
211
- spellKey: 'VampiricStorm',
212
- texturePath: 'spell-icons/vampiric-storm.png',
207
+ spellKey: 'FrostBite',
213
208
  probability: 15,
214
209
  power: 'Medium',
215
210
  },
@@ -253,13 +248,11 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
253
248
  spellKey: 'DeathNova',
254
249
  probability: 30,
255
250
  power: 'VeryHigh',
256
- texturePath: ''
257
251
  },
258
252
  {
259
253
  spellKey: 'SoulDrain',
260
254
  probability: 25,
261
255
  power: 'High',
262
- texturePath: ''
263
256
  },
264
257
  ],
265
258
  loots: [
@@ -304,7 +297,6 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
304
297
  spellKey: 'BoulderThrow',
305
298
  probability: 40,
306
299
  power: 'High',
307
- texturePath: ''
308
300
  },
309
301
  ],
310
302
  loots: [
@@ -349,7 +341,6 @@ export const mockBestiaryItems: IInformationCenterNPC[] = [
349
341
  spellKey: 'ToxicCloud',
350
342
  probability: 20,
351
343
  power: 'Medium',
352
- texturePath: ''
353
344
  },
354
345
  ],
355
346
  loots: [
@@ -5,11 +5,8 @@ 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';
10
8
  import itemsAtlasJSON from '../../../mocks/atlas/items/items.json';
11
9
  import itemsAtlasIMG from '../../../mocks/atlas/items/items.png';
12
-
13
10
  import { mockBestiaryItems, mockFaqItems, mockItems, mockTutorials } from '../../../mocks/informationCenter.mocks';
14
11
 
15
12
 
@@ -30,8 +27,6 @@ const Template: Story = args => (
30
27
  itemsAtlasIMG={itemsAtlasIMG}
31
28
  entitiesAtlasJSON={entitiesAtlasJSON}
32
29
  entitiesAtlasIMG={entitiesAtlasIMG}
33
- iconsAtlasJSON={iconsAtlasJSON}
34
- iconsAtlasIMG={iconsAtlasIMG}
35
30
  faqItems={mockFaqItems}
36
31
  bestiaryItems={mockBestiaryItems}
37
32
  videoGuides={mockTutorials}