from __future__ import annotations import random from dataclasses import dataclass from typing import Dict, List, Tuple import pygame from config import constants ItemType = str @dataclass class ItemSpec: color: Tuple[int, int, int] label: str ITEM_SPECS: Dict[ItemType, ItemSpec] = { # Metals (asteroids) "steel_scrap": ItemSpec((160, 160, 160), "Stl"), "aluminum_sheet": ItemSpec((180, 200, 220), "Al"), "copper_wire": ItemSpec((200, 140, 90), "Cu"), # Enemy drops "lithium_battery": ItemSpec((140, 200, 255), "Li"), "circuit_board": ItemSpec((90, 200, 120), "PCB"), "rare_earth_element": ItemSpec((200, 160, 255), "REE"), "pet_plastic": ItemSpec((200, 200, 220), "PET"), "carbon_fiber": ItemSpec((80, 80, 90), "CF"), "titanium_alloy": ItemSpec((170, 170, 190), "Ti"), "power_core": ItemSpec((255, 100, 80), "Core"), "crystal_capacitor": ItemSpec((180, 220, 255), "Cap"), "exotic_isotope": ItemSpec((255, 180, 80), "Iso"), # Health "repair_nanites": ItemSpec((90, 240, 140), "Heal"), "bio_gel": ItemSpec((90, 200, 90), "Gel"), # Power-ups (enemy drops) "powerup_magnet": ItemSpec((90, 200, 255), "Mag"), "powerup_super_magnet": ItemSpec((110, 220, 255), "SMag"), "powerup_weapon_missile": ItemSpec((255, 190, 80), "MSL"), "powerup_weapon_plasma": ItemSpec((120, 200, 255), "PLS"), "powerup_weapon_ray": ItemSpec((255, 120, 200), "RAY"), "powerup_shieldgen": ItemSpec((140, 220, 255), "SHD"), "powerup_rapidfire": ItemSpec((255, 220, 90), "RF"), "powerup_damage": ItemSpec((255, 120, 120), "Dmg"), "powerup_shield": ItemSpec((120, 180, 255), "Shd"), "powerup_regen": ItemSpec((90, 240, 140), "Regen"), "powerup_slow": ItemSpec((120, 180, 255), "Slow"), "powerup_multishot": ItemSpec((255, 160, 255), "2x"), } def random_metal_drop() -> ItemType: r = random.random() cumulative = 0.0 table = constants.ASTEROID_METAL_DROP_TABLE for name, prob in table: cumulative += prob if r <= cumulative: return name return table[-1][0] def random_enemy_drop() -> ItemType: table = constants.ENEMY_DROP_TABLE total = sum(weight for _, weight in table) r = random.uniform(0, total) cumulative = 0.0 for name, weight in table: cumulative += weight if r <= cumulative: return name return table[-1][0] def random_powerup_drop() -> ItemType: table = constants.POWERUP_DROP_TABLE total = sum(weight for _, weight in table) r = random.uniform(0, total) cumulative = 0.0 for name, weight in table: cumulative += weight if r <= cumulative: return name return table[-1][0] class Item(pygame.sprite.Sprite): """Collectible item dropped from asteroids or enemies.""" ICONS: Dict[str, pygame.Surface] = {} def __init__(self, item_type: ItemType, pos: Tuple[float, float]): super().__init__() spec = ITEM_SPECS.get(item_type, ItemSpec((200, 200, 200), item_type)) self.item_type = item_type icon = self.ICONS.get(item_type) if icon: self.image = pygame.transform.scale(icon, (18, 18)) else: self.image = pygame.Surface((14, 14), pygame.SRCALPHA) self.image.fill(spec.color) font = pygame.font.Font(None, 12) text = font.render(spec.label, True, (0, 0, 0)) text_rect = text.get_rect(center=(7, 7)) self.image.blit(text, text_rect) self.rect = self.image.get_rect(center=pos) self.pos = pygame.Vector2(pos) self.vel = pygame.Vector2(-80, random.uniform(-20, 20)) # drift left with slight variation def update(self, dt: float): self.pos += self.vel * dt self.rect.center = (int(self.pos.x), int(self.pos.y)) class Inventory: """Simple inventory with capacity counting total items.""" def __init__(self, capacity: int = constants.INVENTORY_LIMIT): self.capacity = capacity self.counts: Dict[ItemType, int] = {} self.weapons: Dict[str, int] = {} self.upgrades: Dict[str, int] = {} self.owned_ships = {"salvager_one"} self.current_ship = "salvager_one" @property def total(self) -> int: return sum(self.counts.values()) def can_pick(self, amount: int = 1) -> bool: return True # unlimited inventory def add(self, item_type: ItemType, amount: int = 1) -> bool: if not self.can_pick(amount): return False self.counts[item_type] = self.counts.get(item_type, 0) + amount return True def get(self, item_type: ItemType) -> int: # Allow checking crafted weapons using snake_case keys for recipes weapon_key_map = { "laser_cannon": "Laser Cannon", "missile_launcher": "Missile Launcher", "shield_generator": "Shield Generator", "plasma_weapon": "Plasma Weapon", "destruction_ray": "Destruction Ray", } if item_type in weapon_key_map: return self.weapons.get(weapon_key_map[item_type], 0) return self.counts.get(item_type, 0) def add_weapon(self, weapon_name: str): self.weapons[weapon_name] = self.weapons.get(weapon_name, 0) + 1 def remove_weapon(self, weapon_name: str): if weapon_name not in self.weapons: return self.weapons[weapon_name] = max(0, self.weapons.get(weapon_name, 0) - 1) if self.weapons[weapon_name] <= 0: del self.weapons[weapon_name]