@rpg-engine/long-bow 0.6.70 → 0.6.72

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.6.70",
3
+ "version": "0.6.72",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -1,135 +1,171 @@
1
- import { IQuest } from '@rpg-engine/shared';
2
1
  import React from 'react';
2
+
3
3
  import styled from 'styled-components';
4
- import { uiFonts } from '../constants/uiFonts';
5
- import { DraggableContainer } from './DraggableContainer';
6
- import { RPGUIContainerTypes } from './RPGUI/RPGUIContainer';
4
+
5
+ import {
6
+ IQuest,
7
+ IQuestObjectiveInteraction,
8
+ IQuestObjectiveKill,
9
+ QuestStatus,
10
+ } from '@rpg-engine/shared';
11
+ import { uiColors } from '../constants/uiColors';
12
+ import { Table, TableCell, TableHeader, TableRow } from './Table/Table';
13
+ import { Ellipsis } from './shared/Ellipsis';
14
+ import { SimpleTooltip } from './shared/SimpleTooltip';
7
15
 
8
16
  export interface IQuestListProps {
9
17
  quests?: IQuest[];
10
- onClose: () => void;
11
- scale?: number;
12
18
  }
13
19
 
14
- export const QuestList: React.FC<IQuestListProps> = ({
15
- quests,
16
- onClose,
17
- scale,
18
- }) => {
20
+ export const QuestList: React.FC<IQuestListProps> = ({ quests }) => {
19
21
  return (
20
- <QuestDraggableContainer
21
- type={RPGUIContainerTypes.Framed}
22
- onCloseButton={() => {
23
- if (onClose) onClose();
24
- }}
25
- width="520px"
26
- scale={scale}
27
- >
28
- <div style={{ width: '100%' }}>
29
- <Title>Quests</Title>
30
- <hr className="golden" />
31
-
32
- <QuestListContainer>
33
- {quests ? (
34
- quests.map((quest, i) => (
35
- <div className="quest-item" key={i}>
36
- <span className="quest-number">{i + 1}</span>
37
- <div className="quest-detail">
38
- <p className="quest-detail__title">{quest.title}</p>
39
- <p className="quest-detail__description">
40
- {quest.description}
41
- </p>
42
- </div>
43
- </div>
44
- ))
45
- ) : (
46
- <NoQuestContainer>
47
- <p>There are no ongoing quests</p>
48
- </NoQuestContainer>
49
- )}
50
- </QuestListContainer>
51
- </div>
52
- </QuestDraggableContainer>
22
+ <QuestListContainer>
23
+ {quests && quests.length > 0 ? (
24
+ <>
25
+ <Table>
26
+ <thead>
27
+ <TableRow>
28
+ <TableHeader>Status</TableHeader>
29
+ <TableHeader>Title</TableHeader>
30
+ <TableHeader>Description</TableHeader>
31
+ <TableHeader>Objectives</TableHeader>
32
+ <TableHeader>Rewards</TableHeader>
33
+ </TableRow>
34
+ </thead>
35
+ <tbody>
36
+ {quests.map((quest, i) => (
37
+ <TableRow key={i}>
38
+ <TableCell style={{ color: getStatusColor(quest.status) }}>
39
+ {quest.status ?? 'Unknown'}
40
+ </TableCell>
41
+ <TableCell>
42
+ <SimpleTooltip
43
+ content={formatText(quest.title)}
44
+ direction="bottom"
45
+ >
46
+ <Ellipsis maxWidth="300px">
47
+ {formatText(quest.title)}
48
+ </Ellipsis>
49
+ </SimpleTooltip>
50
+ </TableCell>
51
+ <TableCell>
52
+ <SimpleTooltip
53
+ content={quest.description}
54
+ direction="bottom"
55
+ >
56
+ <Ellipsis maxWidth="300px">{quest.description}</Ellipsis>
57
+ </SimpleTooltip>
58
+ </TableCell>
59
+ <TableCell>
60
+ <SimpleTooltip
61
+ content={formatObjectives(quest.objectives)}
62
+ direction="bottom"
63
+ >
64
+ <Ellipsis maxWidth="300px">
65
+ {formatObjectives(quest.objectives)}
66
+ </Ellipsis>
67
+ </SimpleTooltip>
68
+ </TableCell>
69
+ <TableCell>
70
+ <SimpleTooltip
71
+ content={formatRewards(quest.rewards)}
72
+ direction="bottom"
73
+ >
74
+ <Ellipsis maxWidth="200px">
75
+ {formatRewards(quest.rewards)}
76
+ </Ellipsis>
77
+ </SimpleTooltip>
78
+ </TableCell>
79
+ </TableRow>
80
+ ))}
81
+ </tbody>
82
+ </Table>
83
+ </>
84
+ ) : (
85
+ <NoQuestContainer>
86
+ <p>There are no ongoing quests</p>
87
+ </NoQuestContainer>
88
+ )}
89
+ </QuestListContainer>
53
90
  );
54
91
  };
55
92
 
56
- const QuestDraggableContainer = styled(DraggableContainer)`
57
- .container-close {
58
- top: 10px;
59
- right: 10px;
60
- }
93
+ // Updated helper function to format objectives
94
+ const formatObjectives = (
95
+ objectives: (IQuestObjectiveKill | IQuestObjectiveInteraction)[]
96
+ ) => {
97
+ return objectives
98
+ .map(objective => {
99
+ if ('killCountTarget' in objective) {
100
+ // This is an IQuestObjectiveKill
101
+ const killObjective = objective as IQuestObjectiveKill;
102
+ return `Kill ${formatText(killObjective.creatureKeys.join(', '))}: ${
103
+ killObjective.killCount
104
+ }/${killObjective.killCountTarget}`;
105
+ } else if ('targetNPCkey' in objective) {
106
+ // This is an IQuestObjectiveInteraction
107
+ const interactionObjective = objective as IQuestObjectiveInteraction;
108
+ return `Interact with NPC: ${formatText(
109
+ interactionObjective.targetNPCkey ?? 'Unknown'
110
+ )}`;
111
+ } else {
112
+ return 'Unknown objective';
113
+ }
114
+ })
115
+ .join('; ');
116
+ };
61
117
 
62
- .quest-title {
63
- text-align: left;
64
- margin-left: 44px;
65
- margin-top: 20px;
66
- color: yellow;
67
- }
118
+ // Other helper functions remain the same
119
+ const formatRewards = (rewards: IQuest['rewards']) => {
120
+ return rewards
121
+ .map(reward => {
122
+ return `${formatText(
123
+ reward.itemKeys.map(itemKey => itemKey + ' x' + reward.qty).join(', ')
124
+ )}${
125
+ reward.spellKeys
126
+ ? `, Spells: ${formatText(reward.spellKeys.join(', '))}`
127
+ : ''
128
+ }`;
129
+ })
130
+ .join('; ');
131
+ };
68
132
 
69
- .quest-desc {
70
- margin-top: 12px;
71
- margin-left: 44px;
72
- }
133
+ const formatText = (text: string) => {
134
+ return text
135
+ .split('-')
136
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
137
+ .join(' ');
138
+ };
73
139
 
74
- .rpgui-progress {
75
- min-width: 80%;
76
- margin: 0 auto;
140
+ const getStatusColor = (status?: QuestStatus) => {
141
+ switch (status) {
142
+ case QuestStatus.Pending:
143
+ return uiColors.orange; // Orange
144
+ case QuestStatus.InProgress:
145
+ return uiColors.blue; // Deep Sky Blue
146
+ case QuestStatus.Completed:
147
+ return uiColors.lightGreen; // Lime Green
148
+ default:
149
+ return uiColors.white; // Default to white
77
150
  }
78
- `;
79
-
80
- const Title = styled.h1`
81
- z-index: 22;
82
- font-size: ${uiFonts.size.medium} !important;
83
- color: yellow !important;
84
- `;
151
+ };
85
152
 
86
153
  const QuestListContainer = styled.div`
87
154
  margin-top: 20px;
88
155
  margin-bottom: 40px;
89
156
  overflow-y: auto;
90
157
  max-height: 400px;
91
-
92
- .quest-item {
93
- display: flex;
94
- align-items: flex-start;
95
- margin-bottom: 12px;
96
- }
97
-
98
- .quest-number {
99
- border-radius: 50%;
100
- width: 28px;
101
- height: 28px;
102
- display: flex;
103
- align-items: center;
104
- justify-content: center;
105
- margin-right: 16px;
106
- background-color: brown;
107
- flex-shrink: 0;
108
- }
109
-
110
- .quest-number.completed {
111
- background-color: yellow;
112
- }
113
-
114
- p {
115
- margin: 0;
116
- }
117
-
118
- .quest-detail__title {
119
- color: yellow;
120
- }
121
-
122
- .quest-detail__description {
123
- margin-top: 5px;
124
- }
125
- .Noquest-detail__description {
126
- margin-top: 5px;
127
- margin: auto;
128
- }
158
+ background-color: ${uiColors.darkGray};
159
+ padding: 20px;
160
+ border-radius: 10px;
161
+ border: 1px solid ${uiColors.gray};
162
+ font-size: 0.7rem;
129
163
  `;
164
+
130
165
  const NoQuestContainer = styled.div`
131
166
  text-align: center;
132
167
  p {
133
168
  margin-top: 5px;
169
+ color: ${uiColors.lightGray};
134
170
  }
135
171
  `;
@@ -11,6 +11,9 @@ export const TableRow = styled.tr`
11
11
  &:nth-child(even) {
12
12
  background-color: rgba(255, 255, 255, 0.05) !important;
13
13
  }
14
+ &:hover {
15
+ background-color: rgba(255, 255, 255, 0.1) !important;
16
+ }
14
17
  `;
15
18
 
16
19
  export const TableHeader = styled.th`
@@ -18,13 +21,14 @@ export const TableHeader = styled.th`
18
21
  padding: 0.5rem;
19
22
  color: ${uiColors.yellow} !important;
20
23
  border-bottom: 1px solid ${uiColors.lightGray};
24
+ text-shadow: -2px 0 black, 0 2px black, 2px 0 black, 0 -2px black;
21
25
  `;
22
26
 
23
27
  export const TableCell = styled.td`
24
28
  padding: 0.5rem;
25
29
  color: ${uiColors.white};
26
30
  border-bottom: 1px solid ${uiColors.lightGray};
27
- text-align: ${({ align }) => (align ? align : 'left')}; /* Add this line */
31
+ text-shadow: -2px 0 black, 0 2px black, 2px 0 black, 0 -2px black;
28
32
  `;
29
33
 
30
34
  export const ActionButtons = styled.div`
@@ -3,7 +3,7 @@ import styled from 'styled-components';
3
3
 
4
4
  interface IProps {
5
5
  children: React.ReactNode;
6
- maxLines: 1 | 2 | 3;
6
+ maxLines?: 1 | 2 | 3;
7
7
  maxWidth: string;
8
8
  fontSize?: string;
9
9
  center?: boolean;
@@ -11,7 +11,7 @@ interface IProps {
11
11
 
12
12
  export const Ellipsis = ({
13
13
  children,
14
- maxLines,
14
+ maxLines = 1,
15
15
  maxWidth,
16
16
  fontSize,
17
17
  center,
@@ -0,0 +1,198 @@
1
+ import React, { ReactNode, useEffect, useRef, useState } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import styled, { keyframes } from 'styled-components';
4
+ import { uiColors } from '../../constants/uiColors';
5
+ import { RPGUIRoot } from '../RPGUI/RPGUIRoot';
6
+
7
+ interface TooltipProps {
8
+ content: string | ReactNode;
9
+ direction?: 'top' | 'bottom' | 'left' | 'right';
10
+ backgroundColor?: string;
11
+ textColor?: string;
12
+ children: ReactNode;
13
+ showDelay?: number;
14
+ hideDelay?: number;
15
+ }
16
+
17
+ export const SimpleTooltip: React.FC<TooltipProps> = ({
18
+ content,
19
+ direction = 'top',
20
+ backgroundColor = uiColors.raisinBlack,
21
+ textColor = uiColors.white,
22
+ children,
23
+ showDelay = 200,
24
+ hideDelay = 200,
25
+ }) => {
26
+ const [visible, setVisible] = useState(false);
27
+ const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
28
+ const tooltipRef = useRef<HTMLDivElement>(null);
29
+ const triggerRef = useRef<HTMLDivElement>(null);
30
+ const timeoutRef = useRef<NodeJS.Timeout>();
31
+
32
+ const calculatePosition = () => {
33
+ if (!triggerRef.current || !tooltipRef.current) return;
34
+
35
+ const triggerRect = triggerRef.current.getBoundingClientRect();
36
+ const tooltipRect = tooltipRef.current.getBoundingClientRect();
37
+
38
+ let top, left;
39
+
40
+ switch (direction) {
41
+ case 'top':
42
+ top = triggerRect.top - tooltipRect.height - 8;
43
+ left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
44
+ break;
45
+ case 'bottom':
46
+ top = triggerRect.bottom + 8;
47
+ left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
48
+ break;
49
+ case 'left':
50
+ top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
51
+ left = triggerRect.left - tooltipRect.width - 8;
52
+ break;
53
+ case 'right':
54
+ top = triggerRect.top + (triggerRect.height - tooltipRect.height) / 2;
55
+ left = triggerRect.right + 8;
56
+ break;
57
+ default:
58
+ top = triggerRect.top - tooltipRect.height - 8;
59
+ left = triggerRect.left + (triggerRect.width - tooltipRect.width) / 2;
60
+ }
61
+
62
+ // Ensure the tooltip stays within the viewport
63
+ if (left < 8) left = 8;
64
+ if (left + tooltipRect.width > window.innerWidth)
65
+ left = window.innerWidth - tooltipRect.width - 8;
66
+ if (top < 8) top = 8;
67
+ if (top + tooltipRect.height > window.innerHeight)
68
+ top = window.innerHeight - tooltipRect.height - 8;
69
+
70
+ setTooltipPosition({ top: Math.round(top), left: Math.round(left) });
71
+ };
72
+
73
+ const showTooltip = () => {
74
+ clearTimeout(timeoutRef.current);
75
+ timeoutRef.current = setTimeout(() => {
76
+ setVisible(true);
77
+ calculatePosition();
78
+ }, showDelay);
79
+ };
80
+
81
+ const hideTooltip = () => {
82
+ clearTimeout(timeoutRef.current);
83
+ timeoutRef.current = setTimeout(() => {
84
+ setVisible(false);
85
+ }, hideDelay);
86
+ };
87
+
88
+ useEffect(() => {
89
+ const handleMouseMove = (event: MouseEvent) => {
90
+ if (visible && tooltipRef.current && triggerRef.current) {
91
+ const tooltipRect = tooltipRef.current.getBoundingClientRect();
92
+ const triggerRect = triggerRef.current.getBoundingClientRect();
93
+
94
+ const isOutsideTooltip =
95
+ event.clientX < tooltipRect.left ||
96
+ event.clientX > tooltipRect.right ||
97
+ event.clientY < tooltipRect.top ||
98
+ event.clientY > tooltipRect.bottom;
99
+
100
+ const isOutsideTrigger =
101
+ event.clientX < triggerRect.left ||
102
+ event.clientX > triggerRect.right ||
103
+ event.clientY < triggerRect.top ||
104
+ event.clientY > triggerRect.bottom;
105
+
106
+ if (isOutsideTooltip && isOutsideTrigger) {
107
+ hideTooltip();
108
+ }
109
+ }
110
+ };
111
+
112
+ const handleScroll = () => {
113
+ if (visible) {
114
+ hideTooltip();
115
+ }
116
+ };
117
+
118
+ const handleResize = () => {
119
+ if (visible) {
120
+ calculatePosition();
121
+ }
122
+ };
123
+
124
+ document.addEventListener('mousemove', handleMouseMove);
125
+ window.addEventListener('scroll', handleScroll);
126
+ window.addEventListener('resize', handleResize);
127
+
128
+ return () => {
129
+ document.removeEventListener('mousemove', handleMouseMove);
130
+ window.removeEventListener('scroll', handleScroll);
131
+ window.removeEventListener('resize', handleResize);
132
+ clearTimeout(timeoutRef.current);
133
+ };
134
+ }, [visible]);
135
+
136
+ return (
137
+ <TooltipContainer
138
+ ref={triggerRef}
139
+ onMouseEnter={showTooltip}
140
+ onMouseLeave={hideTooltip}
141
+ onFocus={showTooltip}
142
+ onBlur={hideTooltip}
143
+ >
144
+ {children}
145
+ {visible &&
146
+ ReactDOM.createPortal(
147
+ <RPGUIRoot>
148
+ <TooltipBox
149
+ ref={tooltipRef}
150
+ direction={direction}
151
+ backgroundColor={backgroundColor}
152
+ textColor={textColor}
153
+ style={{ top: tooltipPosition.top, left: tooltipPosition.left }}
154
+ >
155
+ {content}
156
+ </TooltipBox>
157
+ </RPGUIRoot>,
158
+ document.body
159
+ )}
160
+ </TooltipContainer>
161
+ );
162
+ };
163
+
164
+ const TooltipContainer = styled.div`
165
+ display: inline-block;
166
+ cursor: inherit;
167
+ `;
168
+
169
+ const fadeIn = keyframes`
170
+ from {
171
+ opacity: 0;
172
+ transform: scale(0.95);
173
+ }
174
+ to {
175
+ opacity: 1;
176
+ transform: scale(1);
177
+ }
178
+ `;
179
+
180
+ interface TooltipBoxProps {
181
+ direction: 'top' | 'bottom' | 'left' | 'right';
182
+ backgroundColor: string;
183
+ textColor: string;
184
+ }
185
+
186
+ const TooltipBox = styled.div<TooltipBoxProps>`
187
+ position: fixed;
188
+ z-index: 1000;
189
+ background-color: ${({ backgroundColor }) => backgroundColor};
190
+ color: ${({ textColor }) => textColor};
191
+ padding: 8px;
192
+ border-radius: 4px;
193
+ font-size: 0.65rem;
194
+ max-width: 250px;
195
+ box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
196
+ animation: ${fadeIn} 0.2s ease-out;
197
+ line-height: 1.3;
198
+ `;
@@ -22,61 +22,31 @@ const IQuestMock = [
22
22
  _id: '6317996c77f2420047a20b55',
23
23
  rewards: [
24
24
  {
25
- id: '6317996c77f2420047a20b53',
26
- itemKeys: ['6317996c77f2420047a20b53'],
27
- spellKeys: ['6317996c77f2420047a20b53'],
25
+ id: '66cd2b039b486b00436a4ace',
26
+ itemKeys: ['iron-armor'],
28
27
  qty: 1,
29
28
  },
30
29
  ],
31
30
  objectives: [
32
31
  {
33
- id: '6317996c77f2420047a20b53',
34
- type: QuestType.Kill,
32
+ id: '66cd2b049b486b00436a4b1a',
33
+ type: QuestType.Interaction,
35
34
  status: QuestStatus.Pending,
36
- killCount: 10,
37
- killCountTarget: 30,
38
- creatureKeys: ['6317996c77f2420047a20b53'],
35
+ targetNPCkey: 'durgan-miner',
36
+ items: [{ itemKey: 'iron-ingot', qty: 10 }],
39
37
  },
40
38
  ],
41
39
  npcId: '6317996b77f2420047a20825',
42
- title: 'Deliver message to the trader1',
43
- key: 'interaction-trader',
40
+ title: 'Iron Ingot Craftsmanship',
41
+ key: 'interaction-durgan-miner',
44
42
  description:
45
- "Need to send a message to my brother, the trader, about my father's health. I cannot do it because it is too dangerous out there. Please, help me by delivering this message to him.",
46
- createdAt: '2022-09-06T19:03:08.285+0000',
47
- updatedAt: '2022-09-06T19:03:08.304+0000',
48
- },
49
- {
50
- _id: '6317996c77f2420047a20b55',
51
- rewards: [
52
- {
53
- id: '6317996c77f2420047a20b53',
54
- itemKeys: ['6317996c77f2420047a20b53'],
55
- spellKeys: ['6317996c77f2420047a20b53'],
56
- qty: 1,
57
- },
58
- ],
59
- objectives: [
60
- {
61
- id: '6317996c77f2420047a20b53',
62
- type: QuestType.Kill,
63
- status: QuestStatus.Pending,
64
- killCount: 10,
65
- killCountTarget: 30,
66
- creatureKeys: ['6317996c77f2420047a20b53'],
67
- },
68
- ],
69
- npcId: '6317996b77f2420047a20825',
70
- title: 'Deliver message to the trader2',
71
- key: 'interaction-trader',
72
- description:
73
- "Need to send a message to my brother, the trader, about my father's health. I cannot do it because it is too dangerous out there. Please, help me by delivering this message to him.",
74
- createdAt: '2022-09-06T19:03:08.285+0000',
75
- updatedAt: '2022-09-06T19:03:08.304+0000',
43
+ "Employ your pickaxe to mine ore, followed by utilizing your blacksmith hammer. Select 'Use with,' then target an anvil to forge ingots. Provide me with 10 Iron Ingots, and I shall craft you an Iron Armor!",
44
+ createdAt: '2024-08-27T01:25:24.285+0000',
45
+ updatedAt: '2024-08-27T01:25:24.304+0000',
46
+ status: QuestStatus.Pending,
76
47
  },
77
48
  ];
78
49
 
79
50
  Default.args = {
80
51
  quests: IQuestMock,
81
- onClose: () => console.log('closing'),
82
52
  };