<?php
require_once __DIR__ . '/../classes/Database.php'; require_once __DIR__ . '/../classes/Game.php'; class CryptoController { private $db; private $game; private $miningStatusCache = null; private $metaNextEventYearKey = 'tsx_event_next_year'; private $metaNextEventMonthKey = 'tsx_event_next_month'; private $metaNextEventDayKey = 'tsx_event_next_day'; public function __construct($game = null) { $this->db = Database::getInstance(); $this->game = $game ?: new Game(); } public function getMiningStatusAndModifiers() { if ($this->miningStatusCache !== null) { return $this->miningStatusCache; } $status = [ 'unlocked' => false, 'yield_multiplier' => 1.0, 'scan_cost_multiplier' => 1.0, 'loss_chance_reduction' => 0.0, 'install_time_multiplier' => 1.0 ]; $rows = $this->db->query( "SELECT id, name, level, max_level, acquired, effects FROM upgrades WHERE type = ?", ['mining'] )->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $row) { $effects = json_decode($row['effects'] ?? '{}', true) ?: []; $level = intval($row['level'] ?? 0); $acquired = intval($row['acquired'] ?? 0) === 1; if (isset($effects['mining_unlocked']) && $effects['mining_unlocked'] === true && ($acquired || $level > 0)) { $status['unlocked'] = true; } if ($level > 0) { if (isset($effects['mining_yield_multiplier_per_level'])) { $status['yield_multiplier'] *= (1.0 + ($effects['mining_yield_multiplier_per_level'] * $level)); } if (isset($effects['scan_cost_reduction_per_level'])) { $status['scan_cost_multiplier'] *= max(0.1, 1.0 - ($effects['scan_cost_reduction_per_level'] * $level)); } if (isset($effects['loss_chance_reduction_per_level'])) { $status['loss_chance_reduction'] += ($effects['loss_chance_reduction_per_level'] * 100.0) * $level; } if (isset($effects['install_time_reduction_per_level'])) { $status['install_time_multiplier'] *= max(0.1, 1.0 - ($effects['install_time_reduction_per_level'] * $level)); } } } $this->miningStatusCache = $status; return $status; } private function ensureMiningUnlocked() { $status = $this->getMiningStatusAndModifiers(); if (!$status['unlocked']) { return [ 'success' => false, 'message' => 'Mining is locked. Purchase Mining Software to unlock.' ]; } return null; } public function getWalletBalance() { $wallet = $this->db->query("SELECT * FROM crypto_wallet WHERE id = 1")->fetch(); if (!$wallet) { $this->db->query("INSERT INTO crypto_wallet (tsx_balance, total_earned, total_spent) VALUES (0.0, 0.0, 0.0)"); return ['tsx_balance' => 0.0, 'total_earned' => 0.0, 'total_spent' => 0.0]; } return $wallet; } public function getCurrentTsxPrice() { $gameState = $this->game->getGameState(); $currentYear = $gameState['current_year']; $currentMonth = $gameState['current_month']; $currentDay = $gameState['current_day']; $priceData = $this->db->query("
            SELECT * FROM tsx_price_history
            ORDER BY game_year DESC, game_month DESC, game_day DESC
            LIMIT 1
        ")->fetch(); if (!$priceData || $priceData['game_year'] < $currentYear || ($priceData['game_year'] == $currentYear && $priceData['game_month'] < $currentMonth) || ($priceData['game_year'] == $currentYear && $priceData['game_month'] == $currentMonth && $priceData['game_day'] < $currentDay)) { $this->generateDailyPrice($currentYear, $currentMonth, $currentDay); $priceData = $this->db->query("
                SELECT * FROM tsx_price_history
                WHERE game_year = ? AND game_month = ? AND game_day = ?
            ", [$currentYear, $currentMonth, $currentDay])->fetch(); } return $priceData; } public function updateTsxPriceForCurrentDay() { $gameState = $this->game->getGameState(); $currentYear = $gameState['current_year']; $currentMonth = $gameState['current_month']; $currentDay = $gameState['current_day']; $existingPrice = $this->db->query("
            SELECT * FROM tsx_price_history
            WHERE game_year = ? AND game_month = ? AND game_day = ?
        ", [$currentYear, $currentMonth, $currentDay])->fetch(); if (!$existingPrice) { $this->generateDailyPrice($currentYear, $currentMonth, $currentDay); return true; } return false; } private function generateDailyPrice($year, $month, $day) { $previousPrice = $this->db->query("
            SELECT price FROM tsx_price_history
            ORDER BY game_year DESC, game_month DESC, game_day DESC
            LIMIT 1
        ")->fetch(); $basePrice = $previousPrice ? $previousPrice['price'] : 1.0; $changePercent = (mt_rand(-1500, 1500) / 100.0); $event = $this->maybeApplyTsxMarketEvent($year, $month, $day, $changePercent); $newPrice = $basePrice * (1 + ($changePercent / 100)); $newPrice = max(0.1, min(10.0, $newPrice)); $volume = mt_rand(100, 10000) / 100.0; $this->db->query("
            INSERT INTO tsx_price_history (price, game_year, game_month, game_day, change_percent, volume)
            VALUES (?, ?, ?, ?, ?, ?)
        ", [$newPrice, $year, $month, $day, $changePercent, $volume]); $this->db->query("
            DELETE FROM tsx_price_history
            WHERE id NOT IN (
                SELECT id FROM (
                    SELECT id FROM tsx_price_history
                    ORDER BY id DESC
                    LIMIT 15
                ) AS recent
            )
        "); } private function maybeApplyTsxMarketEvent($year, $month, $day, &$changePercent) { $nextYear = $this->getMetaInt($this->metaNextEventYearKey); $nextMonth = $this->getMetaInt($this->metaNextEventMonthKey); $nextDay = $this->getMetaInt($this->metaNextEventDayKey); if (!$nextYear || !$nextMonth || !$nextDay) { $this->scheduleNextTsxEvent($year, $month, $day); $nextYear = $this->getMetaInt($this->metaNextEventYearKey); $nextMonth = $this->getMetaInt($this->metaNextEventMonthKey); $nextDay = $this->getMetaInt($this->metaNextEventDayKey); } if ($this->compareDate($year, $month, $day, $nextYear, $nextMonth, $nextDay) < 0) { return null; } $event = $this->pickRandomTsxEvent(); $impact = $this->randomFloat($event['min'], $event['max']); $impact = round($impact, 2); if ($event['sign'] < 0) { $impact = -abs($impact); } $changePercent += $impact; $this->logEventToFeed($event['label_key'], $impact, $year, $month, $day); $this->scheduleNextTsxEvent($year, $month, $day); return [ 'type' => $event['key'], 'label_key' => $event['label_key'], 'impact' => $impact ]; } private function pickRandomTsxEvent() { $events = [ ['key' => 'network_hype', 'label_key' => 'market_event_network_hype', 'min' => 5.0, 'max' => 20.0, 'sign' => 1], ['key' => 'regulatory_scare', 'label_key' => 'market_event_regulatory_scare', 'min' => 5.0, 'max' => 15.0, 'sign' => -1], ['key' => 'tech_breakthrough', 'label_key' => 'market_event_tech_breakthrough', 'min' => 3.0, 'max' => 10.0, 'sign' => 1], ['key' => 'exchange_outage', 'label_key' => 'market_event_exchange_outage', 'min' => 3.0, 'max' => 8.0, 'sign' => -1] ]; return $events[array_rand($events)]; } private function scheduleNextTsxEvent($year, $month, $day) { $monthsToAdd = mt_rand(7, 12); $totalMonths = ($year * 12) + ($month - 1) + $monthsToAdd; $nextYear = intdiv($totalMonths, 12); $nextMonth = ($totalMonths % 12) + 1; $nextDay = mt_rand(1, 30); $this->setMeta($this->metaNextEventYearKey, (string)$nextYear); $this->setMeta($this->metaNextEventMonthKey, (string)$nextMonth); $this->setMeta($this->metaNextEventDayKey, (string)$nextDay); } private function logEventToFeed($labelKey, $impact, $year, $month, $day) { $sign = $impact >= 0 ? '+' : ''; $msg = json_encode([ 'translation_key' => 'game.crypto.market_event', 'params' => [ 'label_key' => 'game.crypto.' . $labelKey, 'impact' => $sign . number_format($impact, 2) ] ]); $eventTime = sprintf('%02d/%02d/%04d %02d:00', $day, $month, $year, $this->game->getGameState()['current_hour'] ?? 0); $this->db->query( "INSERT INTO event_log (type, message, event_time) VALUES (?, ?, ?)", ['info', $msg, $eventTime] ); } private function compareDate($y1, $m1, $d1, $y2, $m2, $d2) { if ($y1 !== $y2) return $y1 < $y2 ? -1 : 1; if ($m1 !== $m2) return $m1 < $m2 ? -1 : 1; if ($d1 !== $d2) return $d1 < $d2 ? -1 : 1; return 0; } private function randomFloat($min, $max) { return $min + mt_rand() / mt_getrandmax() * ($max - $min); } private function getMetaInt($key) { $row = $this->db->query("SELECT value FROM game_state_meta WHERE key_name = ?", [$key])->fetch(PDO::FETCH_ASSOC); if (!$row) return 0; return intval($row['value']); } private function setMeta($key, $value) { $existing = $this->db->query("SELECT id FROM game_state_meta WHERE key_name = ?", [$key])->fetch(PDO::FETCH_ASSOC); if ($existing) { $this->db->query("UPDATE game_state_meta SET value = ? WHERE key_name = ?", [$value, $key]); } else { $this->db->query("INSERT INTO game_state_meta (key_name, value) VALUES (?, ?)", [$key, $value]); } } public function buyTsx($creditAmount) { $gameState = $this->game->getGameState(); $currentCredits = $gameState['credits']; if ($currentCredits < $creditAmount) { return ['success' => false, 'message' => 'Insufficient credits']; } $priceData = $this->getCurrentTsxPrice(); $tsxAmount = $creditAmount / $priceData['price']; $this->db->query("UPDATE game_state SET credits = credits - ? WHERE id = 1", [$creditAmount]); $this->db->query("
            UPDATE crypto_wallet
            SET tsx_balance = tsx_balance + ?, total_earned = total_earned + ?
            WHERE id = 1
        ", [$tsxAmount, $tsxAmount]); $this->recordTransaction('buy', $tsxAmount, $priceData['price'], $creditAmount, "Bought {$tsxAmount} TSX"); return [ 'success' => true, 'message' => "Successfully bought {$tsxAmount} TSX for {$creditAmount} credits", 'tsx_amount' => $tsxAmount, 'credit_amount' => $creditAmount ]; } public function sellTsx($tsxAmount) { $wallet = $this->getWalletBalance(); if ($wallet['tsx_balance'] < $tsxAmount) { return ['success' => false, 'message' => 'Insufficient TSX balance']; } $priceData = $this->getCurrentTsxPrice(); $creditAmount = $tsxAmount * $priceData['price']; $this->db->query("UPDATE game_state SET credits = credits + ? WHERE id = 1", [$creditAmount]); $this->db->query("
            UPDATE crypto_wallet
            SET tsx_balance = tsx_balance - ?, total_spent = total_spent + ?
            WHERE id = 1
        ", [$tsxAmount, $tsxAmount]); $this->recordTransaction('sell', $tsxAmount, $priceData['price'], $creditAmount, "Sold {$tsxAmount} TSX"); return [ 'success' => true, 'message' => "Successfully sold {$tsxAmount} TSX for {$creditAmount} credits", 'tsx_amount' => $tsxAmount, 'credit_amount' => $creditAmount ]; } private function recordTransaction($type, $tsxAmount, $tsxPrice, $creditAmount, $description) { $gameState = $this->game->getGameState(); $this->db->query("
            INSERT INTO crypto_transactions (type, amount, tsx_price, credit_amount, description, game_year, game_month, game_day)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ", [ $type, $tsxAmount, $tsxPrice, $creditAmount, $description, $gameState['current_year'], $gameState['current_month'], $gameState['current_day'] ]); } public function getTransactionHistory($limit = 4) { return $this->db->query("
            SELECT * FROM crypto_transactions
            ORDER BY created_at DESC
            LIMIT ?
        ", [$limit])->fetchAll(); } public function getPriceHistory($days = 30) { return $this->db->query("
            SELECT * FROM tsx_price_history
            ORDER BY game_year DESC, game_month DESC, game_day DESC
            LIMIT ?
        ", [$days])->fetchAll(); } public function scanForMiningPcs() { $gate = $this->ensureMiningUnlocked(); if ($gate) return $gate; $wallet = $this->getWalletBalance(); $gameState = $this->game->getGameState(); $modifiers = $this->getMiningStatusAndModifiers(); $currentPcCount = $this->db->query("SELECT COUNT(*) as count FROM mining_pcs WHERE status = 'active'")->fetch()['count']; $baseScanCost = 10.0; $scanCost = ($baseScanCost + ($currentPcCount * 2.0)) * ($modifiers['scan_cost_multiplier'] ?? 1.0); $scanCost = max(1.0, $scanCost); if ($wallet['tsx_balance'] < $scanCost) { return ['success' => false, 'message' => 'Insufficient TSX balance for scan']; } $this->db->query("UPDATE crypto_wallet SET tsx_balance = tsx_balance - ? WHERE id = 1", [$scanCost]); $baseChance = 80; $chanceReduction = min(70, $currentPcCount * 5); $finalChance = max(10, $baseChance - $chanceReduction); $pcsFound = 0; $newPcs = []; if (mt_rand(1, 100) <= $finalChance) { $maxPcs = max(1, 4 - intval($currentPcCount / 10)); $weights = [60, 30, 10]; $rand = mt_rand(1, 100); if ($rand <= $weights[0]) { $pcsFound = 1; } elseif ($rand <= $weights[0] + $weights[1]) { $pcsFound = min(2, $maxPcs); } else { $pcsFound = min(3, $maxPcs); } for ($i = 0; $i < $pcsFound; $i++) { $pc = $this->generateRandomPc($gameState['current_year'], $gameState['current_month']); $newPcs[] = $pc; } } $this->db->query("
            INSERT INTO mining_scans (scan_cost, pcs_found, game_year, game_month, game_day)
            VALUES (?, ?, ?, ?, ?)
        ", [ $scanCost, $pcsFound, $gameState['current_year'], $gameState['current_month'], $gameState['current_day'] ]); return [ 'success' => true, 'message' => "Scan completed. Found {$pcsFound} PCs for {$scanCost} TSX", 'scan_cost' => $scanCost, 'pcs_found' => $pcsFound, 'new_pcs' => $newPcs, 'discovery_chance' => $finalChance ]; } private function generateRandomPc($year, $month) { $pcNames = [ 'HomePC-' . mt_rand(1000, 9999), 'OfficeWS-' . mt_rand(100, 999), 'Server-' . mt_rand(10, 99), 'Gaming-' . mt_rand(1000, 9999), 'Laptop-' . mt_rand(100, 999) ]; $name = $pcNames[array_rand($pcNames)]; $ip = mt_rand(1, 254) . '.' . mt_rand(1, 254) . '.' . mt_rand(1, 254) . '.' . mt_rand(1, 254); $powerLevel = mt_rand(10, 200) / 100.0; $dailyTsxRate = $powerLevel * mt_rand(50, 150) / 100.0; $existingPositions = $this->db->query("
            SELECT grid_position FROM mining_pcs
            WHERE status IN ('discovered', 'installing', 'active')
        ")->fetchAll(PDO::FETCH_COLUMN); $gridPosition = null; for ($i = 0; $i < 50; $i++) { if (!in_array($i, $existingPositions)) { $gridPosition = $i; break; } } if ($gridPosition === null) { $gridPosition = mt_rand(0, 49); } $this->db->query("
            INSERT INTO mining_pcs (pc_name, ip_address, power_level, daily_tsx_rate, grid_position, status, discovered_year, discovered_month)
            VALUES (?, ?, ?, ?, ?, 'discovered', ?, ?)
        ", [$name, $ip, $powerLevel, $dailyTsxRate, $gridPosition, $year, $month]); return [ 'id' => $this->db->lastInsertId(), 'pc_name' => $name, 'ip_address' => $ip, 'power_level' => $powerLevel, 'daily_tsx_rate' => $dailyTsxRate, 'grid_position' => $gridPosition ]; } public function getActiveMiningPcs() { $status = $this->getMiningStatusAndModifiers(); if (!$status['unlocked']) { return []; } return $this->db->query("
            SELECT * FROM mining_pcs
            WHERE status IN ('discovered', 'installing', 'active')
            ORDER BY power_level DESC
        ")->fetchAll(); } public function processDailyMining() { $status = $this->getMiningStatusAndModifiers(); if (!$status['unlocked']) { return [ 'total_income' => 0, 'active_pcs' => 0, 'pcs_lost' => 0 ]; } $gameState = $this->game->getGameState(); $currentYear = $gameState['current_year']; $currentMonth = $gameState['current_month']; $activePcs = $this->db->query("
            SELECT * FROM mining_pcs
            WHERE status = 'active'
            AND (last_income_year IS NULL OR last_income_year < ? OR
                 (last_income_year = ? AND (last_income_month IS NULL OR last_income_month < ?)))
        ", [$currentYear, $currentYear, $currentMonth])->fetchAll(); $totalIncome = 0; $yieldMultiplier = $status['yield_multiplier'] ?? 1.0; foreach ($activePcs as $pc) { $attentionLevel = $gameState['attention_level']; $baseLossChance = mt_rand(5, 12); $attentionBonus = min(8, $attentionLevel / 10); $totalLossChance = max(0, $baseLossChance + $attentionBonus - ($status['loss_chance_reduction'] ?? 0)); if (mt_rand(1, 100) <= $totalLossChance) { $this->db->query("
                    UPDATE mining_pcs
                    SET status = 'lost', lost_at = CURRENT_TIMESTAMP
                    WHERE id = ?
                ", [$pc['id']]); $this->recordTransaction('pc_lost', 0, 0, 0, "Lost access to PC: {$pc['pc_name']}"); continue; } $income = $pc['daily_tsx_rate'] * $yieldMultiplier; $totalIncome += $income; $this->db->query("
                UPDATE mining_pcs
                SET last_income_year = ?, last_income_month = ?
                WHERE id = ?
            ", [$currentYear, $currentMonth, $pc['id']]); } if ($totalIncome > 0) { $this->db->query("
                UPDATE crypto_wallet
                SET tsx_balance = tsx_balance + ?, total_earned = total_earned + ?
                WHERE id = 1
            ", [$totalIncome, $totalIncome]); $this->recordTransaction('mining', $totalIncome, 0, 0, "Daily mining income from " . count($activePcs) . " PCs"); } return [ 'total_income' => $totalIncome, 'active_pcs' => count($activePcs), 'pcs_lost' => count($activePcs) - count($this->getActiveMiningPcs()) ]; } public function installMiningSoftware($pcId) { $gate = $this->ensureMiningUnlocked(); if ($gate) return $gate; $pc = $this->db->query("
            SELECT * FROM mining_pcs
            WHERE id = ? AND status = 'discovered'
        ", [$pcId])->fetch(); if (!$pc) { return [ 'success' => false, 'message' => 'PC not found or not available for installation' ]; } $this->db->query("
            UPDATE mining_pcs
            SET status = 'installing'
            WHERE id = ?
        ", [$pcId]); return [ 'success' => true, 'message' => 'Installation started', 'pc_name' => $pc['pc_name'], 'pc_id' => $pcId ]; } public function completeInstallation($pcId) { $gate = $this->ensureMiningUnlocked(); if ($gate) return $gate; $result = $this->db->query("
            UPDATE mining_pcs
            SET status = 'active'
            WHERE id = ? AND status = 'installing'
        ", [$pcId]); if ($result->rowCount() > 0) { $pc = $this->db->query("
                SELECT * FROM mining_pcs WHERE id = ?
            ", [$pcId])->fetch(); return [ 'success' => true, 'message' => 'Installation completed successfully', 'pc' => $pc ]; } return [ 'success' => false, 'message' => 'Installation completion failed' ]; } } 