@rpg-engine/long-bow 0.3.51 → 0.3.53
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/dist/components/CircularController/CircularController.d.ts +6 -3
- package/dist/components/Item/Inventory/ItemContainer.d.ts +4 -1
- package/dist/components/Item/Inventory/ItemSlot.d.ts +1 -0
- package/dist/components/Shortcuts/Shortcuts.d.ts +12 -0
- package/dist/components/Shortcuts/ShortcutsSetter.d.ts +12 -0
- package/dist/components/Shortcuts/SingleShortcut.d.ts +1 -0
- package/dist/components/Spellbook/Spellbook.d.ts +5 -3
- package/dist/components/Spellbook/constants.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/long-bow.cjs.development.js +292 -146
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +293 -146
- package/dist/long-bow.esm.js.map +1 -1
- package/dist/stories/{QuickSpells.stories.d.ts → Shortcuts.stories.d.ts} +2 -2
- package/package.json +2 -2
- package/src/components/Abstractions/SlotsContainer.tsx +2 -2
- package/src/components/CircularController/CircularController.tsx +119 -36
- package/src/components/Item/Inventory/ItemContainer.tsx +39 -4
- package/src/components/Item/Inventory/ItemSlot.tsx +38 -3
- package/src/components/Shortcuts/Shortcuts.tsx +129 -0
- package/src/components/Shortcuts/ShortcutsSetter.tsx +132 -0
- package/src/components/Shortcuts/SingleShortcut.ts +61 -0
- package/src/components/Spellbook/Spellbook.tsx +15 -9
- package/src/components/Spellbook/constants.ts +5 -9
- package/src/components/TradingMenu/TradingMenu.tsx +2 -2
- package/src/components/TradingMenu/items.mock.ts +59 -0
- package/src/index.tsx +1 -1
- package/src/mocks/itemContainer.mocks.ts +22 -20
- package/src/stories/CircullarController.stories.tsx +9 -5
- package/src/stories/ItemContainer.stories.tsx +76 -2
- package/src/stories/Shortcuts.stories.tsx +39 -0
- package/src/stories/Spellbook.stories.tsx +35 -38
- package/dist/components/Spellbook/QuickSpells.d.ts +0 -10
- package/dist/components/Spellbook/SpellbookShortcuts.d.ts +0 -10
- package/src/components/Spellbook/QuickSpells.tsx +0 -120
- package/src/components/Spellbook/SpellbookShortcuts.tsx +0 -77
- package/src/stories/QuickSpells.stories.tsx +0 -38
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Meta } from '@storybook/react';
|
|
2
|
-
import {
|
|
2
|
+
import { ShortcutsProps } from '../components/Shortcuts/Shortcuts';
|
|
3
3
|
declare const meta: Meta;
|
|
4
4
|
export default meta;
|
|
5
|
-
export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework,
|
|
5
|
+
export declare const Default: import("@storybook/csf").AnnotatedStoryFn<import("@storybook/react").ReactFramework, ShortcutsProps>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpg-engine/long-bow",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.53",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@rollup/plugin-image": "^2.1.1",
|
|
86
|
-
"@rpg-engine/shared": "^0.6.
|
|
86
|
+
"@rpg-engine/shared": "^0.6.85",
|
|
87
87
|
"dayjs": "^1.11.2",
|
|
88
88
|
"font-awesome": "^4.7.0",
|
|
89
89
|
"fs-extra": "^10.1.0",
|
|
@@ -29,8 +29,8 @@ export const SlotsContainer: React.FC<IProps> = ({
|
|
|
29
29
|
onClose();
|
|
30
30
|
}
|
|
31
31
|
}}
|
|
32
|
-
width="
|
|
33
|
-
cancelDrag=".item-container-body"
|
|
32
|
+
width="400px"
|
|
33
|
+
cancelDrag=".item-container-body, #shortcuts_list"
|
|
34
34
|
onPositionChange={({ x, y }) => {
|
|
35
35
|
if (onPositionChange) {
|
|
36
36
|
onPositionChange({ x, y });
|
|
@@ -1,23 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getItemTextureKeyPath,
|
|
3
|
+
IItem,
|
|
4
|
+
IItemContainer,
|
|
5
|
+
IRawSpell,
|
|
6
|
+
IShortcut,
|
|
7
|
+
ShortcutType,
|
|
8
|
+
} from '@rpg-engine/shared';
|
|
2
9
|
import React from 'react';
|
|
3
10
|
import styled from 'styled-components';
|
|
4
11
|
import { uiColors } from '../../constants/uiColors';
|
|
5
|
-
import {
|
|
12
|
+
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
13
|
+
import { SingleShortcut } from '../Shortcuts/SingleShortcut';
|
|
6
14
|
|
|
7
15
|
export type CircularControllerProps = {
|
|
8
16
|
onActionClick: () => void;
|
|
9
17
|
onCancelClick: () => void;
|
|
10
|
-
|
|
18
|
+
onShortcutClick: (index: number) => void;
|
|
11
19
|
mana: number;
|
|
12
|
-
|
|
20
|
+
shortcuts: IShortcut[];
|
|
21
|
+
inventory?: IItemContainer | null;
|
|
22
|
+
atlasIMG: any;
|
|
23
|
+
atlasJSON: any;
|
|
13
24
|
};
|
|
14
25
|
|
|
15
26
|
export const CircularController: React.FC<CircularControllerProps> = ({
|
|
16
27
|
onActionClick,
|
|
17
28
|
onCancelClick,
|
|
18
|
-
|
|
29
|
+
onShortcutClick,
|
|
19
30
|
mana,
|
|
20
|
-
|
|
31
|
+
shortcuts,
|
|
32
|
+
inventory,
|
|
33
|
+
atlasIMG,
|
|
34
|
+
atlasJSON,
|
|
21
35
|
}) => {
|
|
22
36
|
const onTouchStart = (e: React.TouchEvent<HTMLButtonElement>) => {
|
|
23
37
|
const target = e.target as HTMLButtonElement;
|
|
@@ -35,33 +49,91 @@ export const CircularController: React.FC<CircularControllerProps> = ({
|
|
|
35
49
|
action();
|
|
36
50
|
};
|
|
37
51
|
|
|
52
|
+
const renderShortcut = (i: number) => {
|
|
53
|
+
let variant = '';
|
|
54
|
+
|
|
55
|
+
if (i === 0) variant = 'top';
|
|
56
|
+
else if (i >= 3) variant = `bottom-${i - 3}`;
|
|
57
|
+
|
|
58
|
+
const onShortcutClickBinded =
|
|
59
|
+
shortcuts[i]?.type !== ShortcutType.None
|
|
60
|
+
? onShortcutClick.bind(null, i)
|
|
61
|
+
: () => {};
|
|
62
|
+
|
|
63
|
+
if (shortcuts[i]?.type === ShortcutType.Item) {
|
|
64
|
+
const payload = shortcuts[i]?.payload as IItem | undefined;
|
|
65
|
+
|
|
66
|
+
let itemsFromEquipment: (IItem | undefined | null)[] = [];
|
|
67
|
+
|
|
68
|
+
if (inventory) {
|
|
69
|
+
Object.keys(inventory.slots).forEach(i => {
|
|
70
|
+
const index = parseInt(i);
|
|
71
|
+
|
|
72
|
+
if (inventory.slots[index]?.key === payload?.key) {
|
|
73
|
+
itemsFromEquipment.push(inventory.slots[index]);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const totalQty = itemsFromEquipment.reduce(
|
|
79
|
+
(acc, item) => acc + (item?.stackQty || 1),
|
|
80
|
+
0
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<StyledShortcut
|
|
85
|
+
key={i}
|
|
86
|
+
onTouchStart={onTouchStart}
|
|
87
|
+
onTouchEnd={onTouchEnd.bind(null, onShortcutClickBinded)}
|
|
88
|
+
disabled={false}
|
|
89
|
+
className={variant}
|
|
90
|
+
>
|
|
91
|
+
{payload && (
|
|
92
|
+
<SpriteFromAtlas
|
|
93
|
+
atlasIMG={atlasIMG}
|
|
94
|
+
atlasJSON={atlasJSON}
|
|
95
|
+
spriteKey={getItemTextureKeyPath(
|
|
96
|
+
{
|
|
97
|
+
key: payload.texturePath,
|
|
98
|
+
texturePath: payload.texturePath,
|
|
99
|
+
stackQty: payload.stackQty || 1,
|
|
100
|
+
},
|
|
101
|
+
atlasJSON
|
|
102
|
+
)}
|
|
103
|
+
width={32}
|
|
104
|
+
height={32}
|
|
105
|
+
imgScale={1.4}
|
|
106
|
+
imgStyle={{ left: '4px' }}
|
|
107
|
+
/>
|
|
108
|
+
)}
|
|
109
|
+
<span className="qty">{totalQty}</span>
|
|
110
|
+
</StyledShortcut>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const payload = shortcuts[i]?.payload as IRawSpell | undefined;
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<StyledShortcut
|
|
118
|
+
key={i}
|
|
119
|
+
onTouchStart={onTouchStart}
|
|
120
|
+
onTouchEnd={onTouchEnd.bind(null, onShortcutClickBinded)}
|
|
121
|
+
disabled={mana < (payload?.manaCost ?? 0)}
|
|
122
|
+
className={variant}
|
|
123
|
+
>
|
|
124
|
+
<span className="mana">{payload && payload.manaCost}</span>
|
|
125
|
+
<span className="magicWords">
|
|
126
|
+
{payload?.magicWords.split(' ').map(word => word[0])}
|
|
127
|
+
</span>
|
|
128
|
+
</StyledShortcut>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
38
132
|
return (
|
|
39
133
|
<ButtonsContainer>
|
|
40
|
-
<
|
|
41
|
-
{Array.from({ length:
|
|
42
|
-
|
|
43
|
-
const spell = spells[i];
|
|
44
|
-
|
|
45
|
-
const onSpellClickBinded = spell
|
|
46
|
-
? onSpellClick.bind(null, spell.key)
|
|
47
|
-
: () => {};
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<StyledShortcut
|
|
51
|
-
key={i}
|
|
52
|
-
disabled={mana < spell?.manaCost}
|
|
53
|
-
onTouchStart={onTouchStart}
|
|
54
|
-
onTouchEnd={onTouchEnd.bind(null, onSpellClickBinded)}
|
|
55
|
-
className={variant}
|
|
56
|
-
>
|
|
57
|
-
<span className="mana">{spell?.key && spell?.manaCost}</span>
|
|
58
|
-
<span className="magicWords">
|
|
59
|
-
{spell?.magicWords.split(' ').map(word => word[0])}
|
|
60
|
-
</span>
|
|
61
|
-
</StyledShortcut>
|
|
62
|
-
);
|
|
63
|
-
})}
|
|
64
|
-
</SpellsContainer>
|
|
134
|
+
<ShortcutsContainer>
|
|
135
|
+
{Array.from({ length: 6 }).map((_, i) => renderShortcut(i))}
|
|
136
|
+
</ShortcutsContainer>
|
|
65
137
|
<Button
|
|
66
138
|
onTouchStart={onTouchStart}
|
|
67
139
|
onTouchEnd={onTouchEnd.bind(null, onActionClick)}
|
|
@@ -93,6 +165,7 @@ const Button = styled.button`
|
|
|
93
165
|
justify-content: center;
|
|
94
166
|
position: relative;
|
|
95
167
|
transition: background-color 0.1s;
|
|
168
|
+
margin-top: -3rem;
|
|
96
169
|
|
|
97
170
|
&.active {
|
|
98
171
|
background-color: ${uiColors.gray};
|
|
@@ -125,28 +198,38 @@ const ButtonsContainer = styled.div`
|
|
|
125
198
|
gap: 0.5rem;
|
|
126
199
|
`;
|
|
127
200
|
|
|
128
|
-
const
|
|
201
|
+
const ShortcutsContainer = styled.div`
|
|
129
202
|
display: flex;
|
|
130
203
|
align-items: center;
|
|
131
204
|
justify-content: center;
|
|
132
|
-
gap: 0.
|
|
205
|
+
gap: 0.5rem;
|
|
133
206
|
flex-direction: column;
|
|
207
|
+
margin-top: 3rem;
|
|
134
208
|
|
|
135
209
|
.top {
|
|
136
210
|
transform: translate(93%, 25%);
|
|
137
211
|
}
|
|
138
212
|
|
|
139
|
-
.bottom {
|
|
213
|
+
.bottom-0 {
|
|
140
214
|
transform: translate(93%, -25%);
|
|
141
215
|
}
|
|
216
|
+
|
|
217
|
+
.bottom-1 {
|
|
218
|
+
transform: translate(-120%, calc(-5.5rem));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.bottom-2 {
|
|
222
|
+
transform: translate(-30%, calc(-5.5rem - 25%));
|
|
223
|
+
}
|
|
142
224
|
`;
|
|
143
225
|
|
|
144
|
-
const StyledShortcut = styled(
|
|
226
|
+
const StyledShortcut = styled(SingleShortcut)`
|
|
145
227
|
width: 2.5rem;
|
|
146
228
|
height: 2.5rem;
|
|
147
229
|
transition: background-color 0.1s;
|
|
148
230
|
|
|
149
|
-
.mana
|
|
231
|
+
.mana,
|
|
232
|
+
.qty {
|
|
150
233
|
font-size: 0.5rem;
|
|
151
234
|
}
|
|
152
235
|
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
IItem,
|
|
3
|
+
IItemContainer,
|
|
4
|
+
IShortcut,
|
|
5
|
+
ItemContainerType,
|
|
6
|
+
ItemType,
|
|
7
|
+
} from '@rpg-engine/shared';
|
|
2
8
|
import React, { useState } from 'react';
|
|
3
9
|
import styled from 'styled-components';
|
|
4
10
|
import { SlotsContainer } from '../../Abstractions/SlotsContainer';
|
|
5
11
|
import { ItemQuantitySelector } from './ItemQuantitySelector';
|
|
6
12
|
|
|
7
13
|
import { IPosition } from '../../../types/eventTypes';
|
|
14
|
+
import { ShortcutsSetter } from '../../Shortcuts/ShortcutsSetter';
|
|
8
15
|
import { ItemSlot } from './ItemSlot';
|
|
9
16
|
|
|
10
17
|
export interface IItemContainerProps {
|
|
@@ -37,6 +44,9 @@ export interface IItemContainerProps {
|
|
|
37
44
|
atlasIMG: any;
|
|
38
45
|
disableContextMenu?: boolean;
|
|
39
46
|
initialPosition?: { x: number; y: number };
|
|
47
|
+
shortcuts?: IShortcut[];
|
|
48
|
+
setItemShortcut?: (key: string, index: number) => void;
|
|
49
|
+
removeShortcut?: (index: number) => void;
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
@@ -57,12 +67,16 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
|
57
67
|
initialPosition,
|
|
58
68
|
checkIfItemShouldDragEnd,
|
|
59
69
|
dragScale,
|
|
70
|
+
shortcuts,
|
|
71
|
+
setItemShortcut,
|
|
72
|
+
removeShortcut,
|
|
60
73
|
}) => {
|
|
61
74
|
const [quantitySelect, setQuantitySelect] = useState({
|
|
62
75
|
isOpen: false,
|
|
63
76
|
maxQuantity: 1,
|
|
64
77
|
callback: (_quantity: number) => {},
|
|
65
78
|
});
|
|
79
|
+
const [settingShortcutIndex, setSettingShortcutIndex] = useState(-1);
|
|
66
80
|
|
|
67
81
|
const onRenderSlots = () => {
|
|
68
82
|
const slots = [];
|
|
@@ -78,8 +92,17 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
|
78
92
|
onMouseOver={(event, slotIndex, item) => {
|
|
79
93
|
if (onMouseOver) onMouseOver(event, slotIndex, item);
|
|
80
94
|
}}
|
|
81
|
-
onClick={(
|
|
82
|
-
if (
|
|
95
|
+
onClick={(itemType, containerType, item) => {
|
|
96
|
+
if (settingShortcutIndex !== -1) {
|
|
97
|
+
setSettingShortcutIndex(-1);
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
itemType === ItemType.Consumable ||
|
|
101
|
+
itemType === ItemType.Tool
|
|
102
|
+
) {
|
|
103
|
+
setItemShortcut?.(item.key, settingShortcutIndex);
|
|
104
|
+
}
|
|
105
|
+
} else if (onItemClick) onItemClick(item, itemType, containerType);
|
|
83
106
|
}}
|
|
84
107
|
onSelected={(optionId: string, item: IItem) => {
|
|
85
108
|
if (onSelected) onSelected(optionId, item);
|
|
@@ -110,6 +133,7 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
|
110
133
|
}}
|
|
111
134
|
atlasIMG={atlasIMG}
|
|
112
135
|
atlasJSON={atlasJSON}
|
|
136
|
+
isSelectingShortcut={settingShortcutIndex !== -1}
|
|
113
137
|
/>
|
|
114
138
|
);
|
|
115
139
|
}
|
|
@@ -123,6 +147,18 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
|
123
147
|
onClose={onClose}
|
|
124
148
|
initialPosition={initialPosition}
|
|
125
149
|
>
|
|
150
|
+
{type === ItemContainerType.Inventory &&
|
|
151
|
+
shortcuts &&
|
|
152
|
+
removeShortcut && (
|
|
153
|
+
<ShortcutsSetter
|
|
154
|
+
setSettingShortcutIndex={setSettingShortcutIndex}
|
|
155
|
+
settingShortcutIndex={settingShortcutIndex}
|
|
156
|
+
shortcuts={shortcuts}
|
|
157
|
+
removeShortcut={removeShortcut}
|
|
158
|
+
atlasIMG={atlasIMG}
|
|
159
|
+
atlasJSON={atlasJSON}
|
|
160
|
+
/>
|
|
161
|
+
)}
|
|
126
162
|
<ItemsContainer className="item-container-body">
|
|
127
163
|
{onRenderSlots()}
|
|
128
164
|
</ItemsContainer>
|
|
@@ -155,7 +191,6 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
|
|
|
155
191
|
};
|
|
156
192
|
|
|
157
193
|
const ItemsContainer = styled.div`
|
|
158
|
-
max-width: 280px;
|
|
159
194
|
display: flex;
|
|
160
195
|
justify-content: center;
|
|
161
196
|
flex-wrap: wrap;
|
|
@@ -74,6 +74,7 @@ interface IProps {
|
|
|
74
74
|
atlasJSON: any;
|
|
75
75
|
atlasIMG: any;
|
|
76
76
|
isContextMenuDisabled?: boolean;
|
|
77
|
+
isSelectingShortcut?: boolean;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
export const ItemSlot: React.FC<IProps> = observer(
|
|
@@ -97,6 +98,7 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
97
98
|
openQuantitySelector,
|
|
98
99
|
checkIfItemShouldDragEnd,
|
|
99
100
|
dragScale,
|
|
101
|
+
isSelectingShortcut,
|
|
100
102
|
}) => {
|
|
101
103
|
const [isTooltipVisible, setTooltipVisible] = useState(false);
|
|
102
104
|
|
|
@@ -117,6 +119,7 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
117
119
|
setIsFocused(false);
|
|
118
120
|
|
|
119
121
|
if (item) {
|
|
122
|
+
console.log(item);
|
|
120
123
|
setContextActions(generateContextMenu(item, containerType));
|
|
121
124
|
}
|
|
122
125
|
}, [item]);
|
|
@@ -278,12 +281,17 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
278
281
|
.elementFromPoint(clientX, clientY)
|
|
279
282
|
?.dispatchEvent(simulatedEvent);
|
|
280
283
|
}}
|
|
284
|
+
isSelectingShortcut={
|
|
285
|
+
isSelectingShortcut &&
|
|
286
|
+
(item?.type === ItemType.Consumable || item?.type === ItemType.Tool)
|
|
287
|
+
}
|
|
281
288
|
>
|
|
282
289
|
<Draggable
|
|
290
|
+
axis={isSelectingShortcut ? 'none' : 'both'}
|
|
283
291
|
defaultClassName={item ? 'draggable' : 'empty-slot'}
|
|
284
292
|
scale={dragScale}
|
|
285
293
|
onStop={(e, data) => {
|
|
286
|
-
if (wasDragged && item) {
|
|
294
|
+
if (wasDragged && item && !isSelectingShortcut) {
|
|
287
295
|
//@ts-ignore
|
|
288
296
|
const classes: string[] = Array.from(e.target?.classList);
|
|
289
297
|
|
|
@@ -330,14 +338,14 @@ export const ItemSlot: React.FC<IProps> = observer(
|
|
|
330
338
|
}
|
|
331
339
|
}, 100);
|
|
332
340
|
} else if (item) {
|
|
333
|
-
if (!isContextMenuDisabled)
|
|
341
|
+
if (!isContextMenuDisabled && !isSelectingShortcut)
|
|
334
342
|
setIsContextMenuVisible(!isContextMenuVisible);
|
|
335
343
|
|
|
336
344
|
onClick(item.type, containerType, item);
|
|
337
345
|
}
|
|
338
346
|
}}
|
|
339
347
|
onStart={() => {
|
|
340
|
-
if (!item) {
|
|
348
|
+
if (!item || isSelectingShortcut) {
|
|
341
349
|
return;
|
|
342
350
|
}
|
|
343
351
|
|
|
@@ -417,6 +425,7 @@ const rarityColor = (item: IItem | null) => {
|
|
|
417
425
|
|
|
418
426
|
interface ContainerTypes {
|
|
419
427
|
item: IItem | null;
|
|
428
|
+
isSelectingShortcut?: boolean;
|
|
420
429
|
}
|
|
421
430
|
|
|
422
431
|
const Container = styled.div<ContainerTypes>`
|
|
@@ -432,6 +441,32 @@ const Container = styled.div<ContainerTypes>`
|
|
|
432
441
|
//background-color: ${({ item }) => rarityColor(item)};
|
|
433
442
|
}
|
|
434
443
|
position: relative;
|
|
444
|
+
|
|
445
|
+
&::before {
|
|
446
|
+
content: '';
|
|
447
|
+
position: absolute;
|
|
448
|
+
top: 0;
|
|
449
|
+
left: 0;
|
|
450
|
+
width: 100%;
|
|
451
|
+
height: 100%;
|
|
452
|
+
z-index: 1;
|
|
453
|
+
border-radius: 12px;
|
|
454
|
+
pointer-events: none;
|
|
455
|
+
animation: ${({ isSelectingShortcut }) =>
|
|
456
|
+
isSelectingShortcut ? 'bg-color-change 1s infinite' : 'none'};
|
|
457
|
+
|
|
458
|
+
@keyframes bg-color-change {
|
|
459
|
+
0% {
|
|
460
|
+
background-color: rgba(255 255 255 / 0.5);
|
|
461
|
+
}
|
|
462
|
+
50% {
|
|
463
|
+
background-color: transparent;
|
|
464
|
+
}
|
|
465
|
+
100% {
|
|
466
|
+
background-color: rgba(255 255 255 / 0.5);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
435
470
|
`;
|
|
436
471
|
|
|
437
472
|
const ItemContainer = styled.div<{ isFocused?: boolean }>`
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getItemTextureKeyPath,
|
|
3
|
+
IItem,
|
|
4
|
+
IItemContainer,
|
|
5
|
+
IRawSpell,
|
|
6
|
+
IShortcut,
|
|
7
|
+
ShortcutType,
|
|
8
|
+
} from '@rpg-engine/shared';
|
|
9
|
+
import React, { useEffect } from 'react';
|
|
10
|
+
import styled from 'styled-components';
|
|
11
|
+
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
12
|
+
import { SingleShortcut } from './SingleShortcut';
|
|
13
|
+
|
|
14
|
+
export type ShortcutsProps = {
|
|
15
|
+
shortcuts: IShortcut[];
|
|
16
|
+
onShortcutCast: (index: number) => void;
|
|
17
|
+
mana: number;
|
|
18
|
+
isBlockedCastingByKeyboard?: boolean;
|
|
19
|
+
inventory?: IItemContainer | null;
|
|
20
|
+
atlasJSON: any;
|
|
21
|
+
atlasIMG: any;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const Shortcuts: React.FC<ShortcutsProps> = ({
|
|
25
|
+
shortcuts,
|
|
26
|
+
onShortcutCast,
|
|
27
|
+
mana,
|
|
28
|
+
isBlockedCastingByKeyboard = false,
|
|
29
|
+
atlasJSON,
|
|
30
|
+
atlasIMG,
|
|
31
|
+
inventory,
|
|
32
|
+
}) => {
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
35
|
+
if (isBlockedCastingByKeyboard) return;
|
|
36
|
+
|
|
37
|
+
const shortcutIndex = Number(e.key) - 1;
|
|
38
|
+
if (shortcutIndex >= 0 && shortcutIndex <= 5) {
|
|
39
|
+
onShortcutCast(shortcutIndex);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
44
|
+
|
|
45
|
+
return () => {
|
|
46
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
47
|
+
};
|
|
48
|
+
}, [shortcuts, isBlockedCastingByKeyboard]);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<List>
|
|
52
|
+
{Array.from({ length: 6 }).map((_, i) => {
|
|
53
|
+
if (shortcuts[i]?.type === ShortcutType.Item) {
|
|
54
|
+
const payload = shortcuts[i]?.payload as IItem | undefined;
|
|
55
|
+
|
|
56
|
+
let itemsFromEquipment: (IItem | undefined | null)[] = [];
|
|
57
|
+
|
|
58
|
+
if (inventory) {
|
|
59
|
+
Object.keys(inventory.slots).forEach(i => {
|
|
60
|
+
const index = parseInt(i);
|
|
61
|
+
|
|
62
|
+
if (inventory.slots[index]?.key === payload?.key) {
|
|
63
|
+
itemsFromEquipment.push(inventory.slots[index]);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const totalQty = itemsFromEquipment.reduce(
|
|
69
|
+
(acc, item) => acc + (item?.stackQty || 1),
|
|
70
|
+
0
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<SingleShortcut
|
|
75
|
+
key={i}
|
|
76
|
+
onClick={onShortcutCast.bind(null, i)}
|
|
77
|
+
disabled={false}
|
|
78
|
+
>
|
|
79
|
+
{payload && (
|
|
80
|
+
<SpriteFromAtlas
|
|
81
|
+
atlasIMG={atlasIMG}
|
|
82
|
+
atlasJSON={atlasJSON}
|
|
83
|
+
spriteKey={getItemTextureKeyPath(
|
|
84
|
+
{
|
|
85
|
+
key: payload.texturePath,
|
|
86
|
+
texturePath: payload.texturePath,
|
|
87
|
+
stackQty: payload.stackQty || 1,
|
|
88
|
+
},
|
|
89
|
+
atlasJSON
|
|
90
|
+
)}
|
|
91
|
+
width={32}
|
|
92
|
+
height={32}
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
<span className="qty">{totalQty}</span>
|
|
96
|
+
<span className="keyboard">{i + 1}</span>
|
|
97
|
+
</SingleShortcut>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const payload = shortcuts[i]?.payload as IRawSpell | undefined;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<SingleShortcut
|
|
105
|
+
key={i}
|
|
106
|
+
onClick={onShortcutCast.bind(null, i)}
|
|
107
|
+
disabled={mana < (payload?.manaCost ?? 0)}
|
|
108
|
+
>
|
|
109
|
+
<span className="mana">{payload && payload.manaCost}</span>
|
|
110
|
+
<span className="magicWords">
|
|
111
|
+
{payload?.magicWords.split(' ').map(word => word[0])}
|
|
112
|
+
</span>
|
|
113
|
+
<span className="keyboard">{i + 1}</span>
|
|
114
|
+
</SingleShortcut>
|
|
115
|
+
);
|
|
116
|
+
})}
|
|
117
|
+
</List>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const List = styled.p`
|
|
122
|
+
width: 100%;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
gap: 0.5rem;
|
|
127
|
+
box-sizing: border-box;
|
|
128
|
+
margin: 0 !important;
|
|
129
|
+
`;
|