{
  "name": "GeneralstockMarket",
  "author": "limon",
  "supportedGames": [
    "all"
  ],
  "takaroVersion": "v0.0.24",
  "versions": [
    {
      "tag": "0.0.6",
      "description": "**Stock Market: A Dynamic Virtual Trading System**\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_marketsall.png)\n\n* **Multiple Economic Sectors:** Configure various industry sectors (Technology, Healthcare, Energy, 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 \"Global Pandemic\" or \"Tech Boom\" impact different sectors in realistic ways.\n\n![Market Events](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_triggerevent.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_buystock.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_sellstock.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_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![Stock Information](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_stockinfo.png)\n![Industry View](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/stockmarket_marketstech.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**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\nPerfect for servers wanting to add economic depth, encourage player engagement, and create opportunities for strategic gameplay!",
      "configSchema": "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"type\":\"object\",\"properties\":{\"sectors\":{\"type\":\"array\",\"title\":\"Market Sectors\",\"description\":\"Define economic sectors for categorizing stocks\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Sector ID (e.g., TECH)\"},\"name\":{\"type\":\"string\",\"description\":\"Sector name (e.g., Technology)\"}}},\"default\":[{\"id\":\"TECH\",\"name\":\"Technology\"},{\"id\":\"HEALTH\",\"name\":\"Healthcare\"},{\"id\":\"ENERGY\",\"name\":\"Energy\"},{\"id\":\"FINANCE\",\"name\":\"Financial\"},{\"id\":\"CONSUMER\",\"name\":\"Consumer Goods\"}]},\"stocks\":{\"type\":\"array\",\"title\":\"Stocks\",\"description\":\"List of stocks available for trading\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Stock ticker symbol (e.g., IRON)\"},\"name\":{\"type\":\"string\",\"description\":\"Full company name\"},\"sector\":{\"type\":\"string\",\"description\":\"Sector this stock belongs to (must match a sector ID)\"},\"initialPrice\":{\"type\":\"number\",\"description\":\"Starting price in whole currency units\",\"minimum\":1},\"volatility\":{\"type\":\"number\",\"description\":\"Base volatility percentage (5 = 5%)\",\"minimum\":1,\"maximum\":25}}},\"default\":[{\"id\":\"AAPL\",\"name\":\"Apple Inc.\",\"sector\":\"TECH\",\"initialPrice\":150,\"volatility\":8},{\"id\":\"MSFT\",\"name\":\"MicroSoft Corp\",\"sector\":\"TECH\",\"initialPrice\":200,\"volatility\":7},{\"id\":\"DRUG\",\"name\":\"MediPharma\",\"sector\":\"HEALTH\",\"initialPrice\":120,\"volatility\":12},{\"id\":\"HOSP\",\"name\":\"Global Healthcare\",\"sector\":\"HEALTH\",\"initialPrice\":80,\"volatility\":9},{\"id\":\"OIL\",\"name\":\"Petrol Giants\",\"sector\":\"ENERGY\",\"initialPrice\":95,\"volatility\":15},{\"id\":\"SOLAR\",\"name\":\"Sun Energy\",\"sector\":\"ENERGY\",\"initialPrice\":45,\"volatility\":18},{\"id\":\"BANK\",\"name\":\"United Banking\",\"sector\":\"FINANCE\",\"initialPrice\":175,\"volatility\":10},{\"id\":\"FOOD\",\"name\":\"Quality Foods\",\"sector\":\"CONSUMER\",\"initialPrice\":65,\"volatility\":5}]},\"marketEvents\":{\"type\":\"array\",\"title\":\"Market Events\",\"description\":\"Special events that can impact the market\",\"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 how this event affects the market\"},\"sectorImpacts\":{\"type\":\"array\",\"description\":\"How each sector is affected\",\"items\":{\"type\":\"object\",\"properties\":{\"sectorId\":{\"type\":\"string\",\"description\":\"Sector ID this impact applies to\"},\"impact\":{\"type\":\"number\",\"description\":\"Percentage impact (-30 = -30%, +20 = +20%)\",\"minimum\":-50,\"maximum\":50}}}}}},\"default\":[{\"id\":\"PANDEMIC\",\"name\":\"Global Pandemic\",\"description\":\"A worldwide health crisis affects markets\",\"sectorImpacts\":[{\"sectorId\":\"TECH\",\"impact\":15},{\"sectorId\":\"HEALTH\",\"impact\":30},{\"sectorId\":\"ENERGY\",\"impact\":-20},{\"sectorId\":\"FINANCE\",\"impact\":-10},{\"sectorId\":\"CONSUMER\",\"impact\":-5}]},{\"id\":\"OIL_CRISIS\",\"name\":\"Oil Supply Crisis\",\"description\":\"Major disruption in oil supply chains\",\"sectorImpacts\":[{\"sectorId\":\"TECH\",\"impact\":-5},{\"sectorId\":\"HEALTH\",\"impact\":0},{\"sectorId\":\"ENERGY\",\"impact\":35},{\"sectorId\":\"FINANCE\",\"impact\":-15},{\"sectorId\":\"CONSUMER\",\"impact\":-20}]},{\"id\":\"TECH_BOOM\",\"name\":\"Technology Innovation Boom\",\"description\":\"Revolutionary new technologies emerge\",\"sectorImpacts\":[{\"sectorId\":\"TECH\",\"impact\":40},{\"sectorId\":\"HEALTH\",\"impact\":10},{\"sectorId\":\"ENERGY\",\"impact\":5},{\"sectorId\":\"FINANCE\",\"impact\":15},{\"sectorId\":\"CONSUMER\",\"impact\":0}]},{\"id\":\"RECESSION\",\"name\":\"Economic Recession\",\"description\":\"General economic downturn affects all markets\",\"sectorImpacts\":[{\"sectorId\":\"TECH\",\"impact\":-25},{\"sectorId\":\"HEALTH\",\"impact\":-10},{\"sectorId\":\"ENERGY\",\"impact\":-30},{\"sectorId\":\"FINANCE\",\"impact\":-35},{\"sectorId\":\"CONSUMER\",\"impact\":-15}]},{\"id\":\"RECOVERY\",\"name\":\"Economic Recovery\",\"description\":\"Markets rebound from previous downturn\",\"sectorImpacts\":[{\"sectorId\":\"TECH\",\"impact\":20},{\"sectorId\":\"HEALTH\",\"impact\":15},{\"sectorId\":\"ENERGY\",\"impact\":25},{\"sectorId\":\"FINANCE\",\"impact\":30},{\"sectorId\":\"CONSUMER\",\"impact\":10}]}]},\"eventFrequency\":{\"title\":\"Event Frequency\",\"type\":\"number\",\"description\":\"Average number of cronjob runs between market events (0 to disable events)\",\"default\":10,\"minimum\":0},\"defaultEventDuration\":{\"title\":\"Default Event Duration\",\"type\":\"number\",\"description\":\"Default number of cronjob runs an event lasts if not specified in the event definition itself\",\"default\":5,\"minimum\":1,\"maximum\":20},\"transactionFee\":{\"title\":\"Transaction Fee\",\"type\":\"number\",\"description\":\"Percentage fee charged on all buy/sell transactions (5 = 5%)\",\"default\":5,\"minimum\":0,\"maximum\":25},\"vipDiscount\":{\"title\":\"VIP Discount\",\"type\":\"number\",\"description\":\"Percentage discount on transaction fees for players with the 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 (10 = 10%)\",\"default\":10,\"minimum\":5,\"maximum\":50}},\"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": "amount",
              "type": "number",
              "helpText": "Number of shares to buy",
              "defaultValue": "",
              "position": 1
            },
            {
              "name": "stock",
              "type": "string",
              "helpText": "The stock ticker symbol ",
              "defaultValue": "",
              "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": "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": "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        // Display stocks by sector\n        for (const [sector, sectorStocks] of Object.entries(stocksBySector)) {\n            let sectorMessage = `\\n--- ${sector} SECTOR ---\\n`;\n\n            sectorStocks.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        // 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": "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": "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    // 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: $${Math.round(data.averagePrice)}\\n`;\n        stockMessage += `Current price: $${Math.round(stock.price)}\\n`;\n        stockMessage += `Total value: $${Math.round(currentValue)}\\n`;\n\n        if (profit >= 0) {\n            stockMessage += `Profit: +$${Math.round(profit)} (+${profitPercent}%)\\n`;\n        } else {\n            stockMessage += `Loss: -$${Math.abs(Math.round(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: $${Math.round(totalInvestment)}\\n`;\n        summaryMessage += `Current value: $${Math.round(totalValue)}\\n`;\n\n        if (totalProfit >= 0) {\n            summaryMessage += `Overall profit: +$${Math.round(totalProfit)} (+${totalProfitPercent}%)\\n`;\n        } else {\n            summaryMessage += `Overall loss: -$${Math.abs(Math.round(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}\n\nawait main();",
          "arguments": []
        }
      ],
      "hooks": [],
      "cronJobs": [
        {
          "name": "marketnews",
          "temporalValue": "0 */1 * * *",
          "description": "",
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, module: mod } = data;\n\n    // Check for online players\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; // No players online, skip the market news\n    }\n\n    // Get current stock data (which should include lastPrice)\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; // Market not initialized yet\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; // Invalid market data\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    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        } catch (error) {\n            takaro.log.warn(`Failed to parse active_market_event: ${error}`);\n            // Continue without event info if parsing fails\n        }\n    }\n\n    // --- Message 1: Header and active event ---\n    let message1 = \"==== DAILY MARKET REPORT ====\\n\";\n    if (activeEvent && activeEvent.name && activeEvent.description) {\n        message1 += `\\n🌍 ACTIVE EVENT: ${activeEvent.name} 🌍\\n${activeEvent.description}\\n`;\n    } else {\n        message1 += '\\nNo active market events today.\\n';\n    }\n\n    // Send first part of the report\n    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n        message: message1\n    }).catch(err => takaro.log.error(`Failed to send message 1: ${err}`));\n\n    // --- Calculate stock performance using stock.price and stock.lastPrice ---\n    const stocksWithPerformance = stocks.map(stock => {\n        let change = 0;\n        let percentChange = 0;\n        let changeSymbol = '→'; // Default: No change or insufficient data\n\n        // Ensure required fields exist and lastPrice is a valid number > 0 for percentage calculation\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) { // Use a small threshold to avoid 'noise'\n                changeSymbol = '↑';\n            } else if (percentChange < -0.05) {\n                changeSymbol = '↓';\n            }\n        } else if (typeof stock.price === 'number' && stock.lastPrice === undefined) {\n            // Handle case where lastPrice might not exist (e.g., new stock)\n            changeSymbol = '🆕'; // Indicate 'New' or similar\n            percentChange = 0; // Or handle as needed\n        }\n        // If lastPrice is 0, or types are wrong, change/percentChange remain 0, symbol remains '→'\n\n        return {\n            ...stock,\n            change,         // Absolute change\n            percentChange,  // Percentage change\n            changeSymbol    // Visual indicator\n        };\n    });\n\n    // --- Group by sector ---\n    const sectorPerformance = {};\n    stocksWithPerformance.forEach(stock => {\n        const sectorId = stock.sector || 'Uncategorized'; // Default sector if missing\n        if (!sectorPerformance[sectorId]) {\n            sectorPerformance[sectorId] = {\n                stocks: [],\n                totalPercentChange: 0, // Sum percentages for averaging\n                count: 0\n            };\n        }\n        sectorPerformance[sectorId].stocks.push(stock);\n        // Only include stocks with valid percentage change in the average\n        if (typeof stock.percentChange === 'number' && isFinite(stock.percentChange)) {\n            sectorPerformance[sectorId].totalPercentChange += stock.percentChange;\n            sectorPerformance[sectorId].count++;\n        }\n    });\n\n    // --- Calculate average sector performance ---\n    for (const sectorId in sectorPerformance) {\n        const sectorData = sectorPerformance[sectorId];\n        if (sectorData.count > 0) {\n            sectorData.avgPerformance = sectorData.totalPercentChange / sectorData.count;\n        } else {\n            sectorData.avgPerformance = 0; // Avoid division by zero if no stocks had valid changes\n        }\n    }\n\n    // Sort sectors by average performance\n    const sortedSectors = Object.entries(sectorPerformance)\n        .sort(([, a], [, b]) => b.avgPerformance - a.avgPerformance);\n\n    // --- Message 2: Sector performance ---\n    let message2 = \"\\n=== SECTOR PERFORMANCE ===\\n\";\n    if (sortedSectors.length > 0) {\n        sortedSectors.forEach(([sectorId, data]) => {\n            const avgSymbol = data.avgPerformance > 0.05 ? '↑' : data.avgPerformance < -0.05 ? '↓' : '→';\n            message2 += `${sectorId}: ${avgSymbol} ${Math.abs(data.avgPerformance).toFixed(1)}%\\n`; // Use toFixed(1) like command\n        });\n    } else {\n        message2 += \"No sector performance data available.\\n\";\n    }\n\n    // Send sector performance report\n    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n        message: message2\n    }).catch(err => takaro.log.error(`Failed to send message 2: ${err}`));\n\n\n    // --- Message 3: Top and Worst performers ---\n    // Filter out stocks where percentChange couldn't be calculated properly before sorting\n    const validPerformers = stocksWithPerformance.filter(s => typeof s.percentChange === 'number' && isFinite(s.percentChange));\n\n    // Sort valid performers\n    validPerformers.sort((a, b) => b.percentChange - a.percentChange);\n\n    let message3 = \"\\n🔥 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            // Use the pre-calculated changeSymbol based on percentChange\n            message3 += `${stock.id}: $${Math.round(stock.price)} ${stock.changeSymbol} ${Math.abs(stock.percentChange).toFixed(1)}%\\n`;\n        }\n    } else {\n        message3 += \"No top performers today.\\n\";\n    }\n\n\n    message3 += \"\\n📉 WORST PERFORMERS 📉\\n\";\n    if (validPerformers.length > 0) {\n        // Sort for worst (ascending order) - no need to create a new sorted array if we just reverse iteration\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            // Use the pre-calculated changeSymbol based on percentChange\n            message3 += `${stock.id}: $${Math.round(stock.price)} ${stock.changeSymbol} ${Math.abs(stock.percentChange).toFixed(1)}%\\n`;\n        }\n    } else {\n        message3 += \"No worst performers today.\\n\";\n    }\n\n\n    // Send top/worst performers report\n    await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n        message: message3\n    }).catch(err => takaro.log.error(`Failed to send message 3: ${err}`));\n\n    // --- IMPORTANT: REMOVED the update of 'stock_market_yesterday' ---\n    // This script now assumes 'stock_market_data' contains 'lastPrice'.\n    // The responsibility of updating 'lastPrice' and the new 'price'\n    // must lie in another script/process that runs *before* this report.\n\n    takaro.log.info('Daily market report sent successfully.');\n}\n\nawait main();"
        },
        {
          "name": "updatestockprices",
          "temporalValue": "5 4 * * *",
          "description": "",
          "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            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        }\n\n        let cooldownCounter = 0;\n        if (cooldownCounterVar.data.data.length > 0) {\n            cooldownCounter = parseInt(cooldownCounterVar.data.data[0].value, 10);\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        }\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            } catch (e) {\n                // In case of parsing error, consider no active event\n                activeEvent = null;\n            }\n        }\n\n        // Check if the current event should end and increment counter\n        if (activeEvent) {\n            // Increment the event execution counter\n            eventCounter++;\n\n            // Update or create the counter\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            // End the event if counter reached duration\n            if (eventCounter >= eventDuration) {\n                // Clear the active event\n                if (activeEventVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(activeEventVar.data.data[0].id, {\n                        value: ''\n                    });\n                }\n\n                // Reset counter to 0\n                if (eventCounterVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(eventCounterVar.data.data[0].id, {\n                        value: '0'\n                    });\n                }\n\n                // Reset cooldown counter to 0 to begin cooldown period\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                // Announce the end of the event to all 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 = `📈 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                // Reset active event\n                activeEvent = null;\n            }\n        } else {\n            // No active event, increment cooldown counter\n            cooldownCounter++;\n\n            // Update or create the cooldown counter\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        // Get last event time for event frequency calculation\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        // Only trigger a new event if:\n        // 1. There's no active event \n        // 2. We have passed the cooldown period (cooldownCounter >= eventFrequency)\n        if (!activeEvent && cooldownCounter >= eventFrequency) {\n            // Base chance to trigger an event\n            shouldTriggerEvent = Math.random() < 0.5; // 50% chance\n\n            // Force an event if we're well past the cooldown period (2x frequency)\n            if (cooldownCounter >= eventFrequency * 2) {\n                shouldTriggerEvent = true;\n            }\n        }\n\n        // If we should trigger a new event, select a random one\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\n                // Generate a random duration between 1 and defaultEventDuration\n                const maxDuration = (mod.userConfig && mod.userConfig.defaultEventDuration) || 5;\n                const randomDuration = Math.floor(Math.random() * maxDuration) + 1; // 1 to maxDuration\n\n                // Save the random duration\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                // Reset the event counter to 0\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                // Reset the cooldown counter\n                if (cooldownCounterVar.data.data.length > 0) {\n                    await takaro.variable.variableControllerUpdate(cooldownCounterVar.data.data[0].id, {\n                        value: '0'\n                    });\n                }\n\n                // Update active event\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                // Update event start time\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                // Update last event time\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                // Announce the event to all 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                    // Format sector impacts for announcement\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                    // 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        }\n\n        // Get stocks\n        if (!marketDataVar.data.data[0] || !marketDataVar.data.data[0].value) {\n            // Re-initialize market if value is missing\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        } catch (e) {\n            // Re-initialize market if data is corrupt\n            await initializeMarketIfNeeded(gameServerId, mod);\n            return;\n        }\n\n        const significantChanges = [];\n\n        // Update each stock price\n        stocks.forEach(stock => {\n            // Save the current price as lastPrice for change tracking\n            if (!stock.lastPrice) {\n                stock.lastPrice = stock.price;\n            } else {\n                stock.lastPrice = stock.price;\n            }\n\n            // Calculate base price change based on volatility\n            // Using a more normalized random approach\n            // Math.random() * 2 - 1 gives a value between -1 and 1\n            const randomFactor = Math.random() * 2 - 1;\n            const baseChangePercent = randomFactor * stock.volatility;\n            let totalChangePercent = baseChangePercent;\n\n            // Apply active event effects if any\n            if (activeEvent) {\n                const sectorImpact = activeEvent.sectorImpacts.find(impact => impact.sectorId === stock.sector);\n                if (sectorImpact) {\n                    // Convert impact percentage to decimal and apply a random factor\n                    // to create varied effects within each sector\n                    const eventImpactPercentage = sectorImpact.impact; // This is already a percentage\n                    const eventImpactDecimal = eventImpactPercentage / 100; // Convert to decimal\n                    const randomImpactFactor = 0.5 + Math.random(); // Between 0.5 and 1.5\n                    const eventImpact = eventImpactDecimal * randomImpactFactor;\n\n                    // Add the event impact to the total change percent\n                    totalChangePercent += eventImpact;\n                }\n            }\n\n            // Apply the price change\n            const oldPrice = stock.price;\n            const priceChange = stock.price * totalChangePercent;\n            stock.price = Math.max(1, stock.price + priceChange);\n\n            // Check if this is a significant change\n            const changeThreshold = ((mod.userConfig && mod.userConfig.priceAlertThreshold) || 10) / 100;\n            const percentChange = (stock.price - stock.lastPrice) / stock.lastPrice;\n\n            if (Math.abs(percentChange) > changeThreshold) {\n                significantChanges.push({\n                    ...stock,\n                    changePercent: percentChange * 100\n                });\n            }\n        });\n\n        // Save updated prices\n        await takaro.variable.variableControllerUpdate(marketDataVar.data.data[0].id, {\n            value: JSON.stringify(stocks)\n        });\n\n        // Broadcast major changes to all players\n        if (significantChanges.length > 0) {\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                // Sort by absolute change percentage\n                significantChanges.sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent));\n\n                // Take top 3 most significant changes\n                const topChanges = significantChanges.slice(0, Math.min(3, significantChanges.length));\n\n                // Formulate message about big market changes\n                let message = \"📊 STOCK MARKET ALERT 📊\\n\";\n                topChanges.forEach(stock => {\n                    const changeDir = stock.changePercent > 0 ? \"up\" : \"down\";\n                    const changePercent = Math.abs(Math.round(stock.changePercent));\n\n                    message += `${stock.id} (${stock.sector}): ${changeDir} ${changePercent}% to $${Math.round(stock.price)}!\\n`;\n                });\n\n                if (activeEvent) {\n                    message += `\\nCurrent market event: ${activeEvent.name}`;\n                }\n\n                // Send the message\n                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                    message\n                });\n            }\n        }\n    } catch (error) {\n        // If something goes wrong, log it\n        console.log(`Error in updateStockPrices: ${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        // 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            await Promise.allSettled(createPromises);\n        }\n\n        // Only announce if we had to create at least the stock data (indicating a new market)\n        if (marketDataVar.data.data.length === 0) {\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 /market to see available stocks and /buy to start investing.\";\n\n                await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {\n                    message\n                });\n            }\n        }\n    } catch (error) {\n        // Log the error but don't throw, so the cronjob can continue\n        console.log(`Error in initializeMarketIfNeeded: ${error.message}`);\n    }\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
        }
      ]
    }
  ]
}