Implement confusion scrolls
This commit is contained in:
parent
bebd9d617e
commit
621d4780e8
@ -1,11 +1,12 @@
|
|||||||
from __future__ import annotations
|
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 numpy as np # type: ignore
|
||||||
import tcod
|
import tcod
|
||||||
|
|
||||||
from actions import Action, MeleeAction, MovementAction, WaitAction
|
from actions import Action, BumpAction, MeleeAction, MovementAction, WaitAction
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from entity import Actor
|
from entity import Actor
|
||||||
@ -43,6 +44,51 @@ class BaseAI(Action):
|
|||||||
return [(index[0], index[1]) for index in path]
|
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):
|
class HostileEnemy(BaseAI):
|
||||||
def __init__(self, entity: Actor):
|
def __init__(self, entity: Actor):
|
||||||
super().__init__(entity)
|
super().__init__(entity)
|
||||||
|
@ -4,9 +4,11 @@ from typing import Optional, TYPE_CHECKING
|
|||||||
|
|
||||||
import actions
|
import actions
|
||||||
import color
|
import color
|
||||||
|
import components.ai
|
||||||
import components.inventory
|
import components.inventory
|
||||||
from components.base_component import BaseComponent
|
from components.base_component import BaseComponent
|
||||||
from exceptions import Impossible
|
from exceptions import Impossible
|
||||||
|
from input_handlers import SingleRangedAttackHandler
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from entity import Actor, Item
|
from entity import Actor, Item
|
||||||
@ -34,6 +36,46 @@ class Consumable(BaseComponent):
|
|||||||
inventory.items.remove(entity)
|
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):
|
class HealingConsumable(Consumable):
|
||||||
def __init__(self, amount: int):
|
def __init__(self, amount: int):
|
||||||
self.amount = amount
|
self.amount = amount
|
||||||
|
@ -30,13 +30,18 @@ troll = Actor(
|
|||||||
inventory=Inventory(capacity=0),
|
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(
|
health_potion = Item(
|
||||||
char="!",
|
char="!",
|
||||||
color=(127, 0, 255),
|
color=(127, 0, 255),
|
||||||
name="Health Potion",
|
name="Health Potion",
|
||||||
consumable=consumable.HealingConsumable(amount=4),
|
consumable=consumable.HealingConsumable(amount=4),
|
||||||
)
|
)
|
||||||
|
|
||||||
lightning_scroll = Item(
|
lightning_scroll = Item(
|
||||||
char="~",
|
char="~",
|
||||||
color=(255, 255, 0),
|
color=(255, 255, 0),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import overload, Optional, TYPE_CHECKING
|
from typing import overload, Callable, Optional, Tuple, TYPE_CHECKING
|
||||||
|
|
||||||
import tcod.event
|
import tcod.event
|
||||||
|
|
||||||
@ -294,6 +294,22 @@ class LookHandler(SelectIndexHandler):
|
|||||||
self.engine.event_handler = MainGameEventHandler(self.engine)
|
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):
|
class MainGameEventHandler(EventHandler):
|
||||||
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
|
def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]:
|
||||||
action: Optional[Action] = None
|
action: Optional[Action] = None
|
||||||
|
@ -70,6 +70,8 @@ def place_entities(
|
|||||||
|
|
||||||
if item_chance < 0.7:
|
if item_chance < 0.7:
|
||||||
entity_factories.health_potion.spawn(dungeon, x, y)
|
entity_factories.health_potion.spawn(dungeon, x, y)
|
||||||
|
elif item_chance < 0.9:
|
||||||
|
entity_factories.confusion_scroll.spawn(dungeon, x, y)
|
||||||
else:
|
else:
|
||||||
entity_factories.lightning_scroll.spawn(dungeon, x, y)
|
entity_factories.lightning_scroll.spawn(dungeon, x, y)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user