mirror of
https://github.com/evennia/evennia.git
synced 2026-03-23 08:16:30 +01:00
Start adding dungeon logic
This commit is contained in:
parent
73d8f24b7c
commit
104f47860f
4 changed files with 623 additions and 26 deletions
262
evennia/contrib/tutorials/evadventure/dungeon.py
Normal file
262
evennia/contrib/tutorials/evadventure/dungeon.py
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
"""
|
||||
Dungeon system
|
||||
|
||||
This creates a procedurally generated dungeon.
|
||||
|
||||
The dungone originates in an entrance room with exits that spawn a new dungeon connection every X
|
||||
minutes. As long as characters go through the same exit within that time, they will all end up in
|
||||
the same dungeon 'branch', otherwise they will go into separate, un-connected dungeon 'branches'.
|
||||
They can always go back to the start room, but this will become a one-way exit back.
|
||||
|
||||
When moving through the dungeon, a new room is not generated until characters
|
||||
decided to go in that direction. Each room is tagged with the specific 'instance'
|
||||
id of that particular branch of dungon. When no characters remain in the branch,
|
||||
the branch is deleted.
|
||||
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from math import sqrt
|
||||
from random import randint, random, shuffle
|
||||
|
||||
from evennia import AttributeProperty, DefaultExit, DefaultScript
|
||||
from evennia.utils import create
|
||||
from evennia.utils.utils import inherits_from
|
||||
|
||||
from .rooms import EvAdventureDungeonRoom
|
||||
|
||||
# aliases for cardinal directions
|
||||
_EXIT_ALIASES = {
|
||||
"north": ("n",),
|
||||
"east": ("w",),
|
||||
"south": ("s",),
|
||||
"west": ("w",),
|
||||
"northeast": ("ne",),
|
||||
"southeast": ("se",),
|
||||
"southwest": ("sw",),
|
||||
"northwest": ("nw",),
|
||||
}
|
||||
# finding the reverse cardinal direction
|
||||
_EXIT_REVERSE_MAPPING = {
|
||||
"north": "south",
|
||||
"east": "west",
|
||||
"south": "north",
|
||||
"west": "east",
|
||||
"northeast": "southwest",
|
||||
"southeast": "northwest",
|
||||
"southwest": "northeast",
|
||||
"northwest": "southeast",
|
||||
}
|
||||
|
||||
# how xy coordinate shifts by going in direction
|
||||
_EXIT_GRID_SHIFT = {
|
||||
"north": (0, 1),
|
||||
"east": (1, 0),
|
||||
"south": (0, -1),
|
||||
"west": (-1, 0),
|
||||
"northeast": (1, 1),
|
||||
"southeast": (1, -1),
|
||||
"southwest": (-1, -1),
|
||||
"northwest": (-1, 1),
|
||||
}
|
||||
|
||||
|
||||
# --------------------------------------------------
|
||||
# Dungeon orchestrator and rooms
|
||||
# --------------------------------------------------
|
||||
|
||||
|
||||
class EvAdventureDungeonExit(DefaultExit):
|
||||
"""
|
||||
Dungeon exit. This will not create the target room until it's traversed.
|
||||
It must be created referencing the dungeon_orchestrator it belongs to.
|
||||
|
||||
"""
|
||||
|
||||
dungeon_orchestrator = AttributeProperty(None, autocreate=False)
|
||||
|
||||
def at_traverse(self, traversing_object, target_location, **kwargs):
|
||||
"""
|
||||
Called when traversing. `target_location` will be None if the
|
||||
target was not yet created.
|
||||
|
||||
"""
|
||||
if not target_location:
|
||||
self.destination = target_location = self.dungeon_orchestrator.new_room(self)
|
||||
super().at_traverse(traversing_object, target_location, **kwargs)
|
||||
|
||||
|
||||
class EvAdventureDungeonOrchestrator(DefaultScript):
|
||||
"""
|
||||
One script is created per dungeon 'branch' created. The orchestrator is
|
||||
responsible for determining what is created next when a character enters an
|
||||
exit within the dungeon.
|
||||
|
||||
"""
|
||||
|
||||
# this determines how branching the dungeon will be
|
||||
max_unexplored_exits = 5
|
||||
max_new_exits_per_room = 3
|
||||
|
||||
rooms = AttributeProperty(list())
|
||||
n_unvisited_exits = AttributeProperty(list())
|
||||
highest_depth = AttributeProperty(0)
|
||||
|
||||
# (x,y): room
|
||||
xy_grid = AttributeProperty(dict())
|
||||
|
||||
def register_exit_traversed(self, exit):
|
||||
"""
|
||||
Tell the system the given exit was traversed. This allows us to track how many unvisited
|
||||
paths we have so as to not have it grow exponentially.
|
||||
|
||||
"""
|
||||
if exit.id in self.unvisited_exits:
|
||||
self.unvisited_exits.remove(exit.id)
|
||||
|
||||
def create_out_exit(self, location, exit_direction="north"):
|
||||
"""
|
||||
Create outgoing exit from a room. The target room is not yet created.
|
||||
|
||||
"""
|
||||
out_exit, _ = EvAdventureDungeonExit.create(
|
||||
key=exit_direction, location=location, aliases=_EXIT_ALIASES[exit_direction]
|
||||
)
|
||||
self.unvisited_exits.append(out_exit.id)
|
||||
|
||||
def _generate_room(self, depth, coords):
|
||||
# TODO - determine what type of room to create here based on location and depth
|
||||
room_typeclass = EvAdventureDungeonRoom
|
||||
new_room = create.create_object(
|
||||
room_typeclass,
|
||||
key="Dungeon room",
|
||||
tags=((self.key,),),
|
||||
attributes=(("xy_coord", coords, "dungeon_xygrid"),),
|
||||
)
|
||||
return new_room
|
||||
|
||||
def new_room(self, from_exit):
|
||||
"""
|
||||
Create a new Dungeon room leading from the provided exit.
|
||||
|
||||
"""
|
||||
# figure out coordinate of old room and figure out what coord the
|
||||
# new one would get
|
||||
source_location = from_exit.location
|
||||
x, y = source_location.get("xy_coord", category="dungeon_xygrid", default=(0, 0))
|
||||
dx, dy = _EXIT_GRID_SHIFT.get(from_exit.key, (1, 0))
|
||||
new_x, new_y = (x + dx, y + dy)
|
||||
|
||||
# the dungeon's depth acts as a measure of the current difficulty level. This is the radial
|
||||
# distance from the (0, 0) (the entrance). The Orchestrator also tracks the highest
|
||||
# depth achieved.
|
||||
depth = int(sqrt(new_x**2 + new_y**2))
|
||||
|
||||
new_room = self._generate_room(depth, (new_x, new_y))
|
||||
|
||||
self.xy_grid[(new_x, new_y)] = new_room
|
||||
|
||||
# always make a return exit back to where we came from
|
||||
back_exit_key = (_EXIT_REVERSE_MAPPING.get(from_exit.key, "back"),)
|
||||
EvAdventureDungeonExit(
|
||||
key=back_exit_key,
|
||||
aliases=_EXIT_ALIASES.get(back_exit_key, ()),
|
||||
location=new_room,
|
||||
destination=from_exit.location,
|
||||
attributes=(("desc", "A dark passage."),),
|
||||
)
|
||||
|
||||
# figure out what other exits should be here, if any
|
||||
n_unexplored = len(self.unvisited_exits)
|
||||
if n_unexplored >= self.max_unexplored_exits:
|
||||
# no more exits to open - this is a dead end.
|
||||
return
|
||||
else:
|
||||
n_exits = randint(1, min(self.max_new_exits_per_room, n_unexplored))
|
||||
back_exit = from_exit.key
|
||||
available_directions = [
|
||||
direction for direction in _EXIT_ALIASES if direction != back_exit
|
||||
]
|
||||
# randomize order of exits
|
||||
shuffle(available_directions)
|
||||
for _ in range(n_exits):
|
||||
while available_directions:
|
||||
# get a random direction and check so there isn't a room already
|
||||
# created in that direction
|
||||
direction = available_directions.pop(0)
|
||||
dx, dy = _EXIT_GRID_SHIFT(direction)
|
||||
target_coord = (new_x + dx, new_y + dy)
|
||||
if target_coord not in self.xy_grid:
|
||||
# no room there - make an exit to it
|
||||
self.create_out_exit(new_room, direction)
|
||||
break
|
||||
|
||||
self.highest_depth = max(self.highest_depth, depth)
|
||||
|
||||
|
||||
# --------------------------------------------------
|
||||
# Start room
|
||||
# --------------------------------------------------
|
||||
|
||||
|
||||
class EvAdventureStartRoomExit(DefaultExit):
|
||||
"""
|
||||
Traversing this exit will either lead to an existing dungeon branch or create
|
||||
a new one.
|
||||
|
||||
"""
|
||||
|
||||
dungeon_orchestrator = AttributeProperty(None, autocreate=False)
|
||||
|
||||
def reset_exit(self):
|
||||
"""
|
||||
Flush the exit, so next traversal creates a new dungeon branch.
|
||||
|
||||
"""
|
||||
self.dungeon_orchestrator = self.destination = None
|
||||
|
||||
def at_traverse(self, traversing_object, target_location, **kwargs):
|
||||
"""
|
||||
When traversing create a new orchestrator if one is not already assigned.
|
||||
|
||||
"""
|
||||
if target_location is None or self.dungeon_orchestrator is None:
|
||||
self.dungeon_orchestrator, _ = EvAdventureDungeonOrchestrator.create(
|
||||
f"dungeon_orchestrator_{datetime.utcnow()}",
|
||||
)
|
||||
target_location = self.destination = self.dungeon_orchestrator.new_room(self)
|
||||
|
||||
super().at_traverse(traversing_object, target_location, **kwargs)
|
||||
|
||||
|
||||
class EvAdventureStartRoomResetter(DefaultScript):
|
||||
"""
|
||||
Simple ticker-script. Introduces a chance of the room's exits cycling every interval.
|
||||
|
||||
"""
|
||||
|
||||
def at_repeat(self):
|
||||
"""
|
||||
Called every time the script repeats.
|
||||
|
||||
"""
|
||||
room = self.obj
|
||||
for exi in room.exits:
|
||||
if inherits_from(exi, EvAdventureStartRoomExit) and random() < 0.5:
|
||||
exi.reset_exit()
|
||||
|
||||
|
||||
class EvAdventureDungeonRoomStart(EvAdventureDungeonRoom):
|
||||
"""
|
||||
Exits leading out of the start room, (except one leading outside) will lead to a different
|
||||
dungeon-branch, and after a certain time, the given exit will instead spawn a new branch. This
|
||||
room is responsible for cycling these exits regularly.
|
||||
|
||||
The actual exits should be created in the build script.
|
||||
|
||||
"""
|
||||
|
||||
recycle_time = 5 * 60 # seconds
|
||||
|
||||
def at_object_creation(self):
|
||||
self.scripts.add(EvAdventureStartRoomResetter, interval=self.recycle_time, autostart=True)
|
||||
335
evennia/contrib/tutorials/evadventure/equipment.py
Normal file
335
evennia/contrib/tutorials/evadventure/equipment.py
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
"""
|
||||
Knave has a system of Slots for its inventory.
|
||||
|
||||
"""
|
||||
|
||||
from .enums import Ability, WieldLocation
|
||||
from .objects import WeaponEmptyHand
|
||||
|
||||
|
||||
class EquipmentError(TypeError):
|
||||
pass
|
||||
|
||||
|
||||
class EquipmentHandler:
|
||||
"""
|
||||
_Knave_ puts a lot of emphasis on the inventory. You have CON_DEFENSE inventory
|
||||
slots. Some things, like torches can fit multiple in one slot, other (like
|
||||
big weapons and armor) use more than one slot. The items carried and wielded has a big impact
|
||||
on character customization - even magic requires carrying a runestone per spell.
|
||||
|
||||
The inventory also doubles as a measure of negative effects. Getting soaked in mud
|
||||
or slime could gunk up some of your inventory slots and make the items there unusuable
|
||||
until you clean them.
|
||||
|
||||
"""
|
||||
|
||||
save_attribute = "inventory_slots"
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
self._load()
|
||||
|
||||
def _load(self):
|
||||
"""
|
||||
Load or create a new slot storage.
|
||||
|
||||
"""
|
||||
self.slots = self.obj.attributes.get(
|
||||
self.save_attribute,
|
||||
category="inventory",
|
||||
default={
|
||||
WieldLocation.WEAPON_HAND: None,
|
||||
WieldLocation.SHIELD_HAND: None,
|
||||
WieldLocation.TWO_HANDS: None,
|
||||
WieldLocation.BODY: None,
|
||||
WieldLocation.HEAD: None,
|
||||
WieldLocation.BACKPACK: [],
|
||||
},
|
||||
)
|
||||
|
||||
def _save(self):
|
||||
"""
|
||||
Save slot to storage.
|
||||
|
||||
"""
|
||||
self.obj.attributes.add(self.save_attribute, self.slots, category="inventory")
|
||||
|
||||
def _count_slots(self):
|
||||
"""
|
||||
Count slot usage. This is fetched from the .size Attribute of the
|
||||
object. The size can also be partial slots.
|
||||
|
||||
"""
|
||||
slots = self.slots
|
||||
wield_usage = sum(
|
||||
getattr(slotobj, "size", 0) or 0
|
||||
for slot, slotobj in slots.items()
|
||||
if slot is not WieldLocation.BACKPACK
|
||||
)
|
||||
backpack_usage = sum(
|
||||
getattr(slotobj, "size", 0) or 0 for slotobj in slots[WieldLocation.BACKPACK]
|
||||
)
|
||||
return wield_usage + backpack_usage
|
||||
|
||||
@property
|
||||
def max_slots(self):
|
||||
"""
|
||||
The max amount of equipment slots ('carrying capacity') is based on
|
||||
the constitution defense.
|
||||
|
||||
"""
|
||||
return getattr(self.obj, Ability.CON.value, 1) + 10
|
||||
|
||||
def validate_slot_usage(self, obj):
|
||||
"""
|
||||
Check if obj can fit in equipment, based on its size.
|
||||
|
||||
Args:
|
||||
obj (EvAdventureObject): The object to add.
|
||||
|
||||
Raise:
|
||||
EquipmentError: If there's not enough room.
|
||||
|
||||
"""
|
||||
size = getattr(obj, "size", 0)
|
||||
max_slots = self.max_slots
|
||||
current_slot_usage = self._count_slots()
|
||||
if current_slot_usage + size > max_slots:
|
||||
slots_left = max_slots - current_slot_usage
|
||||
raise EquipmentError(
|
||||
f"Equipment full ($int2str({slots_left}) slots "
|
||||
f"remaining, {obj.key} needs $int2str({size}) "
|
||||
f"$pluralize(slot, {size}))."
|
||||
)
|
||||
return True
|
||||
|
||||
@property
|
||||
def armor(self):
|
||||
"""
|
||||
Armor provided by actually worn equipment/shield. For body armor
|
||||
this is a base value, like 12, for shield/helmet, it's a bonus, like +1.
|
||||
We treat values and bonuses equal and just add them up. This value
|
||||
can thus be 0, the 'unarmored' default should be handled by the calling
|
||||
method.
|
||||
|
||||
Returns:
|
||||
int: Armor from equipment. Note that this is the +bonus of Armor, not the
|
||||
'defense' (to get that one adds 10).
|
||||
|
||||
"""
|
||||
slots = self.slots
|
||||
return sum(
|
||||
(
|
||||
# armor is listed using its defense, so we remove 10 from it
|
||||
# (11 is base no-armor value in Knave)
|
||||
getattr(slots[WieldLocation.BODY], "armor", 11) - 10,
|
||||
# shields and helmets are listed by their bonus to armor
|
||||
getattr(slots[WieldLocation.SHIELD_HAND], "armor", 0),
|
||||
getattr(slots[WieldLocation.HEAD], "armor", 0),
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def weapon(self):
|
||||
"""
|
||||
Conveniently get the currently active weapon or rune stone.
|
||||
|
||||
Returns:
|
||||
obj or None: The weapon. None if unarmored.
|
||||
|
||||
"""
|
||||
# first checks two-handed wield, then one-handed; the two
|
||||
# should never appear simultaneously anyhow (checked in `use` method).
|
||||
slots = self.slots
|
||||
weapon = slots[WieldLocation.TWO_HANDS]
|
||||
if not weapon:
|
||||
weapon = slots[WieldLocation.WEAPON_HAND]
|
||||
if not weapon:
|
||||
weapon = WeaponEmptyHand()
|
||||
return weapon
|
||||
|
||||
def display_loadout(self):
|
||||
"""
|
||||
Get a visual representation of your current loadout.
|
||||
|
||||
Returns:
|
||||
str: The current loadout.
|
||||
|
||||
"""
|
||||
slots = self.slots
|
||||
weapon_str = "You are fighting with your bare fists"
|
||||
shield_str = " and have no shield."
|
||||
armor_str = "You wear no armor"
|
||||
helmet_str = " and no helmet."
|
||||
|
||||
two_hands = slots[WieldLocation.TWO_HANDS]
|
||||
if two_hands:
|
||||
weapon_str = f"You wield {two_hands} with both hands"
|
||||
shield_str = " (you can't hold a shield at the same time)."
|
||||
else:
|
||||
one_hands = slots[WieldLocation.WEAPON_HAND]
|
||||
if one_hands:
|
||||
weapon_str = f"You are wielding {one_hands} in one hand."
|
||||
shield = slots[WieldLocation.SHIELD_HAND]
|
||||
if shield:
|
||||
shield_str = f"You have {shield} in your off hand."
|
||||
|
||||
armor = slots[WieldLocation.BODY]
|
||||
if armor:
|
||||
armor_str = f"You are wearing {armor}"
|
||||
|
||||
helmet = slots[WieldLocation.BODY]
|
||||
if helmet:
|
||||
helmet_str = f" and {helmet} on your head."
|
||||
|
||||
return f"{weapon_str}{shield_str}\n{armor_str}{helmet_str}"
|
||||
|
||||
def use(self, obj):
|
||||
"""
|
||||
Make use of item - this makes use of the object's wield slot to decide where
|
||||
it goes. If it doesn't have any, it goes into backpack.
|
||||
|
||||
Args:
|
||||
obj (EvAdventureObject): Thing to use.
|
||||
|
||||
Raises:
|
||||
EquipmentError: If there's no room in inventory. It will contains the details
|
||||
of the error, suitable to echo to user.
|
||||
|
||||
Notes:
|
||||
If using an item already in the backpack, it should first be `removed` from the
|
||||
backpack, before applying here - otherwise, it will be added a second time!
|
||||
|
||||
this will cleanly move any 'colliding' items to the backpack to
|
||||
make the use possible (such as moving sword + shield to backpack when wielding
|
||||
a two-handed weapon). If wanting to warn the user about this, it needs to happen
|
||||
before this call.
|
||||
|
||||
"""
|
||||
# first check if we have room for this
|
||||
self.validate_slot_usage(obj)
|
||||
|
||||
slots = self.slots
|
||||
use_slot = getattr(obj, "inventory_use_slot", WieldLocation.BACKPACK)
|
||||
|
||||
if use_slot is WieldLocation.TWO_HANDS:
|
||||
# two-handed weapons can't co-exist with weapon/shield-hand used items
|
||||
slots[WieldLocation.WEAPON_HAND] = slots[WieldLocation.SHIELD_HAND] = None
|
||||
slots[use_slot] = obj
|
||||
elif use_slot in (WieldLocation.WEAPON_HAND, WieldLocation.SHIELD_HAND):
|
||||
# can't keep a two-handed weapon if adding a one-handede weapon or shield
|
||||
slots[WieldLocation.TWO_HANDS] = None
|
||||
slots[use_slot] = obj
|
||||
elif use_slot is WieldLocation.BACKPACK:
|
||||
# backpack has multiple slots.
|
||||
slots[use_slot].append(obj)
|
||||
else:
|
||||
# for others (body, head), just replace whatever's there
|
||||
slots[use_slot] = obj
|
||||
|
||||
# store new state
|
||||
self._save()
|
||||
|
||||
def add(self, obj):
|
||||
"""
|
||||
Put something in the backpack specifically (even if it could be wield/worn).
|
||||
|
||||
"""
|
||||
# check if we have room
|
||||
self.validate_slot_usage(obj)
|
||||
self.slots[WieldLocation.BACKPACK].append(obj)
|
||||
self._save()
|
||||
|
||||
def can_remove(self, leaving_object):
|
||||
"""
|
||||
Called to check if the object can be removed.
|
||||
|
||||
"""
|
||||
return True # TODO - some things may not be so easy, like mud
|
||||
|
||||
def remove(self, obj_or_slot):
|
||||
"""
|
||||
Remove specific object or objects from a slot.
|
||||
|
||||
Args:
|
||||
obj_or_slot (EvAdventureObject or WieldLocation): The specific object or
|
||||
location to empty. If this is WieldLocation.BACKPACK, all items
|
||||
in the backpack will be emptied and returned!
|
||||
Returns:
|
||||
list: A list of 0, 1 or more objects emptied from the inventory.
|
||||
|
||||
"""
|
||||
slots = self.slots
|
||||
ret = []
|
||||
if isinstance(obj_or_slot, WieldLocation):
|
||||
if obj_or_slot is WieldLocation.BACKPACK:
|
||||
# empty entire backpack
|
||||
ret.extend(slots[obj_or_slot])
|
||||
slots[obj_or_slot] = []
|
||||
else:
|
||||
ret.append(slots[obj_or_slot])
|
||||
slots[obj_or_slot] = None
|
||||
elif obj_or_slot in self.slots.values():
|
||||
# obj in use/wear slot
|
||||
for slot, objslot in slots.items():
|
||||
if objslot is obj_or_slot:
|
||||
slots[slot] = None
|
||||
ret.append(objslot)
|
||||
elif obj_or_slot in slots[WieldLocation.BACKPACK]:
|
||||
# obj in backpack slot
|
||||
try:
|
||||
slots[WieldLocation.BACKPACK].remove(obj_or_slot)
|
||||
ret.append(obj_or_slot)
|
||||
except ValueError:
|
||||
pass
|
||||
if ret:
|
||||
self._save()
|
||||
return ret
|
||||
|
||||
def get_wieldable_objects_from_backpack(self):
|
||||
"""
|
||||
Get all wieldable weapons (or spell runes) from backpack. This is useful in order to
|
||||
have a list to select from when swapping your wielded loadout.
|
||||
|
||||
Returns:
|
||||
list: A list of objects with a suitable `inventory_use_slot`. We don't check
|
||||
quality, so this may include broken items (we may want to visually show them
|
||||
in the list after all).
|
||||
|
||||
"""
|
||||
return [
|
||||
obj
|
||||
for obj in self.slots[WieldLocation.BACKPACK]
|
||||
if obj.inventory_use_slot
|
||||
in (WieldLocation.WEAPON_HAND, WieldLocation.TWO_HANDS, WieldLocation.SHIELD_HAND)
|
||||
]
|
||||
|
||||
def get_wearable_objects_from_backpack(self):
|
||||
"""
|
||||
Get all wearable items (armor or helmets) from backpack. This is useful in order to
|
||||
have a list to select from when swapping your worn loadout.
|
||||
|
||||
Returns:
|
||||
list: A list of objects with a suitable `inventory_use_slot`. We don't check
|
||||
quality, so this may include broken items (we may want to visually show them
|
||||
in the list after all).
|
||||
|
||||
"""
|
||||
return [
|
||||
obj
|
||||
for obj in self.slots[WieldLocation.BACKPACK]
|
||||
if obj.inventory_use_slot in (WieldLocation.BODY, WieldLocation.HEAD)
|
||||
]
|
||||
|
||||
def get_usable_objects_from_backpack(self):
|
||||
"""
|
||||
Get all 'usable' items (like potions) from backpack. This is useful for getting a
|
||||
list to select from.
|
||||
|
||||
Returns:
|
||||
list: A list of objects that are usable.
|
||||
|
||||
"""
|
||||
character = self.obj
|
||||
return [obj for obj in self.slots[WieldLocation.BACKPACK] if obj.at_pre_use(character)]
|
||||
|
|
@ -532,7 +532,7 @@ class EvAdventureCharacterGeneration:
|
|||
|
||||
for item in self.backpack:
|
||||
# TODO create here
|
||||
character.equipment.store(item)
|
||||
character.equipment.add(item)
|
||||
|
||||
|
||||
# character improvement
|
||||
|
|
|
|||
|
|
@ -3,15 +3,14 @@ Test the rules and chargen.
|
|||
|
||||
"""
|
||||
|
||||
from unittest.mock import patch, MagicMock, call
|
||||
from parameterized import parameterized
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
from anything import Something
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from parameterized import parameterized
|
||||
|
||||
from .. import characters, enums, equipment, random_tables, rules
|
||||
from .mixins import EvAdventureMixin
|
||||
from .. import rules
|
||||
from .. import enums
|
||||
from .. import random_tables
|
||||
from .. import characters
|
||||
|
||||
|
||||
class EvAdventureRollEngineTest(BaseEvenniaTest):
|
||||
|
|
@ -86,23 +85,24 @@ class EvAdventureRollEngineTest(BaseEvenniaTest):
|
|||
character.dexterity = 1
|
||||
|
||||
self.assertEqual(
|
||||
self.roll_engine.saving_throw(character, bonus_type=enums.Ability.STR), (False, None)
|
||||
self.roll_engine.saving_throw(character, bonus_type=enums.Ability.STR),
|
||||
(False, None, Something),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.roll_engine.saving_throw(character, bonus_type=enums.Ability.DEX, modifier=1),
|
||||
(False, None),
|
||||
(False, None, Something),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.roll_engine.saving_throw(
|
||||
character, advantage=True, bonus_type=enums.Ability.DEX, modifier=6
|
||||
),
|
||||
(False, None),
|
||||
(False, None, Something),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.roll_engine.saving_throw(
|
||||
character, disadvantage=True, bonus_type=enums.Ability.DEX, modifier=7
|
||||
),
|
||||
(True, None),
|
||||
(True, None, Something),
|
||||
)
|
||||
|
||||
mock_randint.return_value = 1
|
||||
|
|
@ -110,7 +110,7 @@ class EvAdventureRollEngineTest(BaseEvenniaTest):
|
|||
self.roll_engine.saving_throw(
|
||||
character, disadvantage=True, bonus_type=enums.Ability.STR, modifier=2
|
||||
),
|
||||
(False, enums.Ability.CRITICAL_FAILURE),
|
||||
(False, enums.Ability.CRITICAL_FAILURE, Something),
|
||||
)
|
||||
|
||||
mock_randint.return_value = 20
|
||||
|
|
@ -118,7 +118,7 @@ class EvAdventureRollEngineTest(BaseEvenniaTest):
|
|||
self.roll_engine.saving_throw(
|
||||
character, disadvantage=True, bonus_type=enums.Ability.STR, modifier=2
|
||||
),
|
||||
(True, enums.Ability.CRITICAL_SUCCESS),
|
||||
(True, enums.Ability.CRITICAL_SUCCESS, Something),
|
||||
)
|
||||
|
||||
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
|
||||
|
|
@ -133,7 +133,7 @@ class EvAdventureRollEngineTest(BaseEvenniaTest):
|
|||
self.roll_engine.opposed_saving_throw(
|
||||
attacker, defender, attack_type=enums.Ability.STR, defense_type=enums.Ability.ARMOR
|
||||
),
|
||||
(False, None),
|
||||
(False, None, Something),
|
||||
)
|
||||
self.assertEqual(
|
||||
self.roll_engine.opposed_saving_throw(
|
||||
|
|
@ -143,7 +143,7 @@ class EvAdventureRollEngineTest(BaseEvenniaTest):
|
|||
defense_type=enums.Ability.ARMOR,
|
||||
modifier=2,
|
||||
),
|
||||
(True, None),
|
||||
(True, None, Something),
|
||||
)
|
||||
|
||||
@patch("evennia.contrib.tutorials.evadventure.rules.randint")
|
||||
|
|
@ -224,7 +224,7 @@ class EvAdventureRollEngineTest(BaseEvenniaTest):
|
|||
# death
|
||||
mock_randint.return_value = 1
|
||||
self.roll_engine.roll_death(character)
|
||||
character.handle_death.assert_called()
|
||||
character.at_death.assert_called()
|
||||
# strength loss
|
||||
mock_randint.return_value = 3
|
||||
self.roll_engine.roll_death(character)
|
||||
|
|
@ -310,7 +310,7 @@ class EvAdventureCharacterGenerationTest(BaseEvenniaTest):
|
|||
self.assertTrue(character.db.desc.startswith("Herbalist"))
|
||||
self.assertEqual(character.armor, "gambeson")
|
||||
|
||||
character.equipment.store.assert_called()
|
||||
character.equipment.add.assert_called()
|
||||
|
||||
|
||||
class EvAdventureEquipmentTest(EvAdventureMixin, BaseEvenniaTest):
|
||||
|
|
@ -350,7 +350,7 @@ class EvAdventureEquipmentTest(EvAdventureMixin, BaseEvenniaTest):
|
|||
if is_ok:
|
||||
self.assertTrue(self.character.equipment.validate_slot_usage(obj))
|
||||
else:
|
||||
with self.assertRaises(characters.EquipmentError):
|
||||
with self.assertRaises(equipment.EquipmentError):
|
||||
self.character.equipment.validate_slot_usage(obj)
|
||||
|
||||
@parameterized.expand(
|
||||
|
|
@ -375,8 +375,8 @@ class EvAdventureEquipmentTest(EvAdventureMixin, BaseEvenniaTest):
|
|||
else:
|
||||
self.assertEqual(self.character.equipment.slots[where], obj)
|
||||
|
||||
def test_store(self):
|
||||
self.character.equipment.store(self.weapon)
|
||||
def test_add(self):
|
||||
self.character.equipment.add(self.weapon)
|
||||
self.assertEqual(self.character.equipment.slots[enums.WieldLocation.WEAPON_HAND], None)
|
||||
self.assertTrue(self.weapon in self.character.equipment.slots[enums.WieldLocation.BACKPACK])
|
||||
|
||||
|
|
@ -408,7 +408,7 @@ class EvAdventureEquipmentTest(EvAdventureMixin, BaseEvenniaTest):
|
|||
def test_remove__with_obj(self):
|
||||
self.character.equipment.use(self.shield)
|
||||
self.character.equipment.use(self.item)
|
||||
self.character.equipment.store(self.weapon)
|
||||
self.character.equipment.add(self.weapon)
|
||||
|
||||
self.assertEqual(
|
||||
self.character.equipment.slots[enums.WieldLocation.SHIELD_HAND], self.shield
|
||||
|
|
@ -428,7 +428,7 @@ class EvAdventureEquipmentTest(EvAdventureMixin, BaseEvenniaTest):
|
|||
def test_remove__with_slot(self):
|
||||
self.character.equipment.use(self.shield)
|
||||
self.character.equipment.use(self.item)
|
||||
self.character.equipment.store(self.helmet)
|
||||
self.character.equipment.add(self.helmet)
|
||||
|
||||
self.assertEqual(
|
||||
self.character.equipment.slots[enums.WieldLocation.SHIELD_HAND], self.shield
|
||||
|
|
@ -449,11 +449,11 @@ class EvAdventureEquipmentTest(EvAdventureMixin, BaseEvenniaTest):
|
|||
|
||||
def test_properties(self):
|
||||
self.character.equipment.use(self.armor)
|
||||
self.assertEqual(self.character.equipment.armor, 11)
|
||||
self.assertEqual(self.character.equipment.armor, 1)
|
||||
self.character.equipment.use(self.shield)
|
||||
self.assertEqual(self.character.equipment.armor, 12)
|
||||
self.assertEqual(self.character.equipment.armor, 2)
|
||||
self.character.equipment.use(self.helmet)
|
||||
self.assertEqual(self.character.equipment.armor, 13)
|
||||
self.assertEqual(self.character.equipment.armor, 3)
|
||||
|
||||
self.character.equipment.use(self.weapon)
|
||||
self.assertEqual(self.character.equipment.weapon, self.weapon)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue