Skip to content

Commit 33b8537

Browse files
committed
WIP
1 parent 21ef3a5 commit 33b8537

23 files changed

+1868
-669
lines changed

StardewValleyDecompiled

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 5225ef409e42a6159a82cf81200bf6eb315c9961

codegen/extract-xml.cjs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
const targets = {
5+
Item: "StardewValleyDecompiled/Stardew Valley/StardewValley/Item.cs",
6+
Object: "StardewValleyDecompiled/Stardew Valley/StardewValley/Object.cs",
7+
Trinket:
8+
"StardewValleyDecompiled/Stardew Valley/StardewValley.Objects/Trinket.cs",
9+
Weapon:
10+
"StardewValleyDecompiled/Stardew Valley/StardewValley.Tools/MeleeWeapon.cs",
11+
Tool: "StardewValleyDecompiled/Stardew Valley/StardewValley/Tool.cs",
12+
Hat: "StardewValleyDecompiled/Stardew Valley/StardewValley.Objects/Hat.cs",
13+
Boots:
14+
"StardewValleyDecompiled/Stardew Valley/StardewValley.Objects/Boots.cs",
15+
Clothing:
16+
"StardewValleyDecompiled/Stardew Valley/StardewValley.Objects/Clothing.cs",
17+
Ring: "StardewValleyDecompiled/Stardew Valley/StardewValley.Objects/Ring.cs",
18+
ColoredObject:
19+
"StardewValleyDecompiled/Stardew Valley/StardewValley/ColoredObject.cs",
20+
};
21+
22+
const workspaceRoot = process.cwd();
23+
const outputDir = path.join(workspaceRoot, "generated");
24+
const outputFile = path.join(outputDir, "xml-element-metadata.json");
25+
26+
/** @type {Record<string, { file: string; baseClass: string | null; fields: any[] }> } */
27+
const metadata = {};
28+
29+
for (const [className, relativePath] of Object.entries(targets)) {
30+
const fullPath = path.join(workspaceRoot, relativePath.replace(/\\/g, "/"));
31+
if (!fs.existsSync(fullPath)) {
32+
console.error(`Missing file for ${className}: ${fullPath}`);
33+
process.exitCode = 1;
34+
continue;
35+
}
36+
37+
const source = fs.readFileSync(fullPath, "utf8");
38+
const baseMatch = source.match(/class\s+[a-zA-Z]+\s:\s([a-zA-Z]+)/);
39+
const baseClass = baseMatch?.[1] ?? null;
40+
const lines = source.split(/\r?\n/);
41+
const fields = [];
42+
43+
let pending = null;
44+
45+
for (let index = 0; index < lines.length; index += 1) {
46+
const rawLine = lines[index];
47+
if (rawLine === undefined) continue;
48+
const trimmed = rawLine.trim();
49+
if (!pending) {
50+
const xmlMatch = trimmed.match(/\[XmlElement\("([^"\]]+)"\)\]/);
51+
if (xmlMatch) {
52+
pending = {
53+
xmlName: xmlMatch[1],
54+
definition: "",
55+
line: index + 1,
56+
};
57+
}
58+
continue;
59+
}
60+
61+
if (!trimmed) continue;
62+
pending.definition +=
63+
(pending.definition ? " " : "") + trimmed.replace(/\s+/g, " ");
64+
if (
65+
!trimmed.includes(";") &&
66+
!trimmed.includes("{") &&
67+
!trimmed.includes("}")
68+
) {
69+
continue;
70+
}
71+
72+
const def = pending.definition.replace(/;.*$/, "");
73+
const defMatch = def.match(
74+
/(public|protected|internal|private)\s+(?:static\s+)?(?:readonly\s+)?([\w<>[\]]+)\s+([\w_@]+)\s*(=)?/,
75+
);
76+
if (!defMatch) {
77+
console.warn(
78+
`Could not parse definition for ${className} at ${relativePath}:${pending.line}`,
79+
);
80+
pending = null;
81+
continue;
82+
}
83+
84+
const [, , fieldType, fieldName, initializer] = defMatch;
85+
fields.push({
86+
xmlName: pending.xmlName,
87+
fieldName,
88+
fieldType,
89+
initializer: Boolean(initializer),
90+
required: !initializer,
91+
line: pending.line,
92+
});
93+
pending = null;
94+
}
95+
96+
metadata[className] = {
97+
file: relativePath,
98+
baseClass,
99+
fields,
100+
};
101+
}
102+
103+
fs.mkdirSync(outputDir, { recursive: true });
104+
fs.writeFileSync(outputFile, JSON.stringify(metadata, null, 2));
105+
console.log(
106+
`Extracted XML metadata for ${Object.keys(metadata).length} classes to ${outputFile}`,
107+
);

codegen/save.ts

Lines changed: 108 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,13 +1112,13 @@ export interface Player {
11121112
hairstyleColor: Color;
11131113
pantsColor: Color;
11141114
newEyeColor: Color;
1115-
hat?: Item;
1116-
boots?: Item;
1117-
leftRing?: Item;
1118-
rightRing?: Item;
1119-
shirtItem?: Item;
1120-
pantsItem?: Item;
1121-
trinketItem?: Item;
1115+
hat?: HatItem;
1116+
boots?: BootsItem;
1117+
leftRing?: RingItem;
1118+
rightRing?: RingItem;
1119+
shirtItem?: ClothingItem;
1120+
pantsItem?: ClothingItem;
1121+
trinketItem?: TrinketItem;
11221122
divorceTonight: boolean;
11231123
changeWalletTypeTonight: boolean;
11241124
gameVersion: string;
@@ -1272,57 +1272,33 @@ export interface Inventory {
12721272
}
12731273

12741274
export interface Item {
1275-
isLostItem: boolean;
1275+
"@_xsi:type": string;
12761276
category?: number;
12771277
hasBeenInInventory: boolean;
12781278
name: string;
1279-
itemId: string | number;
1280-
specialItem: boolean;
1279+
parentSheetIndex?: number;
1280+
itemId?: string | number;
12811281
isRecipe: boolean;
12821282
quality: number;
12831283
stack: number;
1284-
price: number;
1285-
SpecialVariable: number;
1286-
initialParentTileIndex?: number;
1287-
currentParentTileIndex?: number;
1288-
indexOfMenuItemView?: number;
1289-
instantUse?: boolean;
1290-
isEfficient?: boolean;
1291-
animationSpeedModifier?: number;
1292-
swingTicker?: number;
1293-
upgradeLevel?: number;
1294-
numAttachmentSlots?: number;
1295-
attachments?: AttachmentsAttachments | string;
1296-
InitialParentTileIndex?: number;
1297-
IndexOfMenuItemView?: number;
1298-
InstantUse?: boolean;
1299-
IsEfficient?: boolean;
1300-
AnimationSpeedModifier?: number;
1301-
type?: TypeEnum | number;
1302-
minDamage?: number;
1303-
maxDamage?: number;
1304-
speed?: number;
1305-
addedPrecision?: number;
1306-
addedDefense?: number;
1307-
addedAreaOfEffect?: number;
1308-
knockback?: number;
1309-
critChance?: number;
1310-
critMultiplier?: number;
1311-
isOnSpecial?: boolean;
1312-
additionalPower?: IntContainer;
1313-
isBottomless?: boolean;
1314-
WaterLeft?: number;
1315-
IsBottomless?: boolean;
1316-
parentSheetIndex?: number;
1284+
modDataForSerialization?: unknown;
1285+
_type?: string;
1286+
type?: TypeEnum | number | string;
1287+
}
1288+
1289+
export interface ObjectItem extends Item {
1290+
"@_xsi:type": string;
13171291
tileLocation?: TileLocation;
13181292
owner?: number;
1293+
type?: TypeEnum | string;
13191294
canBeSetDown?: boolean;
13201295
canBeGrabbed?: boolean;
13211296
isSpawnedObject?: boolean;
13221297
questItem?: boolean;
1323-
questId?: number;
1298+
questId?: string;
13241299
isOn?: boolean;
13251300
fragility?: number;
1301+
price?: number;
13261302
edibility?: number;
13271303
bigCraftable?: boolean;
13281304
setOutdoors?: boolean;
@@ -1331,74 +1307,111 @@ export interface Item {
13311307
showNextIndex?: boolean;
13321308
flipped?: boolean;
13331309
isLamp?: boolean;
1310+
heldObject?: ObjectItem;
1311+
lastOutputRuleId?: string;
1312+
lastInputItem?: Item;
13341313
minutesUntilReady?: number;
13351314
boundingBox?: BoundingBox;
1336-
scale?: TileLocation;
13371315
uses?: number;
1338-
destroyOvernight?: boolean;
1339-
CastDirection?: number;
1316+
signText?: string;
1317+
orderData?: string;
1318+
preserve?: Preserve | null;
1319+
preservedParentSheetIndex?: string;
1320+
honeyType?: string;
1321+
displayNameFormat?: string;
1322+
}
1323+
1324+
export interface ToolItem extends Item {
1325+
"@_xsi:type": "Tool" | "MeleeWeapon";
1326+
type?: number;
1327+
initialParentTileIndex?: number;
1328+
currentParentTileIndex?: number;
1329+
indexOfMenuItemView?: number;
1330+
instantUse?: boolean;
1331+
isEfficient?: boolean;
1332+
animationSpeedModifier?: number;
1333+
upgradeLevel?: number;
1334+
numAttachmentSlots?: number;
1335+
enchantments?: unknown;
1336+
previousEnchantments?: unknown;
1337+
}
1338+
1339+
export interface WeaponItem extends ToolItem {
1340+
"@_xsi:type": "MeleeWeapon";
1341+
type?: number;
1342+
minDamage?: number;
1343+
maxDamage?: number;
1344+
speed?: number;
1345+
addedPrecision?: number;
1346+
addedDefense?: number;
1347+
addedAreaOfEffect?: number;
1348+
knockback?: number;
1349+
critChance?: number;
1350+
critMultiplier?: number;
1351+
appearance?: string;
1352+
}
1353+
1354+
export interface HatItem extends Item {
1355+
"@_xsi:type": "Hat";
1356+
skipHairDraw?: boolean;
1357+
ignoreHairstyleOffset?: boolean;
1358+
hairDrawType?: number;
1359+
isPrismatic?: boolean;
1360+
enchantments?: unknown;
1361+
previousEnchantments?: unknown;
1362+
}
1363+
1364+
export interface BootsItem extends Item {
1365+
"@_xsi:type": "Boots";
1366+
defenseBonus?: number;
1367+
immunityBonus?: number;
1368+
indexInTileSheet?: number;
1369+
price?: number;
1370+
indexInColorSheet?: number;
1371+
appliedBootSheetIndex?: string;
1372+
}
1373+
1374+
export interface ClothingItem extends Item {
1375+
"@_xsi:type": "Clothing";
1376+
price?: number;
13401377
indexInTileSheet?: number;
1341-
indexInTileSheetFemale?: number;
13421378
clothesType?: ClothesType;
13431379
dyeable?: boolean;
13441380
clothesColor?: Color;
13451381
isPrismatic?: boolean;
1382+
}
1383+
1384+
export interface RingItem extends Item {
1385+
"@_xsi:type": "Ring";
1386+
price?: number;
13461387
uniqueID?: number;
1347-
currentLidFrame?: number;
1348-
lidFrameCount?: IntContainer;
1349-
frameCounter?: number;
1350-
items?: ItemContainer;
1351-
separateWalletItems?: SeparateWalletItems;
1352-
tint?: Color;
1353-
playerChoiceColor?: Color;
1354-
playerChest?: boolean;
1355-
fridge?: boolean;
1356-
giftbox?: boolean;
1357-
giftboxIndex?: number;
1358-
giftboxIsStarterGift?: BoolContainer;
1359-
spriteIndexOverride?: number;
1360-
dropContents?: boolean;
1361-
synchronized?: boolean;
1362-
specialChestType?: SpecialChestType;
1363-
globalInventoryId?: StringContainer;
1364-
preserve?: Preserve;
1365-
preservedParentSheetIndex?: number;
1388+
type?: TypeEnum;
1389+
}
1390+
1391+
export interface ColoredObjectItem extends ObjectItem {
1392+
"@_xsi:type": "ColoredObject";
13661393
color?: Color;
1367-
colorSameIndexAsParentSheetIndex?: boolean;
1368-
defenseBonus?: number;
1369-
immunityBonus?: number;
1370-
indexInColorSheet?: number;
1371-
which?: null;
1394+
}
1395+
1396+
export interface TrinketItem extends ObjectItem {
1397+
"@_xsi:type": "Trinket";
1398+
generationSeed: number;
1399+
displayNameOverrideTemplate: unknown;
1400+
descriptionSubstitutionTemplates: unknown;
1401+
trinketMetadata: unknown;
1402+
}
1403+
1404+
export interface FurnitureItem extends ObjectItem {
1405+
"@_xsi:type": "Furniture";
13721406
furniture_type?: FurnitureType;
13731407
rotations?: number;
13741408
currentRotation?: number;
1409+
sourceIndexOffset?: number;
1410+
drawPosition?: TileLocation;
13751411
sourceRect?: BoundingBox;
13761412
defaultSourceRect?: BoundingBox;
13771413
defaultBoundingBox?: BoundingBox;
13781414
drawHeldObjectLow?: boolean;
1379-
heldObject?: Item;
1380-
lastOutputRuleId?: string;
1381-
lastInputItem?: Item;
1382-
bedType?: string;
1383-
signText?: string;
1384-
skipHairDraw?: boolean;
1385-
ignoreHairstyleOffset?: boolean;
1386-
hairDrawType?: number;
1387-
health?: number;
1388-
maxHealth?: number;
1389-
whichType?: string;
1390-
gatePosition?: number;
1391-
gateMotion?: number;
1392-
isGate?: boolean;
1393-
agingRate?: number;
1394-
daysToMature?: number;
1395-
requiredItem?: Item;
1396-
successColor?: Color;
1397-
lockOnSuccess?: boolean;
1398-
locked?: boolean;
1399-
match?: boolean;
1400-
isIslandShrinePedestal?: boolean;
1401-
generationSeed?: number;
14021415
}
14031416

14041417
export interface AttachmentsAttachments {

0 commit comments

Comments
 (0)