ps99-api 2.4.0 → 2.6.0
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/.github/workflows/release-on-main.yml +1 -2
- package/.idea/runConfigurations/test_changing.xml +1 -1
- package/debug_currency.json +57 -0
- package/debug_goals.json +271 -0
- package/dist/ps99-api.d.ts +2 -0
- package/dist/ps99-api.js +4 -1
- package/dist/ps99-api.js.map +1 -1
- package/dist/request-client/axios.js +6 -1
- package/dist/request-client/axios.js.map +1 -1
- package/dist/responses/collection/index.d.ts +1 -0
- package/dist/responses/collection/index.js +15 -0
- package/dist/responses/collection/index.js.map +1 -1
- package/dist/responses/collection/rarity.d.ts +1 -0
- package/example-web/react/package-lock.json +1504 -1470
- package/example-web/react2/package-lock.json +3089 -2766
- package/example-web/react2/package.json +6 -1
- package/example-web/react2/public/assets/gold_variant_icon.png +0 -0
- package/example-web/react2/public/assets/hot_cocoa_egg.png +0 -0
- package/example-web/react2/public/index.html +34 -31
- package/example-web/react2/src/App.tsx +15 -15
- package/example-web/react2/src/assets/guild_placeholder.png +0 -0
- package/example-web/react2/src/components/AchievementsComponent.tsx +74 -19
- package/example-web/react2/src/components/AutoSizer.tsx +49 -0
- package/example-web/react2/src/components/BoostsComponent.tsx +16 -5
- package/example-web/react2/src/components/BoothsComponent.tsx +22 -52
- package/example-web/react2/src/components/BoxesComponent.tsx +48 -16
- package/example-web/react2/src/components/BuffsComponent.tsx +82 -34
- package/example-web/react2/src/components/CharmsComponent.tsx +84 -24
- package/example-web/react2/src/components/CollectionConfigIndex.tsx +867 -33
- package/example-web/react2/src/components/CollectionsIndex.tsx +380 -27
- package/example-web/react2/src/components/CollectionsLayout.tsx +60 -0
- package/example-web/react2/src/components/CurrencyComponent.tsx +57 -39
- package/example-web/react2/src/components/DynamicCollectionConfigData.tsx +172 -15
- package/example-web/react2/src/components/EggsComponent.tsx +50 -12
- package/example-web/react2/src/components/EnchantsComponent.tsx +88 -42
- package/example-web/react2/src/components/FishingRodsComponent.tsx +36 -22
- package/example-web/react2/src/components/Footer.tsx +18 -8
- package/example-web/react2/src/components/FruitsComponent.tsx +40 -17
- package/example-web/react2/src/components/GenericFetchComponent.tsx +9 -1
- package/example-web/react2/src/components/GuildBattlesComponent.tsx +41 -34
- package/example-web/react2/src/components/Header.tsx +39 -52
- package/example-web/react2/src/components/HomePage.tsx +15 -17
- package/example-web/react2/src/components/HoverboardsComponent.tsx +23 -99
- package/example-web/react2/src/components/ImageComponent.tsx +255 -45
- package/example-web/react2/src/components/ItemCard.tsx +240 -0
- package/example-web/react2/src/components/LootboxesComponent.tsx +22 -7
- package/example-web/react2/src/components/MasteryComponent.tsx +165 -30
- package/example-web/react2/src/components/MerchantsComponent.tsx +41 -16
- package/example-web/react2/src/components/MiscItemsComponent.tsx +26 -31
- package/example-web/react2/src/components/PetsComponent.tsx +100 -61
- package/example-web/react2/src/components/PotionsComponent.tsx +121 -27
- package/example-web/react2/src/components/RandomEventsComponent.tsx +32 -23
- package/example-web/react2/src/components/RanksComponent.tsx +187 -62
- package/example-web/react2/src/components/RarityComponent.tsx +123 -5
- package/example-web/react2/src/components/ReactWindowMock.tsx +73 -0
- package/example-web/react2/src/components/RebirthsComponent.tsx +36 -19
- package/example-web/react2/src/components/SecretRoomsComponent.tsx +5 -4
- package/example-web/react2/src/components/SeedsComponent.tsx +41 -21
- package/example-web/react2/src/components/ShovelsComponent.tsx +21 -9
- package/example-web/react2/src/components/Sidebar.tsx +105 -0
- package/example-web/react2/src/components/SprinklersComponent.tsx +25 -10
- package/example-web/react2/src/components/Tooltip.tsx +36 -0
- package/example-web/react2/src/components/UltimatesComponent.tsx +28 -16
- package/example-web/react2/src/components/UpgradesComponent.tsx +97 -47
- package/example-web/react2/src/components/WateringCansComponent.tsx +20 -14
- package/example-web/react2/src/components/WorldsComponent.tsx +21 -11
- package/example-web/react2/src/components/XPPotionsComponent.tsx +28 -11
- package/example-web/react2/src/components/ZoneFlagsComponent.tsx +25 -14
- package/example-web/react2/src/components/ZonesComponent.tsx +43 -60
- package/example-web/react2/src/constants/collectionIcons.ts +29 -0
- package/example-web/react2/src/context/CollectionDataContext.tsx +62 -0
- package/example-web/react2/src/context/ScrollContext.tsx +35 -0
- package/example-web/react2/src/hooks/useCollapsibleHeader.ts +69 -0
- package/example-web/react2/src/hooks/useExpandableList.ts +38 -0
- package/example-web/react2/src/hooks/useItemResolution.ts +351 -0
- package/example-web/react2/src/index.css +257 -0
- package/example-web/react2/src/index.tsx +2 -1
- package/example-web/react2/src/utils/gigantix.ts +40 -0
- package/example-web/react2/temp_model.rbxm +0 -0
- package/example-web/react2/webpack.config.js +103 -47
- package/package.json +11 -11
- package/ranks.json +1 -0
- package/repro_collection_fetch.ts +33 -0
- package/repro_image_fetch.ts +50 -0
- package/src/__tests__/__snapshots__/ps99-api-changes.ts.snap +34841 -10439
- package/src/__tests__/__snapshots__/ps99-api-live.ts.snap +160667 -67217
- package/src/ps99-api.ts +9 -5
- package/src/request-client/axios.ts +6 -2
- package/src/responses/collection/index.ts +1 -0
- package/src/responses/collection/rarity.ts +1 -0
- package/tsconfig.json +1 -1
- package/example-web/react2/public/service-worker.js +0 -63
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseCollapsibleHeaderOptions {
|
|
4
|
+
defaultHeight?: number;
|
|
5
|
+
triggerStart?: number; // Scroll position to start checking
|
|
6
|
+
threshold?: number; // Scroll delta threshold
|
|
7
|
+
deps?: any[]; // Dependencies to re-run the ResizeObserver effect
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useCollapsibleHeader = (options: UseCollapsibleHeaderOptions = {}) => {
|
|
11
|
+
const { defaultHeight = 120, triggerStart = 50, threshold = 10, deps = [] } = options;
|
|
12
|
+
|
|
13
|
+
// Header Visibility Logic
|
|
14
|
+
const [showHeader, setShowHeader] = useState(true);
|
|
15
|
+
const lastScrollTop = useRef(0);
|
|
16
|
+
const scrollRef = useRef<number>(0);
|
|
17
|
+
|
|
18
|
+
const handleScroll = ({ scrollOffset, scrollTop }: { scrollOffset?: number, scrollTop?: number }) => {
|
|
19
|
+
const currentScrollTop = scrollTop ?? scrollOffset ?? 0;
|
|
20
|
+
const scrollDelta = currentScrollTop - lastScrollTop.current;
|
|
21
|
+
|
|
22
|
+
// Threshold to prevent jitter
|
|
23
|
+
if (Math.abs(scrollDelta) > threshold) {
|
|
24
|
+
if (scrollDelta > 0 && currentScrollTop > triggerStart) {
|
|
25
|
+
// Scrolling Down & checked some distance
|
|
26
|
+
setShowHeader(false);
|
|
27
|
+
} else if (scrollDelta < 0) {
|
|
28
|
+
// Scrolling Up
|
|
29
|
+
setShowHeader(true);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
lastScrollTop.current = currentScrollTop;
|
|
33
|
+
scrollRef.current = currentScrollTop;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Dynamic Header Height Logic
|
|
37
|
+
const headerRef = useRef<HTMLDivElement>(null);
|
|
38
|
+
const [headerHeight, setHeaderHeight] = useState(defaultHeight);
|
|
39
|
+
const [isMeasured, setIsMeasured] = useState(false);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!headerRef.current) return;
|
|
43
|
+
|
|
44
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
45
|
+
for (let entry of entries) {
|
|
46
|
+
if (entry.target.clientHeight > 0) {
|
|
47
|
+
setHeaderHeight(entry.target.clientHeight);
|
|
48
|
+
setIsMeasured(true);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
resizeObserver.observe(headerRef.current);
|
|
54
|
+
return () => resizeObserver.disconnect();
|
|
55
|
+
}, deps);
|
|
56
|
+
|
|
57
|
+
const contentPadding = showHeader ? `${headerHeight + 5}px` : "0px";
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
showHeader,
|
|
61
|
+
setShowHeader,
|
|
62
|
+
handleScroll,
|
|
63
|
+
scrollRef,
|
|
64
|
+
headerRef,
|
|
65
|
+
headerHeight,
|
|
66
|
+
contentPadding,
|
|
67
|
+
isMeasured
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useExpandableList = (totalItems: number, initialExpanded: boolean = false) => {
|
|
4
|
+
const [expandedIndices, setExpandedIndices] = useState<Set<number>>(new Set());
|
|
5
|
+
|
|
6
|
+
// Initialize state when totalItems changes or on mount
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (initialExpanded) {
|
|
9
|
+
setExpandedIndices(new Set(Array.from({ length: totalItems }, (_, i) => i)));
|
|
10
|
+
} else {
|
|
11
|
+
setExpandedIndices(new Set());
|
|
12
|
+
}
|
|
13
|
+
}, [totalItems, initialExpanded]);
|
|
14
|
+
|
|
15
|
+
const toggle = useCallback((index: number) => {
|
|
16
|
+
setExpandedIndices(prev => {
|
|
17
|
+
const next = new Set(prev);
|
|
18
|
+
if (next.has(index)) {
|
|
19
|
+
next.delete(index);
|
|
20
|
+
} else {
|
|
21
|
+
next.add(index);
|
|
22
|
+
}
|
|
23
|
+
return next;
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const expandAll = useCallback(() => {
|
|
28
|
+
setExpandedIndices(new Set(Array.from({ length: totalItems }, (_, i) => i)));
|
|
29
|
+
}, [totalItems]);
|
|
30
|
+
|
|
31
|
+
const collapseAll = useCallback(() => {
|
|
32
|
+
setExpandedIndices(new Set());
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const isExpanded = useCallback((index: number) => expandedIndices.has(index), [expandedIndices]);
|
|
36
|
+
|
|
37
|
+
return { expandedIndices, toggle, expandAll, collapseAll, isExpanded };
|
|
38
|
+
};
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
PetSimulator99API,
|
|
4
|
+
EnchantmentData,
|
|
5
|
+
PotionData,
|
|
6
|
+
MiscItemData,
|
|
7
|
+
CurrencyData,
|
|
8
|
+
FruitData,
|
|
9
|
+
HoverboardData,
|
|
10
|
+
BoothData,
|
|
11
|
+
ZoneFlagData,
|
|
12
|
+
SeedData,
|
|
13
|
+
RandomEventData,
|
|
14
|
+
LootboxData,
|
|
15
|
+
UltimateData,
|
|
16
|
+
RarityData,
|
|
17
|
+
} from "ps99-api";
|
|
18
|
+
|
|
19
|
+
export interface ItemData {
|
|
20
|
+
icon: string | null;
|
|
21
|
+
rarity: any | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const useItemResolution = () => {
|
|
25
|
+
const [collections, setCollections] = useState({
|
|
26
|
+
enchants: [] as EnchantmentData[],
|
|
27
|
+
potions: [] as PotionData[],
|
|
28
|
+
miscItems: [] as MiscItemData[],
|
|
29
|
+
currencies: [] as CurrencyData[],
|
|
30
|
+
fruits: [] as FruitData[],
|
|
31
|
+
hoverboards: [] as HoverboardData[],
|
|
32
|
+
booths: [] as BoothData[],
|
|
33
|
+
zoneFlags: [] as ZoneFlagData[],
|
|
34
|
+
seeds: [] as SeedData[],
|
|
35
|
+
randomEvents: [] as RandomEventData[],
|
|
36
|
+
lootboxes: [] as LootboxData[],
|
|
37
|
+
ultimates: [] as UltimateData[],
|
|
38
|
+
rarities: [] as RarityData[],
|
|
39
|
+
pets: [] as any[], // PetData
|
|
40
|
+
eggs: [] as any[], // EggData
|
|
41
|
+
});
|
|
42
|
+
const [loading, setLoading] = useState(true);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const api = new PetSimulator99API();
|
|
46
|
+
const fetchData = async () => {
|
|
47
|
+
try {
|
|
48
|
+
const results = await Promise.allSettled([
|
|
49
|
+
api.getCollection("Enchants"),
|
|
50
|
+
api.getCollection("Potions"),
|
|
51
|
+
api.getCollection("MiscItems"),
|
|
52
|
+
api.getCollection("Currency"),
|
|
53
|
+
api.getCollection("Fruits"),
|
|
54
|
+
api.getCollection("Hoverboards"),
|
|
55
|
+
api.getCollection("Booths"),
|
|
56
|
+
api.getCollection("ZoneFlags"),
|
|
57
|
+
api.getCollection("Seeds"),
|
|
58
|
+
api.getCollection("RandomEvents"),
|
|
59
|
+
api.getCollection("Lootboxes"),
|
|
60
|
+
api.getCollection("Ultimates"),
|
|
61
|
+
api.getCollection("Rarity"),
|
|
62
|
+
api.getCollection("Pets"),
|
|
63
|
+
api.getCollection("Eggs"),
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const getData = (result: PromiseSettledResult<any>) =>
|
|
67
|
+
result.status === 'fulfilled' && result.value.status === 'ok' ? result.value.data : [];
|
|
68
|
+
|
|
69
|
+
const [
|
|
70
|
+
enchants, potions, miscItems, currencies, fruits, hoverboards,
|
|
71
|
+
booths, zoneFlags, seeds, randomEvents, lootboxes, ultimates, rarities,
|
|
72
|
+
pets, eggs
|
|
73
|
+
] = results.map(getData);
|
|
74
|
+
|
|
75
|
+
// Manual Injection for Missing Items
|
|
76
|
+
miscItems.push({
|
|
77
|
+
category: "MiscItems",
|
|
78
|
+
configName: "Superior Mini Chest",
|
|
79
|
+
configData: {
|
|
80
|
+
id: "76",
|
|
81
|
+
DisplayName: "Superior Mini Chest",
|
|
82
|
+
Icon: "17602729261", // rbxassetid://17602729261
|
|
83
|
+
Rarity: { DisplayName: "Exclusive", RarityNumber: 5 }
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Force rebuild check
|
|
88
|
+
console.log("Seeds (v2) loaded:", seeds.length);
|
|
89
|
+
seeds.forEach((s: any) => {
|
|
90
|
+
if (s.configName.includes("Insta") || s.configData.DisplayName.includes("Insta")) {
|
|
91
|
+
console.log("Found Insta Seed:", s.configName, s.configData.DisplayName);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
setCollections({
|
|
96
|
+
enchants, potions, miscItems, currencies, fruits, hoverboards,
|
|
97
|
+
booths, zoneFlags, seeds, randomEvents, lootboxes, ultimates, rarities,
|
|
98
|
+
pets, eggs
|
|
99
|
+
});
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.error("Failed to load item collections", e);
|
|
102
|
+
} finally {
|
|
103
|
+
setLoading(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
fetchData();
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
const resolveItem = (id: string, tn?: number): ItemData & { name?: string } | null => {
|
|
110
|
+
try {
|
|
111
|
+
const {
|
|
112
|
+
enchants, potions, miscItems, currencies, fruits, hoverboards,
|
|
113
|
+
booths, zoneFlags, seeds, randomEvents, lootboxes, ultimates,
|
|
114
|
+
pets, eggs
|
|
115
|
+
} = collections;
|
|
116
|
+
|
|
117
|
+
const numericId = parseInt(id);
|
|
118
|
+
const isNumeric = !isNaN(numericId);
|
|
119
|
+
|
|
120
|
+
// Manual Mappings for Breakables/Events
|
|
121
|
+
if (id === "76") { // Superior Mini Chest
|
|
122
|
+
// Icon found via investigation: rbxassetid://17602729261
|
|
123
|
+
return { icon: "rbxassetid://17602729261", rarity: null, name: "Superior Mini Chest" };
|
|
124
|
+
}
|
|
125
|
+
if (id === "78" || id === "Hot Cocoa Egg") { // Hot Cocoa Egg
|
|
126
|
+
// Local Asset
|
|
127
|
+
return { icon: "/node-ps99-api/assets/hot_cocoa_egg.png", rarity: null, name: "Hot Cocoa Egg" };
|
|
128
|
+
}
|
|
129
|
+
if (id === "40") { // Lucky Block
|
|
130
|
+
const block = miscItems.find(i => i.configName.includes("Lucky Block")) || randomEvents.find(i => i.configName.includes("Lucky Block"));
|
|
131
|
+
const icon = (block?.configData as any).icon || block?.configData.Icon;
|
|
132
|
+
if (icon) return { icon: icon, rarity: (block?.configData as any).rarity || (block?.configData as any).Rarity, name: "Lucky Block" };
|
|
133
|
+
}
|
|
134
|
+
if (id === "41") { // Piñata
|
|
135
|
+
const pinata = miscItems.find(i => i.configName.includes("Pinata")) || randomEvents.find(i => i.configName.includes("Pinata"));
|
|
136
|
+
const icon = (pinata?.configData as any).icon || pinata?.configData.Icon;
|
|
137
|
+
if (icon) return { icon: icon, rarity: (pinata?.configData as any).rarity || (pinata?.configData as any).Rarity, name: "Piñata" };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (id === "Clan Gift") {
|
|
141
|
+
const box = lootboxes.find(i => i.configName === "Clan Gift");
|
|
142
|
+
if (box?.configData.Icon) return { icon: box.configData.Icon, rarity: (box.configData as any).Rarity, name: "Clan Gift" };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Pets
|
|
146
|
+
// Check exact match, or "Huge [name]", or numeric ID
|
|
147
|
+
const pet = pets.find(i => i.configName === id || i.configData.name === id || (isNumeric && (i.configData as any)._index === numericId));
|
|
148
|
+
if (pet) {
|
|
149
|
+
const data = pet.configData as any;
|
|
150
|
+
let icon = data.thumbnail;
|
|
151
|
+
if (tn === 1 && data.goldenThumbnail) icon = data.goldenThumbnail;
|
|
152
|
+
// if (tn === 2) ... Rainbow usually just uses regular or golden with shader, but for icon usage we might just use thumbnail
|
|
153
|
+
|
|
154
|
+
// If we have specific golden/rainbow thumbnails in data, use them.
|
|
155
|
+
// The PetsComponent used data.goldenThumbnail for pt=1.
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
icon: icon.startsWith("rbxassetid://") ? icon : `rbxassetid://${icon}`,
|
|
159
|
+
rarity: data.rarity || { DisplayName: "Basic", RarityNumber: 1 }, // Default if missing
|
|
160
|
+
name: data.name
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Eggs
|
|
165
|
+
const egg = eggs.find(i => i.configName === id || (i.configData as any).DisplayName === id || (isNumeric && (i.configData as any)._index === numericId));
|
|
166
|
+
if (egg) {
|
|
167
|
+
const data = egg.configData as any;
|
|
168
|
+
const icon = data.icon || data.Icon;
|
|
169
|
+
return {
|
|
170
|
+
icon: icon?.startsWith("rbxassetid://") ? icon : `rbxassetid://${icon}`,
|
|
171
|
+
rarity: data.rarity || data.Rarity,
|
|
172
|
+
name: data.DisplayName || data.name || egg.configName
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if (id === "21" || id === "34") {
|
|
178
|
+
console.log(`Resolving special ID: ${id}, numeric: ${numericId}, tn: ${tn}`);
|
|
179
|
+
const sampleCurrency = currencies[0] as any;
|
|
180
|
+
console.log("Sample Currency _index:", sampleCurrency?._index, "Name:", sampleCurrency?.configName);
|
|
181
|
+
const match = currencies.find(i => (i.configData as any)._index === numericId);
|
|
182
|
+
console.log("Found Currency Match:", match?.configName);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Currency
|
|
186
|
+
const currency = currencies.find(i => i.configName === id || i.configName === `Currency | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
187
|
+
if (currency?.configData.Tiers) {
|
|
188
|
+
const tierIndex = tn ? tn - 1 : 0;
|
|
189
|
+
const tier = currency.configData.Tiers[tierIndex] || currency.configData.Tiers[0];
|
|
190
|
+
if (tier) {
|
|
191
|
+
const icon = tier.tinyImage || tier.orbImage;
|
|
192
|
+
if (icon) return { icon, rarity: null, name: (currency.configData as any).DisplayName || id };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// MiscItems
|
|
197
|
+
const misc = miscItems.find(i => i.configName === id || i.configName === `MiscItem | ${id}` || (i.configData as any).DisplayName === id || (isNumeric && (i.configData as any)._index === numericId));
|
|
198
|
+
if (misc) {
|
|
199
|
+
const data = misc.configData as any;
|
|
200
|
+
const icon = data.icon || data.Icon;
|
|
201
|
+
if (icon) return { icon, rarity: data.rarity || data.Rarity, name: data.DisplayName || data.name || misc.configName };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Enchants
|
|
205
|
+
const enchant = enchants.find(i => i.configName === id || i.configName === `Enchant | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
206
|
+
if (enchant) {
|
|
207
|
+
const data = enchant.configData as any;
|
|
208
|
+
const tier = data.Tiers && data.Tiers[tn ? tn - 1 : 0] ? data.Tiers[tn ? tn - 1 : 0] : data;
|
|
209
|
+
const icon = tier.icon || tier.Icon || data.icon || data.Icon;
|
|
210
|
+
return {
|
|
211
|
+
icon: icon?.startsWith("rbxassetid://") ? icon : `rbxassetid://${icon}`,
|
|
212
|
+
rarity: tier.rarity || tier.Rarity || data.rarity || data.Rarity,
|
|
213
|
+
name: tier.DisplayName || data.DisplayName || data.Name || enchant.configName
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Potions
|
|
218
|
+
const potion = potions.find(i => i.configName === id || i.configName === `Potion | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
219
|
+
if (potion) {
|
|
220
|
+
const data = potion.configData as any;
|
|
221
|
+
const tier = data.Tiers && data.Tiers[tn ? tn - 1 : 0] ? data.Tiers[tn ? tn - 1 : 0] : data;
|
|
222
|
+
const icon = tier.icon || tier.Icon || data.icon || data.Icon;
|
|
223
|
+
return {
|
|
224
|
+
icon: icon?.startsWith("rbxassetid://") ? icon : `rbxassetid://${icon}`,
|
|
225
|
+
rarity: tier.rarity || tier.Rarity || data.rarity || data.Rarity,
|
|
226
|
+
name: tier.DisplayName || data.DisplayName || data.Name || potion.configName
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Fruits
|
|
231
|
+
const fruit = fruits.find(i => i.configName === id || i.configName === `Fruit | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
232
|
+
if (fruit?.configData.Icon) return { icon: fruit.configData.Icon, rarity: (fruit.configData as any).Rarity, name: (fruit.configData as any).DisplayName };
|
|
233
|
+
|
|
234
|
+
// Hoverboards
|
|
235
|
+
const hoverboard = hoverboards.find(i => i.configName === id || i.configName === `Hoverboard | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
236
|
+
if (hoverboard?.configData.Icon) return { icon: hoverboard.configData.Icon, rarity: (hoverboard.configData as any).Rarity, name: (hoverboard.configData as any).DisplayName };
|
|
237
|
+
|
|
238
|
+
// Booths
|
|
239
|
+
const booth = booths.find(i => i.configName === id || i.configName === `Booth | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
240
|
+
if (booth?.configData.Icon) return { icon: booth.configData.Icon, rarity: (booth.configData as any).Rarity, name: (booth.configData as any).DisplayName };
|
|
241
|
+
|
|
242
|
+
// ZoneFlags
|
|
243
|
+
const flag = zoneFlags.find(i => i.configName === id || i.configName === `ZoneFlag | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
244
|
+
if (flag?.configData.Icon) return { icon: flag.configData.Icon, rarity: (flag.configData as any).Rarity, name: (flag.configData as any).DisplayName };
|
|
245
|
+
|
|
246
|
+
// Seeds
|
|
247
|
+
const seed = seeds.find(i => i.configName === id || i.configName === `Seed | ${id}` || (i.configData as any).DisplayName === id || (isNumeric && (i.configData as any)._index === numericId));
|
|
248
|
+
if (seed?.configData.Icon) return { icon: seed.configData.Icon, rarity: (seed.configData as any).Rarity, name: (seed.configData as any).DisplayName };
|
|
249
|
+
|
|
250
|
+
// RandomEvents
|
|
251
|
+
const event = randomEvents.find(i => i.configName === id || i.configName === `RandomEvent | ${id}` || i.configData.Name === id || (isNumeric && (i.configData as any)._index === numericId));
|
|
252
|
+
if (event?.configData.Icon) return { icon: event.configData.Icon, rarity: null, name: event.configData.Name };
|
|
253
|
+
|
|
254
|
+
// Lootboxes
|
|
255
|
+
const box = lootboxes.find(i => i.configName === id || i.configName === `Lootbox | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
256
|
+
if (box) {
|
|
257
|
+
const data = box.configData as any;
|
|
258
|
+
const icon = data.icon || data.Icon;
|
|
259
|
+
if (icon) return { icon, rarity: data.rarity || data.Rarity, name: data.DisplayName || data.name };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Ultimates
|
|
263
|
+
const ult = ultimates.find(i => i.configName === id || i.configName === `Ultimate | ${id}` || (isNumeric && (i.configData as any)._index === numericId));
|
|
264
|
+
if (ult?.configData.Icon) return { icon: ult.configData.Icon, rarity: (ult.configData as any).Rarity, name: (ult.configData as any).DisplayName };
|
|
265
|
+
|
|
266
|
+
return null;
|
|
267
|
+
} catch (e) {
|
|
268
|
+
console.error("resolveItem crashed:", e);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const getRarityColor = (rarityData: any) => {
|
|
274
|
+
if (!rarityData) return null;
|
|
275
|
+
if (rarityData.Color) return rarityData.Color;
|
|
276
|
+
if (rarityData._id) {
|
|
277
|
+
const r = collections.rarities.find(r => r.configData._id === rarityData._id || r.configData.DisplayName === rarityData._id);
|
|
278
|
+
if (r?.configData.Color) return r.configData.Color;
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const resolveIcon = (itemData: any): string | null => {
|
|
284
|
+
try {
|
|
285
|
+
if (!itemData) return null;
|
|
286
|
+
|
|
287
|
+
if (itemData.name === "Hot Cocoa Egg" || itemData.DisplayName === "Hot Cocoa Egg") {
|
|
288
|
+
return "/node-ps99-api/assets/hot_cocoa_egg.png";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Check for nested Tiers/Icons common in Enchants, Potions, Currency, Boxes
|
|
292
|
+
if (itemData.Tiers && Array.isArray(itemData.Tiers) && itemData.Tiers.length > 0) {
|
|
293
|
+
const firstTier = itemData.Tiers[0];
|
|
294
|
+
if (firstTier.Icon) return firstTier.Icon;
|
|
295
|
+
if (firstTier.icon) return firstTier.icon;
|
|
296
|
+
if (firstTier.orbImage) return firstTier.orbImage;
|
|
297
|
+
if (firstTier.tinyImage) return firstTier.tinyImage;
|
|
298
|
+
if (firstTier.imageOutline) return firstTier.imageOutline;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (itemData.Icons && Array.isArray(itemData.Icons) && itemData.Icons.length > 0) {
|
|
302
|
+
const firstIcon = itemData.Icons[0];
|
|
303
|
+
if (firstIcon.Icon) return firstIcon.Icon;
|
|
304
|
+
if (firstIcon.icon) return firstIcon.icon;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Handle Buffs (AssociatedItemID)
|
|
308
|
+
if (itemData.AssociatedItemID) {
|
|
309
|
+
const associatedId = itemData.AssociatedItemID;
|
|
310
|
+
// Check MiscItems first (most common for buffs like Toy Ball, Squeaky Toy)
|
|
311
|
+
const misc = collections.miscItems.find(i => i.configName === associatedId);
|
|
312
|
+
if (misc) {
|
|
313
|
+
const miscIcon = resolveIcon(misc.configData); // Recursive resolve
|
|
314
|
+
if (miscIcon) return miscIcon;
|
|
315
|
+
}
|
|
316
|
+
// Could check other collections if needed
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return (
|
|
320
|
+
itemData.icon ||
|
|
321
|
+
itemData.Icon ||
|
|
322
|
+
itemData.thumbnail ||
|
|
323
|
+
itemData.image ||
|
|
324
|
+
itemData.texture ||
|
|
325
|
+
itemData.orbImage ||
|
|
326
|
+
itemData.titanicIcon ||
|
|
327
|
+
itemData.petIcon ||
|
|
328
|
+
itemData.eggIcon ||
|
|
329
|
+
itemData.enchantIcon ||
|
|
330
|
+
itemData.potionIcon ||
|
|
331
|
+
itemData.fruitIcon ||
|
|
332
|
+
itemData.toyIcon ||
|
|
333
|
+
itemData.charmIcon ||
|
|
334
|
+
itemData.boothIcon ||
|
|
335
|
+
itemData.flagIcon ||
|
|
336
|
+
itemData.keyIcon ||
|
|
337
|
+
itemData.seedIcon ||
|
|
338
|
+
itemData.bookIcon ||
|
|
339
|
+
itemData.giftIcon ||
|
|
340
|
+
itemData.currencyIcon ||
|
|
341
|
+
itemData.miscIcon ||
|
|
342
|
+
null
|
|
343
|
+
);
|
|
344
|
+
} catch (e) {
|
|
345
|
+
console.error("resolveIcon crashed:", e);
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
return { loading, resolveItem, getRarityColor, resolveIcon };
|
|
351
|
+
};
|