This repository has been archived on 2026-05-04. You can view files and clone it, but cannot push or open issues or pull requests.
Galaxy-Strike-Online/Client/js/core/Economy.js
2026-01-24 16:47:19 -04:00

2336 lines
92 KiB
JavaScript

/**
* Galaxy Strike Online - Economy System
* Manages credits, gems, transactions, and shop
*/
class Economy {
constructor(gameEngine) {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.log('Economy constructor called');
this.game = gameEngine;
// Currency
this.credits = 1000;
this.gems = 10;
this.premiumCurrency = 0; // For future premium features
// Transaction history
this.transactionHistory = [];
this.transactions = []; // Add missing transactions array
// Owned cosmetics
this.ownedCosmetics = []; // Add missing owned cosmetics array
// Shop categories
this.shopCategories = {
ships: 'Ships',
weapons: 'Weapons',
armors: 'Armors',
materials: 'Materials',
cosmetics: 'Cosmetics',
// upgrades: 'Upgrades', // Temporarily disabled
consumables: 'Consumables',
buildings: 'Buildings'
};
// Random shop system
this.randomShopItems = {}; // Current random items per category
this.shopRefreshInterval = null; // Timer for 2-hour refresh
this.shopHeartbeatInterval = null; // Timer for live countdown updates
this.lastShopRefresh = null; // Timestamp of last refresh
this.SHOP_REFRESH_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours in milliseconds
this.MAX_ITEMS_PER_CATEGORY = 6;
this.categoryPurchaseLimits = {}; // Track purchases per category per refresh
// Shop items
this.shopItems = {
ships: [
// Starter Cruiser Variants
{
id: 'starter_cruiser_common',
name: 'Starter Cruiser',
type: 'ship',
rarity: 'common',
price: 5000,
currency: 'credits',
description: 'Reliable starter cruiser for new pilots',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 15, speed: 10, defense: 12, hull: 100 }
},
{
id: 'starter_cruiser_uncommon',
name: 'Starter Cruiser II',
type: 'ship',
rarity: 'uncommon',
price: 12000,
currency: 'credits',
description: 'Upgraded starter cruiser with enhanced systems',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 18, speed: 12, defense: 15, hull: 120 }
},
{
id: 'starter_cruiser_rare',
name: 'Starter Cruiser III',
type: 'ship',
rarity: 'rare',
price: 25000,
currency: 'credits',
description: 'Elite starter cruiser with advanced weaponry',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 22, speed: 14, defense: 18, hull: 145 }
},
{
id: 'starter_cruiser_epic',
name: 'Starter Cruiser IV',
type: 'ship',
rarity: 'epic',
price: 45000,
currency: 'credits',
description: 'Master starter cruiser with elite modifications',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 26, speed: 16, defense: 22, hull: 175 }
},
{
id: 'starter_cruiser_legendary',
name: 'Starter Cruiser V',
type: 'ship',
rarity: 'legendary',
price: 75000,
currency: 'credits',
description: 'Legendary starter cruiser with unparalleled performance',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 32, speed: 18, defense: 28, hull: 210 }
},
// Light Destroyer Variants
{
id: 'light_destroyer_common',
name: 'Light Destroyer',
type: 'ship',
rarity: 'common',
price: 12000,
currency: 'credits',
description: 'Fast and maneuverable light destroyer',
texture: 'assets/textures/ships/light_destroyer.png',
stats: { attack: 18, speed: 18, defense: 10, hull: 80 }
},
{
id: 'light_destroyer_uncommon',
name: 'Light Destroyer II',
type: 'ship',
rarity: 'uncommon',
price: 28000,
currency: 'credits',
description: 'Enhanced light destroyer with improved speed',
texture: 'assets/textures/ships/light_destroyer.png',
stats: { attack: 22, speed: 22, defense: 12, hull: 95 }
},
{
id: 'light_destroyer_rare',
name: 'Light Destroyer III',
type: 'ship',
rarity: 'rare',
price: 55000,
currency: 'credits',
description: 'Elite light destroyer with superior agility',
texture: 'assets/textures/ships/light_destroyer.png',
stats: { attack: 26, speed: 26, defense: 15, hull: 115 }
},
{
id: 'light_destroyer_epic',
name: 'Light Destroyer IV',
type: 'ship',
rarity: 'epic',
price: 95000,
currency: 'credits',
description: 'Master light destroyer with exceptional speed',
texture: 'assets/textures/ships/light_destroyer.png',
stats: { attack: 30, speed: 30, defense: 18, hull: 140 }
},
{
id: 'light_destroyer_legendary',
name: 'Light Destroyer V',
type: 'ship',
rarity: 'legendary',
price: 150000,
currency: 'credits',
description: 'Legendary light destroyer with unmatched velocity',
texture: 'assets/textures/ships/light_destroyer.png',
stats: { attack: 36, speed: 36, defense: 22, hull: 170 }
},
// Heavy Destroyer Variants
{
id: 'heavy_destroyer_common',
name: 'Heavy Destroyer',
type: 'ship',
rarity: 'common',
price: 25000,
currency: 'credits',
description: 'Powerful heavy destroyer with strong weapons',
texture: 'assets/textures/ships/heavy_destroyer.png',
stats: { attack: 25, speed: 12, defense: 18, hull: 120 }
},
{
id: 'heavy_destroyer_uncommon',
name: 'Heavy Destroyer II',
type: 'ship',
rarity: 'uncommon',
price: 55000,
currency: 'credits',
description: 'Upgraded heavy destroyer with enhanced firepower',
texture: 'assets/textures/ships/heavy_destroyer.png',
stats: { attack: 30, speed: 14, defense: 22, hull: 145 }
},
{
id: 'heavy_destroyer_rare',
name: 'Heavy Destroyer III',
type: 'ship',
rarity: 'rare',
price: 110000,
currency: 'credits',
description: 'Elite heavy destroyer with devastating weapons',
texture: 'assets/textures/ships/heavy_destroyer.png',
stats: { attack: 35, speed: 16, defense: 26, hull: 175 }
},
{
id: 'heavy_destroyer_epic',
name: 'Heavy Destroyer IV',
type: 'ship',
rarity: 'epic',
price: 190000,
currency: 'credits',
description: 'Master heavy destroyer with overwhelming power',
texture: 'assets/textures/ships/heavy_destroyer.png',
stats: { attack: 40, speed: 18, defense: 30, hull: 210 }
},
{
id: 'heavy_destroyer_legendary',
name: 'Heavy Destroyer V',
type: 'ship',
rarity: 'legendary',
price: 300000,
currency: 'credits',
description: 'Legendary heavy destroyer with ultimate destruction',
texture: 'assets/textures/ships/heavy_destroyer.png',
stats: { attack: 48, speed: 20, defense: 36, hull: 255 }
},
// Heavy Cruiser Variants
{
id: 'heavy_cruiser_common',
name: 'Heavy Cruiser',
type: 'ship',
rarity: 'common',
price: 45000,
currency: 'credits',
description: 'Massive heavy cruiser with excellent defense',
texture: 'assets/textures/ships/heavy_cruiser.png',
stats: { attack: 22, speed: 8, defense: 25, hull: 150 }
},
{
id: 'heavy_cruiser_uncommon',
name: 'Heavy Cruiser II',
type: 'ship',
rarity: 'uncommon',
price: 95000,
currency: 'credits',
description: 'Enhanced heavy cruiser with superior armor',
texture: 'assets/textures/ships/heavy_cruiser.png',
stats: { attack: 26, speed: 9, defense: 30, hull: 180 }
},
{
id: 'heavy_cruiser_rare',
name: 'Heavy Cruiser III',
type: 'ship',
rarity: 'rare',
price: 190000,
currency: 'credits',
description: 'Elite heavy cruiser with fortress-like defense',
texture: 'assets/textures/ships/heavy_cruiser.png',
stats: { attack: 30, speed: 10, defense: 36, hull: 220 }
},
{
id: 'heavy_cruiser_epic',
name: 'Heavy Cruiser IV',
type: 'ship',
rarity: 'epic',
price: 320000,
currency: 'credits',
description: 'Master heavy cruiser with impenetrable armor',
texture: 'assets/textures/ships/heavy_cruiser.png',
stats: { attack: 34, speed: 11, defense: 42, hull: 265 }
},
{
id: 'heavy_cruiser_legendary',
name: 'Heavy Cruiser V',
type: 'ship',
rarity: 'legendary',
price: 500000,
currency: 'credits',
description: 'Legendary heavy cruiser with ultimate defense',
texture: 'assets/textures/ships/heavy_cruiser.png',
stats: { attack: 40, speed: 12, defense: 50, hull: 320 }
}
],
weapons: [
// Starter Blaster Variants
{
id: 'starter_blaster_common',
name: 'Starter Blaster',
type: 'weapon',
rarity: 'common',
price: 2000,
currency: 'credits',
description: 'Basic blaster for new pilots',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 10, fireRate: 2, range: 5, energy: 5 }
},
{
id: 'starter_blaster_uncommon',
name: 'Starter Blaster II',
type: 'weapon',
rarity: 'uncommon',
price: 5000,
currency: 'credits',
description: 'Enhanced starter blaster with better fire rate',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 12, fireRate: 2.5, range: 5.5, energy: 6 }
},
{
id: 'starter_blaster_rare',
name: 'Starter Blaster III',
type: 'weapon',
rarity: 'rare',
price: 12000,
currency: 'credits',
description: 'Elite starter blaster with improved damage',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 15, fireRate: 3, range: 6, energy: 7 }
},
{
id: 'starter_blaster_epic',
name: 'Starter Blaster IV',
type: 'weapon',
rarity: 'epic',
price: 25000,
currency: 'credits',
description: 'Master starter blaster with superior performance',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 18, fireRate: 3.5, range: 6.5, energy: 8 }
},
{
id: 'starter_blaster_legendary',
name: 'Starter Blaster V',
type: 'weapon',
rarity: 'legendary',
price: 50000,
currency: 'credits',
description: 'Legendary starter blaster with ultimate power',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 22, fireRate: 4, range: 7, energy: 10 }
},
// Laser Pistol Variants
{
id: 'laser_pistol_common',
name: 'Laser Pistol',
type: 'weapon',
rarity: 'common',
price: 3000,
currency: 'credits',
description: 'Compact laser pistol for close combat',
texture: 'assets/textures/weapons/laser_pistol.png',
stats: { damage: 8, fireRate: 3, range: 4, energy: 3 }
},
{
id: 'laser_pistol_uncommon',
name: 'Laser Pistol II',
type: 'weapon',
rarity: 'uncommon',
price: 7500,
currency: 'credits',
description: 'Enhanced laser pistol with better accuracy',
texture: 'assets/textures/weapons/laser_pistol.png',
stats: { damage: 10, fireRate: 3.5, range: 4.5, energy: 4 }
},
{
id: 'laser_pistol_rare',
name: 'Laser Pistol III',
type: 'weapon',
rarity: 'rare',
price: 18000,
currency: 'credits',
description: 'Elite laser pistol with piercing shots',
texture: 'assets/textures/weapons/laser_pistol.png',
stats: { damage: 13, fireRate: 4, range: 5, energy: 5 }
},
{
id: 'laser_pistol_epic',
name: 'Laser Pistol IV',
type: 'weapon',
rarity: 'epic',
price: 35000,
currency: 'credits',
description: 'Master laser pistol with rapid fire capability',
texture: 'assets/textures/weapons/laser_pistol.png',
stats: { damage: 16, fireRate: 4.5, range: 5.5, energy: 6 }
},
{
id: 'laser_pistol_legendary',
name: 'Laser Pistol V',
type: 'weapon',
rarity: 'legendary',
price: 70000,
currency: 'credits',
description: 'Legendary laser pistol with devastating power',
texture: 'assets/textures/weapons/laser_pistol.png',
stats: { damage: 20, fireRate: 5, range: 6, energy: 8 }
},
// Laser Sniper Rifle Variants
{
id: 'laser_sniper_rifle_common',
name: 'Laser Sniper Rifle',
type: 'weapon',
rarity: 'common',
price: 8000,
currency: 'credits',
description: 'Long-range laser sniper rifle for precision attacks',
texture: 'assets/textures/weapons/laser_sniper_rifle.png',
stats: { damage: 25, fireRate: 1, range: 10, energy: 8 }
},
{
id: 'laser_sniper_rifle_uncommon',
name: 'Laser Sniper Rifle II',
type: 'weapon',
rarity: 'uncommon',
price: 20000,
currency: 'credits',
description: 'Enhanced laser sniper rifle with better penetration',
texture: 'assets/textures/weapons/laser_sniper_rifle.png',
stats: { damage: 30, fireRate: 1.2, range: 11, energy: 9 }
},
{
id: 'laser_sniper_rifle_rare',
name: 'Laser Sniper Rifle III',
type: 'weapon',
rarity: 'rare',
price: 50000,
currency: 'credits',
description: 'Elite laser sniper rifle with deadly accuracy',
texture: 'assets/textures/weapons/laser_sniper_rifle.png',
stats: { damage: 36, fireRate: 1.4, range: 12, energy: 10 }
},
{
id: 'laser_sniper_rifle_epic',
name: 'Laser Sniper Rifle IV',
type: 'weapon',
rarity: 'epic',
price: 100000,
currency: 'credits',
description: 'Master laser sniper rifle with extreme range',
texture: 'assets/textures/weapons/laser_sniper_rifle.png',
stats: { damage: 42, fireRate: 1.6, range: 13, energy: 12 }
},
{
id: 'laser_sniper_rifle_legendary',
name: 'Laser Sniper Rifle V',
type: 'weapon',
rarity: 'legendary',
price: 200000,
currency: 'credits',
description: 'Legendary laser sniper rifle with unparalleled precision',
texture: 'assets/textures/weapons/laser_sniper_rifle.png',
stats: { damage: 50, fireRate: 2, range: 15, energy: 15 }
}
],
armors: [
// Basic Armor Variants
{
id: 'basic_armor_common',
name: 'Basic Armor',
type: 'armor',
rarity: 'common',
price: 1500,
currency: 'credits',
description: 'Light protection for beginners',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 5, durability: 20, weight: 2, energyShield: 0 }
},
{
id: 'basic_armor_uncommon',
name: 'Basic Armor II',
type: 'armor',
rarity: 'uncommon',
price: 4000,
currency: 'credits',
description: 'Improved basic armor with better durability',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 7, durability: 25, weight: 2.2, energyShield: 2 }
},
{
id: 'basic_armor_rare',
name: 'Basic Armor III',
type: 'armor',
rarity: 'rare',
price: 10000,
currency: 'credits',
description: 'Elite basic armor with energy shielding',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 10, durability: 30, weight: 2.5, energyShield: 5 }
},
{
id: 'basic_armor_epic',
name: 'Basic Armor IV',
type: 'armor',
rarity: 'epic',
price: 20000,
currency: 'credits',
description: 'Master basic armor with advanced protection',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 13, durability: 35, weight: 2.8, energyShield: 8 }
},
{
id: 'basic_armor_legendary',
name: 'Basic Armor V',
type: 'armor',
rarity: 'legendary',
price: 40000,
currency: 'credits',
description: 'Legendary basic armor with ultimate defense',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 17, durability: 40, weight: 3, energyShield: 12 }
},
// Medium Armor Variants
{
id: 'medium_armor_common',
name: 'Medium Armor',
type: 'armor',
rarity: 'common',
price: 5000,
currency: 'credits',
description: 'Balanced armor for general combat',
texture: 'assets/textures/armors/medium_armor.png',
stats: { defense: 12, durability: 40, weight: 5, energyShield: 3 }
},
{
id: 'medium_armor_uncommon',
name: 'Medium Armor II',
type: 'armor',
rarity: 'uncommon',
price: 12000,
currency: 'credits',
description: 'Enhanced medium armor with better shielding',
texture: 'assets/textures/armors/medium_armor.png',
stats: { defense: 15, durability: 50, weight: 5.5, energyShield: 6 }
},
{
id: 'medium_armor_rare',
name: 'Medium Armor III',
type: 'armor',
rarity: 'rare',
price: 30000,
currency: 'credits',
description: 'Elite medium armor with advanced systems',
texture: 'assets/textures/armors/medium_armor.png',
stats: { defense: 19, durability: 60, weight: 6, energyShield: 10 }
},
{
id: 'medium_armor_epic',
name: 'Medium Armor IV',
type: 'armor',
rarity: 'epic',
price: 60000,
currency: 'credits',
description: 'Master medium armor with superior protection',
texture: 'assets/textures/armors/medium_armor.png',
stats: { defense: 23, durability: 70, weight: 6.5, energyShield: 15 }
},
{
id: 'medium_armor_legendary',
name: 'Medium Armor V',
type: 'armor',
rarity: 'legendary',
price: 100000,
currency: 'credits',
description: 'Legendary medium armor with ultimate defense',
texture: 'assets/textures/armors/medium_armor.png',
stats: { defense: 28, durability: 80, weight: 7, energyShield: 20 }
},
// Heavy Armor Variants
{
id: 'heavy_armor_common',
name: 'Heavy Armor',
type: 'armor',
rarity: 'common',
price: 10000,
currency: 'credits',
description: 'Maximum protection with increased weight',
texture: 'assets/textures/armors/heavy_armor.png',
stats: { defense: 20, durability: 60, weight: 8, energyShield: 5 }
},
{
id: 'heavy_armor_uncommon',
name: 'Heavy Armor II',
type: 'armor',
rarity: 'uncommon',
price: 25000,
currency: 'credits',
description: 'Upgraded heavy armor with better energy shielding',
texture: 'assets/textures/armors/heavy_armor.png',
stats: { defense: 25, durability: 75, weight: 9, energyShield: 10 }
},
{
id: 'heavy_armor_rare',
name: 'Heavy Armor III',
type: 'armor',
rarity: 'rare',
price: 60000,
currency: 'credits',
description: 'Elite heavy armor with advanced protection systems',
texture: 'assets/textures/armors/heavy_armor.png',
stats: { defense: 31, durability: 90, weight: 10, energyShield: 16 }
},
{
id: 'heavy_armor_epic',
name: 'Heavy Armor IV',
type: 'armor',
rarity: 'epic',
price: 120000,
currency: 'credits',
description: 'Master heavy armor with superior defense capabilities',
texture: 'assets/textures/armors/heavy_armor.png',
stats: { defense: 37, durability: 105, weight: 11, energyShield: 23 }
},
{
id: 'heavy_armor_legendary',
name: 'Heavy Armor V',
type: 'armor',
rarity: 'legendary',
price: 200000,
currency: 'credits',
description: 'Legendary heavy armor with ultimate protection',
texture: 'assets/textures/armors/heavy_armor.png',
stats: { defense: 45, durability: 120, weight: 12, energyShield: 30 }
}
],
cosmetics: [
{
id: 'blue_paint',
name: 'Blue Paint Job',
type: 'cosmetic',
rarity: 'common',
price: 100,
currency: 'gems',
description: 'Custom blue paint for your ship'
},
{
id: 'golden_trim',
name: 'Golden Trim',
type: 'cosmetic',
rarity: 'rare',
price: 500,
currency: 'gems',
description: 'Luxurious golden trim for your ship'
}
],
consumables: [
{
id: 'health_kit',
name: 'Health Kit',
type: 'consumable',
rarity: 'common',
price: 15,
currency: 'credits',
description: 'Restores 50 health points',
texture: 'assets/textures/items/health_pack.png',
effect: { heal: 50 }
},
{
id: 'mega_health_kit',
name: 'Mega Health Kit',
type: 'consumable',
rarity: 'uncommon',
price: 50,
currency: 'credits',
description: 'Restores full health',
texture: 'assets/textures/items/mega_health_pack.png',
effect: { heal: 999 }
}
],
buildings: [
{
id: 'command_center',
name: 'Command Center',
type: 'building',
rarity: 'uncommon',
price: 50000,
currency: 'credits',
description: 'Central command facility for base operations and coordination',
texture: 'assets/textures/base/command_center.png',
stats: { command: 10, efficiency: 15, capacity: 20 }
},
{
id: 'mining_facility',
name: 'Mining Facility',
type: 'building',
rarity: 'common',
price: 25000,
currency: 'credits',
description: 'Automated mining facility for resource extraction and processing',
texture: 'assets/textures/base/mining_facility.png',
stats: { production: 12, efficiency: 8, storage: 15 }
}
],
materials: [
{
id: 'iron_ore',
name: 'Iron Ore',
type: 'material',
rarity: 'common',
price: 10,
currency: 'credits',
description: 'Basic metal ore used for crafting weapons and armor',
texture: 'assets/textures/items/iron_ore.png',
stackable: true
},
{
id: 'copper_ore',
name: 'Copper Ore',
type: 'material',
rarity: 'common',
price: 8,
currency: 'credits',
description: 'Conductive metal ore used for wiring and electronics',
texture: 'assets/textures/items/copper_ore.png',
stackable: true
},
{
id: 'tin_bar',
name: 'Tin Bar',
type: 'material',
rarity: 'common',
price: 12,
currency: 'credits',
description: 'Refined tin bar used for alloys and soldering',
texture: 'assets/textures/items/tin_bar.png',
stackable: true
},
{
id: 'copper_wire',
name: 'Copper Wire',
type: 'material',
rarity: 'common',
price: 8,
currency: 'credits',
description: 'Conductive wiring for electronic components',
texture: 'assets/textures/items/copper_wire.png',
stackable: true
},
{
id: 'energy_crystal',
name: 'Energy Crystal',
type: 'material',
rarity: 'uncommon',
price: 25,
currency: 'credits',
description: 'Crystallized energy source for power systems',
texture: 'assets/textures/items/energy_crystal.png',
stackable: true
},
{
id: 'leather',
name: 'Leather',
type: 'material',
rarity: 'common',
price: 12,
currency: 'credits',
description: 'Durable material for crafting armor and accessories',
texture: 'assets/textures/items/leather.png',
stackable: true
},
{
id: 'herbs',
name: 'Herbs',
type: 'material',
rarity: 'common',
price: 5,
currency: 'credits',
description: 'Medicinal herbs used for healing items',
texture: 'assets/textures/items/herbs.png',
stackable: true
},
{
id: 'bandages',
name: 'Bandages',
type: 'material',
rarity: 'common',
price: 3,
currency: 'credits',
description: 'Medical bandages used for crafting healing items',
texture: 'assets/textures/items/bandages.png',
stackable: true
},
{
id: 'steel_plate',
name: 'Steel Plate',
type: 'material',
rarity: 'uncommon',
price: 30,
currency: 'credits',
description: 'Reinforced steel plates used for advanced armor and ship components',
texture: 'assets/textures/items/stell_plate.png',
stackable: true
},
{
id: 'advanced_component',
name: 'Advanced Component',
type: 'material',
rarity: 'rare',
price: 75,
currency: 'credits',
description: 'High-tech component used for advanced equipment and upgrades',
texture: 'assets/textures/items/advanced_component.png',
stackable: true
},
{
id: 'advanced_components',
name: 'Advanced Components Set',
type: 'material',
rarity: 'epic',
price: 150,
currency: 'credits',
description: 'Complete set of advanced components for high-end manufacturing',
texture: 'assets/textures/items/advanced_components.png',
stackable: true
},
{
id: 'basic_circuitboard',
name: 'Basic Circuit Board',
type: 'material',
rarity: 'common',
price: 20,
currency: 'credits',
description: 'Basic electronic circuit board for simple devices',
texture: 'assets/textures/items/basic_circuitboard.png',
stackable: true
},
{
id: 'common_circuitboard',
name: 'Common Circuit Board',
type: 'material',
rarity: 'common',
price: 35,
currency: 'credits',
description: 'Standard circuit board for most electronic equipment',
texture: 'assets/textures/items/common_circuitboard.png',
stackable: true
},
{
id: 'advanced_circuitboard',
name: 'Advanced Circuit Board',
type: 'material',
rarity: 'rare',
price: 100,
currency: 'credits',
description: 'High-performance circuit board for advanced systems',
texture: 'assets/textures/items/advanced_circuitboard.png',
stackable: true
},
{
id: 'battery',
name: 'Battery',
type: 'material',
rarity: 'uncommon',
price: 35,
currency: 'credits',
description: 'Power batteries used for energy-based equipment',
texture: 'assets/textures/items/battery.png',
stackable: true
},
{
id: 'advanced_components',
name: 'Advanced Components',
type: 'material',
rarity: 'rare',
price: 150,
currency: 'credits',
description: 'Sophisticated electronic components for advanced ship systems',
stackable: true
}
]
};
// Owned cosmetics
this.ownedCosmetics = [];
if (debugLogger) debugLogger.log('Economy constructor completed', {
initialCredits: this.credits,
initialGems: this.gems,
shopCategoriesCount: Object.keys(this.shopCategories).length,
totalShopItems: Object.values(this.shopItems).reduce((total, category) => total + category.length, 0)
});
}
async initialize() {
const debugLogger = window.debugLogger;
console.log('[ECONOMY] Economy system initializing');
if (debugLogger) await debugLogger.startStep('economyInitialize');
try {
if (debugLogger) await debugLogger.logStep('Economy initialization started', {
currentCredits: this.credits,
currentGems: this.gems,
transactionHistoryLength: this.transactionHistory.length
});
// Setup shop purchase event listeners
this.setupShopEventListeners();
// Initialize random shop system
this.initializeRandomShop();
console.log('[ECONOMY] Economy system initialization completed');
if (debugLogger) await debugLogger.endStep('economyInitialize');
} catch (error) {
console.error('[ECONOMY] Error during initialization:', error);
if (debugLogger) await debugLogger.errorEvent(error, 'Economy Initialize');
}
}
initializeRandomShop() {
const debugLogger = window.debugLogger;
console.log('[ECONOMY] Initializing random shop system');
// Shop data is now loaded through the main save/load system in load()
// Only initialize if no shop data exists (new game)
if (!this.lastShopRefresh || Object.keys(this.randomShopItems).length === 0) {
console.log('[ECONOMY] No shop data found, generating new shop');
this.refreshRandomShop();
} else {
console.log('[ECONOMY] Shop data already loaded from save');
// Start the refresh timer for ongoing updates
this.startShopRefreshTimer();
}
if (debugLogger) debugLogger.logStep('Random shop system initialized', {
hasShopData: Object.keys(this.randomShopItems).length > 0,
lastRefresh: this.lastShopRefresh
});
}
refreshRandomShop() {
const debugLogger = window.debugLogger;
console.log('[ECONOMY] Refreshing random shop items');
const categories = Object.keys(this.shopCategories);
this.randomShopItems = {};
categories.forEach(category => {
const categoryItems = this.shopItems[category] || [];
if (categoryItems.length === 0) return;
// Group items by their base ID (remove tier suffixes)
const baseItemGroups = {};
categoryItems.forEach(item => {
// Extract base ID by removing tier suffixes (common, uncommon, rare, epic, legendary)
const baseId = item.id.replace(/_(common|uncommon|rare|epic|legendary)$/, '');
if (!baseItemGroups[baseId]) {
baseItemGroups[baseId] = [];
}
baseItemGroups[baseId].push(item);
});
// Get all unique base items
const uniqueBaseItems = Object.keys(baseItemGroups);
// Randomly select up to MAX_ITEMS_PER_CATEGORY base items
const shuffledBaseItems = [...uniqueBaseItems].sort(() => Math.random() - 0.5);
const selectedBaseItems = shuffledBaseItems.slice(0, Math.min(this.MAX_ITEMS_PER_CATEGORY, shuffledBaseItems.length));
// For each selected base item, randomly pick one tier variant
this.randomShopItems[category] = selectedBaseItems.map(baseId => {
const variants = baseItemGroups[baseId];
const selectedVariant = variants[Math.floor(Math.random() * variants.length)];
return {
...selectedVariant,
id: `${selectedVariant.id}_random_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
originalId: selectedVariant.id,
baseId: baseId,
price: Math.round(selectedVariant.price * (0.8 + Math.random() * 0.4)), // ±20% price variation
isRandomItem: true,
refreshTimestamp: Date.now()
};
});
// Reset purchase limits for this category
this.categoryPurchaseLimits[category] = 0;
if (debugLogger) debugLogger.logStep('Random shop category refreshed', {
category: category,
totalBaseItems: uniqueBaseItems.length,
baseItemsSelected: selectedBaseItems.length,
itemsGenerated: this.randomShopItems[category].length,
selectedVariants: this.randomShopItems[category].map(item => ({
baseId: item.baseId,
variantId: item.originalId,
rarity: item.rarity,
name: item.name
})),
purchaseLimitReset: true
});
});
this.lastShopRefresh = Date.now();
this.saveRandomShopData();
this.startShopRefreshTimer();
// Update UI if shop tab is active
this.updateShopUI();
this.game.showNotification('Shop inventory refreshed!', 'info', 3000);
if (debugLogger) debugLogger.logStep('Random shop refresh completed', {
categoriesRefreshed: categories.length,
totalItemsGenerated: Object.values(this.randomShopItems).flat().length,
nextRefresh: new Date(this.lastShopRefresh + this.SHOP_REFRESH_INTERVAL)
});
}
startShopRefreshTimer() {
// Clear existing timers
if (this.shopRefreshInterval) {
clearInterval(this.shopRefreshInterval);
}
if (this.shopHeartbeatInterval) {
clearInterval(this.shopHeartbeatInterval);
}
// Set up heartbeat timer for live countdown updates (every second)
this.shopHeartbeatInterval = setInterval(() => {
this.updateShopCountdown();
}, 1000);
// Set up refresh timer (every 2 hours)
this.shopRefreshInterval = setInterval(() => {
this.refreshRandomShop();
}, this.SHOP_REFRESH_INTERVAL);
console.log('[ECONOMY] Shop refresh timers started');
}
updateShopCountdown() {
// Only update if shop tab is active and using random shop
const shopTab = document.getElementById('shop-tab');
if (!shopTab || shopTab.style.display === 'none') {
return;
}
const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships';
if (!this.randomShopItems[activeCategory]) {
return;
}
// Find and update the countdown element
const countdownElement = document.querySelector('.refresh-countdown');
if (countdownElement) {
countdownElement.innerHTML = `
<i class="fas fa-clock"></i>
Next refresh in: ${this.getShopRefreshCountdown()}
`;
}
}
stopShopRefreshTimer() {
if (this.shopRefreshInterval) {
clearInterval(this.shopRefreshInterval);
this.shopRefreshInterval = null;
}
if (this.shopHeartbeatInterval) {
clearInterval(this.shopHeartbeatInterval);
this.shopHeartbeatInterval = null;
}
console.log('[ECONOMY] Shop refresh timers stopped');
}
loadRandomShopData() {
try {
const savedData = localStorage.getItem('gso_random_shop');
if (savedData) {
const data = JSON.parse(savedData);
this.randomShopItems = data.randomShopItems || {};
this.categoryPurchaseLimits = data.categoryPurchaseLimits || {};
this.lastShopRefresh = data.lastShopRefresh || null;
console.log('[ECONOMY] Random shop data loaded from localStorage');
}
} catch (error) {
console.error('[ECONOMY] Error loading random shop data:', error);
this.randomShopItems = {};
this.categoryPurchaseLimits = {};
this.lastShopRefresh = null;
}
}
saveRandomShopData() {
try {
const data = {
randomShopItems: this.randomShopItems,
categoryPurchaseLimits: this.categoryPurchaseLimits,
lastShopRefresh: this.lastShopRefresh
};
localStorage.setItem('gso_random_shop', JSON.stringify(data));
console.log('[ECONOMY] Random shop data saved to localStorage');
} catch (error) {
console.error('[ECONOMY] Error saving random shop data:', error);
}
}
shouldRefreshShop() {
// Check if enough time has passed since the last actual refresh
if (!this.lastShopRefresh) {
return true; // No previous refresh, need to refresh
}
const now = Date.now();
const timeSinceRefresh = now - this.lastShopRefresh;
return timeSinceRefresh >= this.SHOP_REFRESH_INTERVAL;
}
getShopRefreshCountdown() {
const schedule = this.getRefreshSchedule();
const now = Date.now();
// Calculate time until next server refresh
const timeUntilRefresh = schedule.nextRefresh.getTime() - now;
if (timeUntilRefresh <= 0) {
return 'Refreshing...';
}
return this.formatTimeRemaining(timeUntilRefresh);
}
getNextRefreshTime() {
// Calculate the next refresh time based on server time
const now = Date.now();
if (!this.lastShopRefresh) {
// If no last refresh, next refresh is now + interval
return new Date(now + this.SHOP_REFRESH_INTERVAL);
}
// Calculate next refresh time
const timeSinceRefresh = now - this.lastShopRefresh;
const intervalsPassed = Math.floor(timeSinceRefresh / this.SHOP_REFRESH_INTERVAL);
const nextRefreshTime = this.lastShopRefresh + ((intervalsPassed + 1) * this.SHOP_REFRESH_INTERVAL);
return new Date(nextRefreshTime);
}
getRefreshSchedule() {
// Generate a consistent refresh schedule based on server time
const now = new Date();
const currentHour = now.getHours();
// Refresh at even hours: 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22
let nextRefreshHour;
if (currentHour % 2 === 0 && now.getMinutes() === 0 && now.getSeconds() === 0) {
// Exactly on an even hour at :00:00, next refresh is in 2 hours
nextRefreshHour = currentHour + 2;
} else {
// Find the next even hour
nextRefreshHour = currentHour + (2 - (currentHour % 2));
}
// Handle midnight wrap-around
if (nextRefreshHour >= 24) {
nextRefreshHour = nextRefreshHour % 24;
}
const nextRefresh = new Date(now);
nextRefresh.setHours(nextRefreshHour, 0, 0, 0);
// If the calculated time is in the past (shouldn't happen but just in case), add 2 hours
if (nextRefresh <= now) {
nextRefresh.setHours(nextRefresh.getHours() + 2);
}
console.log('[ECONOMY] Current time:', now.toLocaleTimeString());
console.log('[ECONOMY] Next refresh at:', nextRefresh.toLocaleTimeString());
return {
nextRefresh: nextRefresh,
interval: 2 * 60 * 60 * 1000, // 2 hours
schedule: 'Every 2 hours at even hours (2,4,6,8,10,12,14,16,18,20,22,00)'
};
}
formatTimeRemaining(timeUntilRefresh) {
const hours = Math.floor(timeUntilRefresh / (1000 * 60 * 60));
const minutes = Math.floor((timeUntilRefresh % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeUntilRefresh % (1000 * 60)) / 1000);
if (hours > 0) {
return `${hours}h ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
} else {
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
}
getItemCategory(item) {
// Determine category based on item type
switch (item.type) {
case 'ship': return 'ships';
case 'weapon': return 'weapons';
case 'armor': return 'armors';
case 'material': return 'materials';
case 'cosmetic': return 'cosmetics';
case 'upgrade': return 'upgrades';
case 'consumable': return 'consumables';
case 'building': return 'buildings';
default:
// For random items, check if we can find them in shop categories
for (const [category, items] of Object.entries(this.shopItems)) {
if (items.some(shopItem => shopItem.id === item.originalId || shopItem.id === item.id)) {
return category;
}
}
return 'unknown';
}
}
setupShopEventListeners() {
const debugLogger = window.debugLogger;
console.log('[ECONOMY] Setting up shop event listeners');
// Remove existing listeners to prevent duplicates
const shopItemsElement = document.getElementById('shopItems');
if (shopItemsElement) {
// Clone the element to remove all event listeners
const newElement = shopItemsElement.cloneNode(true);
shopItemsElement.parentNode.replaceChild(newElement, shopItemsElement);
// Setup shop purchase button event delegation on the new element
newElement.addEventListener('click', (event) => {
const purchaseBtn = event.target.closest('.shop-item-purchase-btn');
if (purchaseBtn && !purchaseBtn.disabled) {
const itemId = purchaseBtn.getAttribute('data-item-id');
console.log(`[ECONOMY] Shop purchase button clicked for item: ${itemId}`);
if (itemId) {
this.purchaseItem(itemId, 1);
} else {
console.error('[ECONOMY] No item ID found on purchase button');
}
}
});
console.log('[ECONOMY] Shop event listeners setup completed');
} else {
console.error('[ECONOMY] Shop items element not found');
}
}
// Currency management
addCredits(amount, source = 'unknown') {
const oldCredits = this.credits;
this.credits += amount;
this.addTransaction('credits', amount, source);
console.log(`[ECONOMY] Added ${amount} credits from ${source}. New total: ${this.credits}`);
// Update UI to show new credit amount
if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
this.game.systems.ui.updateUI();
}
this.game.showNotification(`+${this.game.formatNumber(amount)} credits`, 'success', 2000);
}
removeCredits(amount) {
const oldCredits = this.credits;
if (this.credits < amount) {
this.game.showNotification('Not enough credits!', 'error', 3000);
return false;
}
this.credits -= amount;
this.addTransaction('credits', -amount, 'purchase');
console.log(`[ECONOMY] Removed ${amount} credits. New total: ${this.credits}`);
// Update UI to show new credit amount
if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
this.game.systems.ui.updateUI();
}
return true;
}
addGems(amount, source = 'unknown') {
const oldGems = this.gems;
this.gems += amount;
this.addTransaction('gems', amount, source);
console.log(`[ECONOMY] Added ${amount} gems from ${source}. New total: ${this.gems}`);
// Update UI to show new gem amount
if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
this.game.systems.ui.updateUI();
}
this.game.showNotification(`+${this.game.formatNumber(amount)} gems`, 'success', 2000);
}
removeGems(amount) {
const oldGems = this.gems;
if (this.gems < amount) {
this.game.showNotification('Not enough gems!', 'error', 3000);
return false;
}
this.gems -= amount;
this.addTransaction('gems', -amount, 'purchase');
console.log(`[ECONOMY] Removed ${amount} gems. New total: ${this.gems}`);
// Update UI to show new gem amount
if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
this.game.systems.ui.updateUI();
}
return true;
}
// Transaction tracking
addTransaction(currency, amount, source) {
const debugLogger = window.debugLogger;
const transaction = {
id: Date.now() + Math.random().toString(36).substr(2, 9),
currency: currency,
amount: amount,
source: source,
timestamp: Date.now()
};
this.transactionHistory.unshift(transaction);
// Keep only last 100 transactions
const oldLength = this.transactionHistory.length;
if (this.transactionHistory.length > 100) {
this.transactionHistory = this.transactionHistory.slice(0, 100);
}
if (debugLogger) debugLogger.logStep('Transaction recorded', {
transactionId: transaction.id,
currency: currency,
amount: amount,
source: source,
timestamp: transaction.timestamp,
historyLength: this.transactionHistory.length,
wasTrimmed: oldLength > 100,
removedCount: oldLength > 100 ? oldLength - 100 : 0
});
}
// Shop functionality
purchaseItem(itemId, quantity = 1) {
const debugLogger = window.debugLogger;
const item = this.findShopItem(itemId);
if (!item) {
if (debugLogger) debugLogger.logStep('Item purchase failed - item not found', {
itemId: itemId,
quantity: quantity
});
this.game.showNotification('Item not found in shop', 'error', 3000);
return false;
}
const totalCost = item.price * quantity;
const currency = item.currency;
const oldCredits = this.credits;
const oldGems = this.gems;
if (debugLogger) debugLogger.logStep('Item purchase attempted', {
itemId: itemId,
itemName: item.name,
itemType: item.type,
quantity: quantity,
unitPrice: item.price,
totalCost: totalCost,
currency: currency,
currentCredits: oldCredits,
currentGems: oldGems
});
// Check if player can afford
if (currency === 'credits' && this.credits < totalCost) {
if (debugLogger) debugLogger.logStep('Item purchase failed - insufficient credits', {
totalCost: totalCost,
currentCredits: oldCredits,
deficit: totalCost - oldCredits
});
this.game.showNotification('Not enough credits!', 'error', 3000);
return false;
}
if (currency === 'gems' && this.gems < totalCost) {
if (debugLogger) debugLogger.logStep('Item purchase failed - insufficient gems', {
totalCost: totalCost,
currentGems: oldGems,
deficit: totalCost - oldGems
});
this.game.showNotification('Not enough gems!', 'error', 3000);
return false;
}
// Check if already owns this cosmetic
if (item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id)) {
this.game.showNotification('You already own this cosmetic!', 'error', 3000);
return false;
}
// Process payment and give item based on type
if (currency === 'credits') {
this.credits -= totalCost;
} else if (currency === 'gems') {
this.gems -= totalCost;
}
switch (item.type) {
case 'ship':
this.purchaseShip(item, quantity);
break;
case 'cosmetic':
this.purchaseCosmetic(item, quantity);
break;
case 'consumable':
this.purchaseConsumable(item, quantity);
break;
case 'material':
this.purchaseMaterial(item, quantity);
break;
default:
console.warn(`[ECONOMY] Unknown item type: ${item.type}`);
return false;
}
// Random shop items don't have purchase limits - unlimited purchases allowed
if (item.isRandomItem) {
const category = this.getItemCategory(item);
if (debugLogger) debugLogger.logStep('Random shop purchase completed', {
itemId: itemId,
itemName: item.name,
category: category
});
}
// Update inventory UI to show the new item
if (this.game.systems.ui && this.game.systems.ui.updateInventory) {
this.game.systems.ui.updateInventory();
} else if (this.game.systems.ui && this.game.systems.ui.updateUI) {
this.game.systems.ui.updateUI();
} else {
console.warn('No UI update method available after purchase');
}
if (debugLogger) debugLogger.logStep('Item purchase completed successfully', {
itemId: itemId,
itemName: item.name,
itemType: item.type,
quantity: quantity,
totalCost: totalCost,
currency: currency,
oldCredits: oldCredits,
newCredits: this.credits,
oldGems: oldGems,
newGems: this.gems
});
// Update UI without calling updateShopUI to avoid circular updates
return true;
}
findShopItem(itemId) {
const debugLogger = window.debugLogger;
for (const category of Object.values(this.shopItems)) {
const item = category.find(i => i.id === itemId);
if (item) {
if (debugLogger) debugLogger.logStep('Shop item found', {
itemId: itemId,
itemName: item.name,
itemType: item.type,
itemPrice: item.price,
itemCurrency: item.currency
});
return item;
}
}
if (debugLogger) debugLogger.logStep('Shop item not found', {
itemId: itemId,
availableCategories: Object.keys(this.shopItems)
});
return null;
}
purchaseShip(ship) {
const debugLogger = window.debugLogger;
const player = this.game.systems.player;
const oldShipName = player.ship.name;
const oldShipClass = player.ship.class;
const oldAttributes = { ...player.attributes };
if (debugLogger) debugLogger.logStep('Ship purchase processing', {
shipId: ship.id,
shipName: ship.name,
shipType: ship.type,
shipStats: ship.stats,
oldShipName: oldShipName,
oldShipClass: oldShipClass
});
// Add ship to player's ship collection
// Add ship to base gallery
if (this.game.systems.baseSystem) {
this.game.systems.baseSystem.addShipToGallery(ship);
if (debugLogger) debugLogger.logStep('Ship added to base gallery');
}
// Replace current ship (no upgrade functionality)
player.ship.name = ship.name;
player.ship.class = ship.type;
player.ship.level = 1; // Reset level for new ship
player.ship.texture = ship.texture; // Copy texture for image display
// Set ship-specific stats (not just attributes)
if (ship.stats.health) {
player.ship.maxHealth = ship.stats.health;
player.ship.health = ship.stats.health;
}
if (ship.stats.attack) {
player.ship.attack = ship.stats.attack;
}
if (ship.stats.defense) {
player.ship.defense = ship.stats.defense;
}
if (ship.stats.speed) {
player.ship.speed = ship.stats.speed;
}
if (ship.stats.criticalChance) {
player.ship.criticalChance = ship.stats.criticalChance;
}
if (ship.stats.criticalDamage) {
player.ship.criticalDamage = ship.stats.criticalDamage;
}
// Also update player attributes for compatibility
for (const [stat, value] of Object.entries(ship.stats)) {
if (player.attributes[stat] !== undefined) {
player.attributes[stat] = value; // Replace stats instead of adding
}
}
player.updateUI();
// Also update ShipSystem display
if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) {
this.game.systems.ship.updateCurrentShipDisplay();
}
if (debugLogger) debugLogger.logStep('Ship purchase completed', {
shipId: ship.id,
oldShipName: oldShipName,
newShipName: player.ship.name,
oldShipClass: oldShipClass,
newShipClass: player.ship.class,
attributeChanges: Object.entries(ship.stats).map(([stat, value]) => ({
stat: stat,
oldValue: oldAttributes[stat],
newValue: player.attributes[stat],
change: value
}))
});
}
purchaseCosmetic(cosmetic) {
const debugLogger = window.debugLogger;
const oldOwnedCount = this.ownedCosmetics.length;
// Add to owned cosmetics
this.ownedCosmetics.push(cosmetic.id);
this.game.showNotification(`Cosmetic unlocked: ${cosmetic.name}`, 'success', 3000);
if (debugLogger) debugLogger.logStep('Cosmetic purchase completed', {
cosmeticId: cosmetic.id,
cosmeticName: cosmetic.name,
oldOwnedCount: oldOwnedCount,
newOwnedCount: this.ownedCosmetics.length,
totalOwnedCosmetics: this.ownedCosmetics.length
});
}
purchaseConsumable(consumable, quantity) {
const debugLogger = window.debugLogger;
const inventory = this.game.systems.inventory;
const oldInventorySize = inventory ? inventory.items.length : 0;
if (debugLogger) debugLogger.logStep('Consumable purchase processing', {
consumableId: consumable.id,
consumableName: consumable.name,
quantity: quantity,
effect: consumable.effect,
oldInventorySize: oldInventorySize
});
// Add to inventory
for (let i = 0; i < quantity; i++) {
const item = {
id: `${consumable.id}_${Date.now()}_${i}`,
name: consumable.name,
type: 'consumable',
rarity: consumable.rarity,
quantity: 1,
description: consumable.description,
consumable: true,
effect: consumable.effect
};
inventory.addItem(item);
}
if (debugLogger) debugLogger.logStep('Consumable purchase completed', {
consumableId: consumable.id,
quantity: quantity,
oldInventorySize: oldInventorySize,
newInventorySize: inventory ? inventory.items.length : 0,
itemsAdded: quantity
});
}
purchaseEquipment(equipment, quantity = 1) {
const inventory = this.game.systems.inventory;
const oldInventorySize = inventory ? inventory.items.length : 0;
// Add to inventory
for (let i = 0; i < quantity; i++) {
const item = {
id: `${equipment.id}_${Date.now()}_${i}`,
name: equipment.name,
type: equipment.type,
rarity: equipment.rarity,
quantity: 1,
description: equipment.description,
texture: equipment.texture,
stats: equipment.stats || {},
equipable: true
};
inventory.addItem(item);
}
}
purchaseMaterial(material, quantity = 1) {
const inventory = this.game.systems.inventory;
const debugLogger = window.debugLogger;
if (!inventory) {
console.error('[ECONOMY] Inventory system not available for material purchase');
return false;
}
try {
const oldInventorySize = inventory.items.length;
console.log(`[DEBUG] Inventory state before purchase: ${oldInventorySize} items`);
// Create item object for inventory
const item = {
id: material.id,
name: material.name,
type: 'material',
rarity: material.rarity || 'common',
quantity: quantity,
description: material.description || `A ${material.name} material`,
stackable: true,
stats: {},
equipable: false,
slot: null
};
console.log(`[DEBUG] Adding item to inventory: ${item.name}`);
const added = inventory.addItem(item);
console.log(`[DEBUG] addItem() returned: ${added}, new inventory size: ${inventory.items.length}`);
if (debugLogger) debugLogger.logStep('Material purchase completed', {
materialId: material.id,
materialName: material.name,
quantity: quantity,
oldInventorySize: oldInventorySize,
newInventorySize: inventory.items.length,
addedSuccessfully: added
});
if (!added) {
console.error('Failed to add material to inventory');
return false;
} else {
// Update inventory UI to show the new material
if (this.game.systems.ui && this.game.systems.ui.updateInventory) {
this.game.systems.ui.updateInventory();
} else if (this.game.systems.ui && this.game.systems.ui.updateUI) {
this.game.systems.ui.updateUI();
} else {
console.warn('No UI update method available after material purchase');
}
console.log('[DEBUG] Material purchase completed and UI update called');
return true;
}
} catch (error) {
console.error('Exception in purchaseMaterial:', error);
if (debugLogger) debugLogger.errorEvent('Purchase Material Exception', error, {
materialId: material.id,
materialName: material.name
});
return false;
}
}
// Reward generation
generateRewards(difficulty = 'normal', source = 'dungeon') {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('generateRewards', {
difficulty: difficulty,
source: source
});
const baseRewards = {
easy: { credits: [50, 150], gems: [0, 1], experience: [20, 50] },
normal: { credits: [100, 300], gems: [1, 3], experience: [50, 100] },
hard: { credits: [200, 500], gems: [2, 5], experience: [100, 200] },
extreme: { credits: [500, 1000], gems: [5, 10], experience: [200, 400] }
};
const rewards = baseRewards[difficulty] || baseRewards.normal;
const generatedRewards = {};
// Generate credits
generatedRewards.credits = Math.floor(
Math.random() * (rewards.credits[1] - rewards.credits[0] + 1) + rewards.credits[0]
);
// Generate gems
generatedRewards.gems = Math.floor(
Math.random() * (rewards.gems[1] - rewards.gems[0] + 1) + rewards.gems[0]
);
// Generate experience
generatedRewards.experience = Math.floor(
Math.random() * (rewards.experience[1] - rewards.experience[0] + 1) + rewards.experience[0]
);
// Bonus for premium currency (rare)
if (Math.random() < 0.05) { // 5% chance
generatedRewards.premiumCurrency = 1;
}
if (debugLogger) debugLogger.endStep('generateRewards', {
difficulty: difficulty,
source: source,
generatedRewards: generatedRewards,
rewardRanges: rewards
});
return generatedRewards;
}
giveRewards(rewards, source = 'unknown') {
const debugLogger = window.debugLogger;
const player = this.game.systems.player;
const oldCredits = this.credits;
const oldGems = this.gems;
const oldExperience = player.stats.experience;
const oldLevel = player.stats.level;
if (debugLogger) debugLogger.startStep('giveRewards', {
source: source,
rewards: rewards,
oldPlayerState: {
credits: oldCredits,
gems: oldGems,
experience: oldExperience,
level: oldLevel
}
});
let totalRewards = [];
// Add credits
if (rewards.credits > 0) {
this.addCredits(rewards.credits, source);
totalRewards.push(`${rewards.credits} credits`);
}
// Add gems
if (rewards.gems > 0) {
this.addGems(rewards.gems, source);
totalRewards.push(`${rewards.gems} gems`);
}
// Add premium currency
if (rewards.premiumCurrency > 0) {
this.addPremiumCurrency(rewards.premiumCurrency, source);
totalRewards.push(`${rewards.premiumCurrency} premium currency`);
}
// Add experience
if (rewards.experience > 0) {
player.addExperience(rewards.experience);
totalRewards.push(`${rewards.experience} experience`);
}
// Add materials
if (rewards.materials && rewards.materials.length > 0) {
const inventory = this.game.systems.inventory;
alert(`[DUNGEON DEBUG] Adding ${rewards.materials.length} materials to inventory\nInventory available: ${!!inventory}\nMaterials: ${JSON.stringify(rewards.materials, null, 2)}`);
for (const material of rewards.materials) {
// Create proper item object like in purchaseMaterial
const itemObject = {
id: material.id,
name: material.id.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()), // Convert ID to readable name
type: 'material',
rarity: 'common',
quantity: material.quantity,
description: `A ${material.id.replace('_', ' ')} material`,
stackable: true,
stats: {},
equipable: false,
slot: null
};
alert(`[DUNGEON DEBUG] Fixed! Adding material as full item object:\n${JSON.stringify(itemObject, null, 2)}`);
inventory.addItem(itemObject);
totalRewards.push(`${material.quantity}x ${material.id}`);
}
}
// Add items
if (rewards.items && rewards.items.length > 0) {
const inventory = this.game.systems.inventory;
for (const item of rewards.items) {
inventory.addItem(item.id, item.quantity || 1);
totalRewards.push(`${item.quantity || 1}x ${item.id}`);
}
}
// Show reward notification
if (totalRewards.length > 0) {
const rewardText = totalRewards.join(', ');
this.game.showNotification(`Rewards: ${rewardText}`, 'success', 3000);
}
if (debugLogger) debugLogger.endStep('giveRewards', {
source: source,
rewardsGiven: rewards,
totalRewardsText: totalRewards.join(', '),
currencyChanges: {
credits: { old: oldCredits, new: this.credits, change: this.credits - oldCredits },
gems: { old: oldGems, new: this.gems, change: this.gems - oldGems }
},
playerChanges: {
experience: { old: oldExperience, new: player.stats.experience, change: player.stats.experience - oldExperience },
level: { old: oldLevel, new: player.stats.level, leveledUp: player.stats.level > oldLevel }
}
});
}
// Daily rewards and bonuses
claimDailyReward() {
const debugLogger = window.debugLogger;
const lastClaim = localStorage.getItem('lastDailyReward');
const today = new Date().toDateString();
if (debugLogger) debugLogger.startStep('claimDailyReward', {
lastClaim: lastClaim,
today: today,
alreadyClaimed: lastClaim === today
});
if (lastClaim === today) {
this.game.showNotification('Daily reward already claimed!', 'warning', 3000);
if (debugLogger) debugLogger.endStep('claimDailyReward', {
success: false,
reason: 'Already claimed today'
});
return false;
}
// Calculate reward based on consecutive days
const consecutiveDays = parseInt(localStorage.getItem('consecutiveDailyRewards') || '0') + 1;
const baseReward = 100;
const bonusMultiplier = Math.min(consecutiveDays * 0.1, 2); // Max 2x bonus
const credits = Math.floor(baseReward * (1 + bonusMultiplier));
const gems = Math.min(Math.floor(consecutiveDays / 7), 5); // 1 gem per week, max 5
if (debugLogger) debugLogger.logStep('Daily reward calculation', {
consecutiveDays: consecutiveDays,
baseReward: baseReward,
bonusMultiplier: bonusMultiplier,
creditsReward: credits,
gemsReward: gems
});
// Give rewards
this.addCredits(credits, 'daily_reward');
this.addGems(gems, 'daily_reward');
// Update daily reward tracking
localStorage.setItem('lastDailyReward', today);
localStorage.setItem('consecutiveDailyRewards', consecutiveDays.toString());
// Show notification
const rewardText = `${credits} credits${gems > 0 ? ` and ${gems} gems` : ''}`;
this.game.showNotification(`Daily reward claimed: ${rewardText}! (${consecutiveDays} day streak)`, 'success', 4000);
if (debugLogger) debugLogger.endStep('claimDailyReward', {
success: true,
consecutiveDays: consecutiveDays,
creditsReward: credits,
gemsReward: gems,
rewardText: rewardText,
newLastClaim: today,
newConsecutiveDays: consecutiveDays
});
return true;
}
// UI updates
updateUI() {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('updateUI', {
currentCredits: this.credits,
currentGems: this.gems,
currentPremiumCurrency: this.premiumCurrency
});
// Update resource displays
const creditsElement = document.getElementById('credits');
if (creditsElement) {
creditsElement.textContent = this.game.formatNumber(this.credits);
}
const gemsElement = document.getElementById('gems');
if (gemsElement) {
gemsElement.textContent = this.game.formatNumber(this.gems);
}
const premiumElement = document.getElementById('premiumCurrency');
if (premiumElement) {
premiumElement.textContent = this.game.formatNumber(this.premiumCurrency);
}
// Update shop UI when shop tab is active
this.updateShopUI();
if (debugLogger) debugLogger.endStep('updateUI', {
creditsUpdated: !!creditsElement,
gemsUpdated: !!gemsElement,
premiumUpdated: !!premiumElement,
shopUIUpdated: true
});
}
// Reset economy (for new game)
reset() {
const debugLogger = window.debugLogger;
const oldState = {
credits: this.credits,
gems: this.gems,
premiumCurrency: this.premiumCurrency,
transactionCount: this.transactions.length,
ownedCosmeticsCount: this.ownedCosmetics.length
};
if (debugLogger) debugLogger.startStep('reset', {
oldState: oldState
});
this.credits = 1000;
this.gems = 10;
this.premiumCurrency = 0;
this.transactions = [];
this.ownedCosmetics = [];
// Reset daily rewards
localStorage.removeItem('lastDailyReward');
localStorage.removeItem('consecutiveDailyRewards');
if (debugLogger) debugLogger.endStep('reset', {
oldState: oldState,
newState: {
credits: this.credits,
gems: this.gems,
premiumCurrency: this.premiumCurrency,
transactionCount: this.transactions.length,
ownedCosmeticsCount: this.ownedCosmetics.length
}
});
}
// Clear all data
clear() {
const debugLogger = window.debugLogger;
const oldState = {
credits: this.credits,
gems: this.gems,
premiumCurrency: this.premiumCurrency,
transactionCount: this.transactions.length,
ownedCosmeticsCount: this.ownedCosmetics.length
};
if (debugLogger) debugLogger.startStep('clear', {
oldState: oldState
});
this.credits = 0;
this.gems = 0;
this.premiumCurrency = 0;
this.transactions = [];
this.ownedCosmetics = [];
if (debugLogger) debugLogger.endStep('clear', {
oldState: oldState,
newState: {
credits: this.credits,
gems: this.gems,
premiumCurrency: this.premiumCurrency,
transactionCount: this.transactions.length,
ownedCosmeticsCount: this.ownedCosmetics.length
}
});
}
updateShopUI() {
const debugLogger = window.debugLogger;
const shopItemsElement = document.getElementById('shopItems');
if (!shopItemsElement) {
if (debugLogger) debugLogger.log('updateShopUI: Shop items element not found');
return;
}
const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships';
// Always use random shop items when the system is available
// If no random items exist for this category, trigger a refresh
if (!this.randomShopItems[activeCategory] || this.randomShopItems[activeCategory].length === 0) {
console.log(`[ECONOMY] No random items for ${activeCategory}, triggering refresh`);
this.refreshRandomShop();
}
const items = this.randomShopItems[activeCategory] || [];
const isRandomShop = true; // Always random shop when system is available
if (debugLogger) debugLogger.startStep('updateShopUI', {
activeCategory: activeCategory,
itemCount: items.length,
isRandomShop: isRandomShop,
currentCredits: this.credits,
currentGems: this.gems,
currentPremiumCurrency: this.premiumCurrency
});
shopItemsElement.innerHTML = '';
// Add shop refresh info if using random shop
if (isRandomShop) {
const refreshInfo = document.createElement('div');
refreshInfo.className = 'shop-refresh-info';
refreshInfo.innerHTML = `
<div class="refresh-info-left">
<div class="refresh-countdown">
<i class="fas fa-clock"></i>
Next refresh in: ${this.getShopRefreshCountdown()}
</div>
</div>
`;
shopItemsElement.appendChild(refreshInfo);
}
items.forEach(item => {
const itemElement = document.createElement('div');
itemElement.className = 'shop-item';
const canAfford = this.canAfford(item);
const isOwned = item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id);
itemElement.innerHTML = `
<div class="shop-item-content">
${item.texture ?
`<div class="shop-item-image">
<img src="${item.texture}" alt="${item.name}" onerror="this.src='assets/textures/missing-texture.png'">
</div>` : ''}
<div class="shop-item-info">
<div class="shop-item-name">${item.name}</div>
<div class="shop-item-description">${item.description}</div>
${item.stats ? `
<div class="shop-item-stats">
${Object.entries(item.stats).map(([stat, value]) =>
`<div class="stat">${stat}: ${value}</div>`
).join('')}
</div>
` : ''}
<div class="shop-item-price">${this.formatPrice(item)}</div>
<div class="shop-item-rarity ${item.rarity}">${item.rarity}</div>
</div>
</div>
<button class="shop-item-purchase-btn ${!canAfford || isOwned ? 'disabled' : ''}"
data-item-id="${item.id}"
${!canAfford || isOwned ? 'disabled' : ''}>
${isOwned ? 'Owned' : canAfford ? 'Purchase' : 'Cannot Afford'}
</button>
`;
shopItemsElement.appendChild(itemElement);
if (debugLogger) debugLogger.logStep('Shop item rendered', {
itemId: item.id,
itemName: item.name,
itemCategory: activeCategory,
itemPrice: item.price,
itemCurrency: item.currency,
canAfford: canAfford,
isOwned: isOwned
});
});
// Re-setup event listeners since we just recreated all the buttons
this.setupShopEventListeners();
if (debugLogger) debugLogger.endStep('updateShopUI', {
activeCategory: activeCategory,
itemsRendered: items.length,
shopUIUpdated: true
});
}
// Testing and utility methods
testPurchase(itemId) {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('testPurchase', {
itemId: itemId
});
const item = this.findShopItem(itemId);
if (!item) {
if (debugLogger) debugLogger.endStep('testPurchase', {
success: false,
reason: 'Item not found',
itemId: itemId
});
return false;
}
const result = this.purchaseItem(itemId);
if (debugLogger) debugLogger.endStep('testPurchase', {
success: result,
itemId: itemId,
itemName: item.name,
itemCategory: item.type
});
return result;
}
formatItemStats(item) {
const debugLogger = window.debugLogger;
if (debugLogger) debugLogger.startStep('formatItemStats', {
itemId: item.id,
itemType: item.type
});
let stats = [];
if (item.stats) {
for (const [stat, value] of Object.entries(item.stats)) {
stats.push(`${stat}: +${value}`);
}
}
if (item.effect) {
if (item.effect.attackMultiplier) {
stats.push(`Attack: x${item.effect.attackMultiplier}`);
}
if (item.effect.defense) {
stats.push(`Defense: +${item.effect.defense}`);
}
if (item.effect.healing) {
stats.push(`Healing: +${item.effect.healing}`);
}
if (item.effect.energyRestore) {
stats.push(`Energy: +${item.effect.energyRestore}`);
}
}
const formattedStats = stats.length > 0 ? stats.join(', ') : 'No special stats';
if (debugLogger) debugLogger.endStep('formatItemStats', {
itemId: item.id,
formattedStats: formattedStats,
statCount: stats.length
});
return formattedStats;
}
// Save and load
save() {
const debugLogger = window.debugLogger;
// if (debugLogger) debugLogger.startStep('save', {
// currentCredits: this.credits,
// currentGems: this.gems,
// currentPremiumCurrency: this.premiumCurrency,
// transactionCount: this.transactions.length,
// ownedCosmeticsCount: this.ownedCosmetics.length
// });
const saveData = {
credits: this.credits,
gems: this.gems,
premiumCurrency: this.premiumCurrency,
transactions: this.transactions,
ownedCosmetics: this.ownedCosmetics,
// Include shop data with timestamp for proper refresh calculation
shopData: {
randomShopItems: this.randomShopItems,
categoryPurchaseLimits: this.categoryPurchaseLimits,
lastShopRefresh: this.lastShopRefresh,
saveTimestamp: Date.now() // When this save was created
}
};
// if (debugLogger) debugLogger.endStep('save', {
// saveDataSize: JSON.stringify(saveData).length,
// economyState: saveData
// });
return saveData;
}
load(data) {
const debugLogger = window.debugLogger;
const oldState = {
credits: this.credits,
gems: this.gems,
premiumCurrency: this.premiumCurrency,
transactionCount: this.transactions.length,
ownedCosmeticsCount: this.ownedCosmetics.length
};
// if (debugLogger) debugLogger.startStep('load', {
// oldState: oldState,
// loadData: data
// });
try {
this.credits = data.credits || 0;
this.gems = data.gems || 0;
this.premiumCurrency = data.premiumCurrency || 0;
this.transactions = data.transactions || [];
this.ownedCosmetics = data.ownedCosmetics || [];
// Load shop data and calculate if refresh is needed
if (data.shopData) {
console.log('[ECONOMY] Loading shop data from save');
// Restore shop data
this.randomShopItems = data.shopData.randomShopItems || {};
this.categoryPurchaseLimits = data.shopData.categoryPurchaseLimits || {};
this.lastShopRefresh = data.shopData.lastShopRefresh || null;
// Calculate if shop should have refreshed while game was closed
const saveTimestamp = data.shopData.saveTimestamp || Date.now();
const currentTime = Date.now();
const timeSinceSave = currentTime - saveTimestamp;
if (this.lastShopRefresh) {
const timeSinceLastRefresh = currentTime - this.lastShopRefresh;
const totalIntervalsPassed = Math.floor(timeSinceLastRefresh / this.SHOP_REFRESH_INTERVAL);
if (totalIntervalsPassed > 0) {
console.log(`[ECONOMY] Shop missed ${totalIntervalsPassed} refresh(es) while game was closed, refreshing now`);
this.refreshRandomShop();
} else {
console.log('[ECONOMY] Shop items still valid, no refresh needed');
}
} else {
console.log('[ECONOMY] No previous shop refresh, generating new shop');
this.refreshRandomShop();
}
} else {
console.log('[ECONOMY] No shop data in save, initializing new shop');
this.refreshRandomShop();
}
if (debugLogger) debugLogger.endStep('load', {
success: true,
oldState: oldState,
newState: {
credits: this.credits,
gems: this.gems,
premiumCurrency: this.premiumCurrency,
transactionCount: this.transactions.length,
ownedCosmeticsCount: this.ownedCosmetics.length
}
});
} catch (error) {
if (debugLogger) debugLogger.errorEvent('load', error, {
oldState: oldState,
error: error.message
});
throw error;
}
}
// Utility methods for shop
canAfford(item) {
if (item.currency === 'credits') {
return this.credits >= item.price;
} else if (item.currency === 'gems') {
return this.gems >= item.price;
} else if (item.currency === 'premium') {
return this.premiumCurrency >= item.price;
}
return false;
}
formatPrice(item) {
const currencySymbols = {
'credits': '₵',
'gems': '💎',
'premium': '⭐'
};
const symbol = currencySymbols[item.currency] || item.currency;
return `${symbol}${this.game.formatNumber(item.price)}`;
}
}