"""Power commands for raising and lowering PL.""" import asyncio from mudlib.combat.engine import get_encounter from mudlib.commands import CommandDefinition, register from mudlib.commands.movement import send_nearby_message from mudlib.gmcp import send_char_vitals from mudlib.player import Player # Power-up mechanics POWER_UP_TICK_INTERVAL = 0.05 # seconds between ticks POWER_UP_PL_PER_TICK = 5.0 # PL increase per tick POWER_UP_STAMINA_COST_PER_TICK = 2.0 # stamina cost per tick POWER_DOWN_MIN_PL = 1.0 # minimum PL when powering down async def _check_can_power_up(player: Player) -> str | None: """Return error message if player can't power up, else None.""" # Check if already powering up if player._power_task is not None and not player._power_task.done(): return "You're already powering up!\r\n" # Check for combat if get_encounter(player) is not None: return "You can't power up during combat!\r\n" # Check stamina if player.stamina <= 0: return "You don't have enough stamina to power up!\r\n" return None async def power_up_loop(player: Player, target_pl: float | None = None) -> None: """Background task that powers up the player over time. Args: player: The player powering up target_pl: Optional target PL to stop at (defaults to max_pl) """ target = target_pl if target_pl is not None else player.max_pl try: while player.pl < target and player.stamina > 0: await asyncio.sleep(POWER_UP_TICK_INTERVAL) # Deduct stamina player.stamina -= POWER_UP_STAMINA_COST_PER_TICK if player.stamina < 0: player.stamina = 0.0 # Increase PL old_pl = player.pl player.pl = min(player.pl + POWER_UP_PL_PER_TICK, target) # Send GMCP update send_char_vitals(player) # Send periodic aura message (every ~0.2s) if int(old_pl / 20) != int(player.pl / 20): await player.send("Your aura flares!\r\n") await send_nearby_message( player, player.x, player.y, f"{player.name}'s aura flares!\r\n" ) # Check if we've run out of stamina if player.stamina <= 0: await player.send("You run out of stamina and stop powering up.\r\n") await send_nearby_message( player, player.x, player.y, f"{player.name} stops powering up.\r\n", ) break # Reached target if player.pl >= target: await player.send(f"You reach your target power level of {target}.\r\n") await send_nearby_message( player, player.x, player.y, f"{player.name} reaches their target power level.\r\n", ) finally: # Clean up the task reference player._power_task = None async def cmd_power(player: Player, args: str) -> None: """Manage power level. Usage: power up - raise PL toward max_pl power down - lower PL to minimum power - set PL to exact value power stop - cancel ongoing power-up """ if not args: await player.send("Usage: power up|down|stop|\r\n") return args = args.strip().lower() # Handle power stop if args == "stop": if player._power_task is None or player._power_task.done(): await player.send("You're not powering up.\r\n") return player._power_task.cancel() player._power_task = None await player.send("You stop powering up.\r\n") await send_nearby_message( player, player.x, player.y, f"{player.name} stops powering up.\r\n" ) return # Handle power down if args == "down": # Cancel any ongoing power-up if player._power_task is not None and not player._power_task.done(): player._power_task.cancel() player._power_task = None player.pl = POWER_DOWN_MIN_PL send_char_vitals(player) await player.send(f"You power down to PL {POWER_DOWN_MIN_PL}.\r\n") await send_nearby_message( player, player.x, player.y, f"{player.name} powers down.\r\n" ) return # Handle power up if args == "up": error = await _check_can_power_up(player) if error: await player.send(error) return # Check if already at max if player.pl >= player.max_pl: await player.send("You're already at maximum power!\r\n") return # Start power-up loop await player.send("You begin powering up...\r\n") await send_nearby_message( player, player.x, player.y, f"{player.name} begins powering up!\r\n" ) player._power_task = asyncio.create_task(power_up_loop(player)) return # Handle power try: target = float(args) except ValueError: await player.send(f"Invalid power level: {args}\r\n") return # Validate target if target <= 0: await player.send("Power level must be greater than 0.\r\n") return if target > player.max_pl: await player.send( f"Power level cannot exceed your maximum of {player.max_pl}.\r\n" ) return # If target is lower than current, instant change if target < player.pl: # Cancel any ongoing power-up if player._power_task is not None and not player._power_task.done(): player._power_task.cancel() player._power_task = None player.pl = target send_char_vitals(player) await player.send(f"You lower your power to {target}.\r\n") await send_nearby_message( player, player.x, player.y, f"{player.name} lowers their power.\r\n" ) return # If target is higher, start power-up loop to that target if target > player.pl: error = await _check_can_power_up(player) if error: await player.send(error) return # Start power-up loop to target await player.send(f"You begin powering up to {target}...\r\n") await send_nearby_message( player, player.x, player.y, f"{player.name} begins powering up!\r\n" ) player._power_task = asyncio.create_task(power_up_loop(player, target)) return # Target equals current PL await player.send(f"You're already at PL {target}.\r\n") register(CommandDefinition("power", cmd_power, aliases=["pow"]))