@rpg-engine/long-bow 0.3.51 → 0.3.52
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 +7 -3
- package/dist/components/Item/Inventory/ItemContainer.d.ts +4 -0
- package/dist/components/Item/Inventory/ItemSlot.d.ts +1 -0
- package/dist/components/Shortcuts/Shortcuts.d.ts +21 -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 -2
- package/dist/components/Spellbook/constants.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/long-bow.cjs.development.js +271 -120
- 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 +273 -120
- 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 +1 -1
- package/src/components/Abstractions/SlotsContainer.tsx +2 -2
- package/src/components/CircularController/CircularController.tsx +118 -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 +138 -0
- package/src/components/Shortcuts/ShortcutsSetter.tsx +127 -0
- package/src/components/Shortcuts/SingleShortcut.ts +61 -0
- package/src/components/Spellbook/Spellbook.tsx +15 -8
- 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 +70 -1
- 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
|
@@ -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,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getItemTextureKeyPath,
|
|
3
|
+
IItem,
|
|
4
|
+
IItemContainer,
|
|
5
|
+
IRawSpell,
|
|
6
|
+
} from '@rpg-engine/shared';
|
|
2
7
|
import React from 'react';
|
|
3
8
|
import styled from 'styled-components';
|
|
4
9
|
import { uiColors } from '../../constants/uiColors';
|
|
5
|
-
import {
|
|
10
|
+
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
11
|
+
import { IShortcut, ShortcutType } from '../Shortcuts/Shortcuts';
|
|
12
|
+
import { SingleShortcut } from '../Shortcuts/SingleShortcut';
|
|
6
13
|
|
|
7
14
|
export type CircularControllerProps = {
|
|
8
15
|
onActionClick: () => void;
|
|
9
16
|
onCancelClick: () => void;
|
|
10
|
-
|
|
17
|
+
onShortcutClick: (index: number) => void;
|
|
11
18
|
mana: number;
|
|
12
|
-
|
|
19
|
+
shortcuts: IShortcut[];
|
|
20
|
+
inventory?: IItemContainer | null;
|
|
21
|
+
atlasIMG: any;
|
|
22
|
+
atlasJSON: any;
|
|
13
23
|
};
|
|
14
24
|
|
|
15
25
|
export const CircularController: React.FC<CircularControllerProps> = ({
|
|
16
26
|
onActionClick,
|
|
17
27
|
onCancelClick,
|
|
18
|
-
|
|
28
|
+
onShortcutClick,
|
|
19
29
|
mana,
|
|
20
|
-
|
|
30
|
+
shortcuts,
|
|
31
|
+
inventory,
|
|
32
|
+
atlasIMG,
|
|
33
|
+
atlasJSON,
|
|
21
34
|
}) => {
|
|
22
35
|
const onTouchStart = (e: React.TouchEvent<HTMLButtonElement>) => {
|
|
23
36
|
const target = e.target as HTMLButtonElement;
|
|
@@ -35,33 +48,91 @@ export const CircularController: React.FC<CircularControllerProps> = ({
|
|
|
35
48
|
action();
|
|
36
49
|
};
|
|
37
50
|
|
|
51
|
+
const renderShortcut = (i: number) => {
|
|
52
|
+
let variant = '';
|
|
53
|
+
|
|
54
|
+
if (i === 0) variant = 'top';
|
|
55
|
+
else if (i >= 3) variant = `bottom-${i - 3}`;
|
|
56
|
+
|
|
57
|
+
const onShortcutClickBinded =
|
|
58
|
+
shortcuts[i]?.type !== ShortcutType.None
|
|
59
|
+
? onShortcutClick.bind(null, i)
|
|
60
|
+
: () => {};
|
|
61
|
+
|
|
62
|
+
if (shortcuts[i]?.type === ShortcutType.Item) {
|
|
63
|
+
const payload = shortcuts[i]?.payload as IItem | undefined;
|
|
64
|
+
|
|
65
|
+
let itemsFromEquipment: (IItem | undefined | null)[] = [];
|
|
66
|
+
|
|
67
|
+
if (inventory) {
|
|
68
|
+
Object.keys(inventory.slots).forEach(i => {
|
|
69
|
+
const index = parseInt(i);
|
|
70
|
+
|
|
71
|
+
if (inventory.slots[index]?.key === payload?.key) {
|
|
72
|
+
itemsFromEquipment.push(inventory.slots[index]);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const totalQty = itemsFromEquipment.reduce(
|
|
78
|
+
(acc, item) => acc + (item?.stackQty || 1),
|
|
79
|
+
0
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<StyledShortcut
|
|
84
|
+
key={i}
|
|
85
|
+
onTouchStart={onTouchStart}
|
|
86
|
+
onTouchEnd={onTouchEnd.bind(null, onShortcutClickBinded)}
|
|
87
|
+
disabled={false}
|
|
88
|
+
className={variant}
|
|
89
|
+
>
|
|
90
|
+
{payload && (
|
|
91
|
+
<SpriteFromAtlas
|
|
92
|
+
atlasIMG={atlasIMG}
|
|
93
|
+
atlasJSON={atlasJSON}
|
|
94
|
+
spriteKey={getItemTextureKeyPath(
|
|
95
|
+
{
|
|
96
|
+
key: payload.texturePath,
|
|
97
|
+
texturePath: payload.texturePath,
|
|
98
|
+
stackQty: payload.stackQty || 1,
|
|
99
|
+
},
|
|
100
|
+
atlasJSON
|
|
101
|
+
)}
|
|
102
|
+
width={32}
|
|
103
|
+
height={32}
|
|
104
|
+
imgScale={1.4}
|
|
105
|
+
imgStyle={{ left: '4px' }}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
<span className="qty">{totalQty}</span>
|
|
109
|
+
</StyledShortcut>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const payload = shortcuts[i]?.payload as IRawSpell | undefined;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<StyledShortcut
|
|
117
|
+
key={i}
|
|
118
|
+
onTouchStart={onTouchStart}
|
|
119
|
+
onTouchEnd={onTouchEnd.bind(null, onShortcutClickBinded)}
|
|
120
|
+
disabled={mana < (payload?.manaCost ?? 0)}
|
|
121
|
+
className={variant}
|
|
122
|
+
>
|
|
123
|
+
<span className="mana">{payload && payload.manaCost}</span>
|
|
124
|
+
<span className="magicWords">
|
|
125
|
+
{payload?.magicWords.split(' ').map(word => word[0])}
|
|
126
|
+
</span>
|
|
127
|
+
</StyledShortcut>
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
|
|
38
131
|
return (
|
|
39
132
|
<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>
|
|
133
|
+
<ShortcutsContainer>
|
|
134
|
+
{Array.from({ length: 6 }).map((_, i) => renderShortcut(i))}
|
|
135
|
+
</ShortcutsContainer>
|
|
65
136
|
<Button
|
|
66
137
|
onTouchStart={onTouchStart}
|
|
67
138
|
onTouchEnd={onTouchEnd.bind(null, onActionClick)}
|
|
@@ -93,6 +164,7 @@ const Button = styled.button`
|
|
|
93
164
|
justify-content: center;
|
|
94
165
|
position: relative;
|
|
95
166
|
transition: background-color 0.1s;
|
|
167
|
+
margin-top: -3rem;
|
|
96
168
|
|
|
97
169
|
&.active {
|
|
98
170
|
background-color: ${uiColors.gray};
|
|
@@ -125,28 +197,38 @@ const ButtonsContainer = styled.div`
|
|
|
125
197
|
gap: 0.5rem;
|
|
126
198
|
`;
|
|
127
199
|
|
|
128
|
-
const
|
|
200
|
+
const ShortcutsContainer = styled.div`
|
|
129
201
|
display: flex;
|
|
130
202
|
align-items: center;
|
|
131
203
|
justify-content: center;
|
|
132
|
-
gap: 0.
|
|
204
|
+
gap: 0.5rem;
|
|
133
205
|
flex-direction: column;
|
|
206
|
+
margin-top: 3rem;
|
|
134
207
|
|
|
135
208
|
.top {
|
|
136
209
|
transform: translate(93%, 25%);
|
|
137
210
|
}
|
|
138
211
|
|
|
139
|
-
.bottom {
|
|
212
|
+
.bottom-0 {
|
|
140
213
|
transform: translate(93%, -25%);
|
|
141
214
|
}
|
|
215
|
+
|
|
216
|
+
.bottom-1 {
|
|
217
|
+
transform: translate(-120%, calc(-5.5rem));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.bottom-2 {
|
|
221
|
+
transform: translate(-30%, calc(-5.5rem - 25%));
|
|
222
|
+
}
|
|
142
223
|
`;
|
|
143
224
|
|
|
144
|
-
const StyledShortcut = styled(
|
|
225
|
+
const StyledShortcut = styled(SingleShortcut)`
|
|
145
226
|
width: 2.5rem;
|
|
146
227
|
height: 2.5rem;
|
|
147
228
|
transition: background-color 0.1s;
|
|
148
229
|
|
|
149
|
-
.mana
|
|
230
|
+
.mana,
|
|
231
|
+
.qty {
|
|
150
232
|
font-size: 0.5rem;
|
|
151
233
|
}
|
|
152
234
|
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
IItem,
|
|
3
|
+
IItemContainer,
|
|
4
|
+
ItemContainerType,
|
|
5
|
+
ItemType,
|
|
6
|
+
} from '@rpg-engine/shared';
|
|
2
7
|
import React, { useState } from 'react';
|
|
3
8
|
import styled from 'styled-components';
|
|
4
9
|
import { SlotsContainer } from '../../Abstractions/SlotsContainer';
|
|
5
10
|
import { ItemQuantitySelector } from './ItemQuantitySelector';
|
|
6
11
|
|
|
7
12
|
import { IPosition } from '../../../types/eventTypes';
|
|
13
|
+
import { IShortcut } from '../../Shortcuts/Shortcuts';
|
|
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,138 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getItemTextureKeyPath,
|
|
3
|
+
IItem,
|
|
4
|
+
IItemContainer,
|
|
5
|
+
IRawSpell,
|
|
6
|
+
} from '@rpg-engine/shared';
|
|
7
|
+
import React, { useEffect } from 'react';
|
|
8
|
+
import styled from 'styled-components';
|
|
9
|
+
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
10
|
+
import { SingleShortcut } from './SingleShortcut';
|
|
11
|
+
|
|
12
|
+
export enum ShortcutType {
|
|
13
|
+
Spell,
|
|
14
|
+
Item,
|
|
15
|
+
None,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IShortcut {
|
|
19
|
+
type: ShortcutType;
|
|
20
|
+
payload: IRawSpell | IItem | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ShortcutsProps = {
|
|
24
|
+
shortcuts: IShortcut[];
|
|
25
|
+
onShortcutCast: (index: number) => void;
|
|
26
|
+
mana: number;
|
|
27
|
+
isBlockedCastingByKeyboard?: boolean;
|
|
28
|
+
inventory?: IItemContainer | null;
|
|
29
|
+
atlasJSON: any;
|
|
30
|
+
atlasIMG: any;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const Shortcuts: React.FC<ShortcutsProps> = ({
|
|
34
|
+
shortcuts,
|
|
35
|
+
onShortcutCast,
|
|
36
|
+
mana,
|
|
37
|
+
isBlockedCastingByKeyboard = false,
|
|
38
|
+
atlasJSON,
|
|
39
|
+
atlasIMG,
|
|
40
|
+
inventory,
|
|
41
|
+
}) => {
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
44
|
+
if (isBlockedCastingByKeyboard) return;
|
|
45
|
+
|
|
46
|
+
const shortcutIndex = Number(e.key) - 1;
|
|
47
|
+
if (shortcutIndex >= 0 && shortcutIndex <= 5) {
|
|
48
|
+
onShortcutCast(shortcutIndex);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
56
|
+
};
|
|
57
|
+
}, [shortcuts, isBlockedCastingByKeyboard]);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<List>
|
|
61
|
+
{Array.from({ length: 6 }).map((_, i) => {
|
|
62
|
+
if (shortcuts[i]?.type === ShortcutType.Item) {
|
|
63
|
+
const payload = shortcuts[i]?.payload as IItem | undefined;
|
|
64
|
+
|
|
65
|
+
let itemsFromEquipment: (IItem | undefined | null)[] = [];
|
|
66
|
+
|
|
67
|
+
if (inventory) {
|
|
68
|
+
Object.keys(inventory.slots).forEach(i => {
|
|
69
|
+
const index = parseInt(i);
|
|
70
|
+
|
|
71
|
+
if (inventory.slots[index]?.key === payload?.key) {
|
|
72
|
+
itemsFromEquipment.push(inventory.slots[index]);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const totalQty = itemsFromEquipment.reduce(
|
|
78
|
+
(acc, item) => acc + (item?.stackQty || 1),
|
|
79
|
+
0
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<SingleShortcut
|
|
84
|
+
key={i}
|
|
85
|
+
onClick={onShortcutCast.bind(null, i)}
|
|
86
|
+
disabled={false}
|
|
87
|
+
>
|
|
88
|
+
{payload && (
|
|
89
|
+
<SpriteFromAtlas
|
|
90
|
+
atlasIMG={atlasIMG}
|
|
91
|
+
atlasJSON={atlasJSON}
|
|
92
|
+
spriteKey={getItemTextureKeyPath(
|
|
93
|
+
{
|
|
94
|
+
key: payload.texturePath,
|
|
95
|
+
texturePath: payload.texturePath,
|
|
96
|
+
stackQty: payload.stackQty || 1,
|
|
97
|
+
},
|
|
98
|
+
atlasJSON
|
|
99
|
+
)}
|
|
100
|
+
width={32}
|
|
101
|
+
height={32}
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
<span className="qty">{totalQty}</span>
|
|
105
|
+
<span className="keyboard">{i + 1}</span>
|
|
106
|
+
</SingleShortcut>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const payload = shortcuts[i]?.payload as IRawSpell | undefined;
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<SingleShortcut
|
|
114
|
+
key={i}
|
|
115
|
+
onClick={onShortcutCast.bind(null, i)}
|
|
116
|
+
disabled={mana < (payload?.manaCost ?? 0)}
|
|
117
|
+
>
|
|
118
|
+
<span className="mana">{payload && payload.manaCost}</span>
|
|
119
|
+
<span className="magicWords">
|
|
120
|
+
{payload?.magicWords.split(' ').map(word => word[0])}
|
|
121
|
+
</span>
|
|
122
|
+
<span className="keyboard">{i + 1}</span>
|
|
123
|
+
</SingleShortcut>
|
|
124
|
+
);
|
|
125
|
+
})}
|
|
126
|
+
</List>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const List = styled.p`
|
|
131
|
+
width: 100%;
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
justify-content: center;
|
|
135
|
+
gap: 0.5rem;
|
|
136
|
+
box-sizing: border-box;
|
|
137
|
+
margin: 0 !important;
|
|
138
|
+
`;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { getItemTextureKeyPath, IItem, IRawSpell } from '@rpg-engine/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { uiColors } from '../../constants/uiColors';
|
|
5
|
+
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
6
|
+
import { IShortcut, ShortcutType } from './Shortcuts';
|
|
7
|
+
|
|
8
|
+
type ShortcutsSetterProps = {
|
|
9
|
+
setSettingShortcutIndex: (index: number) => void;
|
|
10
|
+
settingShortcutIndex: number;
|
|
11
|
+
shortcuts: IShortcut[];
|
|
12
|
+
removeShortcut: (index: number) => void;
|
|
13
|
+
atlasJSON: any;
|
|
14
|
+
atlasIMG: any;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const ShortcutsSetter: React.FC<ShortcutsSetterProps> = ({
|
|
18
|
+
setSettingShortcutIndex,
|
|
19
|
+
settingShortcutIndex,
|
|
20
|
+
shortcuts,
|
|
21
|
+
removeShortcut,
|
|
22
|
+
atlasJSON,
|
|
23
|
+
atlasIMG,
|
|
24
|
+
}) => {
|
|
25
|
+
const getContent = (index: number) => {
|
|
26
|
+
if (shortcuts[index]?.type === ShortcutType.Item) {
|
|
27
|
+
const payload = shortcuts[index]?.payload as IItem | undefined;
|
|
28
|
+
|
|
29
|
+
if (!payload) return null;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<SpriteFromAtlas
|
|
33
|
+
atlasIMG={atlasIMG}
|
|
34
|
+
atlasJSON={atlasJSON}
|
|
35
|
+
spriteKey={getItemTextureKeyPath(
|
|
36
|
+
{
|
|
37
|
+
key: payload.texturePath,
|
|
38
|
+
texturePath: payload.texturePath,
|
|
39
|
+
stackQty: payload.stackQty || 1,
|
|
40
|
+
},
|
|
41
|
+
atlasJSON
|
|
42
|
+
)}
|
|
43
|
+
width={32}
|
|
44
|
+
height={32}
|
|
45
|
+
imgScale={1.6}
|
|
46
|
+
imgStyle={{ left: '5px' }}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const payload = shortcuts[index]?.payload as IRawSpell | undefined;
|
|
52
|
+
|
|
53
|
+
return <span>{payload?.magicWords.split(' ').map(word => word[0])}</span>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Container>
|
|
58
|
+
<p>Shortcuts:</p>
|
|
59
|
+
<List id="shortcuts_list">
|
|
60
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
61
|
+
<Shortcut
|
|
62
|
+
key={i}
|
|
63
|
+
onClick={() => {
|
|
64
|
+
removeShortcut(i);
|
|
65
|
+
if (!shortcuts[i] || shortcuts[i].type === ShortcutType.None)
|
|
66
|
+
setSettingShortcutIndex(i);
|
|
67
|
+
}}
|
|
68
|
+
disabled={settingShortcutIndex !== -1 && settingShortcutIndex !== i}
|
|
69
|
+
isBeingSet={settingShortcutIndex === i}
|
|
70
|
+
>
|
|
71
|
+
{getContent(i)}
|
|
72
|
+
</Shortcut>
|
|
73
|
+
))}
|
|
74
|
+
</List>
|
|
75
|
+
</Container>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const Container = styled.div`
|
|
80
|
+
p {
|
|
81
|
+
margin: 0;
|
|
82
|
+
margin-left: 0.5rem;
|
|
83
|
+
}
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
const Shortcut = styled.button<{ isBeingSet?: boolean }>`
|
|
87
|
+
width: 2.6rem;
|
|
88
|
+
height: 2.6rem;
|
|
89
|
+
background-color: ${uiColors.lightGray};
|
|
90
|
+
border: 2px solid
|
|
91
|
+
${({ isBeingSet }) => (isBeingSet ? uiColors.yellow : uiColors.darkGray)};
|
|
92
|
+
border-radius: 50%;
|
|
93
|
+
text-transform: uppercase;
|
|
94
|
+
font-size: 0.7rem;
|
|
95
|
+
font-weight: bold;
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
|
|
100
|
+
span {
|
|
101
|
+
margin-top: 4px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&:hover,
|
|
105
|
+
&:focus {
|
|
106
|
+
background-color: ${uiColors.darkGray};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
&:active {
|
|
110
|
+
background-color: ${uiColors.gray};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
&:disabled {
|
|
114
|
+
opacity: 0.5;
|
|
115
|
+
}
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
const List = styled.div`
|
|
119
|
+
width: 100%;
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 0.5rem;
|
|
123
|
+
padding-bottom: 0.5rem;
|
|
124
|
+
padding-left: 0.5rem;
|
|
125
|
+
box-sizing: border-box;
|
|
126
|
+
margin: 0 !important;
|
|
127
|
+
`;
|