diff --git a/backend.py b/backend.py index 2f5d2bd..e3112c2 100644 --- a/backend.py +++ b/backend.py @@ -34,6 +34,10 @@ ATTACK_RANGE_BASE = 4 WEAPON_SLOT_COUNT = 5 BLOCK_BUILD_COST_WOOD = 2 BLOCK_HEALTH = 5 +PLAYER_BASE_ATTACK = 5 +PLAYER_BASE_DEFENSE = 2 +PLAYER_BASE_MAX_HEALTH = 100 +PLAYER_BASE_MOVE_CAPACITY = 1 # Data Models @dataclass @@ -248,6 +252,12 @@ class GameWorld: "aura_radius": 5, "aura_bonuses": {"free_move_chance": 0.7}, }, + { + "name": "Traveler Ring", + "attack_bonus": 0, + "range_bonus": 0, + "movement_bonus": 1, + }, ] self.chests[chest_id] = Chest( id=chest_id, @@ -274,8 +284,13 @@ class GameWorld: continue player.level = 1 player.exp = 0 + player.attack = PLAYER_BASE_ATTACK + player.defense = PLAYER_BASE_DEFENSE + player.max_health = PLAYER_BASE_MAX_HEALTH + player.max_action_points = ACTION_POINTS_MAX + player.movement_capacity = PLAYER_BASE_MOVE_CAPACITY player.health = player.max_health - player.action_points = ACTION_POINTS_MAX + player.action_points = player.max_action_points player.inventory = {"wood": 0, "stone": 0} player.weapon_slots = [None] * WEAPON_SLOT_COUNT player.equipped_weapon_slot = -1 @@ -402,6 +417,43 @@ def _player_effective_range(player: Player) -> float: return player.attack_range + float(w.get("range_bonus", 0)) return player.attack_range +def _player_effective_movement(player: Player) -> int: + if 0 <= player.equipped_weapon_slot < len(player.weapon_slots): + w = player.weapon_slots[player.equipped_weapon_slot] + if w: + return max(1, player.movement_capacity + int(w.get("movement_bonus", 0))) + return player.movement_capacity + +def _recompute_player_stats_for_level(player: Player) -> None: + # Slow progression so levels matter without runaway scaling. + level = max(1, int(player.level)) + old_max_health = player.max_health + old_max_ap = player.max_action_points + + player.attack = PLAYER_BASE_ATTACK + (level - 1) // 3 + player.defense = PLAYER_BASE_DEFENSE + (level - 1) // 4 + player.max_health = PLAYER_BASE_MAX_HEALTH + ((level - 1) // 2) * 5 + player.max_action_points = ACTION_POINTS_MAX + (level - 1) // 4 + + if player.max_health > old_max_health: + player.health = min(player.max_health, player.health + (player.max_health - old_max_health)) + else: + player.health = min(player.max_health, player.health) + + if player.max_action_points > old_max_ap: + player.action_points = min(player.max_action_points, player.action_points + (player.max_action_points - old_max_ap)) + else: + player.action_points = min(player.max_action_points, player.action_points) + +def _recompute_player_level_from_exp(player: Player) -> None: + level = 1 + xp_needed = 30 + while player.exp >= xp_needed and level < 100: + level += 1 + xp_needed += 20 + player.level = level + _recompute_player_stats_for_level(player) + def _player_nearby_bonuses(player: Player) -> Dict: if "game_world" not in globals(): return {} @@ -442,6 +494,7 @@ def _serialize_player(player: Player) -> Dict: bonuses = _player_nearby_bonuses(player) effective_defense = _player_effective_defense(player) effective_max_health = _player_effective_max_health(player) + effective_movement = _player_effective_movement(player) if player.health > effective_max_health: player.health = effective_max_health @@ -451,6 +504,8 @@ def _serialize_player(player: Player) -> Dict: data["base_max_health"] = player.max_health data["max_health"] = effective_max_health data["health"] = player.health + data["base_movement_capacity"] = player.movement_capacity + data["movement_capacity"] = effective_movement data["nearby_bonuses"] = bonuses return data @@ -589,7 +644,8 @@ async def move_player(player_id: str, request: Request): # Calculate distance distance = (dx ** 2 + dy ** 2) ** 0.5 - if distance > player.movement_capacity: + effective_movement = _player_effective_movement(player) + if distance > effective_movement: raise HTTPException(status_code=400, detail="Movement exceeds capacity") next_x = max(-GRID_SIZE // 2, min(GRID_SIZE // 2, player.position.x + dx)) @@ -705,13 +761,7 @@ def _handle_attack(player: Player, target_id: str) -> Dict: if monster.health <= 0: del game_world.monsters[target_id] player.exp += monster.level * 10 - # Calculate level based on XP: 30 for level 1, +20 per additional level - # Level N requires: 30 + 20*(N-1) total XP - player.level = 1 - xp_needed = 30 - while player.exp >= xp_needed and player.level < 100: - player.level += 1 - xp_needed += 20 + _recompute_player_level_from_exp(player) return {"success": True, "damage": damage, "exp": monster.level * 10, "monster_killed": True} retaliation = apply_retaliation(monster.attack) @@ -744,6 +794,7 @@ def _handle_attack(player: Player, target_id: str) -> Dict: if p.active: p.level += 5 p.exp += 500 + _recompute_player_stats_for_level(p) game_world.boss = None return {"success": True, "damage": damage, "boss_killed": True} diff --git a/index.html b/index.html index 9eb47ef..0b17f24 100644 --- a/index.html +++ b/index.html @@ -498,7 +498,7 @@