@rpg-engine/long-bow 0.8.231 → 0.8.232
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/Quests/QuestInfo/QuestInfo.d.ts +3 -1
- package/dist/components/Quests/QuestInfo/QuestObjectivesSection.d.ts +8 -0
- package/dist/components/Quests/QuestInfo/QuestRequirementsSection.d.ts +8 -0
- package/dist/components/Quests/QuestInfo/QuestRewardsSection.d.ts +8 -0
- package/dist/components/Quests/QuestInfo/QuestSectionTypes.d.ts +8 -0
- package/dist/components/Quests/QuestList.d.ts +4 -5
- package/dist/components/Quests/QuestListRow.d.ts +15 -0
- package/dist/components/Tutorial/TutorialStepper.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/long-bow.cjs.development.js +435 -138
- 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 +436 -140
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Quests/QuestInfo/QuestInfo.tsx +57 -68
- package/src/components/Quests/QuestInfo/QuestObjectivesSection.tsx +114 -0
- package/src/components/Quests/QuestInfo/QuestRequirementsSection.tsx +74 -0
- package/src/components/Quests/QuestInfo/QuestRewardsSection.tsx +128 -0
- package/src/components/Quests/QuestInfo/QuestSectionTypes.ts +8 -0
- package/src/components/Quests/QuestList.tsx +12 -87
- package/src/components/Quests/QuestListRow.tsx +178 -0
- package/src/components/Store/CartView.tsx +3 -2
- package/src/components/Store/MetadataCollector.tsx +28 -1
- package/src/components/Store/__test__/MetadataCollector.spec.tsx +12 -3
- package/src/components/Tutorial/TutorialStepper.tsx +2 -0
- package/src/index.tsx +1 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { IQuest, QuestStatus, QuestType } from '@rpg-engine/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { uiColors } from '../../constants/uiColors';
|
|
5
|
+
import { ItemInfoWrapper } from '../Item/Cards/ItemInfoWrapper';
|
|
6
|
+
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
7
|
+
|
|
8
|
+
export interface IQuestListAtlasProps {
|
|
9
|
+
itemsAtlasJSON?: any;
|
|
10
|
+
itemsAtlasIMG?: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface IProps extends IQuestListAtlasProps {
|
|
14
|
+
quest: IQuest;
|
|
15
|
+
compact?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const getProgressText = (quest: IQuest): string | null => {
|
|
19
|
+
const objective = (quest.objectives || [])[0] as any;
|
|
20
|
+
if (!objective) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (objective.type === QuestType.Kill) {
|
|
25
|
+
return `${objective.killCount || 0}/${objective.killCountTarget}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (objective.type === QuestType.Interaction && objective.items?.length) {
|
|
29
|
+
const item = objective.items[0];
|
|
30
|
+
return `Bring ${item.playerHasQty || 0}/${item.qty}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return objective.type === QuestType.Interaction && objective.targetNPCkey ? 'Talk' : null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const QuestListRow: React.FC<IProps> = ({
|
|
37
|
+
quest,
|
|
38
|
+
compact,
|
|
39
|
+
itemsAtlasJSON,
|
|
40
|
+
itemsAtlasIMG,
|
|
41
|
+
}) => {
|
|
42
|
+
const rewardItems = (quest.rewards || []).flatMap((reward: any) => reward.items || []);
|
|
43
|
+
const visibleRewards = rewardItems.slice(0, 6);
|
|
44
|
+
const overflowCount = Math.max(rewardItems.length - visibleRewards.length, 0);
|
|
45
|
+
const progressText = getProgressText(quest);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<QuestCard>
|
|
49
|
+
<Header>
|
|
50
|
+
<Title>{formatQuestText(quest.title)}</Title>
|
|
51
|
+
<Status style={{ color: getQuestStatusColor(quest.status) }}>
|
|
52
|
+
{formatQuestStatus(quest.status) || 'Unknown'}
|
|
53
|
+
</Status>
|
|
54
|
+
</Header>
|
|
55
|
+
{!compact && (
|
|
56
|
+
<>
|
|
57
|
+
<Description>{quest.description}</Description>
|
|
58
|
+
<MetaRow>
|
|
59
|
+
{quest.status === QuestStatus.Completed ? 'Completed' : progressText}
|
|
60
|
+
</MetaRow>
|
|
61
|
+
{!!visibleRewards.length && (
|
|
62
|
+
<RewardStrip>
|
|
63
|
+
{visibleRewards.map((item: any) => (
|
|
64
|
+
<RewardIcon key={`${item.key}-${item.qty}`}>
|
|
65
|
+
{itemsAtlasJSON && itemsAtlasIMG && item.texturePath ? (
|
|
66
|
+
<ItemInfoWrapper item={{ ...item, stackQty: item.qty } as any} atlasJSON={itemsAtlasJSON} atlasIMG={itemsAtlasIMG}>
|
|
67
|
+
<SpriteFromAtlas
|
|
68
|
+
atlasJSON={itemsAtlasJSON}
|
|
69
|
+
atlasIMG={itemsAtlasIMG}
|
|
70
|
+
spriteKey={item.texturePath}
|
|
71
|
+
width={24}
|
|
72
|
+
height={24}
|
|
73
|
+
imgScale={1}
|
|
74
|
+
centered
|
|
75
|
+
/>
|
|
76
|
+
</ItemInfoWrapper>
|
|
77
|
+
) : (
|
|
78
|
+
<FallbackIcon>{item.name?.[0] || '?'}</FallbackIcon>
|
|
79
|
+
)}
|
|
80
|
+
</RewardIcon>
|
|
81
|
+
))}
|
|
82
|
+
{overflowCount > 0 && <MoreRewards>+{overflowCount} more</MoreRewards>}
|
|
83
|
+
</RewardStrip>
|
|
84
|
+
)}
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
87
|
+
</QuestCard>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const formatQuestText = (text: string) => {
|
|
92
|
+
if (!text) return '';
|
|
93
|
+
return text
|
|
94
|
+
.split('-')
|
|
95
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
96
|
+
.join(' ');
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const getQuestStatusColor = (status?: QuestStatus) => {
|
|
100
|
+
switch (status) {
|
|
101
|
+
case QuestStatus.Pending:
|
|
102
|
+
return uiColors.orange;
|
|
103
|
+
case QuestStatus.InProgress:
|
|
104
|
+
return uiColors.blue;
|
|
105
|
+
case QuestStatus.Completed:
|
|
106
|
+
return uiColors.lightGreen;
|
|
107
|
+
default:
|
|
108
|
+
return uiColors.white;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const formatQuestStatus = (status?: QuestStatus) => {
|
|
113
|
+
if (!status) return '';
|
|
114
|
+
return status
|
|
115
|
+
.split(/(?=[A-Z])/)
|
|
116
|
+
.join(' ')
|
|
117
|
+
.replace(/^\w/, c => c.toUpperCase());
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const QuestCard = styled.div`
|
|
121
|
+
background-color: ${uiColors.darkGray};
|
|
122
|
+
padding: 12px;
|
|
123
|
+
margin-bottom: 10px;
|
|
124
|
+
border-radius: 8px;
|
|
125
|
+
border: 1px solid ${uiColors.gray};
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
const Header = styled.div`
|
|
129
|
+
display: flex;
|
|
130
|
+
justify-content: space-between;
|
|
131
|
+
gap: 10px;
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
const Title = styled.span`
|
|
135
|
+
color: ${uiColors.yellow};
|
|
136
|
+
font-weight: bold;
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
const Status = styled.span`
|
|
140
|
+
white-space: nowrap;
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
const Description = styled.p`
|
|
144
|
+
color: ${uiColors.white};
|
|
145
|
+
margin: 8px 0;
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
const MetaRow = styled.div`
|
|
149
|
+
color: ${uiColors.lightGreen};
|
|
150
|
+
font-size: 0.7rem;
|
|
151
|
+
margin-bottom: 6px;
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
const RewardStrip = styled.div`
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
gap: 6px;
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
const RewardIcon = styled.div`
|
|
161
|
+
width: 24px;
|
|
162
|
+
height: 24px;
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
const FallbackIcon = styled.div`
|
|
166
|
+
width: 24px;
|
|
167
|
+
height: 24px;
|
|
168
|
+
border: 1px solid ${uiColors.gray};
|
|
169
|
+
color: ${uiColors.yellow};
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
justify-content: center;
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
const MoreRewards = styled.span`
|
|
176
|
+
color: ${uiColors.lightGray};
|
|
177
|
+
font-size: 0.65rem;
|
|
178
|
+
`;
|
|
@@ -53,7 +53,7 @@ const MetadataDisplay: React.FC<{
|
|
|
53
53
|
<InfoBox />
|
|
54
54
|
<span>Skin:</span>
|
|
55
55
|
</MetadataLabel>
|
|
56
|
-
<MetadataValue>{metadata.selectedSkinName || 'Custom skin'}</MetadataValue>
|
|
56
|
+
<MetadataValue>{metadata.selectedSkinName || metadata.selectedSkin || 'Custom skin'}</MetadataValue>
|
|
57
57
|
</MetadataInfo>
|
|
58
58
|
);
|
|
59
59
|
default:
|
|
@@ -159,6 +159,7 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
159
159
|
const getSpriteKey = (textureKey: string) => {
|
|
160
160
|
return textureKey + '/down/standing/0.png';
|
|
161
161
|
};
|
|
162
|
+
const selectedSkinTextureKey = cartItem.metadata?.selectedSkinTextureKey ?? cartItem.metadata?.selectedSkin;
|
|
162
163
|
|
|
163
164
|
return (
|
|
164
165
|
<CartItemRow key={cartItem.item.key}>
|
|
@@ -166,7 +167,7 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
166
167
|
<SpriteFromAtlas
|
|
167
168
|
atlasJSON={cartItem.item.metadataType === MetadataType.CharacterSkin ? characterAtlasJSON : atlasJSON}
|
|
168
169
|
atlasIMG={cartItem.item.metadataType === MetadataType.CharacterSkin ? characterAtlasIMG : atlasIMG}
|
|
169
|
-
spriteKey={cartItem.item.metadataType === MetadataType.CharacterSkin &&
|
|
170
|
+
spriteKey={cartItem.item.metadataType === MetadataType.CharacterSkin && selectedSkinTextureKey ? getSpriteKey(selectedSkinTextureKey) : cartItem.item.texturePath}
|
|
170
171
|
width={24}
|
|
171
172
|
height={24}
|
|
172
173
|
imgScale={1.5}
|
|
@@ -2,6 +2,11 @@ import { MetadataType } from '@rpg-engine/shared';
|
|
|
2
2
|
import React, { useCallback, useEffect, useRef } from 'react';
|
|
3
3
|
import { CharacterSkinSelectionModal } from '../Character/CharacterSkinSelectionModal';
|
|
4
4
|
|
|
5
|
+
interface ICharacterSkinOption {
|
|
6
|
+
name: string;
|
|
7
|
+
textureKey: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
export interface IMetadataCollectorProps {
|
|
6
11
|
metadataType: MetadataType;
|
|
7
12
|
config: Record<string, any>;
|
|
@@ -9,6 +14,21 @@ export interface IMetadataCollectorProps {
|
|
|
9
14
|
onCancel: () => void;
|
|
10
15
|
}
|
|
11
16
|
|
|
17
|
+
const getCharacterSkinMetadata = (
|
|
18
|
+
selectedSkinTextureKey: string,
|
|
19
|
+
availableCharacters: ICharacterSkinOption[]
|
|
20
|
+
) => {
|
|
21
|
+
const selectedSkin = availableCharacters.find(
|
|
22
|
+
(character) => character.textureKey === selectedSkinTextureKey
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
selectedSkin: selectedSkinTextureKey,
|
|
27
|
+
selectedSkinName: selectedSkin?.name ?? selectedSkinTextureKey,
|
|
28
|
+
selectedSkinTextureKey,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
12
32
|
export const MetadataCollector: React.FC<IMetadataCollectorProps> = ({
|
|
13
33
|
metadataType,
|
|
14
34
|
config,
|
|
@@ -54,7 +74,14 @@ export const MetadataCollector: React.FC<IMetadataCollectorProps> = ({
|
|
|
54
74
|
<CharacterSkinSelectionModal
|
|
55
75
|
isOpen
|
|
56
76
|
onClose={handleCancel}
|
|
57
|
-
onConfirm={(selectedSkin:
|
|
77
|
+
onConfirm={(selectedSkin: string) =>
|
|
78
|
+
handleCollect(
|
|
79
|
+
getCharacterSkinMetadata(
|
|
80
|
+
selectedSkin,
|
|
81
|
+
config.availableCharacters || []
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
58
85
|
availableCharacters={config.availableCharacters || []}
|
|
59
86
|
atlasJSON={config.atlasJSON}
|
|
60
87
|
atlasIMG={config.atlasIMG}
|
|
@@ -72,11 +72,16 @@ describe('MetadataCollector', () => {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it('collects once without cancelling again when the modal closes after confirm', () => {
|
|
75
|
+
const availableCharacters = [
|
|
76
|
+
{ name: 'Superior Knight', textureKey: 'superior-knight' },
|
|
77
|
+
{ name: 'Black Knight', textureKey: 'black-knight' },
|
|
78
|
+
];
|
|
79
|
+
|
|
75
80
|
act(() => {
|
|
76
81
|
ReactDOM.render(
|
|
77
82
|
<MetadataCollector
|
|
78
83
|
metadataType={MetadataType.CharacterSkin}
|
|
79
|
-
config={{ availableCharacters
|
|
84
|
+
config={{ availableCharacters }}
|
|
80
85
|
onCollect={mockOnCollect}
|
|
81
86
|
onCancel={mockOnCancel}
|
|
82
87
|
/>,
|
|
@@ -87,11 +92,15 @@ describe('MetadataCollector', () => {
|
|
|
87
92
|
const callArgs = CharacterSkinSelectionModal.mock.calls[0][0];
|
|
88
93
|
|
|
89
94
|
act(() => {
|
|
90
|
-
callArgs.onConfirm('
|
|
95
|
+
callArgs.onConfirm('superior-knight');
|
|
91
96
|
callArgs.onClose();
|
|
92
97
|
});
|
|
93
98
|
|
|
94
|
-
expect(mockOnCollect).toHaveBeenCalledWith({
|
|
99
|
+
expect(mockOnCollect).toHaveBeenCalledWith({
|
|
100
|
+
selectedSkin: 'superior-knight',
|
|
101
|
+
selectedSkinName: 'Superior Knight',
|
|
102
|
+
selectedSkinTextureKey: 'superior-knight',
|
|
103
|
+
});
|
|
95
104
|
expect(mockOnCollect).toHaveBeenCalledTimes(1);
|
|
96
105
|
expect(mockOnCancel).not.toHaveBeenCalled();
|
|
97
106
|
|
package/src/index.tsx
CHANGED
|
@@ -62,6 +62,7 @@ export * from './components/PropertySelect/PropertySelect';
|
|
|
62
62
|
export * from './components/QuantitySelector/QuantitySelectorModal';
|
|
63
63
|
export * from './components/Quests/QuestInfo/QuestInfo';
|
|
64
64
|
export * from './components/Quests/QuestList';
|
|
65
|
+
export * from './components/Quests/QuestListRow';
|
|
65
66
|
export * from './components/RadioButton';
|
|
66
67
|
export * from './components/RadioSelectCard/RadioSelectCard';
|
|
67
68
|
export * from './components/RangeSlider';
|
|
@@ -98,4 +99,3 @@ export * from './components/Truncate';
|
|
|
98
99
|
export * from './components/Tutorial/TutorialStepper';
|
|
99
100
|
export * from './components/typography/DynamicText';
|
|
100
101
|
export { useEventListener } from './hooks/useEventListener';
|
|
101
|
-
|