@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.
Files changed (28) hide show
  1. package/dist/components/Quests/QuestInfo/QuestInfo.d.ts +3 -1
  2. package/dist/components/Quests/QuestInfo/QuestObjectivesSection.d.ts +8 -0
  3. package/dist/components/Quests/QuestInfo/QuestRequirementsSection.d.ts +8 -0
  4. package/dist/components/Quests/QuestInfo/QuestRewardsSection.d.ts +8 -0
  5. package/dist/components/Quests/QuestInfo/QuestSectionTypes.d.ts +8 -0
  6. package/dist/components/Quests/QuestList.d.ts +4 -5
  7. package/dist/components/Quests/QuestListRow.d.ts +15 -0
  8. package/dist/components/Tutorial/TutorialStepper.d.ts +2 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/long-bow.cjs.development.js +435 -138
  11. package/dist/long-bow.cjs.development.js.map +1 -1
  12. package/dist/long-bow.cjs.production.min.js +1 -1
  13. package/dist/long-bow.cjs.production.min.js.map +1 -1
  14. package/dist/long-bow.esm.js +436 -140
  15. package/dist/long-bow.esm.js.map +1 -1
  16. package/package.json +1 -1
  17. package/src/components/Quests/QuestInfo/QuestInfo.tsx +57 -68
  18. package/src/components/Quests/QuestInfo/QuestObjectivesSection.tsx +114 -0
  19. package/src/components/Quests/QuestInfo/QuestRequirementsSection.tsx +74 -0
  20. package/src/components/Quests/QuestInfo/QuestRewardsSection.tsx +128 -0
  21. package/src/components/Quests/QuestInfo/QuestSectionTypes.ts +8 -0
  22. package/src/components/Quests/QuestList.tsx +12 -87
  23. package/src/components/Quests/QuestListRow.tsx +178 -0
  24. package/src/components/Store/CartView.tsx +3 -2
  25. package/src/components/Store/MetadataCollector.tsx +28 -1
  26. package/src/components/Store/__test__/MetadataCollector.spec.tsx +12 -3
  27. package/src/components/Tutorial/TutorialStepper.tsx +2 -0
  28. 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 && cartItem.metadata?.selectedSkinTextureKey ? getSpriteKey(cartItem.metadata.selectedSkinTextureKey) : cartItem.item.texturePath}
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: any) => handleCollect({ 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: ['char1', 'char2'] }}
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('char1');
95
+ callArgs.onConfirm('superior-knight');
91
96
  callArgs.onClose();
92
97
  });
93
98
 
94
- expect(mockOnCollect).toHaveBeenCalledWith({ selectedSkin: 'char1' });
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
 
@@ -9,6 +9,8 @@ export interface ITutorialLesson {
9
9
  title: string;
10
10
  body?: React.ReactNode | string;
11
11
  text?: string;
12
+ bodyHighlights?: string[];
13
+ textHighlights?: string[];
12
14
  image: string;
13
15
  imageUrl?: string;
14
16
  }
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
-