@rpg-engine/long-bow 0.3.95 → 0.3.97
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 +1 -0
- package/dist/components/Shortcuts/Shortcuts.d.ts +1 -0
- package/dist/components/Shortcuts/useShortcutCooldown.d.ts +4 -0
- package/dist/components/Spellbook/Spell.d.ts +1 -0
- package/dist/components/Spellbook/Spellbook.d.ts +1 -0
- package/dist/long-bow.cjs.development.js +128 -41
- 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 +128 -41
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/CircularController/CircularController.tsx +36 -3
- package/src/components/Item/Cards/ItemInfo.tsx +51 -5
- package/src/components/Item/Cards/ItemInfoDisplay.tsx +9 -2
- package/src/components/Item/Cards/MobileItemTooltip.tsx +3 -3
- package/src/components/Shortcuts/Shortcuts.tsx +47 -8
- package/src/components/Shortcuts/SingleShortcut.ts +20 -0
- package/src/components/Shortcuts/useShortcutCooldown.ts +24 -0
- package/src/components/Spellbook/Spell.tsx +27 -2
- package/src/components/Spellbook/Spellbook.tsx +5 -0
- package/src/constants/uiColors.ts +1 -1
- package/src/mocks/equipmentSet.mocks.ts +1 -1
- package/src/mocks/itemContainer.mocks.ts +42 -0
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.97",
|
|
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.7.
|
|
86
|
+
"@rpg-engine/shared": "^0.7.82",
|
|
87
87
|
"dayjs": "^1.11.2",
|
|
88
88
|
"font-awesome": "^4.7.0",
|
|
89
89
|
"fs-extra": "^10.1.0",
|
|
@@ -11,6 +11,7 @@ import styled from 'styled-components';
|
|
|
11
11
|
import { uiColors } from '../../constants/uiColors';
|
|
12
12
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
13
13
|
import { SingleShortcut } from '../Shortcuts/SingleShortcut';
|
|
14
|
+
import { useShortcutCooldown } from '../Shortcuts/useShortcutCooldown';
|
|
14
15
|
|
|
15
16
|
export type CircularControllerProps = {
|
|
16
17
|
onActionClick: () => void;
|
|
@@ -21,6 +22,7 @@ export type CircularControllerProps = {
|
|
|
21
22
|
inventory?: IItemContainer | null;
|
|
22
23
|
atlasIMG: any;
|
|
23
24
|
atlasJSON: any;
|
|
25
|
+
spellCooldowns?: Record<string, number>;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
export const CircularController: React.FC<CircularControllerProps> = ({
|
|
@@ -32,7 +34,12 @@ export const CircularController: React.FC<CircularControllerProps> = ({
|
|
|
32
34
|
inventory,
|
|
33
35
|
atlasIMG,
|
|
34
36
|
atlasJSON,
|
|
37
|
+
spellCooldowns,
|
|
35
38
|
}) => {
|
|
39
|
+
const { handleShortcutCast, shortcutCooldown } = useShortcutCooldown(
|
|
40
|
+
onShortcutClick
|
|
41
|
+
);
|
|
42
|
+
|
|
36
43
|
const onTouchStart = (e: React.TouchEvent<HTMLButtonElement>) => {
|
|
37
44
|
const target = e.target as HTMLButtonElement;
|
|
38
45
|
target?.classList.add('active');
|
|
@@ -50,6 +57,10 @@ export const CircularController: React.FC<CircularControllerProps> = ({
|
|
|
50
57
|
};
|
|
51
58
|
|
|
52
59
|
const renderShortcut = (i: number) => {
|
|
60
|
+
const buildClassName = (classBase: string, isOnCooldown: boolean) => {
|
|
61
|
+
return `${classBase} ${isOnCooldown ? 'onCooldown' : ''}`;
|
|
62
|
+
};
|
|
63
|
+
|
|
53
64
|
let variant = '';
|
|
54
65
|
|
|
55
66
|
if (i === 0) variant = 'top';
|
|
@@ -57,9 +68,11 @@ export const CircularController: React.FC<CircularControllerProps> = ({
|
|
|
57
68
|
|
|
58
69
|
const onShortcutClickBinded =
|
|
59
70
|
shortcuts[i]?.type !== ShortcutType.None
|
|
60
|
-
?
|
|
71
|
+
? handleShortcutCast.bind(null, i)
|
|
61
72
|
: () => {};
|
|
62
73
|
|
|
74
|
+
const isOnShortcutCooldown = shortcutCooldown > 0;
|
|
75
|
+
|
|
63
76
|
if (shortcuts[i]?.type === ShortcutType.Item) {
|
|
64
77
|
const payload = shortcuts[i]?.payload as IItem | undefined;
|
|
65
78
|
|
|
@@ -88,6 +101,9 @@ export const CircularController: React.FC<CircularControllerProps> = ({
|
|
|
88
101
|
disabled={false}
|
|
89
102
|
className={variant}
|
|
90
103
|
>
|
|
104
|
+
{isOnShortcutCooldown && (
|
|
105
|
+
<span className="cooldown">{shortcutCooldown.toFixed(1)}</span>
|
|
106
|
+
)}
|
|
91
107
|
{payload && (
|
|
92
108
|
<SpriteFromAtlas
|
|
93
109
|
atlasIMG={atlasIMG}
|
|
@@ -108,13 +124,23 @@ export const CircularController: React.FC<CircularControllerProps> = ({
|
|
|
108
124
|
containerStyle={{ pointerEvents: 'none' }}
|
|
109
125
|
/>
|
|
110
126
|
)}
|
|
111
|
-
<span className=
|
|
127
|
+
<span className={buildClassName('qty', isOnShortcutCooldown)}>
|
|
128
|
+
{totalQty}
|
|
129
|
+
</span>
|
|
112
130
|
</StyledShortcut>
|
|
113
131
|
);
|
|
114
132
|
}
|
|
115
133
|
|
|
116
134
|
const payload = shortcuts[i]?.payload as IRawSpell | undefined;
|
|
117
135
|
|
|
136
|
+
const spellCooldown = !payload
|
|
137
|
+
? 0
|
|
138
|
+
: spellCooldowns?.[payload.magicWords.replaceAll(' ', '_')] ??
|
|
139
|
+
shortcutCooldown;
|
|
140
|
+
const cooldown =
|
|
141
|
+
spellCooldown > shortcutCooldown ? spellCooldown : shortcutCooldown;
|
|
142
|
+
const isOnCooldown = cooldown > 0 && !!payload;
|
|
143
|
+
|
|
118
144
|
return (
|
|
119
145
|
<StyledShortcut
|
|
120
146
|
key={i}
|
|
@@ -123,7 +149,14 @@ export const CircularController: React.FC<CircularControllerProps> = ({
|
|
|
123
149
|
disabled={mana < (payload?.manaCost ?? 0)}
|
|
124
150
|
className={variant}
|
|
125
151
|
>
|
|
126
|
-
|
|
152
|
+
{isOnCooldown && (
|
|
153
|
+
<span className="cooldown">
|
|
154
|
+
{cooldown.toFixed(cooldown < 10 ? 1 : 0)}
|
|
155
|
+
</span>
|
|
156
|
+
)}
|
|
157
|
+
<span className={buildClassName('mana', isOnCooldown)}>
|
|
158
|
+
{payload && payload.manaCost}
|
|
159
|
+
</span>
|
|
127
160
|
<span className="magicWords">
|
|
128
161
|
{payload?.magicWords.split(' ').map(word => word[0])}
|
|
129
162
|
</span>
|
|
@@ -99,6 +99,16 @@ export const ItemInfo: React.FC<IItemInfoProps> = ({
|
|
|
99
99
|
return statistics;
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
const renderEntityEffects = () => {
|
|
103
|
+
if (!item.entityEffects || !item.entityEffectChance) return null;
|
|
104
|
+
|
|
105
|
+
return item.entityEffects.map((effect, index) => (
|
|
106
|
+
<Statistic key={index} $isSpecial>
|
|
107
|
+
{effect[0].toUpperCase() + effect.slice(1)} ({item.entityEffectChance}%)
|
|
108
|
+
</Statistic>
|
|
109
|
+
));
|
|
110
|
+
};
|
|
111
|
+
|
|
102
112
|
const renderAvaibleSlots = () => {
|
|
103
113
|
if (!item.allowedEquipSlotType) return null;
|
|
104
114
|
|
|
@@ -130,8 +140,28 @@ export const ItemInfo: React.FC<IItemInfoProps> = ({
|
|
|
130
140
|
<AllowedSlots>{renderAvaibleSlots()}</AllowedSlots>
|
|
131
141
|
</Header>
|
|
132
142
|
|
|
143
|
+
{item.minRequirements && (
|
|
144
|
+
<LevelRequirement>
|
|
145
|
+
<div className="title">Requirements:</div>
|
|
146
|
+
<div>- Level: {item.minRequirements.level}</div>
|
|
147
|
+
<div>
|
|
148
|
+
-{' '}
|
|
149
|
+
{item.minRequirements.skill.name[0].toUpperCase() +
|
|
150
|
+
item.minRequirements.skill.name.slice(1)}
|
|
151
|
+
: {item.minRequirements.skill.level}
|
|
152
|
+
</div>
|
|
153
|
+
</LevelRequirement>
|
|
154
|
+
)}
|
|
155
|
+
|
|
133
156
|
{renderStatistics()}
|
|
134
|
-
{
|
|
157
|
+
{renderEntityEffects()}
|
|
158
|
+
{item.usableEffectDescription && (
|
|
159
|
+
<Statistic $isSpecial>{item.usableEffectDescription}</Statistic>
|
|
160
|
+
)}
|
|
161
|
+
{item.equippedBuffDescription && (
|
|
162
|
+
<Statistic $isSpecial>{item.equippedBuffDescription}</Statistic>
|
|
163
|
+
)}
|
|
164
|
+
{item.isTwoHanded && <Statistic $isSpecial>Two handed</Statistic>}
|
|
135
165
|
|
|
136
166
|
<Description>{item.description}</Description>
|
|
137
167
|
|
|
@@ -159,9 +189,9 @@ const Container = styled.div<{ item: IItem }>`
|
|
|
159
189
|
font-size: ${uiFonts.size.small};
|
|
160
190
|
border: 3px solid ${({ item }) => rarityColor(item) ?? uiColors.lightGray};
|
|
161
191
|
height: max-content;
|
|
162
|
-
width:
|
|
192
|
+
width: 18rem;
|
|
163
193
|
|
|
164
|
-
@media (max-width:
|
|
194
|
+
@media (max-width: 640px) {
|
|
165
195
|
width: 80vw;
|
|
166
196
|
}
|
|
167
197
|
`;
|
|
@@ -189,9 +219,25 @@ const Type = styled.div`
|
|
|
189
219
|
color: ${uiColors.lightGray};
|
|
190
220
|
`;
|
|
191
221
|
|
|
192
|
-
const
|
|
222
|
+
const LevelRequirement = styled.div`
|
|
223
|
+
font-size: ${uiFonts.size.small};
|
|
224
|
+
margin-top: 0.2rem;
|
|
225
|
+
margin-bottom: 1rem;
|
|
226
|
+
color: ${uiColors.orange};
|
|
227
|
+
|
|
228
|
+
.title {
|
|
229
|
+
margin-bottom: 4px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
div {
|
|
233
|
+
margin-bottom: 2px;
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
const Statistic = styled.div<{ $isSpecial?: boolean }>`
|
|
193
238
|
margin-bottom: 0.4rem;
|
|
194
|
-
width:
|
|
239
|
+
width: 100%;
|
|
240
|
+
color: ${({ $isSpecial }) => ($isSpecial ? uiColors.darkYellow : 'inherit')};
|
|
195
241
|
|
|
196
242
|
.label {
|
|
197
243
|
display: inline-block;
|
|
@@ -66,7 +66,13 @@ export const ItemInfoDisplay: React.FC<IItemInfoDisplayProps> = ({
|
|
|
66
66
|
itemSubTypeCamelCase
|
|
67
67
|
);
|
|
68
68
|
|
|
69
|
-
const
|
|
69
|
+
const itemSubTypeFromEquipment = Object.values(equipmentSet).find(
|
|
70
|
+
item => camelCase(item?.subType ?? '') === itemSubTypeCamelCase
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const itemFromEquipment = itemSubTypeFromEquipment
|
|
74
|
+
? itemSubTypeFromEquipment
|
|
75
|
+
: (equipmentSet[slotType] as IItem);
|
|
70
76
|
|
|
71
77
|
if (
|
|
72
78
|
itemFromEquipment &&
|
|
@@ -109,8 +115,9 @@ const Flex = styled.div<{ $isMobile?: boolean }>`
|
|
|
109
115
|
display: flex;
|
|
110
116
|
gap: 0.5rem;
|
|
111
117
|
flex-direction: ${({ $isMobile }) => ($isMobile ? 'row-reverse' : 'row')};
|
|
118
|
+
align-items: center;
|
|
112
119
|
|
|
113
|
-
@media (max-width:
|
|
120
|
+
@media (max-width: 640px) {
|
|
114
121
|
flex-direction: column-reverse;
|
|
115
122
|
align-items: center;
|
|
116
123
|
}
|
|
@@ -113,7 +113,7 @@ const Container = styled.div<{ scale: number }>`
|
|
|
113
113
|
animation: fadeOut 0.1s forwards;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
@media (max-width:
|
|
116
|
+
@media (max-width: 640px) {
|
|
117
117
|
flex-direction: column;
|
|
118
118
|
}
|
|
119
119
|
`;
|
|
@@ -124,7 +124,7 @@ const OptionsContainer = styled.div`
|
|
|
124
124
|
gap: 0.5rem;
|
|
125
125
|
flex-wrap: wrap;
|
|
126
126
|
|
|
127
|
-
@media (max-width:
|
|
127
|
+
@media (max-width: 640px) {
|
|
128
128
|
flex-direction: row;
|
|
129
129
|
justify-content: center;
|
|
130
130
|
}
|
|
@@ -143,7 +143,7 @@ const Option = styled.button`
|
|
|
143
143
|
background-color: #555;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
@media (max-width:
|
|
146
|
+
@media (max-width: 640px) {
|
|
147
147
|
padding: 1rem 0.5rem;
|
|
148
148
|
}
|
|
149
149
|
`;
|
|
@@ -12,6 +12,7 @@ import { uiColors } from '../../constants/uiColors';
|
|
|
12
12
|
import { countItemFromInventory } from '../../libs/itemCounter';
|
|
13
13
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
14
14
|
import { SingleShortcut } from './SingleShortcut';
|
|
15
|
+
import { useShortcutCooldown } from './useShortcutCooldown';
|
|
15
16
|
|
|
16
17
|
export type ShortcutsProps = {
|
|
17
18
|
shortcuts: IShortcut[];
|
|
@@ -21,6 +22,7 @@ export type ShortcutsProps = {
|
|
|
21
22
|
inventory?: IItemContainer | null;
|
|
22
23
|
atlasJSON: any;
|
|
23
24
|
atlasIMG: any;
|
|
25
|
+
spellCooldowns?: Record<string, number>;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
export const Shortcuts: React.FC<ShortcutsProps> = ({
|
|
@@ -31,16 +33,21 @@ export const Shortcuts: React.FC<ShortcutsProps> = ({
|
|
|
31
33
|
atlasJSON,
|
|
32
34
|
atlasIMG,
|
|
33
35
|
inventory,
|
|
36
|
+
spellCooldowns,
|
|
34
37
|
}) => {
|
|
35
38
|
const shortcutsRefs = useRef<HTMLButtonElement[]>([]);
|
|
36
39
|
|
|
40
|
+
const { handleShortcutCast, shortcutCooldown } = useShortcutCooldown(
|
|
41
|
+
onShortcutCast
|
|
42
|
+
);
|
|
43
|
+
|
|
37
44
|
useEffect(() => {
|
|
38
45
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
39
46
|
if (isBlockedCastingByKeyboard) return;
|
|
40
47
|
|
|
41
48
|
const shortcutIndex = Number(e.key) - 1;
|
|
42
49
|
if (shortcutIndex >= 0 && shortcutIndex <= 5) {
|
|
43
|
-
|
|
50
|
+
handleShortcutCast(shortcutIndex);
|
|
44
51
|
shortcutsRefs.current[shortcutIndex]?.classList.add('active');
|
|
45
52
|
setTimeout(() => {
|
|
46
53
|
shortcutsRefs.current[shortcutIndex]?.classList.remove('active');
|
|
@@ -53,11 +60,17 @@ export const Shortcuts: React.FC<ShortcutsProps> = ({
|
|
|
53
60
|
return () => {
|
|
54
61
|
window.removeEventListener('keydown', handleKeyDown);
|
|
55
62
|
};
|
|
56
|
-
}, [shortcuts, isBlockedCastingByKeyboard]);
|
|
63
|
+
}, [shortcuts, isBlockedCastingByKeyboard, shortcutCooldown]);
|
|
57
64
|
|
|
58
65
|
return (
|
|
59
66
|
<List>
|
|
60
67
|
{Array.from({ length: 6 }).map((_, i) => {
|
|
68
|
+
const buildClassName = (classBase: string, isOnCooldown: boolean) => {
|
|
69
|
+
return `${classBase} ${isOnCooldown ? 'onCooldown' : ''}`;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const isOnShortcutCooldown = shortcutCooldown > 0;
|
|
73
|
+
|
|
61
74
|
if (shortcuts[i]?.type === ShortcutType.Item) {
|
|
62
75
|
const payload = shortcuts[i]?.payload as IItem | undefined;
|
|
63
76
|
|
|
@@ -81,12 +94,15 @@ export const Shortcuts: React.FC<ShortcutsProps> = ({
|
|
|
81
94
|
return (
|
|
82
95
|
<StyledShortcut
|
|
83
96
|
key={i}
|
|
84
|
-
onPointerDown={
|
|
97
|
+
onPointerDown={handleShortcutCast.bind(null, i)}
|
|
85
98
|
disabled={false}
|
|
86
99
|
ref={el => {
|
|
87
100
|
if (el) shortcutsRefs.current[i] = el;
|
|
88
101
|
}}
|
|
89
102
|
>
|
|
103
|
+
{isOnShortcutCooldown && (
|
|
104
|
+
<span className="cooldown">{shortcutCooldown.toFixed(1)}</span>
|
|
105
|
+
)}
|
|
90
106
|
{payload && (
|
|
91
107
|
<SpriteFromAtlas
|
|
92
108
|
atlasIMG={atlasIMG}
|
|
@@ -104,28 +120,51 @@ export const Shortcuts: React.FC<ShortcutsProps> = ({
|
|
|
104
120
|
height={32}
|
|
105
121
|
/>
|
|
106
122
|
)}
|
|
107
|
-
<span className=
|
|
108
|
-
|
|
123
|
+
<span className={buildClassName('qty', isOnShortcutCooldown)}>
|
|
124
|
+
{totalQty}
|
|
125
|
+
</span>
|
|
126
|
+
<span
|
|
127
|
+
className={buildClassName('keyboard', isOnShortcutCooldown)}
|
|
128
|
+
>
|
|
129
|
+
{i + 1}
|
|
130
|
+
</span>
|
|
109
131
|
</StyledShortcut>
|
|
110
132
|
);
|
|
111
133
|
}
|
|
112
134
|
|
|
113
135
|
const payload = shortcuts[i]?.payload as IRawSpell | undefined;
|
|
114
136
|
|
|
137
|
+
const spellCooldown = !payload
|
|
138
|
+
? 0
|
|
139
|
+
: spellCooldowns?.[payload.magicWords.replaceAll(' ', '_')] ??
|
|
140
|
+
shortcutCooldown;
|
|
141
|
+
const cooldown =
|
|
142
|
+
spellCooldown > shortcutCooldown ? spellCooldown : shortcutCooldown;
|
|
143
|
+
const isOnCooldown = cooldown > 0 && !!payload;
|
|
144
|
+
|
|
115
145
|
return (
|
|
116
146
|
<StyledShortcut
|
|
117
147
|
key={i}
|
|
118
|
-
onPointerDown={
|
|
148
|
+
onPointerDown={handleShortcutCast.bind(null, i)}
|
|
119
149
|
disabled={mana < (payload?.manaCost ?? 0)}
|
|
120
150
|
ref={el => {
|
|
121
151
|
if (el) shortcutsRefs.current[i] = el;
|
|
122
152
|
}}
|
|
123
153
|
>
|
|
124
|
-
|
|
154
|
+
{isOnCooldown && (
|
|
155
|
+
<span className="cooldown">
|
|
156
|
+
{cooldown.toFixed(cooldown < 10 ? 1 : 0)}
|
|
157
|
+
</span>
|
|
158
|
+
)}
|
|
159
|
+
<span className={buildClassName('mana', isOnCooldown)}>
|
|
160
|
+
{payload && payload.manaCost}
|
|
161
|
+
</span>
|
|
125
162
|
<span className="magicWords">
|
|
126
163
|
{payload?.magicWords.split(' ').map(word => word[0])}
|
|
127
164
|
</span>
|
|
128
|
-
<span className=
|
|
165
|
+
<span className={buildClassName('keyboard', isOnCooldown)}>
|
|
166
|
+
{i + 1}
|
|
167
|
+
</span>
|
|
129
168
|
</StyledShortcut>
|
|
130
169
|
);
|
|
131
170
|
})}
|
|
@@ -46,6 +46,26 @@ export const SingleShortcut = styled.button`
|
|
|
46
46
|
color: ${uiColors.yellow};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
.onCooldown {
|
|
50
|
+
color: ${uiColors.gray};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.cooldown {
|
|
54
|
+
position: absolute;
|
|
55
|
+
z-index: 1;
|
|
56
|
+
top: 0;
|
|
57
|
+
left: 0;
|
|
58
|
+
width: 100%;
|
|
59
|
+
height: 100%;
|
|
60
|
+
border-radius: inherit;
|
|
61
|
+
background-color: rgba(0 0 0 / 60%);
|
|
62
|
+
font-size: 0.7rem;
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
justify-content: center;
|
|
66
|
+
color: ${uiColors.darkYellow};
|
|
67
|
+
}
|
|
68
|
+
|
|
49
69
|
&:hover,
|
|
50
70
|
&:focus {
|
|
51
71
|
background-color: ${uiColors.darkGray};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export const useShortcutCooldown = (onShortcutCast: (index: number) => void) => {
|
|
4
|
+
const [shortcutCooldown, setShortcutCooldown] = useState(0);
|
|
5
|
+
const cooldownTimeout = useRef<NodeJS.Timeout | null>(null);
|
|
6
|
+
|
|
7
|
+
const handleShortcutCast = (index: number) => {
|
|
8
|
+
console.log(shortcutCooldown);
|
|
9
|
+
if (shortcutCooldown <= 0) setShortcutCooldown(1.5);
|
|
10
|
+
onShortcutCast(index);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (cooldownTimeout.current) clearTimeout(cooldownTimeout.current);
|
|
15
|
+
|
|
16
|
+
if (shortcutCooldown > 0) {
|
|
17
|
+
cooldownTimeout.current = setTimeout(() => {
|
|
18
|
+
setShortcutCooldown(shortcutCooldown - 0.1);
|
|
19
|
+
}, 100);
|
|
20
|
+
}
|
|
21
|
+
}, [shortcutCooldown]);
|
|
22
|
+
|
|
23
|
+
return { shortcutCooldown, handleShortcutCast };
|
|
24
|
+
};
|
|
@@ -10,6 +10,7 @@ interface Props extends IRawSpell {
|
|
|
10
10
|
onPointerUp?: (spellKey: string) => void;
|
|
11
11
|
isSettingShortcut?: boolean;
|
|
12
12
|
spellKey: string;
|
|
13
|
+
activeCooldown?: number;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export const Spell: React.FC<Props> = ({
|
|
@@ -23,6 +24,7 @@ export const Spell: React.FC<Props> = ({
|
|
|
23
24
|
onPointerUp,
|
|
24
25
|
isSettingShortcut,
|
|
25
26
|
minMagicLevelRequired,
|
|
27
|
+
activeCooldown,
|
|
26
28
|
}) => {
|
|
27
29
|
const disabled = isSettingShortcut
|
|
28
30
|
? charMagicLevel < minMagicLevelRequired
|
|
@@ -30,7 +32,7 @@ export const Spell: React.FC<Props> = ({
|
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<Container
|
|
33
|
-
disabled={disabled}
|
|
35
|
+
disabled={disabled || (activeCooldown ?? 0) > 0}
|
|
34
36
|
onPointerUp={onPointerUp?.bind(null, spellKey)}
|
|
35
37
|
isSettingShortcut={isSettingShortcut && !disabled}
|
|
36
38
|
className="spell"
|
|
@@ -42,7 +44,14 @@ export const Spell: React.FC<Props> = ({
|
|
|
42
44
|
: manaCost > charMana && 'No mana'}
|
|
43
45
|
</Overlay>
|
|
44
46
|
)}
|
|
45
|
-
<SpellImage>
|
|
47
|
+
<SpellImage>
|
|
48
|
+
{activeCooldown && activeCooldown > 0 ? (
|
|
49
|
+
<span className="cooldown">
|
|
50
|
+
{activeCooldown.toFixed(activeCooldown > 10 ? 0 : 1)}
|
|
51
|
+
</span>
|
|
52
|
+
) : null}
|
|
53
|
+
{magicWords.split(' ').map(word => word[0])}
|
|
54
|
+
</SpellImage>
|
|
46
55
|
<Info>
|
|
47
56
|
<Title>
|
|
48
57
|
<span>{name}</span>
|
|
@@ -110,6 +119,22 @@ const SpellImage = styled.div`
|
|
|
110
119
|
justify-content: center;
|
|
111
120
|
align-items: center;
|
|
112
121
|
text-transform: uppercase;
|
|
122
|
+
position: relative;
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
|
|
125
|
+
.cooldown {
|
|
126
|
+
position: absolute;
|
|
127
|
+
top: 0;
|
|
128
|
+
left: 0;
|
|
129
|
+
width: 100%;
|
|
130
|
+
height: 100%;
|
|
131
|
+
background-color: rgba(0 0 0 / 20%);
|
|
132
|
+
color: ${uiColors.darkYellow};
|
|
133
|
+
font-weight: bold;
|
|
134
|
+
display: flex;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
align-items: center;
|
|
137
|
+
}
|
|
113
138
|
`;
|
|
114
139
|
|
|
115
140
|
const Info = styled.span`
|
|
@@ -22,6 +22,7 @@ export interface ISpellbookProps {
|
|
|
22
22
|
atlasIMG: any;
|
|
23
23
|
atlasJSON: any;
|
|
24
24
|
scale?: number;
|
|
25
|
+
spellCooldowns?: Record<string, number>;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export const Spellbook: React.FC<ISpellbookProps> = ({
|
|
@@ -38,6 +39,7 @@ export const Spellbook: React.FC<ISpellbookProps> = ({
|
|
|
38
39
|
atlasIMG,
|
|
39
40
|
atlasJSON,
|
|
40
41
|
scale,
|
|
42
|
+
spellCooldowns,
|
|
41
43
|
}) => {
|
|
42
44
|
const [search, setSearch] = useState('');
|
|
43
45
|
const [settingShortcutIndex, setSettingShortcutIndex] = useState(-1);
|
|
@@ -118,6 +120,9 @@ export const Spellbook: React.FC<ISpellbookProps> = ({
|
|
|
118
120
|
}
|
|
119
121
|
spellKey={spell.key}
|
|
120
122
|
isSettingShortcut={settingShortcutIndex !== -1}
|
|
123
|
+
activeCooldown={
|
|
124
|
+
spellCooldowns?.[spell.magicWords.replaceAll(' ', '_')]
|
|
125
|
+
}
|
|
121
126
|
{...spell}
|
|
122
127
|
/>
|
|
123
128
|
</Fragment>
|
|
@@ -383,7 +383,7 @@ export const equipmentSetMock: IEquipmentSet = {
|
|
|
383
383
|
legs: undefined,
|
|
384
384
|
boot: items.boot,
|
|
385
385
|
leftHand: items.leftHand,
|
|
386
|
-
rightHand:
|
|
386
|
+
rightHand: items.rightHand,
|
|
387
387
|
neck: undefined,
|
|
388
388
|
ring: undefined,
|
|
389
389
|
accessory: items.accessory,
|
|
@@ -42,6 +42,16 @@ export const items: IItem[] = [
|
|
|
42
42
|
createdAt: '2022-06-04T03:18:09.335Z',
|
|
43
43
|
updatedAt: '2022-06-04T18:16:49.056Z',
|
|
44
44
|
rarity: ItemRarities.Legendary,
|
|
45
|
+
minRequirements: {
|
|
46
|
+
level: 10,
|
|
47
|
+
skill: {
|
|
48
|
+
name: 'sword',
|
|
49
|
+
level: 5,
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
equippedBuffDescription: "Character speed +10%",
|
|
53
|
+
entityEffectChance: 50,
|
|
54
|
+
entityEffects: ['freezing']
|
|
45
55
|
},
|
|
46
56
|
{
|
|
47
57
|
_id: '629acef1c7c8e8002ff73564',
|
|
@@ -530,6 +540,37 @@ export const items: IItem[] = [
|
|
|
530
540
|
updatedAt: '2022-06-04T18:16:49.056Z',
|
|
531
541
|
rarity: ItemRarities.Common,
|
|
532
542
|
},
|
|
543
|
+
{
|
|
544
|
+
"type": ItemType.Consumable,
|
|
545
|
+
"subType": ItemSubType.Food,
|
|
546
|
+
"rarity": "Common",
|
|
547
|
+
"textureAtlas": "items",
|
|
548
|
+
"allowedEquipSlotType": [],
|
|
549
|
+
"maxStackSize": 100,
|
|
550
|
+
"isUsable": false,
|
|
551
|
+
"isStorable": true,
|
|
552
|
+
"isItemContainer": false,
|
|
553
|
+
"isSolid": false,
|
|
554
|
+
"requiredAmmoKeys": [],
|
|
555
|
+
"isTwoHanded": false,
|
|
556
|
+
"hasUseWith": false,
|
|
557
|
+
"entityEffects": [],
|
|
558
|
+
"entityEffectChance": 0,
|
|
559
|
+
"_id": "64529049d45546003b2c6c6d",
|
|
560
|
+
"key": "apple",
|
|
561
|
+
"texturePath": "foods/apple.png",
|
|
562
|
+
textureKey: 'apple',
|
|
563
|
+
isEquipable: false,
|
|
564
|
+
isStackable: true,
|
|
565
|
+
"name": "Apple",
|
|
566
|
+
"description": "A red apple.",
|
|
567
|
+
"weight": 0.05,
|
|
568
|
+
"stackQty": 16,
|
|
569
|
+
"attack": 0,
|
|
570
|
+
"defense": 0,
|
|
571
|
+
fullDescription: "",
|
|
572
|
+
usableEffectDescription: "Regenerates 10 HP and Mana 5 times"
|
|
573
|
+
}
|
|
533
574
|
];
|
|
534
575
|
|
|
535
576
|
export const itemContainerMock: IItemContainer = {
|
|
@@ -553,6 +594,7 @@ export const itemContainerMock: IItemContainer = {
|
|
|
553
594
|
12: items[12],
|
|
554
595
|
13: items[13],
|
|
555
596
|
14: items[14],
|
|
597
|
+
15: items[15],
|
|
556
598
|
//remaining slots are considered null by default
|
|
557
599
|
},
|
|
558
600
|
allowedItemTypes: [],
|