{
  "name": "StockMarket",
  "author": "limon",
  "supportedGames": [
    "7 Days to Die"
  ],
  "takaroVersion": "v0.0.29",
  "versions": [
    {
      "tag": "latest",
      "description": "# Stock Market: A Dynamic Virtual Trading System\n\n---\n🎥 **SETUP TUTORIAL**  \n**[► Watch the Complete Guide](https://youtu.be/JBtQiaXnB4I?si=df6whYX-SKeKlM2s)**  \n*Learn how to set up and use the Stock Market module*\n---\n\nCreate an immersive economic experience with this comprehensive stock market module that lets players invest, trade, and react to market events.\n\n**Market Features:**\n\n![Market Summary](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_7dtd_stocks.png)\n\n* **Multiple Economic Sectors:** Configure various industry sectors (Resource Extraction, Manufacturing, Defense, etc.) each with their own market dynamics.\n* **Realistic Stock Behavior:** Stocks have configurable volatility, sector-based pricing, and react differently to market events.\n* **Dynamic Market Events:** Random or admin-triggered events like \"Horde Night Approaching\" or \"Societal Collapse Setback\" impact different sectors in realistic ways.\n\n![Market Events](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_7dtd_events.png)\n\n**Trading System:**\n\n* **Buy & Sell Shares:** Players can purchase stocks and sell them later for profit or loss.\n* **Transaction Fees:** Configurable fee percentage on all trades creates a realistic market economy.\n* **VIP Benefits:** Special permissions for designated \"Stock Brokers\" who receive fee discounts.\n\n![Buy Stock Example](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_7dtd_buy.png)\n\nPlayers can track price changes and sell at the right moment to maximize profits or minimize losses.\n\n![Sell Stock Example](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_7dtd_sell.png)\n\n**Player Portfolio:**\n\n* **Portfolio Tracking:** Players can view their holdings, average purchase prices, and current profit/loss.\n* **Transaction History:** System tracks all buy/sell transactions for each player.\n* **Summary Statistics:** See overall portfolio performance and value across all holdings.\n\n![Portfolio View](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_7dtd_stockportfolio.png)\n\n**Market Information:**\n\n* **Detailed Stock Info:** Players can research specific stocks, including risk levels and sector trends.\n* **Industry Filters:** View stocks by specific sectors to better understand market segments.\n* **Market Alerts:** Server-wide notifications for significant price changes keep players engaged.\n\n![Market Alerts](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_7dtd_stockpricechange.png)\n\n**Configuration Options:**\n\n* **Sectors & Stocks:** Define your own market sectors and individual stocks with customizable starting prices and volatility.\n* **Market Events:** Create unique events with specific sector impacts to drive market dynamics.\n* **Transaction Settings:** Configure fee percentages, VIP discounts, and alert thresholds.\n* **Event Frequency:** Control how often random market events occur and how long they last.\n\n![Configuration](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_7dtd_config.png)\n![Sectors](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_7dtd_sectors.png)\n\n**Commands:**\n\n* `/markets [industry]` - View all stocks or filter by industry\n* `/stockinfo [ticker]` - Get detailed information about a specific stock\n* `/buystock <ticker> <amount>` - Purchase shares of a stock\n* `/sellstock <ticker> <amount>` - Sell shares from your portfolio\n* `/stockportfolio` - View your current holdings and performance\n* `/triggerevent [event]` - Admin command to trigger specific market events\n\nWho says the apocalypse can't have a thriving economy? Now players can stress about both zombie hordes AND their Duke's Casino Tokens dropping 40% after a market event! As one trader put it: \"I used to check my 401K daily, and now I check my AMMO stock before every Blood Moon. Some habits die harder than zombies.\"",
      "configSchema": "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"sectors\":{\"type\":\"array\",\"title\":\"Wasteland Economic Sectors\",\"description\":\"Define economic sectors relevant to survival in Navezgane\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Sector ID (e.g., Very Low Risk)\"},\"name\":{\"type\":\"string\",\"description\":\"Sector name (e.g., Very Low Risk)\"}}},\"default\":[{\"id\":\"Very High Risk\",\"name\":\"Very High Risk\"},{\"id\":\"High Risk\",\"name\":\"High Risk\"},{\"id\":\"Medium Risk\",\"name\":\"Medium Risk\"},{\"id\":\"Low Risk\",\"name\":\"Low Risk\"},{\"id\":\"Very Low Risk\",\"name\":\"Very Low Risk\"}]},\"stocks\":{\"type\":\"array\",\"title\":\"Wasteland Stocks\",\"description\":\"List of stocks representing Navezgane entities and resources\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Stock ticker symbol (e.g., NMC)\"},\"name\":{\"type\":\"string\",\"description\":\"Entity/Resource name\"},\"sector\":{\"type\":\"string\",\"description\":\"Sector ID this stock belongs to\"},\"initialPrice\":{\"type\":\"number\",\"description\":\"Starting price in Dukes\",\"minimum\":1},\"volatility\":{\"type\":\"number\",\"description\":\"Base volatility percentage (5 = 5%)\",\"minimum\":1,\"maximum\":60}}},\"default\":[{\"id\":\"NMC\",\"name\":\"Navezgane Mining Co.\",\"sector\":\"High Risk\",\"initialPrice\":50,\"volatility\":15},{\"id\":\"NIOM\",\"name\":\"Navezgane Iron Ore Mining Co.\",\"sector\":\"High Risk\",\"initialPrice\":55,\"volatility\":16},{\"id\":\"NLMC\",\"name\":\"Navezgane Lead Mining Co.\",\"sector\":\"Medium Risk\",\"initialPrice\":45,\"volatility\":14},{\"id\":\"WOOD\",\"name\":\"Wasteland Lumberjacks\",\"sector\":\"Low Risk\",\"initialPrice\":20,\"volatility\":8},{\"id\":\"SHALE\",\"name\":\"Oil Shale Extractors\",\"sector\":\"Very High Risk\",\"initialPrice\":40,\"volatility\":45},{\"id\":\"FORGE\",\"name\":\"Forge Ahead Metals\",\"sector\":\"Medium Risk\",\"initialPrice\":75,\"volatility\":12},{\"id\":\"TOOLS\",\"name\":\"Working Stiff Tools\",\"sector\":\"Low Risk\",\"initialPrice\":60,\"volatility\":10},{\"id\":\"AIA\",\"name\":\"A1 Auto\",\"sector\":\"Medium Risk\",\"initialPrice\":40,\"volatility\":14},{\"id\":\"AMC\",\"name\":\"Atlas Metal Company\",\"sector\":\"Medium Risk\",\"initialPrice\":65,\"volatility\":13},{\"id\":\"COC\",\"name\":\"COC Chemicals\",\"sector\":\"Very High Risk\",\"initialPrice\":55,\"volatility\":50},{\"id\":\"MECH\",\"name\":\"JunkTech Robotics\",\"sector\":\"High Risk\",\"initialPrice\":110,\"volatility\":25},{\"id\":\"BUILD\",\"name\":\"Barricade Builders\",\"sector\":\"Low Risk\",\"initialPrice\":45,\"volatility\":9},{\"id\":\"ZBI\",\"name\":\"Zombie Bashers Inc.\",\"sector\":\"High Risk\",\"initialPrice\":85,\"volatility\":17},{\"id\":\"TTC\",\"name\":\"Turret Tracker Co.\",\"sector\":\"Very High Risk\",\"initialPrice\":125,\"volatility\":60},{\"id\":\"AMMO\",\"name\":\"AmmoNation Surplus\",\"sector\":\"Very High Risk\",\"initialPrice\":90,\"volatility\":35},{\"id\":\"SHM\",\"name\":\"Shotgun Messiah\",\"sector\":\"Very High Risk\",\"initialPrice\":120,\"volatility\":30},{\"id\":\"SHAM\",\"name\":\"Shamway Foods\",\"sector\":\"Very Low Risk\",\"initialPrice\":30,\"volatility\":5},{\"id\":\"SSHM\",\"name\":\"Super Shamway\",\"sector\":\"Very Low Risk\",\"initialPrice\":32,\"volatility\":4},{\"id\":\"MEDS\",\"name\":\"Pop-N-Pills Medical\",\"sector\":\"Very High Risk\",\"initialPrice\":100,\"volatility\":48},{\"id\":\"SAVG\",\"name\":\"Savage Country\",\"sector\":\"Low Risk\",\"initialPrice\":35,\"volatility\":6},{\"id\":\"CRKB\",\"name\":\"Crack-A-Book\",\"sector\":\"High Risk\",\"initialPrice\":80,\"volatility\":16},{\"id\":\"TRADE\",\"name\":\"Trader Guild Network\",\"sector\":\"Very High Risk\",\"initialPrice\":150,\"volatility\":40},{\"id\":\"PNG\",\"name\":\"Pass-N-Gas\",\"sector\":\"Medium Risk\",\"initialPrice\":50,\"volatility\":11},{\"id\":\"BOB\",\"name\":\"Trader Bob's\",\"sector\":\"Low Risk\",\"initialPrice\":160,\"volatility\":9},{\"id\":\"HUGH\",\"name\":\"Trader Hugh's\",\"sector\":\"Medium Risk\",\"initialPrice\":170,\"volatility\":12},{\"id\":\"JEN\",\"name\":\"Trader Jen's\",\"sector\":\"Low Risk\",\"initialPrice\":180,\"volatility\":10},{\"id\":\"JOEL\",\"name\":\"Trader Joel's\",\"sector\":\"Low Risk\",\"initialPrice\":175,\"volatility\":11},{\"id\":\"REKT\",\"name\":\"Trader Rekt's\",\"sector\":\"Low Risk\",\"initialPrice\":165,\"volatility\":6},{\"id\":\"DUKES\",\"name\":\"Duke's Casino Tokens\",\"sector\":\"Very Low Risk\",\"initialPrice\":1,\"volatility\":3}]},\"marketEvents\":{\"type\":\"array\",\"title\":\"Wasteland Events\",\"description\":\"Events impacting the Navezgane economy\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Unique event ID\"},\"name\":{\"type\":\"string\",\"description\":\"Name of the event\"},\"description\":{\"type\":\"string\",\"description\":\"Description of the event's market effect\"},\"sectorImpacts\":{\"type\":\"array\",\"description\":\"How each sector is affected\",\"items\":{\"type\":\"object\",\"properties\":{\"sectorId\":{\"type\":\"string\",\"description\":\"Sector ID\"},\"impact\":{\"type\":\"number\",\"description\":\"Percentage impact (-99 = -99%)\",\"minimum\":-99,\"maximum\":75}}}}}},\"default\":[{\"id\":\"HORDE_INCOMING\",\"name\":\"Horde Night Approaching\",\"description\":\"Increased Zed activity signals the Blood Moon. Very High and High Risk defense stocks soar, while Very Low Risk stocks see minimal impact.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":50},{\"sectorId\":\"High Risk\",\"impact\":30},{\"sectorId\":\"Medium Risk\",\"impact\":15},{\"sectorId\":\"Low Risk\",\"impact\":5},{\"sectorId\":\"Very Low Risk\",\"impact\":1}]},{\"id\":\"STABLE_ECONOMY\",\"name\":\"Period of Stability\",\"description\":\"A rare period of peace has settled over the wasteland. Very Low and Low Risk assets flourish, with higher risk stocks seeing less significant gains.\",\"sectorImpacts\":[{\"sectorId\":\"Very Low Risk\",\"impact\":15},{\"sectorId\":\"Low Risk\",\"impact\":10},{\"sectorId\":\"Medium Risk\",\"impact\":5},{\"sectorId\":\"High Risk\",\"impact\":2},{\"sectorId\":\"Very High Risk\",\"impact\":1}]},{\"id\":\"GENERAL_PANIC\",\"name\":\"General Market Panic\",\"description\":\"A major setback reminds everyone how fragile survival is. Fear grips the entire market, causing higher volatility sectors to plummet.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":-90},{\"sectorId\":\"High Risk\",\"impact\":-70},{\"sectorId\":\"Medium Risk\",\"impact\":-40},{\"sectorId\":\"Low Risk\",\"impact\":-15},{\"sectorId\":\"Very Low Risk\",\"impact\":-5}]},{\"id\":\"ADVANCED_WEAPONRY_COLLAPSE\",\"name\":\"Turret AI Virus\",\"description\":\"A malicious code is spreading through advanced turret systems, causing them to malfunction violently. Very High Risk weaponry stocks are obliterated.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":-99},{\"sectorId\":\"High Risk\",\"impact\":-20}]},{\"id\":\"VOLATILE_CHEMICALS_COLLAPSE\",\"name\":\"Irreversible Contamination\",\"description\":\"A major chemical disaster has occurred. Very High Risk chemical stocks are worthless, impacting related sectors.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":-99},{\"sectorId\":\"High Risk\",\"impact\":-25},{\"sectorId\":\"Medium Risk\",\"impact\":-10}]},{\"id\":\"FOSSIL_FUELS_COLLAPSE\",\"name\":\"Clean Energy Breakthrough\",\"description\":\"A new energy source has been discovered. Very High Risk fossil fuel stocks become obsolete, while other sectors see minor adjustments.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":-99},{\"sectorId\":\"Medium Risk\",\"impact\":10},{\"sectorId\":\"Low Risk\",\"impact\":5}]},{\"id\":\"PHARMACEUTICALS_COLLAPSE\",\"name\":\"The Cure is Real\",\"description\":\"A cure for the zombie infection has been found! Very High Risk pharmaceutical stocks are wiped out, and even low-risk survival goods see a hit.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":-99},{\"sectorId\":\"High Risk\",\"impact\":-30},{\"sectorId\":\"Medium Risk\",\"impact\":-15},{\"sectorId\":\"Low Risk\",\"impact\":-10},{\"sectorId\":\"Very Low Risk\",\"impact\":-5}]},{\"id\":\"GUILD_OPERATIONS_COLLAPSE\",\"name\":\"Trader Guild Dissolved\",\"description\":\"The Trader's Guild has been formally dissolved. Very High Risk trade operation stocks become worthless, affecting overall trade.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":-99},{\"sectorId\":\"High Risk\",\"impact\":-40},{\"sectorId\":\"Medium Risk\",\"impact\":-20},{\"sectorId\":\"Low Risk\",\"impact\":-10}]},{\"id\":\"AMMUNITION_DEPLETION\",\"name\":\"Ammunition Shortage\",\"description\":\"A major supply chain disruption has caused ammunition stockpiles to dwindle. Very High Risk ammunition stocks plummet.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":-99},{\"sectorId\":\"High Risk\",\"impact\":-30},{\"sectorId\":\"Medium Risk\",\"impact\":-15}]},{\"id\":\"RESOURCE_BOOM\",\"name\":\"Resource Discovery\",\"description\":\"A massive new ore deposit has been found! All resource-related stocks are surging, with higher risk ones seeing the biggest gains.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":60},{\"sectorId\":\"High Risk\",\"impact\":40},{\"sectorId\":\"Medium Risk\",\"impact\":20},{\"sectorId\":\"Low Risk\",\"impact\":10}]},{\"id\":\"MANUFACTURING_SURGE\",\"name\":\"Innovation Breakthrough\",\"description\":\"New manufacturing techniques have greatly increased production efficiency. Manufacturing stocks are up across the board.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":40},{\"sectorId\":\"High Risk\",\"impact\":30},{\"sectorId\":\"Medium Risk\",\"impact\":20},{\"sectorId\":\"Low Risk\",\"impact\":10},{\"sectorId\":\"Very Low Risk\",\"impact\":5}]},{\"id\":\"TRADE_ROUTE_OPENED\",\"name\":\"New Trade Route Established\",\"description\":\"A secure trade route has been opened, boosting commerce. Trade-related stocks are seeing significant gains.\",\"sectorImpacts\":[{\"sectorId\":\"Very High Risk\",\"impact\":50},{\"sectorId\":\"High Risk\",\"impact\":30},{\"sectorId\":\"Medium Risk\",\"impact\":20},{\"sectorId\":\"Low Risk\",\"impact\":15},{\"sectorId\":\"Very Low Risk\",\"impact\":10}]}]},\"eventFrequency\":{\"title\":\"Event Frequency\",\"type\":\"number\",\"description\":\"Average number of update cycles between market events (0 to disable). Depends on mod's update frequency (e.g., per in-game hour?).\",\"default\":8,\"minimum\":1},\"defaultEventDuration\":{\"title\":\"Default Event Duration\",\"type\":\"number\",\"description\":\"Default number of update cycles an event lasts.\",\"default\":3,\"minimum\":1,\"maximum\":96},\"transactionFee\":{\"title\":\"Transaction Fee (Dukes Tax)\",\"type\":\"number\",\"description\":\"Percentage fee charged on all buy/sell transactions (5 = 5%)\",\"default\":3,\"minimum\":0,\"maximum\":25},\"vipDiscount\":{\"title\":\"Trader's Favor Discount\",\"type\":\"number\",\"description\":\"Percentage discount on transaction fees for players with 'STOCK_MARKET_BROKER' permission (50 = 50%)\",\"default\":50,\"minimum\":0,\"maximum\":100},\"priceAlertThreshold\":{\"title\":\"Price Alert Threshold\",\"type\":\"number\",\"description\":\"Percentage change that triggers a market alert (15 = 15%)\",\"default\":15,\"minimum\":5,\"maximum\":50},\"marketNewsChannelId\":{\"title\":\"Discord Channel ID (for Market News)\",\"type\":\"string\",\"description\":\"The ID of the Discord channel where market news messages should be posted. Leave empty to disable.\",\"default\":\"\"}},\"additionalProperties\":false}",
      "uiSchema": "{\"sectors\":{\"items\":{\"ui:order\":[\"id\",\"name\"]}},\"stocks\":{\"items\":{\"ui:order\":[\"id\",\"name\",\"sector\",\"initialPrice\",\"volatility\"]}},\"marketEvents\":{\"items\":{\"ui:order\":[\"id\",\"name\",\"description\",\"sectorImpacts\"],\"sectorImpacts\":{\"items\":{\"ui:order\":[\"sectorId\",\"impact\"]}}}},\"transactionFee\":{\"ui:widget\":\"range\"},\"vipDiscount\":{\"ui:widget\":\"range\"},\"eventFrequency\":{\"ui:help\":\"Set to 0 to disable random events\"},\"priceAlertThreshold\":{\"ui:widget\":\"range\"}}",
      "commands": [
        {
          "name": "buystock",
          "trigger": "buystock",
          "helpText": "Buy shares of a stock",
          "function": "import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, module: mod, arguments: args } = data;\n\n    // Check permission\n    if (!checkPermission(data.pog, 'STOCK_MARKET_TRADE')) {\n        throw new TakaroUserError(\"You don't have permission to trade stocks.\");\n    }\n\n    // Validate input parameters\n    if (!args.stock) {\n        throw new TakaroUserError(\"Please specify a stock ticker. Usage: /buystock STOCK AMOUNT\");\n    }\n\n    // Convert amount to integer and validate\n    const amount = parseInt(args.amount);\n    if (isNaN(amount) || amount <= 0) {\n        throw new TakaroUserError(\"Amount must be a positive whole number. Usage: /buystock STOCK AMOUNT\");\n    }\n\n    try {\n        // Get current stock data\n        const marketDataVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_market_data'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        if (marketDataVar.data.data.length === 0) {\n            throw new TakaroUserError(\"The stock market isn't available right now. Please try again later.\");\n        }\n\n        const stocks = JSON.parse(marketDataVar.data.data[0].value);\n        const stock = stocks.find(s => s.id.toUpperCase() === args.stock.toUpperCase());\n\n        if (!stock) {\n            throw new TakaroUserError(`Stock ${args.stock} not found. Use /market to see available stocks.`);\n        }\n\n        // Calculate transaction fee - using integers to avoid floating point issues\n        let feePercentage = mod.userConfig.transactionFee || 5;\n\n        // Check for VIP discount\n        if (checkPermission(data.pog, 'STOCK_MARKET_BROKER')) {\n            const discount = (mod.userConfig.vipDiscount || 50) / 100; // Convert to decimal\n            feePercentage = feePercentage * (1 - discount);\n        }\n\n        // Calculate costs using Math.round to ensure we work with integers\n        const subtotal = Math.round(stock.price) * amount;\n        const fee = Math.round((subtotal * feePercentage) / 100);\n        const totalCost = subtotal + fee;\n\n        // Check if player has enough currency\n        const playerData = await takaro.playerOnGameserver.playerOnGameServerControllerGetOne(gameServerId, player.id);\n        const currentBalance = playerData.data.data.currency;\n\n        if (currentBalance < totalCost) {\n            throw new TakaroUserError(`You don't have enough currency. Cost: $${subtotal} + $${fee} fee = $${totalCost}. Your balance: $${currentBalance}`);\n        }\n\n        // Deduct the currency - using integer value to avoid precision errors\n        await takaro.playerOnGameserver.playerOnGameServerControllerDeductCurrency(\n            gameServerId,\n            player.id,\n            {\n                currency: totalCost\n            }\n        );\n\n        // Get player's portfolio or create new one\n        const portfolioVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_portfolio'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId],\n                playerId: [player.id]\n            }\n        });\n\n        let portfolio;\n        if (portfolioVar.data.data.length === 0) {\n            portfolio = {};\n        } else {\n            portfolio = JSON.parse(portfolioVar.data.data[0].value);\n        }\n\n        // Update portfolio using rounded values for consistency\n        const stockPrice = Math.round(stock.price);\n        if (!portfolio[stock.id]) {\n            portfolio[stock.id] = {\n                shares: amount,\n                averagePrice: stockPrice\n            };\n        } else {\n            const totalShares = portfolio[stock.id].shares + amount;\n            const totalValue = (portfolio[stock.id].shares * portfolio[stock.id].averagePrice) + (amount * stockPrice);\n            portfolio[stock.id].shares = totalShares;\n            portfolio[stock.id].averagePrice = Math.round(totalValue / totalShares);\n        }\n\n        // Save updated portfolio\n        if (portfolioVar.data.data.length === 0) {\n            await takaro.variable.variableControllerCreate({\n                key: 'stock_portfolio',\n                value: JSON.stringify(portfolio),\n                gameServerId,\n                moduleId: mod.moduleId,\n                playerId: player.id\n            });\n        } else {\n            await takaro.variable.variableControllerUpdate(portfolioVar.data.data[0].id, {\n                value: JSON.stringify(portfolio)\n            });\n        }\n\n        // Track this transaction in transaction history\n        await recordTransaction(gameServerId, mod.moduleId, player.id, {\n            type: 'BUY',\n            stockId: stock.id,\n            shares: amount,\n            pricePerShare: stockPrice,\n            subtotal: subtotal,\n            fee: fee,\n            total: totalCost,\n            timestamp: new Date().toISOString()\n        });\n\n        let message = `Successfully bought ${amount} shares of ${stock.id} at $${stockPrice} each.\\n`;\n        message += `Subtotal: $${subtotal}\\n`;\n        message += `Transaction fee: $${fee}\\n`;\n        message += `Total cost: $${totalCost}\\n`;\n        message += `Current portfolio: ${portfolio[stock.id].shares} shares of ${stock.id}`;\n\n        await player.pm(message);\n\n    } catch (error) {\n        // If it's our custom error, just pass it through\n        if (error instanceof TakaroUserError) {\n            throw error;\n        }\n\n        // For unexpected errors, log them and provide a friendlier message\n        console.error(\"Error in buystock command:\", error);\n        throw new TakaroUserError(\"An error occurred while processing your purchase. Please try again later.\");\n    }\n}\n\n// Record transaction history for reporting and analytics\nasync function recordTransaction(gameServerId, moduleId, playerId, transaction) {\n    try {\n        const historyVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_transaction_history'],\n                gameServerId: [gameServerId],\n                moduleId: [moduleId],\n                playerId: [playerId]\n            }\n        });\n\n        let history = [];\n        if (historyVar.data.data.length > 0) {\n            history = JSON.parse(historyVar.data.data[0].value);\n        }\n\n        // Add new transaction to history\n        history.push(transaction);\n\n        // Keep only the last 50 transactions to avoid variable size limits\n        if (history.length > 50) {\n            history = history.slice(history.length - 50);\n        }\n\n        if (historyVar.data.data.length === 0) {\n            await takaro.variable.variableControllerCreate({\n                key: 'stock_transaction_history',\n                value: JSON.stringify(history),\n                gameServerId,\n                moduleId,\n                playerId\n            });\n        } else {\n            await takaro.variable.variableControllerUpdate(historyVar.data.data[0].id, {\n                value: JSON.stringify(history)\n            });\n        }\n    } catch (error) {\n        // Don't let transaction history errors prevent the main operation\n        console.error(\"Error recording transaction history:\", error);\n    }\n}\n\nawait main();",
          "arguments": [
            {
              "name": "stock",
              "type": "string",
              "helpText": "The stock ticker symbol ",
              "defaultValue": "",
              "position": 0
            },
            {
              "name": "amount",
              "type": "number",
              "helpText": "Number of shares to buy",
              "defaultValue": "",
              "position": 1
            }
          ]
        },
        {
          "name": "triggerevent",
          "trigger": "triggerevent",
          "helpText": "Shows all available market events when run without parameters. Use with an event name (e.g., /triggerevent TECH_BOOM) to trigger a specific market event.",
          "function": "import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, module: mod, arguments: args } = data;\n\n    // Check permission\n    if (!checkPermission(data.pog, 'STOCK_MARKET_TRIGGER_EVENT')) {\n        throw new TakaroUserError(\"You don't have permission to trigger market events.\");\n    }\n\n    try {\n        // Get current stock data to check market initialization\n        const marketDataVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_market_data'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        // Initialize market if needed\n        if (marketDataVar.data.data.length === 0) {\n            await initializeMarketIfNeeded(gameServerId, mod);\n            await player.pm(\"Market was not initialized. Initializing now...\");\n            return;\n        }\n\n        // Get events from module config\n        const marketEvents = mod.userConfig.marketEvents || [];\n\n        if (marketEvents.length === 0) {\n            throw new TakaroUserError(\"No market events configured in this module.\");\n        }\n\n        // Handle case when no event is specified or \"ALL\" is provided\n        if (!args.EventName || args.EventName.toUpperCase() === \"ALL\") {\n            await player.pm(\"=== AVAILABLE MARKET EVENTS ===\\n\");\n\n            // Group events by category or type if possible\n            const eventCategories = {};\n\n            // Create a simple categorization based on positive/negative impact\n            marketEvents.forEach(event => {\n                let category = \"Mixed\";\n                // Calculate net impact across all sectors\n                const netImpact = event.sectorImpacts.reduce((sum, impact) => sum + impact.impact, 0);\n\n                if (netImpact > 0) category = \"Positive\";\n                else if (netImpact < 0) category = \"Negative\";\n\n                if (!eventCategories[category]) {\n                    eventCategories[category] = [];\n                }\n                eventCategories[category].push(event);\n            });\n\n            // Display events by category\n            for (const [category, events] of Object.entries(eventCategories)) {\n                if (events.length > 0) {\n                    await player.pm(`\\n--- ${category.toUpperCase()} EVENTS ---`);\n\n                    let message = \"\";\n                    events.forEach(event => {\n                        // Format primary sectors affected\n                        const primarySectors = event.sectorImpacts\n                            .filter(impact => Math.abs(impact.impact) >= 15)\n                            .map(impact => {\n                                const direction = impact.impact >= 0 ? \"↑\" : \"↓\";\n                                return `${impact.sectorId} ${direction}${Math.abs(impact.impact)}%`;\n                            })\n                            .join(\", \");\n\n                        message += `${event.id}: ${event.name}\\n`;\n                        message += `   ${event.description}\\n`;\n                        if (primarySectors) {\n                            message += `   Major impacts: ${primarySectors}\\n`;\n                        }\n                        message += \"\\n\";\n                    });\n\n                    await player.pm(message);\n                }\n            }\n\n            await player.pm(\"=== HOW TO USE ===\\nUse `/triggerevent <EventName>` to trigger a specific event (e.g., `/triggerevent TECH_BOOM`)\");\n            return;\n        }\n\n        // Find the requested event\n        const eventId = args.EventName.toUpperCase();\n        const event = marketEvents.find(e => e.id === eventId);\n\n        if (!event) {\n            throw new TakaroUserError(`Event \"${eventId}\" not found. Use /triggerevent without parameters to see all available events.`);\n        }\n\n        // Get necessary variables for managing the event\n        const [activeEventVar, eventStartTimeVar, eventCounterVar, cooldownCounterVar, eventDurationVar] =\n            await Promise.all([\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['active_market_event'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                }),\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['event_start_time'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                }),\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['event_execution_counter'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                }),\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['event_cooldown_counter'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                }),\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['current_event_duration'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                })\n            ]);\n\n        // Check if there's an active event we need to cancel\n        let activeEventObj = null;\n        if (activeEventVar.data.data.length > 0 && activeEventVar.data.data[0].value) {\n            try {\n                activeEventObj = JSON.parse(activeEventVar.data.data[0].value);\n                if (activeEventObj && Object.keys(activeEventObj).length > 0) {\n                    await player.pm(`Cancelling active event \"${activeEventObj.name}\" to trigger new event.`);\n                }\n            } catch (e) {\n                // Invalid event data, will be overwritten\n            }\n        }\n\n        // Generate a random duration for the event\n        const maxDuration = (mod.userConfig && mod.userConfig.defaultEventDuration) || 5;\n        const randomDuration = Math.floor(Math.random() * maxDuration) + 1; // 1 to maxDuration\n\n        // Update or create all event-related variables\n        const updatePromises = [];\n\n        // Save or update the random duration\n        if (eventDurationVar.data.data.length > 0) {\n            updatePromises.push(\n                takaro.variable.variableControllerUpdate(eventDurationVar.data.data[0].id, {\n                    value: randomDuration.toString()\n                })\n            );\n        } else {\n            updatePromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'current_event_duration',\n                    value: randomDuration.toString(),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Reset the event counter to 0\n        if (eventCounterVar.data.data.length > 0) {\n            updatePromises.push(\n                takaro.variable.variableControllerUpdate(eventCounterVar.data.data[0].id, {\n                    value: '0'\n                })\n            );\n        } else {\n            updatePromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'event_execution_counter',\n                    value: '0',\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Reset the cooldown counter\n        if (cooldownCounterVar.data.data.length > 0) {\n            updatePromises.push(\n                takaro.variable.variableControllerUpdate(cooldownCounterVar.data.data[0].id, {\n                    value: '0'\n                })\n            );\n        } else {\n            updatePromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'event_cooldown_counter',\n                    value: '0',\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Update active event\n        if (activeEventVar.data.data.length > 0) {\n            updatePromises.push(\n                takaro.variable.variableControllerUpdate(activeEventVar.data.data[0].id, {\n                    value: JSON.stringify(event)\n                })\n            );\n        } else {\n            updatePromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'active_market_event',\n                    value: JSON.stringify(event),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Update event start time\n        if (eventStartTimeVar.data.data.length > 0) {\n            updatePromises.push(\n                takaro.variable.variableControllerUpdate(eventStartTimeVar.data.data[0].id, {\n                    value: new Date().toISOString()\n                })\n            );\n        } else {\n            updatePromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'event_start_time',\n                    value: new Date().toISOString(),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Update last event time\n        const lastEventTimeVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['last_market_event_time'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        if (lastEventTimeVar.data.data.length > 0) {\n            updatePromises.push(\n                takaro.variable.variableControllerUpdate(lastEventTimeVar.data.data[0].id, {\n                    value: new Date().toISOString()\n                })\n            );\n        } else {\n            updatePromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'last_market_event_time',\n                    value: new Date().toISOString(),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Wait for all updates to complete\n        await Promise.all(updatePromises);\n\n        // Check if there are online players to announce the event\n        const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                online: [true]\n            }\n        });\n\n        if (onlinePlayers.data.meta.total > 0) {\n            // Format sector impacts for announcement\n            let impactText = \"\";\n            for (const impact of event.sectorImpacts) {\n                const direction = impact.impact >= 0 ? \"↑\" : \"↓\";\n                impactText += `\\n${impact.sectorId}: ${direction} ${Math.abs(impact.impact)}%`;\n            }\n\n            const message = `🌍 BREAKING MARKET NEWS 🌍\\n\\n${event.name}\\n${event.description}\\n\\nSector Impacts:${impactText}\\n\\nThis event will influence stock prices! Use /markets to see opportunities!`;\n\n            // Split the message if it's too long\n            const maxLength = 400; // Safe limit for most games\n            for (let i = 0; i < message.length; i += maxLength) {\n                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                    message: message.substring(i, i + maxLength)\n                });\n            }\n        }\n\n        await player.pm(`Successfully triggered the \"${event.name}\" market event for ${randomDuration} cycles!`);\n    } catch (error) {\n        // If something goes wrong, log it and let the player know\n        console.log(`Error in triggerEvent: ${error.message}`);\n        throw new TakaroUserError(`Error triggering event: ${error.message}`);\n    }\n}\n\n// Initialize the market with stocks from the configuration\nasync function initializeMarketIfNeeded(gameServerId, mod) {\n    try {\n        // Get stocks from config\n        const configStocks = (mod.userConfig && mod.userConfig.stocks) || [];\n\n        if (!configStocks || configStocks.length === 0) {\n            console.log(\"Error: No stocks defined in configuration\");\n            return;\n        }\n\n        // Map config stocks to our internal format\n        const stocks = configStocks.map(stock => ({\n            id: stock.id,\n            name: stock.name,\n            sector: stock.sector,\n            price: stock.initialPrice,\n            volatility: stock.volatility / 100, // Convert percentage to decimal\n            lastPrice: stock.initialPrice\n        }));\n\n        // Store the stocks data\n        await takaro.variable.variableControllerCreate({\n            key: 'stock_market_data',\n            value: JSON.stringify(stocks),\n            gameServerId,\n            moduleId: mod.moduleId\n        });\n\n        // Initialize last event time to now\n        await takaro.variable.variableControllerCreate({\n            key: 'last_market_event_time',\n            value: new Date().toISOString(),\n            gameServerId,\n            moduleId: mod.moduleId\n        });\n\n        // Initialize event start time variable\n        await takaro.variable.variableControllerCreate({\n            key: 'event_start_time',\n            value: new Date().toISOString(),\n            gameServerId,\n            moduleId: mod.moduleId\n        });\n\n        // Initialize active event (empty string means no active event)\n        await takaro.variable.variableControllerCreate({\n            key: 'active_market_event',\n            value: '',\n            gameServerId,\n            moduleId: mod.moduleId\n        });\n\n        // Mark market as initialized\n        await takaro.variable.variableControllerCreate({\n            key: 'stock_market_initialized',\n            value: 'true',\n            gameServerId,\n            moduleId: mod.moduleId\n        });\n\n        // Announce market initialization to online players\n        const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                online: [true]\n            }\n        });\n\n        if (onlinePlayers.data.meta.total > 0) {\n            const message = \"📈 STOCK MARKET INITIALIZED 📈\\n\\nThe stock market is now open for trading! Use /markets to see available stocks and /buystock to start investing.\";\n\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message\n            });\n        }\n    } catch (error) {\n        console.log(`Error in initializeMarketIfNeeded: ${error.message}`);\n    }\n}\n\nawait main();",
          "arguments": [
            {
              "name": "EventName",
              "type": "string",
              "helpText": "Event name to trigger a specific market event",
              "defaultValue": "all",
              "position": 0
            }
          ]
        },
        {
          "name": "sellstock",
          "trigger": "sellstock",
          "helpText": "Sell shares of a stock",
          "function": "import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, module: mod, arguments: args } = data;\n\n    // Check permission\n    if (!checkPermission(data.pog, 'STOCK_MARKET_TRADE')) {\n        throw new TakaroUserError(\"You don't have permission to trade stocks.\");\n    }\n\n    // Validate input parameters\n    if (!args.stock) {\n        throw new TakaroUserError(\"Please specify a stock ticker. Usage: /sellstock STOCK AMOUNT\");\n    }\n\n    // Convert amount to integer and validate\n    const amount = parseInt(args.amount);\n    if (isNaN(amount) || amount <= 0) {\n        throw new TakaroUserError(\"Amount must be a positive whole number. Usage: /sellstock STOCK AMOUNT\");\n    }\n\n    try {\n        // Get player's portfolio\n        const portfolioVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_portfolio'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId],\n                playerId: [player.id]\n            }\n        });\n\n        if (portfolioVar.data.data.length === 0) {\n            throw new TakaroUserError(\"You don't own any stocks to sell.\");\n        }\n\n        const portfolio = JSON.parse(portfolioVar.data.data[0].value);\n        const stockId = args.stock.toUpperCase();\n\n        if (!portfolio[stockId] || portfolio[stockId].shares < amount) {\n            throw new TakaroUserError(`You don't own ${amount} shares of ${stockId}.`);\n        }\n\n        // Get current stock data\n        const marketDataVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_market_data'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        if (marketDataVar.data.data.length === 0) {\n            throw new TakaroUserError(\"The stock market isn't available right now. Please try again later.\");\n        }\n\n        const stocks = JSON.parse(marketDataVar.data.data[0].value);\n        const stock = stocks.find(s => s.id.toUpperCase() === stockId);\n\n        if (!stock) {\n            throw new TakaroUserError(`Stock ${stockId} not found in current market data. Please contact an admin.`);\n        }\n\n        // Calculate transaction fee - using integers to avoid floating point issues\n        let feePercentage = mod.userConfig.transactionFee || 5;\n\n        // Check for VIP discount\n        if (checkPermission(data.pog, 'STOCK_MARKET_BROKER')) {\n            const discount = (mod.userConfig.vipDiscount || 50) / 100; // Convert to decimal\n            feePercentage = feePercentage * (1 - discount);\n        }\n\n        // Calculate sale proceeds using Math.round to ensure we work with integers\n        const stockPrice = Math.round(stock.price);\n        const subtotal = stockPrice * amount;\n        const fee = Math.round((subtotal * feePercentage) / 100);\n        const netProceeds = subtotal - fee;\n\n        // Add money to player - using integer value to avoid precision errors\n        await takaro.playerOnGameserver.playerOnGameServerControllerAddCurrency(\n            gameServerId,\n            player.id,\n            {\n                currency: netProceeds\n            }\n        );\n\n        // Calculate profit/loss\n        const profitPerShare = stockPrice - portfolio[stockId].averagePrice;\n        const totalProfit = Math.round(profitPerShare * amount);\n        const profitText = totalProfit >= 0\n            ? `profit of $${totalProfit}`\n            : `loss of $${Math.abs(totalProfit)}`;\n\n        // Update portfolio\n        portfolio[stockId].shares -= amount;\n        if (portfolio[stockId].shares === 0) {\n            delete portfolio[stockId];\n        }\n\n        // Save updated portfolio\n        await takaro.variable.variableControllerUpdate(portfolioVar.data.data[0].id, {\n            value: JSON.stringify(portfolio)\n        });\n\n        // Track this transaction in transaction history\n        await recordTransaction(gameServerId, mod.moduleId, player.id, {\n            type: 'SELL',\n            stockId: stock.id,\n            shares: amount,\n            pricePerShare: stockPrice,\n            subtotal: subtotal,\n            fee: fee,\n            total: netProceeds,\n            profit: totalProfit,\n            timestamp: new Date().toISOString()\n        });\n\n        let message = `Successfully sold ${amount} shares of ${stockId} at $${stockPrice} each.\\n`;\n        message += `Subtotal: $${subtotal}\\n`;\n        message += `Transaction fee: $${fee}\\n`;\n        message += `Net proceeds: $${netProceeds} (${profitText})`;\n\n        // Add remaining shares info if player still has some\n        if (portfolio[stockId]) {\n            message += `\\nRemaining shares: ${portfolio[stockId].shares}`;\n        } else {\n            message += `\\nYou've sold all your ${stockId} shares.`;\n        }\n\n        await player.pm(message);\n\n    } catch (error) {\n        // If it's our custom error, just pass it through\n        if (error instanceof TakaroUserError) {\n            throw error;\n        }\n\n        // For unexpected errors, log them and provide a friendlier message\n        console.error(\"Error in sellstock command:\", error);\n        throw new TakaroUserError(\"An error occurred while processing your sale. Please try again later.\");\n    }\n}\n\n// Record transaction history for reporting and analytics\nasync function recordTransaction(gameServerId, moduleId, playerId, transaction) {\n    try {\n        const historyVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_transaction_history'],\n                gameServerId: [gameServerId],\n                moduleId: [moduleId],\n                playerId: [playerId]\n            }\n        });\n\n        let history = [];\n        if (historyVar.data.data.length > 0) {\n            history = JSON.parse(historyVar.data.data[0].value);\n        }\n\n        // Add new transaction to history\n        history.push(transaction);\n\n        // Keep only the last 50 transactions to avoid variable size limits\n        if (history.length > 50) {\n            history = history.slice(history.length - 50);\n        }\n\n        if (historyVar.data.data.length === 0) {\n            await takaro.variable.variableControllerCreate({\n                key: 'stock_transaction_history',\n                value: JSON.stringify(history),\n                gameServerId,\n                moduleId,\n                playerId\n            });\n        } else {\n            await takaro.variable.variableControllerUpdate(historyVar.data.data[0].id, {\n                value: JSON.stringify(history)\n            });\n        }\n    } catch (error) {\n        // Don't let transaction history errors prevent the main operation\n        console.error(\"Error recording transaction history:\", error);\n    }\n}\n\nawait main();",
          "arguments": [
            {
              "name": "amount",
              "type": "number",
              "helpText": "Number of shares to sell",
              "defaultValue": "",
              "position": 1
            },
            {
              "name": "stock",
              "type": "string",
              "helpText": "The stock ticker symbol",
              "defaultValue": "",
              "position": 0
            }
          ]
        },
        {
          "name": "markets",
          "trigger": "markets",
          "helpText": "View stock market prices and activity. Use '/markets ALL' to see all industries, or specify an industry name (e.g., '/markets TECH') to see stocks in that industry only. \n",
          "function": "import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, module: mod, arguments: args } = data;\n\n    // Check permission\n    if (!checkPermission(data.pog, 'STOCK_MARKET_USE')) {\n        throw new TakaroUserError(\"You don't have permission to use the stock market.\");\n    }\n\n    // Handle the industry argument - use \"ALL\" as a special value to show all industries\n    const industryFilter = args.Industry ? args.Industry.toUpperCase() : \"ALL\";\n\n    // Get current stock data\n    const marketDataVar = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: ['stock_market_data'],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId]\n        }\n    });\n\n    // If market data doesn't exist, try to initialize it\n    if (marketDataVar.data.data.length === 0) {\n        const initialized = await initializeMarketIfNeeded(gameServerId, mod);\n        if (!initialized) {\n            throw new TakaroUserError(\"The stock market isn't available right now. Please try again later.\");\n        }\n\n        // Get the freshly initialized market data\n        const refreshedMarketData = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_market_data'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        if (refreshedMarketData.data.data.length === 0) {\n            throw new TakaroUserError(\"There was an issue initializing the stock market. Please try again later.\");\n        }\n\n        // Continue with the refreshed data\n        await displayMarketSummary(player, gameServerId, mod, refreshedMarketData.data.data[0], industryFilter);\n    } else {\n        // Market data exists, display it\n        await displayMarketSummary(player, gameServerId, mod, marketDataVar.data.data[0], industryFilter);\n    }\n}\n\n// Display the market summary to the player\nasync function displayMarketSummary(player, gameServerId, mod, marketDataVariable, industryFilter) {\n    // Get active market event\n    const activeEventVar = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: ['active_market_event'],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId]\n        }\n    });\n\n    let activeEvent = null;\n    if (activeEventVar.data.data.length > 0 && activeEventVar.data.data[0].value) {\n        try {\n            activeEvent = JSON.parse(activeEventVar.data.data[0].value);\n            // Handle empty string or empty object\n            if (!activeEvent || Object.keys(activeEvent).length === 0) {\n                activeEvent = null;\n            }\n        } catch (e) {\n            // In case of parsing error\n            activeEvent = null;\n        }\n    }\n\n    const stocks = JSON.parse(marketDataVariable.value);\n\n    // Get all available industries for reference\n    const availableIndustries = [...new Set(stocks.map(stock => stock.sector))];\n\n    // If industry filter is provided and not \"ALL\", check if it's valid\n    if (industryFilter !== \"ALL\") {\n        // Check if the industry exists\n        const industryExists = availableIndustries.includes(industryFilter);\n\n        if (!industryExists) {\n            throw new TakaroUserError(`Industry \"${industryFilter}\" not found. Available industries: ${availableIndustries.join(', ')}\\nUse \"ALL\" to view all industries.`);\n        }\n    }\n\n    // Send header message\n    let headerMessage = \"=== STOCK MARKET SUMMARY ===\\n\";\n\n    // If filtering by industry, mention it in the header\n    if (industryFilter !== \"ALL\") {\n        headerMessage = `=== ${industryFilter} INDUSTRY ===\\n`;\n    }\n\n    // If there's an active event, include it in the header\n    if (activeEvent) {\n        headerMessage += `\\n🌍 ACTIVE EVENT: ${activeEvent.name} 🌍\\n`;\n        headerMessage += `${activeEvent.description}\\n\\n`;\n\n        // If filtering by industry, only show relevant impacts\n        if (industryFilter !== \"ALL\") {\n            const relevantImpact = activeEvent.sectorImpacts.find(\n                impact => impact.sectorId === industryFilter\n            );\n\n            if (relevantImpact) {\n                const direction = relevantImpact.impact >= 0 ? \"↑\" : \"↓\";\n                headerMessage += `Industry Impact: ${direction} ${Math.abs(relevantImpact.impact)}%\\n`;\n            } else {\n                headerMessage += \"This industry is not directly affected by the current event.\\n\";\n            }\n        } else {\n            // Show all industry impacts\n            headerMessage += \"Industry Impacts:\\n\";\n            for (const impact of activeEvent.sectorImpacts) {\n                const direction = impact.impact >= 0 ? \"↑\" : \"↓\";\n                headerMessage += `${impact.sectorId}: ${direction} ${Math.abs(impact.impact)}%\\n`;\n            }\n        }\n    }\n\n    // Send header message first\n    await player.pm(headerMessage);\n\n    // Group stocks by industry\n    const stocksByIndustry = {};\n    stocks.forEach(stock => {\n        if (!stocksByIndustry[stock.sector]) {\n            stocksByIndustry[stock.sector] = [];\n        }\n        stocksByIndustry[stock.sector].push(stock);\n    });\n\n    // If industry filter is not \"ALL\", only show that industry\n    if (industryFilter !== \"ALL\") {\n        const filteredStocks = stocksByIndustry[industryFilter] || [];\n\n        if (filteredStocks.length > 0) {\n            let stockMessage = \"\";\n\n            filteredStocks.forEach(stock => {\n                let changeIcon = '';\n                if (stock.lastPrice) {\n                    const percentChange = ((stock.price - stock.lastPrice) / stock.lastPrice) * 100;\n                    changeIcon = percentChange > 0 ? `↑ ${percentChange.toFixed(1)}%` :\n                        percentChange < 0 ? `↓ ${Math.abs(percentChange).toFixed(1)}%` : '→';\n                }\n\n                stockMessage += `${stock.id}: $${Math.round(stock.price)} ${changeIcon}\\n`;\n            });\n\n            await player.pm(stockMessage);\n        } else {\n            await player.pm(`No stocks found in the ${industryFilter} industry.`);\n        }\n    } else {\n        // Send each industry as a separate message\n        for (const industryId in stocksByIndustry) {\n            let industryMessage = `=== ${industryId} INDUSTRY ===\\n`;\n\n            stocksByIndustry[industryId].forEach(stock => {\n                let changeIcon = '';\n                if (stock.lastPrice) {\n                    const percentChange = ((stock.price - stock.lastPrice) / stock.lastPrice) * 100;\n                    changeIcon = percentChange > 0 ? `↑ ${percentChange.toFixed(1)}%` :\n                        percentChange < 0 ? `↓ ${Math.abs(percentChange).toFixed(1)}%` : '→';\n                }\n\n                industryMessage += `${stock.id}: $${Math.round(stock.price)} ${changeIcon}\\n`;\n            });\n\n            await player.pm(industryMessage);\n        }\n    }\n\n    // Add a help message for industry filtering if showing all industries\n    if (industryFilter === \"ALL\") {\n        const allIndustries = Object.keys(stocksByIndustry).join(', ');\n        await player.pm(`\\nTip: Use '/markets [industry]' to view only stocks in a specific industry. Available industries: ${allIndustries}`);\n    }\n}\n\n// Initialize the market with stocks from the configuration\nasync function initializeMarketIfNeeded(gameServerId, mod) {\n    try {\n        // Get stocks from config\n        const configStocks = (mod.userConfig && mod.userConfig.stocks) || [];\n\n        if (!configStocks || configStocks.length === 0) {\n            console.log(\"Error: No stocks defined in configuration\");\n            return false;\n        }\n\n        // Check for each required variable first\n        const [marketDataVar, lastEventTimeVar, eventStartTimeVar, activeEventVar, marketInitVar] =\n            await Promise.all([\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['stock_market_data'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                }),\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['last_market_event_time'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                }),\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['event_start_time'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                }),\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['active_market_event'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                }),\n                takaro.variable.variableControllerSearch({\n                    filters: {\n                        key: ['stock_market_initialized'],\n                        gameServerId: [gameServerId],\n                        moduleId: [mod.moduleId]\n                    }\n                })\n            ]);\n\n        // Map config stocks to our internal format\n        const stocks = configStocks.map(stock => ({\n            id: stock.id,\n            name: stock.name,\n            sector: stock.sector,\n            price: stock.initialPrice,\n            volatility: stock.volatility / 100, // Convert percentage to decimal\n            lastPrice: stock.initialPrice\n        }));\n\n        // Create each variable only if it doesn't exist already\n        const createPromises = [];\n\n        // Store the stocks data if it doesn't exist\n        if (marketDataVar.data.data.length === 0) {\n            createPromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'stock_market_data',\n                    value: JSON.stringify(stocks),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Initialize last event time if it doesn't exist\n        if (lastEventTimeVar.data.data.length === 0) {\n            createPromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'last_market_event_time',\n                    value: new Date().toISOString(),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Initialize event start time variable if it doesn't exist\n        if (eventStartTimeVar.data.data.length === 0) {\n            createPromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'event_start_time',\n                    value: new Date().toISOString(),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Initialize active event if it doesn't exist\n        if (activeEventVar.data.data.length === 0) {\n            createPromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'active_market_event',\n                    value: '',\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Mark market as initialized if not already marked\n        if (marketInitVar.data.data.length === 0) {\n            createPromises.push(\n                takaro.variable.variableControllerCreate({\n                    key: 'stock_market_initialized',\n                    value: 'true',\n                    gameServerId,\n                    moduleId: mod.moduleId\n                })\n            );\n        }\n\n        // Wait for all creation promises to complete\n        if (createPromises.length > 0) {\n            const results = await Promise.allSettled(createPromises);\n            // Check if the critical stock_market_data was created successfully\n            const stockDataPromise = results[0];\n            if (stockDataPromise && stockDataPromise.status === 'rejected') {\n                console.log(`Failed to create stock_market_data: ${stockDataPromise.reason}`);\n                return false;\n            }\n        }\n\n        return true;\n    } catch (error) {\n        console.log(`Error in initializeMarketIfNeeded: ${error.message}`);\n        return false;\n    }\n}\n\nawait main();",
          "arguments": [
            {
              "name": "Industry",
              "type": "string",
              "helpText": "View stock market prices and activity. Use '/markets ALL' to see all industries, or specify an industry name (e.g., '/markets TECH') to see stocks in that industry only. ",
              "defaultValue": "all",
              "position": 0
            }
          ]
        },
        {
          "name": "stockinfo",
          "trigger": "stockinfo",
          "helpText": "Shows all available stocks in the market. Use with a ticker symbol (e.g., /stockinfo ticker) to see detailed information about a specific stock.",
          "function": "import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, module: mod, arguments: args } = data;\n\n    // Check permission\n    if (!checkPermission(data.pog, 'STOCK_MARKET_USE')) {\n        throw new TakaroUserError(\"You don't have permission to use the stock market.\");\n    }\n\n    // Get current stock data\n    const marketDataVar = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: ['stock_market_data'],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId]\n        }\n    });\n\n    if (marketDataVar.data.data.length === 0) {\n        throw new TakaroUserError(\"The stock market isn't available right now. Please try again later.\");\n    }\n\n    const stocks = JSON.parse(marketDataVar.data.data[0].value);\n\n    // If a specific stock ticker is provided, show detailed info for that stock\n    const specificTicker = args.ticker ? args.ticker.toUpperCase() : null;\n\n    if (specificTicker && specificTicker !== \"ALL\") {\n        const stock = stocks.find(s => s.id.toUpperCase() === specificTicker);\n\n        if (!stock) {\n            throw new TakaroUserError(`Stock ${specificTicker} not found. Use /stockinfo without parameters to see all available stocks.`);\n        }\n\n        // Get active event to see if this stock's sector is affected\n        const activeEventVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['active_market_event'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        let activeEvent = null;\n        let sectorImpact = null;\n\n        if (activeEventVar.data.data.length > 0 && activeEventVar.data.data[0].value) {\n            try {\n                activeEvent = JSON.parse(activeEventVar.data.data[0].value);\n                if (activeEvent && activeEvent.sectorImpacts) {\n                    sectorImpact = activeEvent.sectorImpacts.find(impact =>\n                        impact.sectorId === stock.sector\n                    );\n                }\n            } catch (e) {\n                // Ignore parsing errors\n            }\n        }\n\n        // Calculate price change indicators\n        let changeText = '';\n        if (stock.lastPrice) {\n            const percentChange = ((stock.price - stock.lastPrice) / stock.lastPrice) * 100;\n            const changeIcon = percentChange > 0 ? '↑' : percentChange < 0 ? '↓' : '→';\n            changeText = ` ${changeIcon} ${Math.abs(percentChange).toFixed(1)}%`;\n        }\n\n        // Add risk assessment based on volatility without showing the raw value\n        let riskLevel;\n        if (stock.volatility <= 0.05) riskLevel = \"Very Low\";\n        else if (stock.volatility <= 0.10) riskLevel = \"Low\";\n        else if (stock.volatility <= 0.15) riskLevel = \"Moderate\";\n        else if (stock.volatility <= 0.20) riskLevel = \"High\";\n        else riskLevel = \"Very High\";\n\n        // Build detailed stock info message\n        let message = `=== ${stock.id}: ${stock.name} ===\\n\\n`;\n        message += `Sector: ${stock.sector}\\n`;\n        message += `Current Price: $${Math.round(stock.price)}${changeText}\\n`;\n        message += `Risk Level: ${riskLevel}\\n`;\n\n        // Add sector trend info if available from event\n        if (activeEvent && sectorImpact) {\n            const direction = sectorImpact.impact >= 0 ? \"Positive\" : \"Negative\";\n            const strength = Math.abs(sectorImpact.impact);\n            let trend;\n\n            if (strength < 10) trend = \"Slight\";\n            else if (strength < 25) trend = \"Moderate\";\n            else trend = \"Strong\";\n\n            message += `\\nCurrent Trend: ${trend} ${direction} (${activeEvent.name})\\n`;\n            message += `Event: ${activeEvent.description}\\n`;\n        }\n\n        // Add trading guidance based on sector and risk, without mentioning volatility\n        message += `\\nTrading Notes:\\n`;\n\n        if (activeEvent && sectorImpact) {\n            if (sectorImpact.impact > 0) {\n                message += `- Currently bullish due to the ${activeEvent.name} event\\n`;\n            } else {\n                message += `- Currently bearish due to the ${activeEvent.name} event\\n`;\n            }\n        }\n\n        if (riskLevel === \"High\" || riskLevel === \"Very High\") {\n            message += `- Expect significant price fluctuations with this stock\\n`;\n        } else if (riskLevel === \"Low\" || riskLevel === \"Very Low\") {\n            message += `- Typically has stable price movement\\n`;\n        }\n\n        message += `\\nUse /buystock ${stock.id} [amount] to purchase shares`;\n\n        await player.pm(message);\n\n    } else {\n        // No specific ticker provided or ALL specified, show summary of all stocks\n        await player.pm(\"=== STOCK MARKET LISTINGS ===\\n\");\n\n        // Group stocks by sector\n        const stocksBySector = {};\n        stocks.forEach(stock => {\n            if (!stocksBySector[stock.sector]) {\n                stocksBySector[stock.sector] = [];\n            }\n            stocksBySector[stock.sector].push(stock);\n        });\n\n        // MODIFICATION START\n        // Display stocks by sector, chunking them into messages of 3\n        const chunkSize = 3;\n        for (const [sector, sectorStocks] of Object.entries(stocksBySector)) {\n            for (let i = 0; i < sectorStocks.length; i += chunkSize) {\n                const chunk = sectorStocks.slice(i, i + chunkSize);\n\n                // Add the sector header only to the first message for that sector.\n                let sectorMessage = (i === 0) ? `\\n--- ${sector} SECTOR ---\\n` : '';\n\n                chunk.forEach(stock => {\n                    // Add price change indicators\n                    let changeText = '';\n                    if (stock.lastPrice) {\n                        const percentChange = ((stock.price - stock.lastPrice) / stock.lastPrice) * 100;\n                        const changeIcon = percentChange > 0 ? '↑' : percentChange < 0 ? '↓' : '→';\n                        changeText = ` ${changeIcon} ${Math.abs(percentChange).toFixed(1)}%`;\n                    }\n\n                    // Add risk level based on volatility\n                    let riskIndicator;\n                    if (stock.volatility <= 0.05) riskIndicator = \"VL\";\n                    else if (stock.volatility <= 0.10) riskIndicator = \"L\";\n                    else if (stock.volatility <= 0.15) riskIndicator = \"M\";\n                    else if (stock.volatility <= 0.20) riskIndicator = \"H\";\n                    else riskIndicator = \"VH\";\n\n                    sectorMessage += `${stock.id} (${riskIndicator}): ${stock.name} - $${Math.round(stock.price)}${changeText}\\n`;\n                });\n\n                await player.pm(sectorMessage);\n            }\n        }\n        // MODIFICATION END\n\n        // Add legend for risk indicators\n        const legend = \"\\n=== LEGEND ===\\n\" +\n            \"Risk Levels: VL=Very Low, L=Low, M=Moderate, H=High, VH=Very High\\n\" +\n            \"Use /stockinfo [ticker] for detailed information about a specific stock\";\n\n        await player.pm(legend);\n    }\n}\n\nawait main();",
          "arguments": [
            {
              "name": "ticker",
              "type": "string",
              "helpText": "stock ticker or ALL to get detailed information about a specific stock",
              "defaultValue": "all",
              "position": 0
            }
          ]
        },
        {
          "name": "stockportfolio",
          "trigger": "stockportfolio",
          "helpText": "No help text available",
          "function": "import { takaro, data, TakaroUserError, checkPermission } from '@takaro/helpers';\n\nasync function main() {\n    const { player, gameServerId, module: mod } = data;\n\n    // Check permission\n    if (!checkPermission(data.pog, 'STOCK_MARKET_USE')) {\n        throw new TakaroUserError(\"You don't have permission to use the stock market.\");\n    }\n\n    // Get current stock data\n    const marketDataVar = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: ['stock_market_data'],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId]\n        }\n    });\n\n    if (marketDataVar.data.data.length === 0) {\n        throw new TakaroUserError(\"The stock market isn't available right now. Please try again later.\");\n    }\n\n    const stocks = JSON.parse(marketDataVar.data.data[0].value);\n\n    // Get player's portfolio\n    const portfolioVar = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: ['stock_portfolio'],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId],\n            playerId: [player.id]\n        }\n    });\n\n    let portfolio = {};\n    if (portfolioVar.data.data.length > 0) {\n        portfolio = JSON.parse(portfolioVar.data.data[0].value);\n    }\n\n    // Send header\n    await player.pm(\"=== YOUR STOCK PORTFOLIO ===\");\n\n    let hasStocks = false;\n    let totalValue = 0;\n    let totalInvestment = 0;\n\n    // If no stocks, send a simple message\n    if (Object.keys(portfolio).length === 0) {\n        await player.pm(\"You don't own any stocks yet.\\n\" +\n            \"Use /markets to see available stocks and prices.\\n\" +\n            \"Use /buy <stock> <amount> to purchase stocks.\");\n        return;\n    }\n\n    // Helper function to format numbers with commas and a dollar sign\n    const formatNumber = (num) => {\n        return `$${Math.round(num).toLocaleString()}`;\n    };\n\n    // Send each stock as a separate message to avoid length issues\n    for (const [stockId, data] of Object.entries(portfolio)) {\n        hasStocks = true;\n        const stock = stocks.find(s => s.id === stockId);\n        if (!stock) continue; // Stock might have been removed from config\n\n        const currentValue = stock.price * data.shares;\n        const investmentValue = data.averagePrice * data.shares;\n        totalValue += currentValue;\n        totalInvestment += investmentValue;\n        const profit = currentValue - investmentValue;\n        const profitPercent = ((profit / investmentValue) * 100).toFixed(1);\n\n        let stockMessage = `--- ${stockId} (${stock.sector}) ---\\n`;\n        stockMessage += `Shares: ${data.shares}\\n`;\n        stockMessage += `Avg buy: ${formatNumber(data.averagePrice)}\\n`;\n        stockMessage += `Current price: ${formatNumber(stock.price)}\\n`;\n        stockMessage += `Total value: ${formatNumber(currentValue)}\\n`;\n\n        if (profit >= 0) {\n            stockMessage += `Profit: +${formatNumber(profit)} (+${profitPercent}%)\\n`;\n        } else {\n            stockMessage += `Loss: -${formatNumber(Math.abs(profit))} (${profitPercent}%)\\n`;\n        }\n\n        await player.pm(stockMessage);\n    }\n\n    if (hasStocks) {\n        const totalProfit = totalValue - totalInvestment;\n        const totalProfitPercent = ((totalProfit / totalInvestment) * 100).toFixed(1);\n\n        let summaryMessage = \"=== PORTFOLIO SUMMARY ===\\n\";\n        summaryMessage += `Total investment: ${formatNumber(totalInvestment)}\\n`;\n        summaryMessage += `Current value: ${formatNumber(totalValue)}\\n`;\n\n        if (totalProfit >= 0) {\n            summaryMessage += `Overall profit: +${formatNumber(totalProfit)} (+${totalProfitPercent}%)\\n`;\n        } else {\n            summaryMessage += `Overall loss: -${formatNumber(Math.abs(totalProfit))} (${totalProfitPercent}%)\\n`;\n        }\n\n        // Get transaction history count\n        try {\n            const historyVar = await takaro.variable.variableControllerSearch({\n                filters: {\n                    key: ['stock_transaction_history'],\n                    gameServerId: [gameServerId],\n                    moduleId: [mod.moduleId],\n                    playerId: [player.id]\n                }\n            });\n\n            if (historyVar.data.data.length > 0) {\n                const history = JSON.parse(historyVar.data.data[0].value);\n                const buyCount = history.filter(t => t.type === 'BUY').length;\n                const sellCount = history.filter(t => t.type === 'SELL').length;\n\n                summaryMessage += `\\nTransactions: ${history.length} (${buyCount} buys, ${sellCount} sells)\\n`;\n            }\n        } catch (error) {\n            // Just ignore history errors\n        }\n\n        await player.pm(summaryMessage);\n    }\n}\n\nawait main();",
          "arguments": []
        }
      ],
      "hooks": [],
      "cronJobs": [
        {
          "name": "updatestockprices",
          "temporalValue": "0 */1 * * *",
          "description": "When he cronjob runs",
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    try {\n        const { gameServerId, module: mod } = data;\n\n        // Get current stock data\n        const marketDataVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['stock_market_data'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        // Check if market is initialized\n        if (marketDataVar.data.data.length === 0) {\n            console.log(\"Market data not found, initializing...\");\n            await initializeMarketIfNeeded(gameServerId, mod);\n            return; // Exit after initialization\n        }\n\n        // Get active market event\n        const activeEventVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['active_market_event'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        // Get event start time\n        const eventStartTimeVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['event_start_time'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        // Get event execution counter\n        const eventCounterVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['event_execution_counter'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        // Get event cooldown counter\n        const cooldownCounterVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['event_cooldown_counter'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        let eventCounter = 0;\n        if (eventCounterVar.data.data.length > 0) {\n            eventCounter = parseInt(eventCounterVar.data.data[0].value, 10);\n            console.log(`Event counter: ${eventCounter}`);\n        }\n\n        let cooldownCounter = 0;\n        if (cooldownCounterVar.data.data.length > 0) {\n            cooldownCounter = parseInt(cooldownCounterVar.data.data[0].value, 10);\n            console.log(`Cooldown counter: ${cooldownCounter}`);\n        }\n\n        // Get event duration\n        const eventDurationVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['current_event_duration'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        // Use stored random duration if available, otherwise use defaultEventDuration\n        let eventDuration = (mod.userConfig && mod.userConfig.defaultEventDuration) || 5;\n        if (eventDurationVar.data.data.length > 0) {\n            eventDuration = parseInt(eventDurationVar.data.data[0].value, 10);\n            console.log(`Event duration: ${eventDuration}`);\n        }\n\n        let activeEvent = null;\n        if (activeEventVar.data.data.length > 0 && activeEventVar.data.data[0].value) {\n            try {\n                activeEvent = JSON.parse(activeEventVar.data.data[0].value);\n                // If it's an empty string, treat as no active event\n                if (!activeEvent || Object.keys(activeEvent).length === 0) {\n                    activeEvent = null;\n                }\n                console.log(`Active event: ${activeEvent ? activeEvent.name : 'None'}`);\n            } catch (e) {\n                // In case of parsing error, consider no active event\n                activeEvent = null;\n                console.log(`Error parsing active event: ${e.message}`);\n            }\n        }\n\n        // Check if the current event should end and increment counter\n        if (activeEvent) {\n            eventCounter++;\n            console.log(`Incremented event counter to: ${eventCounter}`);\n\n            if (eventCounterVar.data.data.length > 0) {\n                await takaro.variable.variableControllerUpdate(eventCounterVar.data.data[0].id, {\n                    value: eventCounter.toString()\n                });\n            } else {\n                await takaro.variable.variableControllerCreate({\n                    key: 'event_execution_counter',\n                    value: eventCounter.toString(),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                });\n            }\n\n            if (eventCounter >= eventDuration) {\n                console.log(\"Event duration reached, ending event.\");\n                if (activeEventVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(activeEventVar.data.data[0].id, {\n                        value: ''\n                    });\n                }\n\n                if (eventCounterVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(eventCounterVar.data.data[0].id, {\n                        value: '0'\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'event_execution_counter',\n                        value: '0',\n                        gameServerId,\n                        moduleId: mod.moduleId\n                    });\n                }\n\n                if (cooldownCounterVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(cooldownCounterVar.data.data[0].id, {\n                        value: '0'\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'event_cooldown_counter',\n                        value: '0',\n                        gameServerId,\n                        moduleId: mod.moduleId\n                    });\n                }\n\n                const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({\n                    filters: {\n                        gameServerId: [gameServerId],\n                        online: [true]\n                    }\n                });\n\n                if (onlinePlayers.data.meta.total > 0) {\n                    const message = `📈 MARKET UPDATE 📉\\n\\nThe \"${activeEvent.name}\" event has ended. Markets are returning to normal conditions.`;\n\n                    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                        message: message\n                    });\n                }\n\n                activeEvent = null;\n            }\n        } else {\n            cooldownCounter++;\n            console.log(`Incremented cooldown counter to: ${cooldownCounter}`);\n\n            if (cooldownCounterVar.data.data.length > 0) {\n                await takaro.variable.variableControllerUpdate(cooldownCounterVar.data.data[0].id, {\n                    value: cooldownCounter.toString()\n                });\n            } else {\n                await takaro.variable.variableControllerCreate({\n                    key: 'event_cooldown_counter',\n                    value: cooldownCounter.toString(),\n                    gameServerId,\n                    moduleId: mod.moduleId\n                });\n            }\n        }\n\n        const lastEventTimeVar = await takaro.variable.variableControllerSearch({\n            filters: {\n                key: ['last_market_event_time'],\n                gameServerId: [gameServerId],\n                moduleId: [mod.moduleId]\n            }\n        });\n\n        const eventFrequency = (mod.userConfig && mod.userConfig.eventFrequency) || 10;\n        let shouldTriggerEvent = false;\n\n        if (!activeEvent && cooldownCounter >= eventFrequency) {\n            shouldTriggerEvent = Math.random() < 0.5;\n            if (cooldownCounter >= eventFrequency * 2) {\n                shouldTriggerEvent = true;\n            }\n            console.log(`Should trigger new event: ${shouldTriggerEvent}`);\n        }\n\n        if (shouldTriggerEvent) {\n            const events = (mod.userConfig && mod.userConfig.marketEvents) || [];\n            if (events.length > 0) {\n                const randomEvent = events[Math.floor(Math.random() * events.length)];\n                activeEvent = randomEvent;\n                console.log(`New event triggered: ${activeEvent.name}`);\n\n                const maxDuration = (mod.userConfig && mod.userConfig.defaultEventDuration) || 5;\n                const randomDuration = Math.floor(Math.random() * maxDuration) + 1;\n\n                if (eventDurationVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(eventDurationVar.data.data[0].id, {\n                        value: randomDuration.toString()\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'current_event_duration',\n                        value: randomDuration.toString(),\n                        gameServerId,\n                        moduleId: mod.moduleId\n                    });\n                }\n\n                if (eventCounterVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(eventCounterVar.data.data[0].id, {\n                        value: '0'\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'event_execution_counter',\n                        value: '0',\n                        gameServerId,\n                        moduleId: mod.moduleId\n                    });\n                }\n\n                if (cooldownCounterVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(cooldownCounterVar.data.data[0].id, {\n                        value: '0'\n                    });\n                }\n\n                if (activeEventVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(activeEventVar.data.data[0].id, {\n                        value: JSON.stringify(activeEvent)\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'active_market_event',\n                        value: JSON.stringify(activeEvent),\n                        gameServerId,\n                        moduleId: mod.moduleId\n                    });\n                }\n\n                if (eventStartTimeVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(eventStartTimeVar.data.data[0].id, {\n                        value: new Date().toISOString()\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'event_start_time',\n                        value: new Date().toISOString(),\n                        gameServerId,\n                        moduleId: mod.moduleId\n                    });\n                }\n\n                if (lastEventTimeVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(lastEventTimeVar.data.data[0].id, {\n                        value: new Date().toISOString()\n                    });\n                } else {\n                    await takaro.variable.variableControllerCreate({\n                        key: 'last_market_event_time',\n                        value: new Date().toISOString(),\n                        gameServerId,\n                        moduleId: mod.moduleId\n                    });\n                }\n\n                const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({\n                    filters: {\n                        gameServerId: [gameServerId],\n                        online: [true]\n                    }\n                });\n\n                if (onlinePlayers.data.meta.total > 0) {\n                    let impactText = \"\";\n                    for (const impact of activeEvent.sectorImpacts) {\n                        const direction = impact.impact >= 0 ? \"↑\" : \"↓\";\n                        impactText += `\\n${impact.sectorId}: ${direction} ${Math.abs(impact.impact)}%`;\n                    }\n\n                    const message = `🌍 BREAKING MARKET NEWS 🌍\\n\\n${activeEvent.name}\\n${activeEvent.description}\\n\\nSector Impacts:${impactText}\\n\\nThis event will influence stock prices! Use /market to see opportunities!`;\n\n                    const maxLength = 400;\n                    for (let i = 0; i < message.length; i += maxLength) {\n                        await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                            message: message.substring(i, i + maxLength)\n                        });\n                    }\n                }\n            }\n        }\n\n        // Get stocks\n        if (!marketDataVar.data.data[0] || !marketDataVar.data.data[0].value) {\n            console.log(\"Market data is empty or invalid after initial check, re-initializing.\");\n            await initializeMarketIfNeeded(gameServerId, mod);\n            return;\n        }\n\n        let stocks;\n        try {\n            stocks = JSON.parse(marketDataVar.data.data[0].value);\n            if (!Array.isArray(stocks)) {\n                throw new Error(\"Parsed stocks data is not an array\");\n            }\n            // CRITICAL FIX: Ensure each stock has valid numerical price properties\n            stocks = stocks.map(s => {\n                const sanitizedPrice = typeof s.price === 'number' && s.price > 0 ? s.price : 1;\n                const sanitizedLastPrice = typeof s.lastPrice === 'number' && s.lastPrice > 0 ? s.lastPrice : sanitizedPrice;\n                return {\n                    ...s,\n                    price: sanitizedPrice,\n                    lastPrice: sanitizedLastPrice\n                };\n            });\n            console.log(\"Stocks loaded and sanitized successfully.\");\n            console.log(\"Initial stocks for this cycle:\");\n            stocks.forEach(s => console.log(`  ${s.id}: Price ${s.price}, LastPrice ${s.lastPrice}`));\n\n        } catch (e) {\n            console.log(`Error parsing or sanitizing stock data: ${e.message}`);\n            await initializeMarketIfNeeded(gameServerId, mod);\n            return;\n        }\n\n        const significantChanges = [];\n\n        // Update each stock price\n        stocks.forEach(stock => {\n            // Always capture oldPrice at the very beginning of the iteration\n            const oldPrice = stock.price;\n            // Set lastPrice to the value before this current update cycle for accurate change tracking\n            stock.lastPrice = oldPrice;\n\n            console.log(`Processing stock: ${stock.id}, Old Price: ${oldPrice}`);\n\n            // Define the threshold for \"penny stock\" and the chance/magnitude of a breakout\n            const pennyStockThreshold = 1.9;\n            const breakoutChance = 0.6;\n\n            const targetMinPrice = 2.0;\n            const targetMaxPrice = 10.0;\n\n            let calculatedNewPrice;\n\n            // Check if it's a penny stock and qualifies for a breakout\n            if (oldPrice <= pennyStockThreshold && Math.random() < breakoutChance) {\n                console.log(`${stock.id} is a penny stock (${oldPrice}) and hit breakout chance!`);\n                calculatedNewPrice = targetMinPrice + Math.random() * (targetMaxPrice - targetMinPrice);\n                calculatedNewPrice = Math.max(calculatedNewPrice, oldPrice + 0.01); // Ensure at least a penny increase\n                console.log(`${stock.id} calculated breakout price: ${calculatedNewPrice}`);\n            } else {\n                const randomFactor = Math.random() * 2 - 1;\n                const totalChangePercent = randomFactor * stock.volatility;\n                calculatedNewPrice = oldPrice * (1 + totalChangePercent);\n                console.log(`${stock.id} calculated normal price: ${calculatedNewPrice} (Change: ${totalChangePercent * 100}%)`);\n            }\n\n            let finalPrice = calculatedNewPrice;\n            if (activeEvent) {\n                const sectorImpact = activeEvent.sectorImpacts.find(impact => impact.sectorId === stock.sector);\n                if (sectorImpact) {\n                    const eventImpactPercentage = sectorImpact.impact;\n                    const eventImpactDecimal = eventImpactPercentage / 100;\n                    const randomImpactFactor = 0.5 + Math.random();\n                    const eventImpact = eventImpactDecimal * randomImpactFactor;\n\n                    finalPrice = calculatedNewPrice * (1 + eventImpact);\n                    console.log(`${stock.id} final price after event impact: ${finalPrice} (Event impact: ${eventImpact * 100}%)`);\n                }\n            }\n\n            stock.price = finalPrice;\n\n            if (stock.price < 1) {\n                console.log(`${stock.id} price fell below 1, setting to 1.`);\n                stock.price = 1;\n            }\n\n            const actualPercentChange = ((stock.price - oldPrice) / oldPrice) * 100;\n            console.log(`${stock.id} - Final Price: ${stock.price.toFixed(2)}, Actual Change: ${actualPercentChange.toFixed(2)}%`);\n\n            const changeThreshold = ((mod.userConfig && mod.userConfig.priceAlertThreshold) || 10) / 100;\n            if (Math.abs(actualPercentChange) > changeThreshold * 100) {\n                significantChanges.push({\n                    ...stock,\n                    changePercent: actualPercentChange\n                });\n            }\n        });\n\n        console.log(\"All stocks processed, saving updated prices...\");\n        await takaro.variable.variableControllerUpdate(marketDataVar.data.data[0].id, {\n            value: JSON.stringify(stocks)\n        });\n        console.log(\"Updated prices saved.\");\n\n        if (significantChanges.length > 0) {\n            console.log(\"Significant changes detected, sending alert.\");\n            const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({\n                filters: {\n                    gameServerId: [gameServerId],\n                    online: [true]\n                }\n            });\n\n            if (onlinePlayers.data.meta.total > 0) {\n                significantChanges.sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent));\n                const topChanges = significantChanges.slice(0, Math.min(3, significantChanges.length));\n\n                let message = \"📊 STOCK MARKET ALERT 📊\\n\";\n                topChanges.forEach(stock => {\n                    const changeDir = stock.changePercent > 0 ? \"up\" : \"down\";\n                    const displayPrice = stock.price.toFixed(2);\n                    const changePercent = Math.abs(Math.round(stock.changePercent));\n\n                    message += `${stock.id} (${stock.sector}): ${changeDir} ${changePercent}% to $${displayPrice}!\\n`;\n                });\n\n                if (activeEvent) {\n                    message += `\\nCurrent market event: ${activeEvent.name}`;\n                }\n\n                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                    message\n                });\n            } else {\n                console.log(\"No online players to send alert to.\");\n            }\n        } else {\n            console.log(\"No significant changes to alert players about.\");\n        }\n    } catch (error) {\n        console.log(`FATAL Error in updateStockPrices: ${error.message}`);\n        // Optionally, send an admin message if a fatal error occurs\n        // await takaro.gameserver.gameServerControllerSendMessage(data.gameServerId, {\n        //     message: `🚨 Market Error: An error occurred during stock update: ${error.message}`\n        // });\n    }\n}\n\nasync function initializeMarketIfNeeded(gameServerId, mod) {\n    try {\n        console.log(\"Initializing market if needed...\");\n        const configStocks = (mod.userConfig && mod.userConfig.stocks) || [];\n\n        if (!configStocks || configStocks.length === 0) {\n            console.log(\"Error: No stocks defined in module configuration for initialization.\");\n            return;\n        }\n\n        const [marketDataVar, lastEventTimeVar, eventStartTimeVar, activeEventVar, marketInitVar, eventCounterVar, cooldownCounterVar, eventDurationVar] =\n            await Promise.all([\n                takaro.variable.variableControllerSearch({ filters: { key: ['stock_market_data'], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }),\n                takaro.variable.variableControllerSearch({ filters: { key: ['last_market_event_time'], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }),\n                takaro.variable.variableControllerSearch({ filters: { key: ['event_start_time'], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }),\n                takaro.variable.variableControllerSearch({ filters: { key: ['active_market_event'], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }),\n                takaro.variable.variableControllerSearch({ filters: { key: ['stock_market_initialized'], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }),\n                takaro.variable.variableControllerSearch({ filters: { key: ['event_execution_counter'], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }),\n                takaro.variable.variableControllerSearch({ filters: { key: ['event_cooldown_counter'], gameServerId: [gameServerId], moduleId: [mod.moduleId] } }),\n                takaro.variable.variableControllerSearch({ filters: { key: ['current_event_duration'], gameServerId: [gameServerId], moduleId: [mod.moduleId] } })\n            ]);\n\n        const stocks = configStocks.map(stock => ({\n            id: stock.id,\n            name: stock.name,\n            sector: stock.sector,\n            price: stock.initialPrice,\n            volatility: stock.volatility / 100, // Convert percentage to decimal\n            lastPrice: stock.initialPrice\n        }));\n        console.log(\"Mapped initial stocks from config.\");\n\n        const createPromises = [];\n\n        if (marketDataVar.data.data.length === 0) {\n            createPromises.push(takaro.variable.variableControllerCreate({ key: 'stock_market_data', value: JSON.stringify(stocks), gameServerId, moduleId: mod.moduleId }));\n        }\n        if (lastEventTimeVar.data.data.length === 0) {\n            createPromises.push(takaro.variable.variableControllerCreate({ key: 'last_market_event_time', value: new Date().toISOString(), gameServerId, moduleId: mod.moduleId }));\n        }\n        if (eventStartTimeVar.data.data.length === 0) {\n            createPromises.push(takaro.variable.variableControllerCreate({ key: 'event_start_time', value: new Date().toISOString(), gameServerId, moduleId: mod.moduleId }));\n        }\n        if (activeEventVar.data.data.length === 0) {\n            createPromises.push(takaro.variable.variableControllerCreate({ key: 'active_market_event', value: '', gameServerId, moduleId: mod.moduleId }));\n        }\n        if (eventCounterVar.data.data.length === 0) {\n            createPromises.push(takaro.variable.variableControllerCreate({ key: 'event_execution_counter', value: '0', gameServerId, moduleId: mod.moduleId }));\n        }\n        if (cooldownCounterVar.data.data.length === 0) {\n            createPromises.push(takaro.variable.variableControllerCreate({ key: 'event_cooldown_counter', value: '0', gameServerId, moduleId: mod.moduleId }));\n        }\n        if (eventDurationVar.data.data.length === 0) {\n            const defaultDuration = (mod.userConfig && mod.userConfig.defaultEventDuration) || 5;\n            createPromises.push(takaro.variable.variableControllerCreate({ key: 'current_event_duration', value: defaultDuration.toString(), gameServerId, moduleId: mod.moduleId }));\n        }\n        if (marketInitVar.data.data.length === 0) {\n            createPromises.push(takaro.variable.variableControllerCreate({ key: 'stock_market_initialized', value: 'true', gameServerId, moduleId: mod.moduleId }));\n        }\n\n        if (createPromises.length > 0) {\n            console.log(`Creating ${createPromises.length} initial market variables.`);\n            await Promise.allSettled(createPromises);\n        } else {\n            console.log(\"All market variables already exist, no new initialization needed.\");\n        }\n\n        if (marketDataVar.data.data.length === 0) { // Only announce if we actually created the stock_market_data variable\n            const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({\n                filters: {\n                    gameServerId: [gameServerId],\n                    online: [true]\n                }\n            });\n\n            if (onlinePlayers.data.meta.total > 0) {\n                const message = \"📈 STOCK MARKET INITIALIZED 📈\\n\\nThe stock market is now open for trading! Use /market to see available stocks and /buy to start investing.\";\n                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, { message });\n            }\n        }\n        console.log(\"Market initialization process complete.\");\n    } catch (error) {\n        console.log(`Error in initializeMarketIfNeeded: ${error.message}`);\n    }\n}\n\nawait main();"
        },
        {
          "name": "marketnews",
          "temporalValue": "1 */1 * * *",
          "description": "gives you market news",
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, module: mod } = data;\n\n    const discordChannelId = mod.userConfig.marketNewsChannelId;\n\n    if (!discordChannelId) {\n        takaro.log.info('Market news Discord channel ID (marketNewsChannelId) not configured in module settings. Skipping Discord messages.');\n    }\n\n    const currentPlayers = (await takaro.playerOnGameserver.playerOnGameServerControllerSearch({\n        filters: {\n            gameServerId: [gameServerId],\n            online: [true]\n        }\n    })).data.meta;\n\n    if (currentPlayers.total === 0) {\n        takaro.log.info('Skipping daily market report: No players online.');\n        return;\n    }\n\n    const marketDataVar = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: ['stock_market_data'],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId]\n        }\n    });\n\n    if (marketDataVar.data.data.length === 0) {\n        takaro.log.warn('Skipping daily market report: Market data variable not found.');\n        return;\n    }\n\n    let stocks;\n    try {\n        stocks = JSON.parse(marketDataVar.data.data[0].value);\n        if (!Array.isArray(stocks)) {\n            throw new Error('Parsed market data is not an array.');\n        }\n    } catch (error) {\n        takaro.log.error(`Failed to parse stock_market_data: ${error}`);\n        return;\n    }\n\n    const activeEventVar = await takaro.variable.variableControllerSearch({\n        filters: {\n            key: ['active_market_event'],\n            gameServerId: [gameServerId],\n            moduleId: [mod.moduleId]\n        }\n    });\n\n    let activeEvent = null;\n    if (activeEventVar.data.data.length > 0 && activeEventVar.data.data[0].value) {\n        try {\n            activeEvent = JSON.parse(activeEventVar.data.data[0].value);\n            if (typeof activeEvent !== 'object' || activeEvent === null || Object.keys(activeEvent).length === 0) {\n                activeEvent = null;\n            }\n        } catch (error) {\n            takaro.log.warn(`Failed to parse active_market_event: ${error}`);\n            activeEvent = null;\n        }\n    }\n\n    let gameMessage1 = \"==== DAILY MARKET REPORT ====\\n\";\n\n    if (activeEvent && activeEvent.name && activeEvent.description) {\n        const eventText = `\\n🌍 ACTIVE EVENT: ${activeEvent.name} 🌍\\n${activeEvent.description}\\n`;\n        gameMessage1 += eventText;\n    } else {\n        const noEventText = '\\nNo active market events today.\\n';\n        gameMessage1 += noEventText;\n    }\n\n    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n        message: gameMessage1\n    }).catch(err => takaro.log.error(`Failed to send game message 1: ${err}`));\n\n    const stocksWithPerformance = stocks.map(stock => {\n        let change = 0;\n        let percentChange = 0;\n        let changeSymbol = '→';\n\n        if (typeof stock.price === 'number' && typeof stock.lastPrice === 'number' && stock.lastPrice !== 0) {\n            change = stock.price - stock.lastPrice;\n            percentChange = (change / stock.lastPrice) * 100;\n            if (percentChange > 0.05) {\n                changeSymbol = '↑';\n            } else if (percentChange < -0.05) {\n                changeSymbol = '↓';\n            }\n        } else if (typeof stock.price === 'number' && stock.lastPrice === undefined) {\n            changeSymbol = '🆕';\n            percentChange = 0;\n        }\n        return {\n            ...stock,\n            change,\n            percentChange,\n            changeSymbol\n        };\n    });\n\n    const sectorPerformance = {};\n    // Define the desired display order and mapping for sorting\n    const riskOrder = {\n        \"Very High Risk\": 5,\n        \"High Risk\": 4,\n        \"Medium Risk\": 3,\n        \"Low Risk\": 2,\n        \"Very Low Risk\": 1,\n        \"Uncategorized\": 0 // Fallback for any stock without a defined sector\n    };\n\n    stocksWithPerformance.forEach(stock => {\n        const sectorName = stock.sector || 'Uncategorized'; // Use the sector name directly\n        if (!sectorPerformance[sectorName]) {\n            sectorPerformance[sectorName] = {\n                stocks: [],\n                totalPercentChange: 0,\n                count: 0\n            };\n        }\n        sectorPerformance[sectorName].stocks.push(stock);\n        if (typeof stock.percentChange === 'number' && isFinite(stock.percentChange)) {\n            sectorPerformance[sectorName].totalPercentChange += stock.percentChange;\n            sectorPerformance[sectorName].count++;\n        }\n    });\n\n    for (const sectorName in sectorPerformance) {\n        const sectorData = sectorPerformance[sectorName];\n        if (sectorData.count > 0) {\n            sectorData.avgPerformance = sectorData.totalPercentChange / sectorData.count;\n        } else {\n            sectorData.avgPerformance = 0;\n        }\n    }\n\n    // Sort sector performance by risk order (VH to VL)\n    const sortedSectors = Object.entries(sectorPerformance)\n        .sort(([sectorNameA], [sectorNameB]) => riskOrder[sectorNameB] - riskOrder[sectorNameA]);\n\n    let gameMessage2 = \"\\n=== SECTOR PERFORMANCE ===\\n\";\n\n    if (sortedSectors.length > 0) {\n        sortedSectors.forEach(([sectorName, sectorData]) => { // Use sectorName directly\n            const avgSymbol = sectorData.avgPerformance > 0.05 ? '↑' : sectorData.avgPerformance < -0.05 ? '↓' : '→';\n            let line = `${sectorName}: ${avgSymbol} ${Math.abs(sectorData.avgPerformance).toFixed(1)}%\\n`;\n\n            if (sectorData.avgPerformance > 0.05) {\n                line = `[70FE02]${sectorName}: ${avgSymbol} ${Math.abs(sectorData.avgPerformance).toFixed(1)}%[-]\\n`;\n            } else if (sectorData.avgPerformance < -0.05) {\n                line = `[FF0001]${sectorName}: ${avgSymbol} ${Math.abs(sectorData.avgPerformance).toFixed(1)}%[-]\\n`;\n            }\n            gameMessage2 += line;\n        });\n    } else {\n        const noDataText = \"No sector performance data available.\\n\";\n        gameMessage2 += noDataText;\n    }\n\n    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n        message: gameMessage2\n    }).catch(err => takaro.log.error(`Failed to send game message 2: ${err}`));\n\n    const validPerformers = stocksWithPerformance.filter(s => typeof s.percentChange === 'number' && isFinite(s.percentChange));\n    validPerformers.sort((a, b) => b.percentChange - a.percentChange);\n\n    let gameMessage3 = \"\";\n\n    const topPerformersTitleGame = \"\\n🔥 TOP PERFORMERS 🔥\\n\";\n    gameMessage3 += topPerformersTitleGame;\n\n    if (validPerformers.length > 0) {\n        for (let i = 0; i < Math.min(3, validPerformers.length); i++) {\n            const stock = validPerformers[i];\n            gameMessage3 += `[70FE02]${stock.id}: $${Math.round(stock.price)} ${stock.changeSymbol} ${Math.abs(stock.percentChange).toFixed(1)}%[-]\\n`;\n        }\n    } else {\n        gameMessage3 += \"No top performers today.\\n\";\n    }\n\n    const worstPerformersTitleGame = \"\\n📉 WORST PERFORMERS 📉\\n\";\n    gameMessage3 += worstPerformersTitleGame;\n\n    if (validPerformers.length > 0) {\n        const worstStartIndex = Math.max(0, validPerformers.length - 3);\n        for (let i = validPerformers.length - 1; i >= worstStartIndex; i--) {\n            const stock = validPerformers[i];\n            gameMessage3 += `[FF0001]${stock.id}: $${Math.round(stock.price)} ${stock.changeSymbol} ${Math.abs(stock.percentChange).toFixed(1)}%[-]\\n`;\n        }\n    } else {\n        gameMessage3 += \"No worst performers today.\\n\";\n    }\n\n    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n        message: gameMessage3\n    }).catch(err => takaro.log.error(`Failed to send game message 3: ${err}`));\n\n    function hslToRgb(h, s, l) { let a = s * Math.min(l, 1 - l); let f = n => { let k = (n + h / 30) % 12; return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); }; return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))]; }\n    function rgbToHex(r, g, b) { return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase(); }\n    function stringToHexColor(str) { let hash = 0; for (let i = 0; i < str.length; i++) { hash = str.charCodeAt(i) + ((hash << 5) - hash); } hash = Math.abs(hash); const hue = (hash * 137.508) % 360; const [r, g, b] = hslToRgb(hue, 0.9, 0.7); return rgbToHex(r, g, b); }\n\n    const stockColors = {};\n    stocksWithPerformance.forEach(stock => {\n        stockColors[stock.id] = stringToHexColor(stock.name);\n    });\n\n    const stocksBySectorGame = {};\n    stocksWithPerformance.forEach(stock => {\n        const sector = stock.sector || 'Uncategorized'; // Use the sector name directly\n        if (!stocksBySectorGame[sector]) {\n            stocksBySectorGame[sector] = [];\n        }\n        stocksBySectorGame[sector].push(stock);\n    });\n\n    // Sort the sector names for display in the desired order (VH, H, M, L, VL)\n    const sortedSectorNamesGame = Object.keys(stocksBySectorGame).sort((sectorNameA, sectorNameB) => riskOrder[sectorNameB] - riskOrder[sectorNameA]);\n\n\n    for (const sectorName of sortedSectorNamesGame) { // Iterate through sorted sector names\n        const sectorStocks = stocksBySectorGame[sectorName];\n        const chunkSize = 3;\n        for (let i = 0; i < sectorStocks.length; i += chunkSize) {\n            const chunk = sectorStocks.slice(i, i + chunkSize);\n            // Use sectorName directly for the header\n            let sectorMessage = (i === 0) ? `\\n--- ${sectorName} SECTOR ---\\n` : '';\n\n            chunk.forEach(stock => {\n                let changeText = '';\n                let priceColorPrefix = '', priceColorSuffix = '';\n\n                if (stock.lastPrice) {\n                    const percentChange = ((stock.price - stock.lastPrice) / stock.lastPrice) * 100;\n                    changeText = ` ${stock.changeSymbol} ${Math.abs(percentChange).toFixed(1)}%`;\n                    if (percentChange > 0) {\n                        priceColorPrefix = '[70FE02]';\n                        priceColorSuffix = '[-]';\n                    } else if (percentChange < 0) {\n                        priceColorPrefix = '[FF0001]';\n                        priceColorSuffix = '[-]';\n                    }\n                }\n\n                const stockNameColorPrefix = stockColors[stock.id] ? `[${stockColors[stock.id]}]` : '';\n                const stockNameColorSuffix = stockColors[stock.id] ? '[-]' : '';\n\n                let riskIndicator; // This is derived from volatility, not directly from sector ID\n                if (stock.volatility <= 0.05) riskIndicator = \"VL\";\n                else if (stock.volatility <= 0.10) riskIndicator = \"L\";\n                else if (stock.volatility <= 0.15) riskIndicator = \"M\";\n                else if (stock.volatility <= 0.25) riskIndicator = \"H\";\n                else riskIndicator = \"VH\";\n\n                sectorMessage += `${stockNameColorPrefix}${stock.name} (${stock.id})${stockNameColorSuffix} (${riskIndicator}): ${priceColorPrefix}$${Math.round(stock.price)}${changeText}${priceColorSuffix}\\n`;\n            });\n\n            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                message: sectorMessage\n            }).catch(err => takaro.log.error(`Failed to send game message for sector ${sectorName}: ${err}`));\n        }\n    }\n\n    const legendMessage = \"\\n=== LEGEND ===\\n\" +\n        \"Risk Levels: VL=Very Low, L=Low, M=Moderate, H=High, VH=Very High\\n\";\n    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n        message: legendMessage\n    }).catch(err => takaro.log.error(`Failed to send game legend message: ${err}`));\n\n    if (discordChannelId) {\n        // --- MESSAGE 1: The Summary ---\n        let discordMessageSummary = \"**==== DAILY MARKET REPORT ====**\\n\\n\";\n\n        if (activeEvent && activeEvent.name && activeEvent.description) {\n            discordMessageSummary += `🌍 **ACTIVE EVENT: ${activeEvent.name}** 🌍\\n${activeEvent.description}\\n\\n`;\n        } else {\n            discordMessageSummary += 'No active market events today.\\n\\n';\n        }\n\n        discordMessageSummary += \"**=== SECTOR PERFORMANCE ===**\\n\";\n        if (sortedSectors.length > 0) {\n            sortedSectors.forEach(([sectorName, sectorData]) => { // Use sectorName directly\n                const avgSymbol = sectorData.avgPerformance > 0.05 ? '↑' : sectorData.avgPerformance < -0.05 ? '↓' : '→';\n                discordMessageSummary += `${sectorName}: ${avgSymbol} ${Math.abs(sectorData.avgPerformance).toFixed(1)}%\\n`;\n            });\n        } else {\n            discordMessageSummary += \"No sector performance data available.\\n\";\n        }\n        discordMessageSummary += \"\\n\";\n\n        discordMessageSummary += \"**🔥 TOP PERFORMERS 🔥**\\n\";\n        if (validPerformers.length > 0) {\n            for (let i = 0; i < Math.min(3, validPerformers.length); i++) {\n                const stock = validPerformers[i];\n                discordMessageSummary += `${stock.id}: $${Math.round(stock.price)} ${stock.changeSymbol} ${Math.abs(stock.percentChange).toFixed(1)}%\\n`;\n            }\n        } else {\n            discordMessageSummary += \"No top performers today.\\n\";\n        }\n        discordMessageSummary += \"\\n\";\n\n        discordMessageSummary += \"**📉 WORST PERFORMERS 📉**\\n\";\n        if (validPerformers.length > 0) {\n            const worstStartIndex = Math.max(0, validPerformers.length - 3);\n            for (let i = validPerformers.length - 1; i >= worstStartIndex; i--) {\n                const stock = validPerformers[i];\n                discordMessageSummary += `${stock.id}: $${Math.round(stock.price)} ${stock.changeSymbol} ${Math.abs(stock.percentChange).toFixed(1)}%\\n`;\n            }\n        } else {\n            discordMessageSummary += \"No worst performers today.\\n\";\n        }\n\n        await takaro.discord.discordControllerSendMessage(discordChannelId, {\n            message: discordMessageSummary\n        }).catch(err => takaro.log.error(`Failed to send summary Discord message: ${err.message || err}`));\n\n        // --- MESSAGE 2: All Stock Listings ---\n        let discordMessageDetails = \"**=== ALL STOCK LISTINGS ===**\\n\";\n        const stocksBySector = {};\n        stocksWithPerformance.forEach(stock => {\n            const sector = stock.sector || 'Uncategorized'; // Use the sector name directly\n            if (!stocksBySector[sector]) {\n                stocksBySector[sector] = [];\n            }\n            stocksBySector[sector].push(stock);\n        });\n\n        // Sort the sector names for display in the desired order (VH, H, M, L, VL)\n        const sortedSectorNamesDiscord = Object.keys(stocksBySector).sort((sectorNameA, sectorNameB) => riskOrder[sectorNameB] - riskOrder[sectorNameA]);\n\n\n        for (const sectorName of sortedSectorNamesDiscord) { // Iterate through sorted sector names\n            const sectorStocks = stocksBySector[sectorName];\n            discordMessageDetails += `\\n--- **${sectorName} SECTOR** ---\\n`; // Use sectorName directly\n            sectorStocks.forEach(stock => {\n                let changeText = '';\n                if (stock.lastPrice) {\n                    const percentChange = ((stock.price - stock.lastPrice) / stock.lastPrice) * 100;\n                    changeText = ` ${stock.changeSymbol} ${Math.abs(percentChange).toFixed(1)}%`;\n                }\n\n                let riskIndicator;\n                if (stock.volatility <= 0.05) riskIndicator = \"VL\";\n                else if (stock.volatility <= 0.10) riskIndicator = \"L\";\n                else if (stock.volatility <= 0.15) riskIndicator = \"M\";\n                else if (stock.volatility <= 0.25) riskIndicator = \"H\";\n                else riskIndicator = \"VH\";\n\n                discordMessageDetails += `${stock.id} (${riskIndicator}): ${stock.name} - $${Math.round(stock.price)}${changeText}\\n`;\n            });\n        }\n\n        discordMessageDetails += \"\\n**=== LEGEND ===**\\n\" +\n            \"Risk Levels: VL=Very Low, L=Low, M=Moderate, H=High, VH=Very High\\n\";\n\n        await takaro.discord.discordControllerSendMessage(discordChannelId, {\n            message: discordMessageDetails\n        }).catch(err => takaro.log.error(`Failed to send details Discord message: ${err.message || err}`));\n    }\n\n    takaro.log.info('Daily market report sent successfully.');\n}\n\nawait main();"
        }
      ],
      "functions": [],
      "permissions": [
        {
          "permission": "STOCK_MARKET_USE",
          "friendlyName": "Use Stock Market",
          "description": "Allows the player to view market prices and their portfolio",
          "canHaveCount": false
        },
        {
          "permission": "STOCK_MARKET_TRADE",
          "friendlyName": "Trade Stocks",
          "description": "Allows the player to buy and sell stocks",
          "canHaveCount": false
        },
        {
          "permission": "STOCK_MARKET_BROKER",
          "friendlyName": "Stock Broker",
          "description": "VIP status that reduces transaction fees",
          "canHaveCount": false
        },
        {
          "permission": "STOCK_MARKET_TRIGGER_EVENT",
          "friendlyName": "Trigger Market Events",
          "description": "Allows admins to manually trigger market events",
          "canHaveCount": false
        }
      ]
    }
  ]
}