Add tutorial doc for Rooms. Finish tests for combat
This commit is contained in:
parent
998cbb870b
commit
bdc3f37954
16 changed files with 627 additions and 275 deletions
|
|
@ -2,6 +2,8 @@
|
|||
#
|
||||
# Set up a combat area for testing combat. Requires developer or superuser perm.
|
||||
#
|
||||
# Run from in-game as batchcmd contrib.tutorials.evadventure.batchscripts.combat_demo
|
||||
#
|
||||
|
||||
# start from limbo
|
||||
|
||||
|
|
@ -13,13 +15,13 @@ type self = evennia.contrib.tutorials.evadventure.characters.EvAdventureCharacte
|
|||
|
||||
# assign us the twitch combat cmdset (requires superuser/developer perms)
|
||||
|
||||
py self.cmdset.add("evennia.contrib.tutorials.evadventure.combat.TwitchAttackCmdSet")
|
||||
py self.cmdset.add("evennia.contrib.tutorials.evadventure.combat_twitch.TwitchAttackCmdSet", persistent=True)
|
||||
|
||||
# Create and give us a weapons (this will use defaults on the class)
|
||||
|
||||
create sword:evennia.contrib.tutorials.evadventure.objects.EvAdventureWeapon
|
||||
|
||||
# create a consumable to use
|
||||
# create a consumable to use
|
||||
|
||||
create potion:evennia.contrib.tutorials.evadventure.objects.EvAdventureConsumable
|
||||
|
||||
|
|
@ -45,8 +47,8 @@ desc dummy = This is is an ugly training dummy made out of hay and wood.
|
|||
|
||||
# make the dummy crazy tough
|
||||
|
||||
dummy.hp_max = 1000
|
||||
set dummy/hp_max = 1000
|
||||
|
||||
#
|
||||
|
||||
dummy.hp = 1000
|
||||
set dummy/hp = 1000
|
||||
|
|
|
|||
|
|
@ -89,8 +89,9 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
|
|||
for comb in self.obj.location.contents
|
||||
if hasattr(comb, "scripts") and comb.scripts.has(self.key)
|
||||
]
|
||||
location = self.obj.location
|
||||
|
||||
if self.obj.location.allow_pvp:
|
||||
if hasattr(location, "allow_pvp") and location.allow_pvp:
|
||||
# in pvp, everyone else is an enemy
|
||||
allies = [combatant]
|
||||
enemies = [comb for comb in combatants if comb != combatant]
|
||||
|
|
@ -202,17 +203,21 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
|
|||
self.action_dict = self.fallback_action_dict
|
||||
self.queue_action(self.fallback_action_dict)
|
||||
|
||||
self.check_stop_combat()
|
||||
|
||||
def check_stop_combat(self):
|
||||
"""
|
||||
Check if the combat is over.
|
||||
"""
|
||||
|
||||
allies, enemies = self.get_sides()
|
||||
allies, enemies = self.get_sides(self.obj)
|
||||
allies.append(self.obj)
|
||||
|
||||
# remove all dead combatants
|
||||
allies = [comb for comb in allies if comb.hp > 0]
|
||||
enemies = [comb for comb in enemies if comb.hp > 0]
|
||||
location = self.obj.location
|
||||
|
||||
# only keep combatants that are alive and still in the same room
|
||||
allies = [comb for comb in allies if comb.hp > 0 and comb.location == location]
|
||||
enemies = [comb for comb in enemies if comb.hp > 0 and comb.location == location]
|
||||
|
||||
if not allies and not enemies:
|
||||
self.msg("Noone stands after the dust settles.", broadcast=False)
|
||||
|
|
@ -220,11 +225,14 @@ class EvAdventureCombatTwitchHandler(EvAdventureCombatHandlerBase):
|
|||
return
|
||||
|
||||
if not allies or not enemies:
|
||||
still_standing = list_to_string(f"$You({comb.key})" for comb in allies + enemies)
|
||||
self.msg(
|
||||
f"The combat is over. Still standing: {still_standing}.",
|
||||
broadcast=False,
|
||||
)
|
||||
if allies + enemies == [self.obj]:
|
||||
self.msg("The combat is over.")
|
||||
else:
|
||||
still_standing = list_to_string(f"$You({comb.key})" for comb in allies + enemies)
|
||||
self.msg(
|
||||
f"The combat is over. Still standing: {still_standing}.",
|
||||
broadcast=False,
|
||||
)
|
||||
self.stop_combat()
|
||||
|
||||
def stop_combat(self):
|
||||
|
|
@ -274,11 +282,18 @@ class _BaseTwitchCombatCommand(Command):
|
|||
rhs = " ".join(rhs)
|
||||
self.lhs, self.rhs = lhs.strip(), rhs.strip()
|
||||
|
||||
def get_or_create_combathandler(self, combathandler_name="combathandler"):
|
||||
def get_or_create_combathandler(self, target=None, combathandler_name="combathandler"):
|
||||
"""
|
||||
Get or create the combathandler assigned to this combatant.
|
||||
|
||||
"""
|
||||
if target:
|
||||
# add/check combathandler to the target
|
||||
if target.hp_max is None:
|
||||
self.msg("You can't attack that!")
|
||||
raise InterruptCommand()
|
||||
|
||||
EvAdventureCombatTwitchHandler.get_or_create_combathandler(target)
|
||||
return EvAdventureCombatTwitchHandler.get_or_create_combathandler(self.caller)
|
||||
|
||||
|
||||
|
|
@ -301,19 +316,19 @@ class CmdAttack(_BaseTwitchCombatCommand):
|
|||
if not target:
|
||||
return
|
||||
|
||||
combathandler = self.get_or_create_combathandler()
|
||||
combathandler = self.get_or_create_combathandler(target)
|
||||
# we use a fixed dt of 3 here, to mimic Diku style; one could also picture
|
||||
# attacking at a different rate, depending on skills/weapon etc.
|
||||
combathandler.queue_action({"key": "attack", "target": target, "dt": 3})
|
||||
combathandler.msg(f"$You() $conj(attack) $You({target.key})!", self.caller)
|
||||
|
||||
|
||||
class CmdLook(default_cmds.CmdLook):
|
||||
class CmdLook(default_cmds.CmdLook, _BaseTwitchCombatCommand):
|
||||
def func(self):
|
||||
# get regular look, followed by a combat summary
|
||||
super().func()
|
||||
if not self.args:
|
||||
combathandler = self.get_or_create_combathandler(self.caller.location)
|
||||
combathandler = self.get_or_create_combathandler()
|
||||
txt = str(combathandler.get_combat_summary(self.caller))
|
||||
maxwidth = max(display_len(line) for line in txt.strip().split("\n"))
|
||||
self.msg(f"|r{pad(' Combat Status ', width=maxwidth, fillchar='-')}|n\n{txt}")
|
||||
|
|
@ -416,7 +431,7 @@ class CmdStunt(_BaseTwitchCombatCommand):
|
|||
self.target = target.strip()
|
||||
|
||||
def func(self):
|
||||
combathandler = self.get_or_create_combathandler()
|
||||
combathandler = self.get_or_create_combathandler(self.target)
|
||||
|
||||
target = self.caller.search(self.target)
|
||||
if not target:
|
||||
|
|
@ -478,7 +493,7 @@ class CmdUseItem(_BaseTwitchCombatCommand):
|
|||
if not target:
|
||||
return
|
||||
|
||||
combathandler = self.get_or_create_combathandler()
|
||||
combathandler = self.get_or_create_combathandler(self.target)
|
||||
combathandler.queue_action({"key": "use", "item": item, "target": target})
|
||||
combathandler.msg(
|
||||
f"$You() prepare to use {item.get_display_name(self.caller)}!", self.caller
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Knave has a system of Slots for its inventory.
|
|||
from evennia.utils.utils import inherits_from
|
||||
|
||||
from .enums import Ability, WieldLocation
|
||||
from .objects import EvAdventureObject, WeaponEmptyHand
|
||||
from .objects import BARE_HANDS, EvAdventureObject
|
||||
|
||||
|
||||
class EquipmentError(TypeError):
|
||||
|
|
@ -167,7 +167,7 @@ class EquipmentHandler:
|
|||
if not weapon:
|
||||
weapon = slots[WieldLocation.WEAPON_HAND]
|
||||
if not weapon:
|
||||
weapon = WeaponEmptyHand()
|
||||
weapon = BARE_HANDS
|
||||
return weapon
|
||||
|
||||
def display_loadout(self):
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ rune sword (weapon+quest).
|
|||
|
||||
"""
|
||||
|
||||
from evennia import AttributeProperty
|
||||
from evennia import AttributeProperty, create_object, search_object
|
||||
from evennia.objects.objects import DefaultObject
|
||||
from evennia.utils.utils import make_iter
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ class EvAdventureTreasure(EvAdventureObject):
|
|||
"""
|
||||
|
||||
obj_type = ObjType.TREASURE
|
||||
value = AttributeProperty(100)
|
||||
value = AttributeProperty(100, autocreate=False)
|
||||
|
||||
|
||||
class EvAdventureConsumable(EvAdventureObject):
|
||||
|
|
@ -136,14 +136,26 @@ class EvAdventureConsumable(EvAdventureObject):
|
|||
"""
|
||||
|
||||
obj_type = ObjType.CONSUMABLE
|
||||
size = AttributeProperty(0.25)
|
||||
uses = AttributeProperty(1)
|
||||
size = AttributeProperty(0.25, autocreate=False)
|
||||
uses = AttributeProperty(1, autocreate=False)
|
||||
|
||||
def use(self, user, target, *args, **kwargs):
|
||||
def at_pre_use(self, user, target=None, *args, **kwargs):
|
||||
if target and user.location != target.location:
|
||||
user.msg("You are not close enough to the target!")
|
||||
return False
|
||||
|
||||
if self.uses <= 0:
|
||||
user.msg(f"|w{self.key} is used up.|n")
|
||||
return False
|
||||
|
||||
return super().at_pre_use(user, target=target, *args, **kwargs)
|
||||
|
||||
def use(self, user, target=None, *args, **kwargs):
|
||||
"""
|
||||
Use the consumable.
|
||||
|
||||
"""
|
||||
|
||||
if user.location:
|
||||
user.location.msg_contents(
|
||||
f"$You() $conj(use) {self.get_display_name(user)}.", from_obj=user
|
||||
|
|
@ -193,11 +205,16 @@ class EvAdventureWeapon(EvAdventureObject):
|
|||
|
||||
return super().get_display_name(looker=looker, **kwargs) + quality_txt
|
||||
|
||||
def at_pre_use(self, user, *args, **kwargs):
|
||||
def at_pre_use(self, user, target=None, *args, **kwargs):
|
||||
if target and user.location != target.location:
|
||||
# we assume weapons can only be used in the same location
|
||||
user.msg("You are not close enough to the target!")
|
||||
return False
|
||||
|
||||
if self.quality <= 0:
|
||||
user.msg(f"{self.get_display_name(user)} is broken and can't be used!")
|
||||
return False
|
||||
return super().at_pre_use(user, *args, **kwargs)
|
||||
return super().at_pre_use(user, target=target, *args, **kwargs)
|
||||
|
||||
def use(self, attacker, target, *args, advantage=False, disadvantage=False, **kwargs):
|
||||
"""When a weapon is used, it attacks an opponent"""
|
||||
|
|
@ -239,7 +256,8 @@ class EvAdventureWeapon(EvAdventureObject):
|
|||
message = f" $You() $conj(miss) $You({target.key})."
|
||||
if quality is Ability.CRITICAL_FAILURE:
|
||||
message += ".. it's a |rcritical miss!|n, damaging the weapon."
|
||||
self.quality -= 1
|
||||
if self.quality is not None:
|
||||
self.quality -= 1
|
||||
location.msg_contents(message, from_obj=attacker, mapping={target.key: target})
|
||||
|
||||
def at_post_use(self, user, *args, **kwargs):
|
||||
|
|
@ -262,21 +280,6 @@ class EvAdventureThrowable(EvAdventureWeapon, EvAdventureConsumable):
|
|||
damage_roll = AttributeProperty("1d6")
|
||||
|
||||
|
||||
class WeaponEmptyHand(EvAdventureWeapon):
|
||||
"""
|
||||
This is a dummy-class loaded when you wield no weapons. We won't create any db-object for it.
|
||||
|
||||
"""
|
||||
|
||||
obj_type = ObjType.WEAPON
|
||||
key = "Empty Fists"
|
||||
inventory_use_slot = WieldLocation.WEAPON_HAND
|
||||
attack_type = Ability.STR
|
||||
defense_type = Ability.ARMOR
|
||||
damage_roll = "1d4"
|
||||
quality = 100000 # let's assume fists are always available ...
|
||||
|
||||
|
||||
class EvAdventureRunestone(EvAdventureWeapon, EvAdventureConsumable):
|
||||
"""
|
||||
Base class for magic runestones. In _Knave_, every spell is represented by a rune stone
|
||||
|
|
@ -335,3 +338,23 @@ class EvAdventureHelmet(EvAdventureArmor):
|
|||
|
||||
obj_type = ObjType.HELMET
|
||||
inventory_use_slot = WieldLocation.HEAD
|
||||
|
||||
|
||||
class WeaponBareHands(EvAdventureWeapon):
|
||||
"""
|
||||
This is a dummy-class loaded when you wield no weapons. We won't create any db-object for it.
|
||||
|
||||
"""
|
||||
|
||||
obj_type = ObjType.WEAPON
|
||||
key = "Bare hands"
|
||||
inventory_use_slot = WieldLocation.WEAPON_HAND
|
||||
attack_type = Ability.STR
|
||||
defense_type = Ability.ARMOR
|
||||
damage_roll = "1d4"
|
||||
quality = 100000 # let's assume fists are always available ...
|
||||
|
||||
|
||||
BARE_HANDS = search_object("Bare hands", typeclass=WeaponBareHands)
|
||||
if not BARE_HANDS:
|
||||
BARE_HANDS = create_object(WeaponBareHands, key="Bare hands")
|
||||
|
|
|
|||
54
evennia/contrib/tutorials/evadventure/tests/test_rooms.py
Normal file
54
evennia/contrib/tutorials/evadventure/tests/test_rooms.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
Test of EvAdventure Rooms
|
||||
|
||||
"""
|
||||
|
||||
from evennia import DefaultExit, create_object
|
||||
from evennia.utils.ansi import strip_ansi
|
||||
from evennia.utils.test_resources import EvenniaTestCase
|
||||
|
||||
from ..characters import EvAdventureCharacter
|
||||
from ..rooms import EvAdventureRoom
|
||||
|
||||
|
||||
class EvAdventureRoomTest(EvenniaTestCase):
|
||||
def setUp(self):
|
||||
self.char = create_object(EvAdventureCharacter, key="TestChar")
|
||||
|
||||
def test_map(self):
|
||||
center_room = create_object(EvAdventureRoom, key="room_center")
|
||||
n_room = create_object(EvAdventureRoom, key="room_n")
|
||||
create_object(DefaultExit, key="north", location=center_room, destination=n_room)
|
||||
ne_room = create_object(EvAdventureRoom, key="room_ne")
|
||||
create_object(DefaultExit, key="northeast", location=center_room, destination=ne_room)
|
||||
e_room = create_object(EvAdventureRoom, key="room_e")
|
||||
create_object(DefaultExit, key="east", location=center_room, destination=e_room)
|
||||
se_room = create_object(EvAdventureRoom, key="room_se")
|
||||
create_object(DefaultExit, key="southeast", location=center_room, destination=se_room)
|
||||
s_room = create_object(EvAdventureRoom, key="room_")
|
||||
create_object(DefaultExit, key="south", location=center_room, destination=s_room)
|
||||
sw_room = create_object(EvAdventureRoom, key="room_sw")
|
||||
create_object(DefaultExit, key="southwest", location=center_room, destination=sw_room)
|
||||
w_room = create_object(EvAdventureRoom, key="room_w")
|
||||
create_object(DefaultExit, key="west", location=center_room, destination=w_room)
|
||||
nw_room = create_object(EvAdventureRoom, key="room_nw")
|
||||
create_object(DefaultExit, key="northwest", location=center_room, destination=nw_room)
|
||||
|
||||
desc = center_room.return_appearance(self.char)
|
||||
|
||||
expected = r"""
|
||||
o o o
|
||||
\|/
|
||||
o-@-o
|
||||
/|\
|
||||
o o o
|
||||
room_center
|
||||
You see nothing special.
|
||||
Exits: north, northeast, east, southeast, south, southwest, west, and northwest"""
|
||||
|
||||
result = "\n".join(part.rstrip() for part in strip_ansi(desc).split("\n"))
|
||||
expected = "\n".join(part.rstrip() for part in expected.split("\n"))
|
||||
print(result)
|
||||
print(expected)
|
||||
|
||||
self.assertEqual(result, expected)
|
||||
Loading…
Add table
Add a link
Reference in a new issue