1
0
Fork 0

Implement confusion scrolls

This commit is contained in:
Timothy Warren 2022-01-14 16:45:07 -05:00
parent bebd9d617e
commit 621d4780e8
5 changed files with 115 additions and 4 deletions

View File

@ -1,11 +1,12 @@
from __future__ import annotations
from typing import List, Tuple, TYPE_CHECKING
import random
from typing import List, Optional, Tuple, TYPE_CHECKING
import numpy as np # type: ignore
import tcod
from actions import Action, MeleeAction, MovementAction, WaitAction
from actions import Action, BumpAction, MeleeAction, MovementAction, WaitAction
if TYPE_CHECKING:
from entity import Actor
@ -43,6 +44,51 @@ class BaseAI(Action):
return [(index[0], index[1]) for index in path]
class ConfusedEnemy(BaseAI):
"""
A confused enemy will stumble around aimlessly for a given number of turns, then
reverts back to its previous AI. If an actor occupies a tile it is randomly
moving into, it will attack.
"""
def __init__(
self,
entity: Actor,
previous_ai: Optional[BaseAI],
turns_remaining: int
):
super().__init__(entity)
self.previous_ai = previous_ai
self.turns_remaining = turns_remaining
def perform(self) -> None:
# Rever the AI back to the original state if the effect has run its course.
if self.turns_remaining <= 0:
self.engine.message_log.add_message(f"The {self.entity.name} is no longer confused.")
self.entity.ai = self.previous_ai
else:
# Pick a random direction
direction_x, direction_y = random.choice(
[
(-1, -1), # Northwest
(0, -1), # North
(1, -1), # Northeast
(-1, 0), # West
(1, 0), # East
(-1, 1), # Southwest
(0, 1), # South
(1, 1), # Southeast
]
)
self.turns_remaining -= 1
# The actor will either try to move or attack in the chosen random direction.
# It's possible the actor will just bump into the wall, wasting a turn.
return BumpAction(self.entity, direction_x, direction_y).perform()
class HostileEnemy(BaseAI):
def __init__(self, entity: Actor):
super().__init__(entity)

View File

@ -4,9 +4,11 @@ from typing import Optional, TYPE_CHECKING
import actions
import color
import components.ai
import components.inventory
from components.base_component import BaseComponent
from exceptions import Impossible
from input_handlers import SingleRangedAttackHandler
if TYPE_CHECKING:
from entity import Actor, Item
@ -34,6 +36,46 @@ class Consumable(BaseComponent):
inventory.items.remove(entity)
class ConfusionConsumable(Consumable):
def __init__(self, number_of_turns: int):
self.number_of_turns = number_of_turns
def get_action(self, consumer: Actor) -> Optional[actions.Action]:
self.engine.message_log.add_message(
"Select a target location.",
color.needs_target
)
self.engine.event_handler = SingleRangedAttackHandler(
self.engine,
callback=lambda xy: actions.ItemAction(consumer, self.parent, xy),
)
return None
def activate(self, action: actions.ItemAction) -> None:
consumer = action.entity
target = action.target_actor
if not self.engine.game_map.visible[action.target_xy]:
raise Impossible("You cannot target an area that you cannot see.")
if not target:
raise Impossible("You must select an enemy to target.")
if target is consumer:
raise Impossible("You cannot confuse yourself!")
self.engine.message_log.add_message(
f"The eyes of the {target.name} look vacant, as it starts to stumble around!",
color.status_effect_applied,
)
target.ai = components.ai.ConfusedEnemy(
entity=target,
previous_ai=target.ai,
turns_remaining=self.number_of_turns,
)
self.consume()
class HealingConsumable(Consumable):
def __init__(self, amount: int):
self.amount = amount

View File

@ -30,13 +30,18 @@ troll = Actor(
inventory=Inventory(capacity=0),
)
confusion_scroll = Item(
char="~",
color=(207, 63, 255),
name="Confusion Scroll",
consumable=consumable.ConfusionConsumable(number_of_turns=10),
)
health_potion = Item(
char="!",
color=(127, 0, 255),
name="Health Potion",
consumable=consumable.HealingConsumable(amount=4),
)
lightning_scroll = Item(
char="~",
color=(255, 255, 0),

View File

@ -1,6 +1,6 @@
from __future__ import annotations
from typing import overload, Optional, TYPE_CHECKING
from typing import overload, Callable, Optional, Tuple, TYPE_CHECKING
import tcod.event
@ -294,6 +294,22 @@ class LookHandler(SelectIndexHandler):
self.engine.event_handler = MainGameEventHandler(self.engine)
class SingleRangedAttackHandler(SelectIndexHandler):
"""Handles targeting a single enemy. Only the enemy selected will be affected."""
def __init__(
self,
engine: Engine,
callback: Callable[[Tuple[int, int]], Optional[Action]]
):
super().__init__(engine)
self.callback = callback
def on_index_selected(self, x: int, y: int) -> Optional[Action]:
return self.callback((x, y))
class MainGameEventHandler(EventHandler):
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
action: Optional[Action] = None

View File

@ -70,6 +70,8 @@ def place_entities(
if item_chance < 0.7:
entity_factories.health_potion.spawn(dungeon, x, y)
elif item_chance < 0.9:
entity_factories.confusion_scroll.spawn(dungeon, x, y)
else:
entity_factories.lightning_scroll.spawn(dungeon, x, y)