62ed7ffb18
Features: - Add dynamic weather effects system (snow, rain, mist, sunshine, storm, wind, blizzard) - Add separate Clothing tab in inventory system - Weather effects auto-update based on Info Box weather field - Combined effects for storm (rain+lightning) and blizzard (snow+wind) Improvements: - Settings migration system for automatic feature enablement - Weather effects positioned behind chat interface (z-index: 1) - Dynamic weather enabled by default for new users Bug Fixes: - Fix tab visibility issues (disabled tabs now properly hide) - Fix theme-aware borders (remove hardcoded blue colors) - Fix double scrollbar in Edit Trackers window - Fix scroll position jumping when editing Present Characters - Fix dynamic weather toggle hiding issue Technical: - Update inventory schema to v2.1 with clothing field - Add automatic migration for existing v2 inventories - Update parsers and prompts to handle clothing separately - Add translations (EN/ZH-TW) for new features
143 lines
4.2 KiB
JavaScript
143 lines
4.2 KiB
JavaScript
/**
|
|
* Inventory Parser Module
|
|
* Extracts v2 inventory data from AI-generated text
|
|
*/
|
|
|
|
// Type imports
|
|
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
|
|
|
/**
|
|
* Extracts inventory data from AI-generated stats text in v2 multi-line format.
|
|
*
|
|
* Expected format from AI:
|
|
* ```
|
|
* On Person: Sword (equipped), 3x Health Potions, Leather Armor
|
|
* Stored - Home: Spare clothes, Tools, 50 gold coins
|
|
* Stored - Bank: Family heirloom, Important documents
|
|
* Assets: Motorcycle (garage), Downtown apartment (owned)
|
|
* ```
|
|
*
|
|
* @param {string} statsText - Raw stats text from AI response
|
|
* @returns {InventoryV2|null} Parsed inventory v2 object or null if not found
|
|
*/
|
|
export function extractInventoryData(statsText) {
|
|
if (!statsText || typeof statsText !== 'string') {
|
|
return null;
|
|
}
|
|
|
|
const result = {
|
|
version: 2,
|
|
onPerson: "None",
|
|
clothing: "None",
|
|
stored: {},
|
|
assets: "None"
|
|
};
|
|
|
|
let foundAnyInventoryData = false;
|
|
|
|
// Split into lines for parsing
|
|
const lines = statsText.split('\n');
|
|
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
|
|
// Parse "On Person: ..." line
|
|
const onPersonMatch = trimmed.match(/^On Person:\s*(.+)$/i);
|
|
if (onPersonMatch) {
|
|
result.onPerson = onPersonMatch[1].trim() || "None";
|
|
foundAnyInventoryData = true;
|
|
continue;
|
|
}
|
|
|
|
// Parse "Clothing: ..." line
|
|
const clothingMatch = trimmed.match(/^Clothing:\s*(.+)$/i);
|
|
if (clothingMatch) {
|
|
result.clothing = clothingMatch[1].trim() || "None";
|
|
foundAnyInventoryData = true;
|
|
continue;
|
|
}
|
|
|
|
// Parse "Stored - [Location]: ..." lines
|
|
const storedMatch = trimmed.match(/^Stored\s*-\s*([^:]+):\s*(.+)$/i);
|
|
if (storedMatch) {
|
|
const locationName = storedMatch[1].trim();
|
|
const items = storedMatch[2].trim();
|
|
if (locationName && items) {
|
|
result.stored[locationName] = items;
|
|
foundAnyInventoryData = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Parse "Assets: ..." line
|
|
const assetsMatch = trimmed.match(/^Assets:\s*(.+)$/i);
|
|
if (assetsMatch) {
|
|
result.assets = assetsMatch[1].trim() || "None";
|
|
foundAnyInventoryData = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Return null if we didn't find any inventory data
|
|
return foundAnyInventoryData ? result : null;
|
|
}
|
|
|
|
/**
|
|
* Attempts to parse legacy v1 inventory format (single line).
|
|
* Fallback for old AI responses that haven't been updated to v2 format.
|
|
*
|
|
* Expected format: "Inventory: Sword, Shield, 3x Potions, Gold coins"
|
|
*
|
|
* @param {string} text - Text that may contain legacy inventory
|
|
* @returns {string|null} Legacy inventory string or null
|
|
*/
|
|
export function extractLegacyInventory(text) {
|
|
if (!text || typeof text !== 'string') {
|
|
return null;
|
|
}
|
|
|
|
// Match old single-line format: "Inventory: ..."
|
|
const match = text.match(/Inventory:\s*(.+?)(?:\n|$)/i);
|
|
if (match && match[1]) {
|
|
const inventoryText = match[1].trim();
|
|
// Return null for empty values like "None" or ""
|
|
if (!inventoryText || inventoryText.toLowerCase() === 'none') {
|
|
return null;
|
|
}
|
|
return inventoryText;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Main inventory extraction function that tries v2 format first, then falls back to v1.
|
|
* Converts v1 format to v2 automatically if found.
|
|
*
|
|
* @param {string} statsText - Raw stats text from AI response
|
|
* @returns {InventoryV2|null} Parsed inventory in v2 format or null
|
|
*/
|
|
export function extractInventory(statsText) {
|
|
// Try v2 format first
|
|
const v2Data = extractInventoryData(statsText);
|
|
if (v2Data) {
|
|
return v2Data;
|
|
}
|
|
|
|
// Fallback to v1 format and convert to v2
|
|
const v1Data = extractLegacyInventory(statsText);
|
|
if (v1Data) {
|
|
// Convert v1 string to v2 format (place in onPerson)
|
|
return {
|
|
version: 2,
|
|
onPerson: v1Data,
|
|
clothing: "None",
|
|
stored: {},
|
|
assets: "None"
|
|
};
|
|
}
|
|
|
|
// No inventory data found
|
|
return null;
|
|
}
|