feat: more items, keyboard controls
This commit is contained in:
+60
-9
@@ -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}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user