208 lines
6.6 KiB
Python
208 lines
6.6 KiB
Python
"""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 <number> - set PL to exact value
|
|
power stop - cancel ongoing power-up
|
|
"""
|
|
if not args:
|
|
await player.send("Usage: power up|down|stop|<number>\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 <number>
|
|
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"]))
|