mirror of
https://github.com/evennia/evennia.git
synced 2026-03-21 23:36:30 +01:00
Add more parts of the turnbased combat tutorial
This commit is contained in:
parent
83395211cc
commit
e7f8926b23
7 changed files with 399 additions and 70 deletions
|
|
@ -19,7 +19,7 @@ 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) use more than one slot. The items carried and wielded has a big impact
|
||||
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
|
||||
|
|
@ -147,6 +147,43 @@ class EquipmentHandler:
|
|||
weapon = slots[WieldLocation.WEAPON_HAND]
|
||||
return weapon
|
||||
|
||||
def display_loadout(self):
|
||||
"""
|
||||
Get a visual representation of your current loadout.
|
||||
|
||||
Returns:
|
||||
str: The current loadout.
|
||||
|
||||
"""
|
||||
slots = self.slots
|
||||
one_hand = None
|
||||
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 = f" (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
|
||||
|
|
@ -242,6 +279,51 @@ class EquipmentHandler:
|
|||
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 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 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.
|
||||
|
||||
"""
|
||||
return [obj for obj in slots[WieldLocation.BACKPACK] if obj.uses > 0]
|
||||
|
||||
|
||||
class LivingMixin:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -108,7 +108,9 @@ from evennia.utils import evtable, dbserialize, delay, evmenu
|
|||
from .enums import Ability
|
||||
from . import rules
|
||||
|
||||
# for simplicity, we have a default duration for advantages/disadvantages
|
||||
|
||||
COMBAT_HANDLER_KEY = "evadventure_turnbased_combathandler"
|
||||
COMBAT_HANDLER_INTERVAL = 60
|
||||
|
||||
|
||||
class CombatFailure(RuntimeError):
|
||||
|
|
@ -134,8 +136,9 @@ class CombatAction:
|
|||
aliases = []
|
||||
help_text = "Combat action to perform."
|
||||
|
||||
# if no target is needed (always affect oneself)
|
||||
no_target = False
|
||||
# the next combat menu node to go to - this ties the combat action into the UI
|
||||
# use None to do nothing (jump directly to registering the action)
|
||||
next_menu_node = "node_select_target"
|
||||
|
||||
# action to echo to everyone.
|
||||
post_action_text = "{combatant} performed an action."
|
||||
|
|
@ -199,7 +202,7 @@ class CombatAction:
|
|||
if available, should describe what the action does.
|
||||
|
||||
"""
|
||||
return True if self.uses is None else self.uses < self.max_uses
|
||||
return True if self.uses is None else self.uses < (self.max_uses or 0)
|
||||
|
||||
def pre_use(self, *args, **kwargs):
|
||||
pass
|
||||
|
|
@ -364,7 +367,7 @@ class CombatActionFlee(CombatAction):
|
|||
aliases = ("d", "disengage", "flee")
|
||||
|
||||
# this only affects us
|
||||
no_target = True
|
||||
next_menu_node = "node_register_action"
|
||||
|
||||
help_text = (
|
||||
"Disengage from combat. Use successfully two times in a row to leave combat at the "
|
||||
|
|
@ -419,6 +422,45 @@ class CombatActionBlock(CombatAction):
|
|||
pass # they are getting away!
|
||||
|
||||
|
||||
class CombatActionSwapWieldedWeaponOrSpell(CombatAction):
|
||||
"""
|
||||
Swap Wielded weapon or spell.
|
||||
|
||||
"""
|
||||
key = "Swap weapon/rune/shield"
|
||||
desc = "Swap currently wielded weapon, shield or spell-rune."
|
||||
aliases = ("s", "swap", "draw", "swap weapon", "draw weapon",
|
||||
"swap rune", "draw rune", "swap spell", "draw spell")
|
||||
help_text = ("Draw a new weapon or spell-rune from your inventory, "
|
||||
"replacing your current loadout")
|
||||
|
||||
next_menu_node = "node_select_wield_from_inventory"
|
||||
|
||||
post_action_text = "{combatant} switches weapons."
|
||||
|
||||
def use(self, combatant, item, *args, **kwargs):
|
||||
# this will make use of the item
|
||||
combatant.inventory.use(item)
|
||||
|
||||
|
||||
class CombatActionUseItem(CombatAction):
|
||||
"""
|
||||
Use an item from inventory.
|
||||
|
||||
"""
|
||||
key = "Use an item from backpack"
|
||||
desc = "Use an item from your inventory."
|
||||
aliases = ("u", "use", "use item")
|
||||
help_text = "Choose an item from your inventory to use."
|
||||
|
||||
next_menu_node = "node_select_use_item_from_inventory"
|
||||
|
||||
post_action_text = "{combatant} used an item."
|
||||
|
||||
def use(self, combatant, item, *args, **kwargs):
|
||||
item.use(combatant, *args, **kwargs)
|
||||
|
||||
|
||||
class CombatActionDoNothing(CombatAction):
|
||||
"""
|
||||
Do nothing this turn.
|
||||
|
|
@ -431,7 +473,7 @@ class CombatActionDoNothing(CombatAction):
|
|||
help_text = "Hold you position, doing nothing."
|
||||
|
||||
# affects noone else
|
||||
no_target = True
|
||||
next_menu_node = "node_register_action"
|
||||
|
||||
post_action_text = "{combatant} does nothing this turn."
|
||||
|
||||
|
|
@ -439,7 +481,9 @@ class CombatActionDoNothing(CombatAction):
|
|||
class EvAdventureCombatHandler(DefaultScript):
|
||||
"""
|
||||
This script is created when combat is initialized and stores a queue
|
||||
of all active participants. It's also possible to join (or leave) the fray later.
|
||||
of all active participants.
|
||||
|
||||
It's also possible to join (or leave) the fray later.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -450,6 +494,7 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
default_action_classes = [
|
||||
CombatActionAttack,
|
||||
CombatActionStunt,
|
||||
CombatActionSwapWieldedWeaponOrSpell,
|
||||
CombatActionUseItem,
|
||||
CombatActionFlee,
|
||||
CombatActionBlock,
|
||||
|
|
@ -481,7 +526,8 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
def at_script_creation(self):
|
||||
|
||||
# how often this script ticks - the max length of each turn (in seconds)
|
||||
self.interval = 60
|
||||
self.key = COMBAT_HANDLER_KEY
|
||||
self.interval = COMBAT_HANDLER_INTERVAL
|
||||
|
||||
def at_repeat(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -621,6 +667,7 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
"""
|
||||
if combatant not in self.combatants:
|
||||
self.combatants.append(combatant)
|
||||
combatant.db.turnbased_combathandler = self
|
||||
|
||||
# allow custom character actions (not used by default)
|
||||
custom_action_classes = combatant.db.custom_combat_actions or []
|
||||
|
|
@ -637,13 +684,14 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
{
|
||||
"node_wait_start": node_wait_start,
|
||||
"node_select_target": node_select_target,
|
||||
"node_selct_action": node_select_action,
|
||||
"node_select_action": node_select_action,
|
||||
"node_wait_turn": node_wait_turn,
|
||||
},
|
||||
startnode="node_wait_turn",
|
||||
auto_quit=False,
|
||||
persistent=True,
|
||||
session=session,
|
||||
combathandler=self # makes this available as combatant.ndb._evmenu.combathandler
|
||||
)
|
||||
|
||||
def remove_combatant(self, combatant):
|
||||
|
|
@ -658,6 +706,7 @@ class EvAdventureCombatHandler(DefaultScript):
|
|||
self.combatants.remove(combatant)
|
||||
self.combatant_actions.pop(combatant, None)
|
||||
combatant.ndb._evmenu.close_menu()
|
||||
del combatant.db.turnbased_combathandler
|
||||
|
||||
def start_combat(self):
|
||||
"""
|
||||
|
|
@ -866,7 +915,7 @@ def _register_action(caller, raw_string, **kwargs):
|
|||
action_key = kwargs.get["action_key"]
|
||||
action_args = kwargs["action_args"]
|
||||
action_kwargs = kwargs["action_kwargs"]
|
||||
action_target = kwargs["action_target"]
|
||||
action_target = kwargs.get("action_target")
|
||||
combat_handler = caller._evmenu.combathandler
|
||||
combat_handler.register_action(
|
||||
caller, action_key, action_target, *action_args, **action_kwargs)
|
||||
|
|
@ -884,44 +933,128 @@ def node_select_target(caller, raw_string, **kwargs):
|
|||
action_key = kwargs.get("action_key")
|
||||
action_args = kwargs.get("action_args")
|
||||
action_kwargs = kwargs.get("action_kwargs")
|
||||
combat = caller.scripts.get("combathandler")
|
||||
combat = caller.ndb._evmenu.combathandler
|
||||
text = "Select target for |w{action_key}|n."
|
||||
|
||||
# make the apply-self option always the first one, give it key 0
|
||||
kwargs["action_target"] = caller
|
||||
options = [
|
||||
{
|
||||
"key": "0",
|
||||
"desc": "(yourself)",
|
||||
"goto": (
|
||||
_register_action,
|
||||
{
|
||||
"action_key": action_key,
|
||||
"action_args": action_args,
|
||||
"action_kwargs": action_kwargs,
|
||||
"action_target": caller,
|
||||
},
|
||||
),
|
||||
"goto": (_register_action, kwargs)
|
||||
}
|
||||
]
|
||||
# filter out ourselves and then make options for everyone else
|
||||
combatants = [combatant for combatant in combat.combatants if combatant is not caller]
|
||||
for combatant in combatants:
|
||||
# automatic menu numbering starts from 1
|
||||
kwargs["action_target"] = combatant
|
||||
options.append(
|
||||
{
|
||||
"desc": combatant.key,
|
||||
"goto": (
|
||||
_register_action,
|
||||
{
|
||||
"action_key": action_key,
|
||||
"action_args": action_args,
|
||||
"action_kwargs": action_kwargs,
|
||||
"action_target": combatant,
|
||||
},
|
||||
),
|
||||
"goto": (_register_action, kwargs)
|
||||
}
|
||||
)
|
||||
|
||||
# add ability to cancel
|
||||
options.append(
|
||||
{
|
||||
"key": "_default",
|
||||
"desc": "(No input to Abort and go back)",
|
||||
"goto": "node_select_action"
|
||||
}
|
||||
)
|
||||
|
||||
return text, options
|
||||
|
||||
|
||||
def _item_broken(caller, raw_string, **kwargs):
|
||||
caller.msg("|rThis item is broken and unusable!|n")
|
||||
return None # back to previous node
|
||||
|
||||
|
||||
def node_select_wield_from_inventory(caller, raw_string, **kwargs):
|
||||
"""
|
||||
Menu node allowing for wielding item(s) from inventory.
|
||||
|
||||
"""
|
||||
combat = caller.ndb._evmenu.combathandler
|
||||
loadout = caller.inventory.display_loadout()
|
||||
text = (f"{loadout}\nSelect weapon, spell or shield to draw. It will swap out "
|
||||
"anything already in the same hand (you can't change armor or helmet in combat).")
|
||||
|
||||
# get a list of all suitable weapons/spells/shields
|
||||
options = []
|
||||
for obj in caller.inventory.get_wieldable_objects_from_backpack():
|
||||
if obj.quality <= 0:
|
||||
# object is broken
|
||||
options.append(
|
||||
{
|
||||
"desc": f"|Rstr(obj)|n",
|
||||
"goto": _item_broken,
|
||||
}
|
||||
)
|
||||
else:
|
||||
# normally working item
|
||||
kwargs['action_args'] = (obj,)
|
||||
options.append(
|
||||
{
|
||||
"desc": str(obj),
|
||||
"goto": (_register_action, kwargs)
|
||||
}
|
||||
)
|
||||
|
||||
# add ability to cancel
|
||||
options.append(
|
||||
{
|
||||
"key": "_default",
|
||||
"desc": "(No input to Abort and go back)",
|
||||
"goto": "node_select_action"
|
||||
}
|
||||
)
|
||||
|
||||
return text, options
|
||||
|
||||
|
||||
def node_select_use_item_from_inventory(caller, raw_string, **kwargs):
|
||||
"""
|
||||
Menu item allowing for using usable items (like potions) from inventory.
|
||||
|
||||
"""
|
||||
combat = caller.ndb._evmenu.combathandler
|
||||
text = "Select an item to use."
|
||||
|
||||
# get a list of all suitable weapons/spells/shields
|
||||
options = []
|
||||
for obj in caller.inventory.get_usable_objects_from_backpack():
|
||||
if obj.quality <= 0:
|
||||
# object is broken
|
||||
options.append(
|
||||
{
|
||||
"desc": f"|Rstr(obj)|n",
|
||||
"goto": _item_broken,
|
||||
}
|
||||
)
|
||||
else:
|
||||
# normally working item
|
||||
kwargs['action_args'] = (obj,)
|
||||
options.append(
|
||||
{
|
||||
"desc": str(obj),
|
||||
"goto": (_register_action, kwargs)
|
||||
}
|
||||
)
|
||||
|
||||
# add ability to cancel
|
||||
options.append(
|
||||
{
|
||||
"key": "_default",
|
||||
"desc": "(No input to Abort and go back)",
|
||||
"goto": "node_select_action"
|
||||
}
|
||||
)
|
||||
|
||||
return text, options
|
||||
|
||||
|
||||
|
|
@ -941,8 +1074,8 @@ def node_select_action(caller, raw_string, **kwargs):
|
|||
Menu node for selecting a combat action.
|
||||
|
||||
"""
|
||||
combat = caller.scripts.get("combathandler")
|
||||
text = combat.get_previous_turn_status(caller)
|
||||
combat = caller.ndb._evmenu.combathandler
|
||||
text = combat.get_combat_summary(caller)
|
||||
|
||||
options = []
|
||||
for icount, action in enumerate(combat.get_available_actions(caller)):
|
||||
|
|
@ -968,9 +1101,9 @@ def node_select_action(caller, raw_string, **kwargs):
|
|||
)
|
||||
}
|
||||
)
|
||||
elif action.no_target:
|
||||
# action is available, and requires no target. Redirect to register
|
||||
# without going via the select-target node.
|
||||
elif action.next_menu_node is None:
|
||||
# action is available, but needs no intermediary step. Redirect to register
|
||||
# the action immediately
|
||||
options.append(
|
||||
{
|
||||
"key": key,
|
||||
|
|
@ -987,13 +1120,13 @@ def node_select_action(caller, raw_string, **kwargs):
|
|||
}
|
||||
)
|
||||
else:
|
||||
# action is available and requires a target, so we will select a target next.
|
||||
# action is available and next_menu_node is set to point to the next node we want
|
||||
options.append(
|
||||
{
|
||||
"key": key,
|
||||
"desc": desc,
|
||||
"goto": (
|
||||
"node_select_target",
|
||||
action.next_menu_node,
|
||||
{
|
||||
"action_key": action.key,
|
||||
"action_args": (),
|
||||
|
|
@ -1002,6 +1135,14 @@ def node_select_action(caller, raw_string, **kwargs):
|
|||
),
|
||||
}
|
||||
)
|
||||
# add ability to cancel
|
||||
options.append(
|
||||
{
|
||||
"key": "_default",
|
||||
"desc": "(No input to Abort and go back)",
|
||||
"goto": "node_select_action"
|
||||
}
|
||||
)
|
||||
|
||||
return text, options
|
||||
|
||||
|
|
@ -1044,9 +1185,12 @@ def node_wait_start(caller, raw_string, **kwargs):
|
|||
# -------------- end of combat menu definitions
|
||||
|
||||
|
||||
def join_combat(caller, *targets, combathandler=None, session=None):
|
||||
def join_combat(caller, *targets, session=None):
|
||||
"""
|
||||
Join or create a new combat involving caller and at least one target,
|
||||
Join or create a new combat involving caller and at least one target. The combat
|
||||
is started on the current room location - this means there can only be one combat
|
||||
in each room (this is not hardcoded in the combat per-se, but it makes sense for
|
||||
this implementation).
|
||||
|
||||
Args:
|
||||
caller (Object): The one starting the combat.
|
||||
|
|
@ -1055,9 +1199,6 @@ def join_combat(caller, *targets, combathandler=None, session=None):
|
|||
one opponent!).
|
||||
|
||||
Keyword Args:
|
||||
combathandler (EvAdventureCombatHandler): If not given, a new combat will be created and
|
||||
at least one `*targets` argument must be provided. If given, caller will
|
||||
join an existing combat.
|
||||
session (Session, optional): A player session to use. This is useful for multisession modes.
|
||||
|
||||
Returns:
|
||||
|
|
@ -1065,15 +1206,19 @@ def join_combat(caller, *targets, combathandler=None, session=None):
|
|||
|
||||
"""
|
||||
created = False
|
||||
location = caller.location
|
||||
if not location:
|
||||
raise CombatFailure("Must have a location to start combat.")
|
||||
|
||||
if not targets:
|
||||
raise CombatFailure("Must have an opponent to start combat.")
|
||||
|
||||
combathandler = location.scripts.get(COMBAT_HANDLER_KEY).first()
|
||||
if not combathandler:
|
||||
if not targets:
|
||||
raise CombatFailure("Must have an opponent to start combat.")
|
||||
combathandler, _ = EvAdventureCombatHandler.create(
|
||||
f"Combat_{datetime.utcnow()}",
|
||||
autostart=False, # means we must use .start() to start the script
|
||||
)
|
||||
combathandler = location.scripts.add(EvAdventureCombatHandler, autostart=False)
|
||||
created = True
|
||||
|
||||
# it's safe to add a combatant to the same combat more than once
|
||||
combathandler.add_combatant(caller, session=session)
|
||||
for target in targets:
|
||||
combathandler.add_combatant(target, session=session)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
"""
|
||||
EvAdventure commands and cmdsets.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from evennia import Command, default_cmds
|
||||
from . combat_turnbased import join_combat
|
||||
|
||||
|
||||
class EvAdventureCommand(Command):
|
||||
"""
|
||||
Base EvAdventure command. This is on the form
|
||||
|
||||
command <args>
|
||||
|
||||
where whitespace around the argument(s) are stripped.
|
||||
|
||||
"""
|
||||
def parse(self):
|
||||
self.args = self.args.strip()
|
||||
|
||||
|
||||
class CmdAttackTurnBased(EvAdventureCommand):
|
||||
"""
|
||||
Attack a target or join an existing combat.
|
||||
|
||||
Usage:
|
||||
attack <target>
|
||||
attack <target>, <target>, ...
|
||||
|
||||
If the target is involved in combat already, you'll join combat with
|
||||
the first target you specify. Attacking multiple will draw them all into
|
||||
combat.
|
||||
|
||||
This will start/join turn-based, combat, where you have a limited
|
||||
time to decide on your next action from a menu of options.
|
||||
|
||||
"""
|
||||
|
||||
def parse(self):
|
||||
super().parse()
|
||||
self.targets = [name.strip() for name in self.args.split(",")]
|
||||
|
||||
def func(self):
|
||||
|
||||
# find if
|
||||
|
||||
target_objs = []
|
||||
for target in self.targets:
|
||||
target_obj = self.caller.search(target)
|
||||
if target_obj:
|
||||
# show a warning but don't abort
|
||||
continue
|
||||
target_objs.append(target_obj)
|
||||
|
||||
if target_objs:
|
||||
join_combat(self.caller, *target_objs, session=self.session)
|
||||
|
|
@ -20,13 +20,16 @@ class EvAdventureObject(DefaultObject):
|
|||
"""
|
||||
|
||||
# inventory management
|
||||
inventory_use_slot = AttributeProperty(default=WieldLocation.BACKPACK)
|
||||
inventory_use_slot = AttributeProperty(WieldLocation.BACKPACK)
|
||||
# how many inventory slots it uses (can be a fraction)
|
||||
size = AttributeProperty(default=1)
|
||||
armor = AttributeProperty(default=0)
|
||||
size = AttributeProperty(1)
|
||||
armor = AttributeProperty(0)
|
||||
# items that are usable (like potions) have a value larger than 0. Wieldable items
|
||||
# like weapons, armor etc are not 'usable' in this respect.
|
||||
uses = AttributeProperty(0)
|
||||
# when 0, item is destroyed and is unusable
|
||||
quality = AttributeProperty(default=1)
|
||||
value = AttributeProperty(default=0)
|
||||
quality = AttributeProperty(1)
|
||||
value = AttributeProperty(0)
|
||||
|
||||
|
||||
class EvAdventureObjectFiller(EvAdventureObject):
|
||||
|
|
@ -41,8 +44,30 @@ class EvAdventureObjectFiller(EvAdventureObject):
|
|||
meaning it's unusable.
|
||||
|
||||
"""
|
||||
quality = AttributeProperty(0)
|
||||
|
||||
quality = AttributeProperty(default=0)
|
||||
|
||||
class EvAdventureConsumable(EvAdventureObject):
|
||||
"""
|
||||
Item that can be 'used up', like a potion or food. Weapons, armor etc does not
|
||||
have a limited usage in this way.
|
||||
|
||||
"""
|
||||
inventory_use_slot = AttributeProperty(WieldLocation.BACKPACK)
|
||||
size = AttributeProperty(0.25)
|
||||
uses = AttributeProperty(1)
|
||||
|
||||
def use(self, user, *args, **kwargs):
|
||||
"""
|
||||
Consume a 'use' of this item. Once it reaches 0 uses, it should normally
|
||||
not be usable anymore and probably be deleted.
|
||||
|
||||
Args:
|
||||
user (Object): The one using the item.
|
||||
*args, **kwargs: Extra arguments depending on the usage and item.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class EvAdventureWeapon(EvAdventureObject):
|
||||
|
|
@ -53,13 +78,9 @@ class EvAdventureWeapon(EvAdventureObject):
|
|||
|
||||
inventory_use_slot = AttributeProperty(WieldLocation.WEAPON_HAND)
|
||||
|
||||
attack_type = AttributeProperty(default=Ability.STR)
|
||||
defense_type = AttributeProperty(default=Ability.ARMOR)
|
||||
damage_roll = AttributeProperty(default="1d6")
|
||||
|
||||
# at which ranges this weapon can be used. If not listed, unable to use
|
||||
distance_optimal = AttributeProperty(default=0) # normal usage (fists)
|
||||
distance_suboptimal = AttributeProperty(default=None) # disadvantage (fists)
|
||||
attack_type = AttributeProperty(Ability.STR)
|
||||
defense_type = AttributeProperty(Ability.ARMOR)
|
||||
damage_roll = AttributeProperty("1d6")
|
||||
|
||||
|
||||
class EvAdventureRunestone(EvAdventureWeapon):
|
||||
|
|
@ -70,3 +91,8 @@ class EvAdventureRunestone(EvAdventureWeapon):
|
|||
they are quite powerful (and scales with caster level).
|
||||
|
||||
"""
|
||||
inventory_use_slot = AttributeProperty(WieldLocation.TWO_HANDS)
|
||||
|
||||
attack_type = AttributeProperty(Ability.INT)
|
||||
defense_type = AttributeProperty(Ability.CON)
|
||||
damage_roll = AttributeProperty("1d8")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
"""
|
||||
EvAdventure rooms.
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
from evennia import DefaultRoom
|
||||
|
||||
|
||||
class EvAdventureRoom(DefaultRoom):
|
||||
pass
|
||||
|
|
@ -6,6 +6,7 @@ Helpers for testing evadventure modules.
|
|||
from evennia.utils import create
|
||||
from ..characters import EvAdventureCharacter
|
||||
from ..objects import EvAdventureObject
|
||||
from ..rooms import EvAdventureRoom
|
||||
from .. import enums
|
||||
|
||||
|
||||
|
|
@ -17,7 +18,9 @@ class EvAdventureMixin:
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.character = create.create_object(EvAdventureCharacter, key="testchar")
|
||||
self.location = create.create_object(EvAdventureRoom, key="testroom")
|
||||
self.character = create.create_object(EvAdventureCharacter, key="testchar",
|
||||
location=self.location)
|
||||
self.helmet = create.create_object(
|
||||
EvAdventureObject,
|
||||
key="helmet",
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ class ScriptHandler(object):
|
|||
in script definition and listings)
|
||||
autostart (bool, optional): Start the script upon adding it.
|
||||
|
||||
Returns:
|
||||
Script: The newly created Script.
|
||||
|
||||
"""
|
||||
if self.obj.__dbclass__.__name__ == "AccountDB":
|
||||
# we add to an Account, not an Object
|
||||
|
|
@ -76,21 +79,21 @@ class ScriptHandler(object):
|
|||
scriptclass, key=key, account=self.obj, autostart=autostart
|
||||
)
|
||||
else:
|
||||
# the normal - adding to an Object. We wait to autostart so we can differentiate
|
||||
# adding to an Object. We wait to autostart so we can differentiate
|
||||
# a failing creation from a script that immediately starts/stops.
|
||||
script = create.create_script(scriptclass, key=key, obj=self.obj, autostart=False)
|
||||
if not script:
|
||||
logger.log_err("Script %s failed to be created/started." % scriptclass)
|
||||
return False
|
||||
logger.log_err(f"Script {scriptclass} failed to be created.")
|
||||
return None
|
||||
if autostart:
|
||||
script.start()
|
||||
if not script.id:
|
||||
# this can happen if the script has repeats=1 or calls stop() in at_repeat.
|
||||
logger.log_info(
|
||||
"Script %s started and then immediately stopped; "
|
||||
"it could probably be a normal function." % scriptclass
|
||||
f"Script {scriptclass} started and then immediately stopped; "
|
||||
"it could probably be a normal function."
|
||||
)
|
||||
return True
|
||||
return script
|
||||
|
||||
def start(self, key):
|
||||
"""
|
||||
|
|
@ -118,10 +121,10 @@ class ScriptHandler(object):
|
|||
key (str): Search criterion, the script's key or dbref.
|
||||
|
||||
Returns:
|
||||
scripts (list): The found scripts matching `key`.
|
||||
scripts (queryset): The found scripts matching `key`.
|
||||
|
||||
"""
|
||||
return list(ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=key))
|
||||
return ScriptDB.objects.get_all_scripts_on_obj(self.obj, key=key)
|
||||
|
||||
def delete(self, key=None):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue