@rpg-engine/long-bow 0.3.96 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.3.96",
3
+ "version": "0.3.97",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -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
- ? onShortcutClick.bind(null, i)
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="qty">{totalQty}</span>
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
- <span className="mana">{payload && payload.manaCost}</span>
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>
@@ -142,8 +142,10 @@ export const ItemInfo: React.FC<IItemInfoProps> = ({
142
142
 
143
143
  {item.minRequirements && (
144
144
  <LevelRequirement>
145
- <div>Level: {item.minRequirements.level}</div>
145
+ <div className="title">Requirements:</div>
146
+ <div>- Level: {item.minRequirements.level}</div>
146
147
  <div>
148
+ -{' '}
147
149
  {item.minRequirements.skill.name[0].toUpperCase() +
148
150
  item.minRequirements.skill.name.slice(1)}
149
151
  : {item.minRequirements.skill.level}
@@ -222,6 +224,14 @@ const LevelRequirement = styled.div`
222
224
  margin-top: 0.2rem;
223
225
  margin-bottom: 1rem;
224
226
  color: ${uiColors.orange};
227
+
228
+ .title {
229
+ margin-bottom: 4px;
230
+ }
231
+
232
+ div {
233
+ margin-bottom: 2px;
234
+ }
225
235
  `;
226
236
 
227
237
  const Statistic = styled.div<{ $isSpecial?: boolean }>`
@@ -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
- onShortcutCast(shortcutIndex);
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={onShortcutCast.bind(null, i)}
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="qty">{totalQty}</span>
108
- <span className="keyboard">{i + 1}</span>
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={onShortcutCast.bind(null, i)}
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
- <span className="mana">{payload && payload.manaCost}</span>
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="keyboard">{i + 1}</span>
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>{magicWords.split(' ').map(word => word[0])}</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>