@rpg-engine/long-bow 0.8.28 → 0.8.30

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.28",
3
+ "version": "0.8.30",
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.0",
87
+ "@rpg-engine/shared": "^0.9.104",
88
88
  "dayjs": "^1.11.2",
89
89
  "font-awesome": "^4.7.0",
90
90
  "fs-extra": "^10.1.0",
@@ -9,9 +9,9 @@ import {
9
9
  } from '@rpg-engine/shared';
10
10
  import { DraggableContainer } from '../DraggableContainer';
11
11
  import { InternalTabs } from '../InternalTabs/InternalTabs';
12
- import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
13
12
  import { InformationCenterBestiarySection } from './sections/bestiary/InformationCenterBestiarySection';
14
13
  import { InformationCenterFAQSection } from './sections/faq/InformationCenterFaqSection';
14
+ import { InformationCenterItemDetails } from './sections/items/InformationCenterItemDetails';
15
15
  import { InformationCenterItemsSection } from './sections/items/InformationCenterItemsSection';
16
16
  import { InformationCenterTutorialsSection } from './sections/tutorials/InformationCenterTutorialsSection';
17
17
 
@@ -47,6 +47,10 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
47
47
  initialSearchQuery = '',
48
48
  }) => {
49
49
  const [activeTab, setActiveTab] = useState('bestiary');
50
+ const [
51
+ selectedItem,
52
+ setSelectedItem,
53
+ ] = useState<IInformationCenterItem | null>(null);
50
54
 
51
55
  if (loading) {
52
56
  return <LoadingMessage>Loading...</LoadingMessage>;
@@ -113,10 +117,7 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
113
117
  ];
114
118
 
115
119
  return (
116
- <DraggableContainer
117
- title="Information Center"
118
- type={RPGUIContainerTypes.Framed}
119
- >
120
+ <DraggableContainer title="Information Center">
120
121
  <Container>
121
122
  <InternalTabs
122
123
  tabs={tabs}
@@ -128,6 +129,19 @@ export const InformationCenter: React.FC<IInformationCenterProps> = ({
128
129
  borderColor="#f59e0b"
129
130
  hoverColor="#fef3c7"
130
131
  />
132
+ {selectedItem && (
133
+ <InformationCenterItemDetails
134
+ item={selectedItem}
135
+ itemsAtlasJSON={itemsAtlasJSON}
136
+ itemsAtlasIMG={itemsAtlasIMG}
137
+ droppedBy={bestiaryItems.filter(npc =>
138
+ npc.loots?.some(
139
+ loot => loot.itemBlueprintKey === selectedItem.key
140
+ )
141
+ )}
142
+ onBack={() => setSelectedItem(null)}
143
+ />
144
+ )}
131
145
  </Container>
132
146
  </DraggableContainer>
133
147
  );
@@ -1,8 +1,4 @@
1
- import {
2
- IInformationCenterNPC,
3
- NPCAlignment,
4
- isMobileOrTablet,
5
- } from '@rpg-engine/shared';
1
+ import { IInformationCenterNPC, NPCAlignment } from '@rpg-engine/shared';
6
2
  import React, { useMemo, useState } from 'react';
7
3
  import styled from 'styled-components';
8
4
  import { IOptionsProps } from '../../../Dropdown';
@@ -12,7 +8,6 @@ import { InformationCenterCell } from '../../InformationCenterCell';
12
8
  import { formatItemType } from '../items/InformationCenterItemsSection';
13
9
  import { InformationCenterNPCDetails } from './InformationCenterNPCDetails';
14
10
  import { InformationCenterNPCTooltip } from './InformationCenterNPCTooltip';
15
-
16
11
  interface IBestiarySectionProps {
17
12
  bestiaryItems: IInformationCenterNPC[];
18
13
  itemsAtlasJSON: Record<string, any>;
@@ -36,7 +31,6 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
36
31
  initialSearchQuery,
37
32
  tabId,
38
33
  }) => {
39
- const isMobile = isMobileOrTablet();
40
34
  const [searchQuery, setSearchQuery] = useState(initialSearchQuery);
41
35
  const [tooltipData, setTooltipData] = useState<{
42
36
  npc: IInformationCenterNPC;
@@ -46,12 +40,13 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
46
40
  selectedMonster,
47
41
  setSelectedMonster,
48
42
  ] = useState<IInformationCenterNPC | null>(null);
43
+ const [isTouchDevice] = useState('ontouchstart' in window);
49
44
 
50
45
  const handleMouseEnter = (
51
46
  monster: IInformationCenterNPC,
52
47
  event: React.MouseEvent
53
48
  ) => {
54
- if (!isMobile && !selectedMonster) {
49
+ if (!isTouchDevice && !selectedMonster) {
55
50
  setTooltipData({
56
51
  npc: monster,
57
52
  position: { x: event.clientX, y: event.clientY },
@@ -60,13 +55,13 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
60
55
  };
61
56
 
62
57
  const handleMouseLeave = () => {
63
- if (!isMobile) {
58
+ if (!isTouchDevice) {
64
59
  setTooltipData(null);
65
60
  }
66
61
  };
67
62
 
68
63
  const handleMouseMove = (event: React.MouseEvent) => {
69
- if (!isMobile && tooltipData) {
64
+ if (!isTouchDevice && tooltipData) {
70
65
  setTooltipData({
71
66
  ...tooltipData,
72
67
  position: { x: event.clientX, y: event.clientY },
@@ -78,9 +73,18 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
78
73
  monster: IInformationCenterNPC,
79
74
  event: React.TouchEvent
80
75
  ) => {
81
- event.preventDefault();
82
- setSelectedMonster(monster);
83
- setTooltipData(null);
76
+ if (isTouchDevice) {
77
+ event.preventDefault();
78
+ const touch = event.touches[0];
79
+ if (tooltipData?.npc.id === monster.id) {
80
+ setTooltipData(null);
81
+ } else {
82
+ setTooltipData({
83
+ npc: monster,
84
+ position: { x: touch.clientX, y: touch.clientY },
85
+ });
86
+ }
87
+ }
84
88
  };
85
89
 
86
90
  const handleMonsterClick = (monster: IInformationCenterNPC) => {
@@ -166,7 +170,7 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
166
170
  dependencies={[selectedBestiaryCategory]}
167
171
  itemHeight="180px"
168
172
  />
169
- {!isMobile && tooltipData && (
173
+ {tooltipData && (
170
174
  <Portal>
171
175
  <TooltipWrapper
172
176
  style={{
@@ -184,18 +188,16 @@ export const InformationCenterBestiarySection: React.FC<IBestiarySectionProps> =
184
188
  </Portal>
185
189
  )}
186
190
  {selectedMonster && (
187
- <Portal>
188
- <InformationCenterNPCDetails
189
- npc={selectedMonster}
190
- itemsAtlasJSON={itemsAtlasJSON}
191
- itemsAtlasIMG={itemsAtlasIMG}
192
- iconAtlasIMG={iconsAtlasIMG}
193
- iconAtlasJSON={iconsAtlasJSON}
194
- entitiesAtlasJSON={entitiesAtlasJSON}
195
- entitiesAtlasIMG={entitiesAtlasIMG}
196
- onBack={() => setSelectedMonster(null)}
197
- />
198
- </Portal>
191
+ <InformationCenterNPCDetails
192
+ npc={selectedMonster}
193
+ itemsAtlasJSON={itemsAtlasJSON}
194
+ itemsAtlasIMG={itemsAtlasIMG}
195
+ iconAtlasIMG={iconsAtlasIMG}
196
+ iconAtlasJSON={iconsAtlasJSON}
197
+ entitiesAtlasJSON={entitiesAtlasJSON}
198
+ entitiesAtlasIMG={entitiesAtlasIMG}
199
+ onBack={() => setSelectedMonster(null)}
200
+ />
199
201
  )}
200
202
  </>
201
203
  );
@@ -2,13 +2,11 @@ import {
2
2
  IInformationCenterItem,
3
3
  IInformationCenterNPC,
4
4
  ItemType,
5
- isMobileOrTablet,
6
5
  } from '@rpg-engine/shared';
7
6
  import React, { useState } from 'react';
8
7
  import styled from 'styled-components';
9
8
  import { IOptionsProps } from '../../../Dropdown';
10
9
  import { PaginatedContent } from '../../../shared/PaginatedContent/PaginatedContent';
11
- import { Portal } from '../../../shared/Portal/Portal';
12
10
  import { InformationCenterCell } from '../../InformationCenterCell';
13
11
  import { InformationCenterItemDetails } from './InformationCenterItemDetails';
14
12
  import { InformationCenterItemTooltip } from './InformationCenterItemTooltip';
@@ -49,7 +47,6 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
49
47
  initialSearchQuery,
50
48
  tabId,
51
49
  }) => {
52
- const isMobile = isMobileOrTablet();
53
50
  const [searchQuery, setSearchQuery] = useState(initialSearchQuery);
54
51
  const [selectedItemCategory, setSelectedItemCategory] = useState<string>(
55
52
  'all'
@@ -93,17 +90,15 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
93
90
  e: React.MouseEvent,
94
91
  item: IInformationCenterItem
95
92
  ) => {
96
- if (!isMobile) {
97
- setTooltipPosition({
98
- x: e.clientX + TOOLTIP_OFFSET,
99
- y: e.clientY,
100
- });
101
- setHoveredItem(item);
102
- }
93
+ setTooltipPosition({
94
+ x: e.clientX + TOOLTIP_OFFSET,
95
+ y: e.clientY,
96
+ });
97
+ setHoveredItem(item);
103
98
  };
104
99
 
105
100
  const handleMouseMove = (e: React.MouseEvent) => {
106
- if (!isMobile && hoveredItem) {
101
+ if (hoveredItem) {
107
102
  setTooltipPosition({
108
103
  x: e.clientX + TOOLTIP_OFFSET,
109
104
  y: e.clientY,
@@ -112,17 +107,19 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
112
107
  };
113
108
 
114
109
  const handleMouseLeave = () => {
115
- if (!isMobile) {
116
- setHoveredItem(null);
117
- }
110
+ setHoveredItem(null);
118
111
  };
119
112
 
120
113
  const handleTouchStart = (
121
114
  e: React.TouchEvent,
122
115
  item: IInformationCenterItem
123
116
  ) => {
124
- e.preventDefault();
125
- setSelectedItem(item);
117
+ const touch = e.touches[0];
118
+ setTooltipPosition({
119
+ x: touch.clientX + TOOLTIP_OFFSET,
120
+ y: touch.clientY,
121
+ });
122
+ setHoveredItem(item);
126
123
  };
127
124
 
128
125
  const handleItemClick = (item: IInformationCenterItem) => {
@@ -173,7 +170,7 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
173
170
  layout="grid"
174
171
  itemHeight="180px"
175
172
  />
176
- {!isMobile && hoveredItem && (
173
+ {hoveredItem && (
177
174
  <TooltipWrapper
178
175
  style={{ top: tooltipPosition.y, left: tooltipPosition.x }}
179
176
  >
@@ -181,15 +178,13 @@ export const InformationCenterItemsSection: React.FC<IItemsSectionProps> = ({
181
178
  </TooltipWrapper>
182
179
  )}
183
180
  {selectedItem && (
184
- <Portal>
185
- <InformationCenterItemDetails
186
- item={selectedItem}
187
- itemsAtlasJSON={itemsAtlasJSON}
188
- itemsAtlasIMG={itemsAtlasIMG}
189
- droppedBy={getDroppedByNPCs(selectedItem.key, bestiaryItems)}
190
- onBack={() => setSelectedItem(null)}
191
- />
192
- </Portal>
181
+ <InformationCenterItemDetails
182
+ item={selectedItem}
183
+ itemsAtlasJSON={itemsAtlasJSON}
184
+ itemsAtlasIMG={itemsAtlasIMG}
185
+ droppedBy={getDroppedByNPCs(selectedItem.key, bestiaryItems)}
186
+ onBack={() => setSelectedItem(null)}
187
+ />
193
188
  )}
194
189
  </>
195
190
  );
@@ -49,29 +49,24 @@ export const BaseInformationDetails: React.FC<IBaseInformationDetailsProps> = ({
49
49
  };
50
50
 
51
51
  const Container = styled.div`
52
- position: fixed;
52
+ position: absolute;
53
53
  inset: 0;
54
54
  display: flex;
55
55
  justify-content: center;
56
56
  align-items: center;
57
- z-index: 9999;
57
+ z-index: 1000;
58
58
  `;
59
59
 
60
60
  const Overlay = styled.div`
61
- position: fixed;
61
+ position: absolute;
62
62
  inset: 0;
63
63
  background-color: rgba(0, 0, 0, 0.8);
64
64
  `;
65
65
 
66
66
  const Modal = styled.div`
67
- position: fixed;
68
- top: 50%;
69
- left: 50%;
70
- transform: translate(-50%, -50%);
71
- width: 100%;
72
- height: 100%;
73
- max-width: 90%;
74
- max-height: 90%;
67
+ position: relative;
68
+ width: 90%;
69
+ height: 90%;
75
70
  background-color: rgba(0, 0, 0, 0.95);
76
71
  border-radius: 4px;
77
72
  padding: 16px;
@@ -81,12 +76,6 @@ const Modal = styled.div`
81
76
  border: 1px solid ${uiColors.darkGray};
82
77
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
83
78
 
84
- @media (max-width: 768px) {
85
- max-width: 100%;
86
- max-height: 100%;
87
- border-radius: 0;
88
- }
89
-
90
79
  &::-webkit-scrollbar {
91
80
  width: 2px;
92
81
  }
@@ -1,4 +1,4 @@
1
- import { IItem } from '@rpg-engine/shared';
1
+ import { IItem, ItemQualityLevel } from '@rpg-engine/shared';
2
2
  import React from 'react';
3
3
  import styled from 'styled-components';
4
4
  import { uiColors } from '../../../constants/uiColors';
@@ -6,6 +6,7 @@ import { uiFonts } from '../../../constants/uiFonts';
6
6
  import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
7
7
  import { ErrorBoundary } from '../Inventory/ErrorBoundary';
8
8
  import { EquipmentSlotSpriteByType } from '../Inventory/ItemSlot';
9
+ import { qualityColorHex } from '../Inventory/ItemSlotQuality';
9
10
  import { rarityColor } from '../Inventory/ItemSlotRarity';
10
11
 
11
12
  interface IItemInfoProps {
@@ -191,8 +192,24 @@ const Container = styled.div<{ item: IItem }>`
191
192
  padding: 0.5rem;
192
193
  font-size: ${uiFonts.size.small};
193
194
  border: 3px solid ${({ item }) => rarityColor(item) ?? uiColors.lightGray};
195
+ box-shadow: ${({ item }) => `0 0 5px 2px ${rarityColor(item)}`};
194
196
  height: max-content;
195
197
  width: 18rem;
198
+ position: relative;
199
+
200
+ ${({ item }) =>
201
+ item?.quality && item.quality !== ItemQualityLevel.Normal &&
202
+ `
203
+ &::before {
204
+ content: '★';
205
+ position: absolute;
206
+ top: 0.2rem;
207
+ left: 0.5rem;
208
+ font-size: 1.2rem;
209
+ color: ${qualityColorHex(item)};
210
+ text-shadow: 0 0 3px black;
211
+ }
212
+ `}
196
213
 
197
214
  @media (max-width: 640px) {
198
215
  width: 80vw;
@@ -6,7 +6,7 @@ import {
6
6
  ItemContainerType,
7
7
  ItemSlotType,
8
8
  ItemSubType,
9
- ItemType,
9
+ ItemType
10
10
  } from '@rpg-engine/shared';
11
11
 
12
12
  import { observer } from 'mobx-react-lite';
@@ -15,10 +15,11 @@ import Draggable, { DraggableEventHandler } from 'react-draggable';
15
15
  import styled from 'styled-components';
16
16
  import useTouchTarget from '../../../hooks/useTouchTarget';
17
17
  import { IPosition } from '../../../types/eventTypes';
18
- import { rarityColor } from './ItemSlotRarity';
19
- import { ItemSlotRenderer } from './ItemSlotRenderer';
20
18
  import { useItemSlotDetails } from './context/ItemSlotDetailsContext';
21
19
  import { useItemSlotDragging } from './context/ItemSlotDraggingContext';
20
+ import { qualityColorHex } from './ItemSlotQuality';
21
+ import { rarityColor } from './ItemSlotRarity';
22
+ import { ItemSlotRenderer } from './ItemSlotRenderer';
22
23
 
23
24
  export const EquipmentSlotSpriteByType: any = {
24
25
  Neck: 'accessories/corruption-necklace.png',
@@ -539,6 +540,17 @@ const Container = styled.div<ContainerTypes>`
539
540
  ${({ item }) => `0 0 4px 3px ${rarityColor(item)}`};
540
541
  }
541
542
 
543
+ .quality-star {
544
+ position: absolute;
545
+ top: 0.5rem;
546
+ left: 0.5rem;
547
+ font-size: 1.2rem;
548
+ z-index: 2;
549
+ text-shadow: 0 0 3px black;
550
+ pointer-events: none;
551
+ color: ${({ item }) => qualityColorHex(item)};
552
+ }
553
+
542
554
  &::before {
543
555
  content: '';
544
556
  position: absolute;
@@ -0,0 +1,18 @@
1
+ import { IItem, ItemQualityLevel } from '@rpg-engine/shared';
2
+
3
+ export const qualityColorHex = (item: IItem | null) => {
4
+ switch (item?.quality) {
5
+ case ItemQualityLevel.HighQuality:
6
+ return '#00bfff';
7
+ case ItemQualityLevel.Exceptional:
8
+ return '#ff8c00';
9
+ case ItemQualityLevel.Mastercrafted:
10
+ return '#ff00ff';
11
+ case ItemQualityLevel.Ancient:
12
+ return '#ffd700';
13
+ case ItemQualityLevel.Mythic:
14
+ return '#ff0080';
15
+ default:
16
+ return 'transparent';
17
+ }
18
+ };
@@ -1,8 +1,9 @@
1
1
  import {
2
+ getItemTextureKeyPath,
2
3
  IItem,
3
4
  ItemContainerType,
5
+ ItemQualityLevel,
4
6
  ItemSlotType,
5
- getItemTextureKeyPath,
6
7
  } from '@rpg-engine/shared';
7
8
  import React from 'react';
8
9
  import { v4 as uuidv4 } from 'uuid';
@@ -46,6 +47,9 @@ export const ItemSlotRenderer: React.FC<IProps> = ({
46
47
 
47
48
  return (
48
49
  <ErrorBoundary key={item._id}>
50
+ {item.quality && item.quality !== ItemQualityLevel.Normal && (
51
+ <div className="quality-star">★</div>
52
+ )}
49
53
  <SpriteFromAtlas
50
54
  atlasIMG={atlasIMG}
51
55
  atlasJSON={atlasJSON}
@@ -101,5 +105,5 @@ export const ItemSlotRenderer: React.FC<IProps> = ({
101
105
  }
102
106
  };
103
107
 
104
- return <>{onRenderSlot(item)}</>;
108
+ return onRenderSlot(item);
105
109
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  IItem,
3
3
  IItemContainer,
4
+ ItemQualityLevel,
4
5
  ItemRarities,
5
6
  ItemSlotType,
6
7
  ItemSubType,
@@ -81,6 +82,7 @@ export const items: IItem[] = [
81
82
  createdAt: '2022-06-04T03:18:09.335Z',
82
83
  updatedAt: '2022-06-04T18:16:49.056Z',
83
84
  rarity: ItemRarities.Legendary,
85
+ quality: ItemQualityLevel.Mythic,
84
86
  canBePurchasedOnlyByPremiumPlans: [
85
87
  UserAccountTypes.PremiumGold,
86
88
  UserAccountTypes.PremiumUltimate,
@@ -167,6 +169,7 @@ export const items: IItem[] = [
167
169
  createdAt: '2022-06-04T03:18:09.335Z',
168
170
  updatedAt: '2022-06-04T18:16:49.056Z',
169
171
  rarity: ItemRarities.Epic,
172
+ quality: ItemQualityLevel.Normal,
170
173
  attachedGems: [
171
174
  {
172
175
  gemEntityEffectsAdd: ['poison'],
@@ -221,6 +224,7 @@ export const items: IItem[] = [
221
224
  createdAt: '2022-06-04T03:18:09.335Z',
222
225
  updatedAt: '2022-06-04T18:16:49.056Z',
223
226
  rarity: ItemRarities.Uncommon,
227
+ quality: ItemQualityLevel.HighQuality,
224
228
  },
225
229
  {
226
230
  _id: '629acek4j7c8e8002ff60034',
@@ -255,6 +259,7 @@ export const items: IItem[] = [
255
259
  createdAt: '2022-06-04T03:18:09.335Z',
256
260
  updatedAt: '2022-06-04T18:16:49.056Z',
257
261
  rarity: ItemRarities.Rare,
262
+ quality: ItemQualityLevel.Ancient,
258
263
  },
259
264
  {
260
265
  _id: '629acek4j7c8e8002fg60034',
@@ -289,6 +294,7 @@ export const items: IItem[] = [
289
294
  createdAt: '2022-06-04T03:18:09.335Z',
290
295
  updatedAt: '2022-06-04T18:16:49.056Z',
291
296
  rarity: ItemRarities.Common,
297
+ quality: ItemQualityLevel.Mastercrafted,
292
298
  },
293
299
  {
294
300
  _id: '392acek4j7c8e8002ff60403',
@@ -323,6 +329,7 @@ export const items: IItem[] = [
323
329
  createdAt: '2022-06-04T03:18:09.335Z',
324
330
  updatedAt: '2022-06-04T18:16:49.056Z',
325
331
  rarity: ItemRarities.Common,
332
+ quality: ItemQualityLevel.Exceptional,
326
333
  },
327
334
  createBagItem('392acek4j7c8e8002ff60404', '#FF0000'), // red
328
335
  createBagItem('392acek4j7c8e8002ff60405', '#0000FF'), // blue