import random from typing import Tuple, Dict, Any from data.items import ITEMS from data.models import Action, Outcome def calculate_inventory_load(player_inventory: list) -> Tuple[float, float]: """Calculates the total weight and volume of a player's inventory.""" total_weight = 0.0 total_volume = 0.0 for item in player_inventory: item_def = ITEMS.get(item["item_id"]) if item_def: total_weight += item_def["weight"] * item["quantity"] total_volume += item_def["volume"] * item["quantity"] return round(total_weight, 2), round(total_volume, 2) def get_player_capacity(player_inventory: list, player_stats: dict) -> Tuple[float, float]: """Calculates the total carrying capacity of a player.""" base_weight_cap = player_stats['strength'] * 5 # Example formula base_volume_cap = player_stats['strength'] * 2 # Example formula for item in player_inventory: if item["is_equipped"]: item_def = ITEMS.get(item["item_id"]) if item_def and item_def.get("type") == "equipment": effects = item_def.get("effects", {}) base_weight_cap += effects.get("capacity_weight", 0) base_volume_cap += effects.get("capacity_volume", 0) return base_weight_cap, base_volume_cap def resolve_action(player_stats: dict, action_obj: Action) -> Outcome: """ Resolves a player action, like searching, based on stats and luck. Returns the resulting Outcome object. """ # A simple success chance calculation base_chance = 50 + (player_stats.get('intellect', 5) * 2) roll = random.randint(1, 100) outcome_key = "failure" if roll <= 5 and "critical_failure" in action_obj.outcomes: outcome_key = "critical_failure" elif roll <= base_chance and "success" in action_obj.outcomes: outcome_key = "success" return action_obj.outcomes.get(outcome_key, action_obj.outcomes["failure"]) async def can_add_item_to_inventory(user_id: int, item_id: str, quantity: int) -> Tuple[bool, str]: """ Check if an item can be added to the player's inventory. Returns (can_add, reason_if_not) """ from .api_client import api_client player = await api_client.get_player(user_id) if not player: return False, "Player not found." inventory = await api_client.get_inventory(user_id) item_def = ITEMS.get(item_id) if not item_def: return False, "Invalid item." # Calculate current and projected weight/volume current_weight, current_volume = calculate_inventory_load(inventory) max_weight, max_volume = get_player_capacity(inventory, player) item_weight = item_def["weight"] * quantity item_volume = item_def["volume"] * quantity new_weight = current_weight + item_weight new_volume = current_volume + item_volume if new_weight > max_weight: return False, f"Too heavy! ({new_weight:.1f}/{max_weight:.1f} kg)" if new_volume > max_volume: return False, f"Not enough space! ({new_volume:.1f}/{max_volume:.1f} vol)" return True, "" def calculate_travel_stamina_cost(player: dict, inventory: list, from_location, to_location) -> int: """ Calculate stamina cost for traveling between locations. Based on distance, endurance (reduces cost), and carried weight (increases cost). Args: player: Player stats dictionary inventory: Player's inventory list from_location: Location object being traveled from to_location: Location object being traveled to """ from data.travel_helpers import calculate_base_stamina_cost # Get base cost from shared helper (used by map and game) distance_cost = calculate_base_stamina_cost(from_location, to_location) # Endurance reduces cost (each point reduces by 0.5) endurance_reduction = player['endurance'] * 0.5 # Calculate weight burden current_weight, _ = calculate_inventory_load(inventory) max_weight, _ = get_player_capacity(inventory, player) # Weight penalty: if carrying more than 50% capacity, add extra cost weight_ratio = current_weight / max_weight if max_weight > 0 else 0 weight_penalty = 0 if weight_ratio > 0.5: # Each 10% over 50% adds 1 stamina weight_penalty = int((weight_ratio - 0.5) * 10) # Calculate final cost (minimum 3) final_cost = max(3, int(distance_cost - endurance_reduction + weight_penalty)) return final_cost