Made simple map, start shops
This commit is contained in:
parent
a471f0fd86
commit
306d6b44aa
5 changed files with 242 additions and 38 deletions
|
|
@ -69,3 +69,20 @@ class WieldLocation(Enum):
|
||||||
# combat-related
|
# combat-related
|
||||||
OPTIMAL_DISTANCE = "optimal_distance"
|
OPTIMAL_DISTANCE = "optimal_distance"
|
||||||
SUBOPTIMAL_DISTANCE = "suboptimal_distance"
|
SUBOPTIMAL_DISTANCE = "suboptimal_distance"
|
||||||
|
|
||||||
|
|
||||||
|
class ObjType(Enum):
|
||||||
|
"""
|
||||||
|
Object types
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
WEAPON = "weapon"
|
||||||
|
ARMOR = "armor"
|
||||||
|
SHIELD = "shield"
|
||||||
|
HELMET = "helmet"
|
||||||
|
CONSUMABLE = "consumable"
|
||||||
|
GEAR = "gear"
|
||||||
|
MAGIC = "magic"
|
||||||
|
QUEST = "quest"
|
||||||
|
TREASURE = "treasure"
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,26 @@ All items in the game inherit from a base object. The properties (what you can d
|
||||||
with an object, such as wear, wield, eat, drink, kill etc) are all controlled by
|
with an object, such as wear, wield, eat, drink, kill etc) are all controlled by
|
||||||
Tags.
|
Tags.
|
||||||
|
|
||||||
|
Every object has one of a few `obj_type`-category tags:
|
||||||
|
- weapon
|
||||||
|
- armor
|
||||||
|
- shield
|
||||||
|
- helmet
|
||||||
|
- consumable (potions, torches etc)
|
||||||
|
- magic (runestones, magic items)
|
||||||
|
- quest (quest-items)
|
||||||
|
- treasure (valuable to sell)
|
||||||
|
|
||||||
|
It's possible for an item to have more than one tag, such as a golden helmet (helmet+treasure) or
|
||||||
|
rune sword (weapon+quest).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from evennia import AttributeProperty, TagProperty
|
||||||
from evennia.objects.objects import DefaultObject
|
from evennia.objects.objects import DefaultObject
|
||||||
from evennia.typeclasses.attributes import AttributeProperty
|
from evennia.utils.utils import make_iter
|
||||||
|
|
||||||
from .enums import Ability, WieldLocation
|
from .enums import Ability, ObjType, WieldLocation
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureObject(DefaultObject):
|
class EvAdventureObject(DefaultObject):
|
||||||
|
|
@ -23,15 +35,23 @@ class EvAdventureObject(DefaultObject):
|
||||||
inventory_use_slot = AttributeProperty(WieldLocation.BACKPACK)
|
inventory_use_slot = AttributeProperty(WieldLocation.BACKPACK)
|
||||||
# how many inventory slots it uses (can be a fraction)
|
# how many inventory slots it uses (can be a fraction)
|
||||||
size = AttributeProperty(1)
|
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(1)
|
|
||||||
value = AttributeProperty(0)
|
value = AttributeProperty(0)
|
||||||
|
|
||||||
help_text = AttributeProperty("")
|
# can also be an iterable, for adding multiple obj-type tags
|
||||||
|
obj_type = ObjType.TREASURE.value
|
||||||
|
|
||||||
|
def at_object_creation(self):
|
||||||
|
for obj_type in make_iter(self.obj_type):
|
||||||
|
self.tags.add(obj_type, category="obj_type")
|
||||||
|
|
||||||
|
def has_obj_type(self, objtype):
|
||||||
|
"""
|
||||||
|
Check if object is of a particular type.
|
||||||
|
|
||||||
|
typeobj_enum (enum.ObjType): A type to check, like enums.TypeObj.TREASURE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return objtype.value in make_iter(self.obj_type)
|
||||||
|
|
||||||
def get_help(self):
|
def get_help(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -93,9 +113,30 @@ class EvAdventureObjectFiller(EvAdventureObject):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
obj_type = ObjType.QUEST.value # can't be sold
|
||||||
quality = AttributeProperty(0)
|
quality = AttributeProperty(0)
|
||||||
|
|
||||||
|
|
||||||
|
class EvAdventureQuest(EvAdventureObject):
|
||||||
|
"""
|
||||||
|
A quest object. These cannot be sold and only be used for quest resolution.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj_type = ObjType.QUEST.value
|
||||||
|
value = AttributeProperty(0)
|
||||||
|
|
||||||
|
|
||||||
|
class EvAdventureTreasure(EvAdventureObject):
|
||||||
|
"""
|
||||||
|
A 'treasure' is mainly useful to sell for coin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj_type = ObjType.TREASURE.value
|
||||||
|
value = AttributeProperty(100)
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureConsumable(EvAdventureObject):
|
class EvAdventureConsumable(EvAdventureObject):
|
||||||
"""
|
"""
|
||||||
Item that can be 'used up', like a potion or food. Weapons, armor etc does not
|
Item that can be 'used up', like a potion or food. Weapons, armor etc does not
|
||||||
|
|
@ -103,7 +144,7 @@ class EvAdventureConsumable(EvAdventureObject):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
inventory_use_slot = AttributeProperty(WieldLocation.BACKPACK)
|
obj_type = ObjType.CONSUMABLE.value
|
||||||
size = AttributeProperty(0.25)
|
size = AttributeProperty(0.25)
|
||||||
uses = AttributeProperty(1)
|
uses = AttributeProperty(1)
|
||||||
|
|
||||||
|
|
@ -134,25 +175,13 @@ class EvAdventureConsumable(EvAdventureObject):
|
||||||
self.delete()
|
self.delete()
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureWeapon(EvAdventureObject):
|
|
||||||
"""
|
|
||||||
Base weapon class for all EvAdventure weapons.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
inventory_use_slot = AttributeProperty(WieldLocation.WEAPON_HAND)
|
|
||||||
|
|
||||||
attack_type = AttributeProperty(Ability.STR)
|
|
||||||
defense_type = AttributeProperty(Ability.ARMOR)
|
|
||||||
damage_roll = AttributeProperty("1d6")
|
|
||||||
|
|
||||||
|
|
||||||
class WeaponEmptyHand:
|
class WeaponEmptyHand:
|
||||||
"""
|
"""
|
||||||
This is used when you wield no weapons. We won't create any db-object for it.
|
This is a dummy-class loaded when you wield no weapons. We won't create any db-object for it.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
obj_type = ObjType.WEAPON.value
|
||||||
key = "Empty Fists"
|
key = "Empty Fists"
|
||||||
inventory_use_slot = WieldLocation.WEAPON_HAND
|
inventory_use_slot = WieldLocation.WEAPON_HAND
|
||||||
attack_type = Ability.STR
|
attack_type = Ability.STR
|
||||||
|
|
@ -164,6 +193,23 @@ class WeaponEmptyHand:
|
||||||
return "<WeaponEmptyHand>"
|
return "<WeaponEmptyHand>"
|
||||||
|
|
||||||
|
|
||||||
|
class EvAdventureWeapon(EvAdventureObject):
|
||||||
|
"""
|
||||||
|
Base weapon class for all EvAdventure weapons.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj_type = ObjType.WEAPON.value
|
||||||
|
inventory_use_slot = AttributeProperty(WieldLocation.WEAPON_HAND)
|
||||||
|
quality = AttributeProperty(3)
|
||||||
|
|
||||||
|
# what ability used to attack with this weapon
|
||||||
|
attack_type = AttributeProperty(Ability.STR)
|
||||||
|
# what defense stat of the enemy it must defeat
|
||||||
|
defense_type = AttributeProperty(Ability.ARMOR)
|
||||||
|
damage_roll = AttributeProperty("1d6")
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureRunestone(EvAdventureWeapon):
|
class EvAdventureRunestone(EvAdventureWeapon):
|
||||||
"""
|
"""
|
||||||
Base class for magic runestones. In _Knave_, every spell is represented by a rune stone
|
Base class for magic runestones. In _Knave_, every spell is represented by a rune stone
|
||||||
|
|
@ -173,8 +219,44 @@ class EvAdventureRunestone(EvAdventureWeapon):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
obj_type = (ObjType.WEAPON.value, ObjType.MAGIC.value)
|
||||||
inventory_use_slot = AttributeProperty(WieldLocation.TWO_HANDS)
|
inventory_use_slot = AttributeProperty(WieldLocation.TWO_HANDS)
|
||||||
|
quality = AttributeProperty(3)
|
||||||
|
|
||||||
attack_type = AttributeProperty(Ability.INT)
|
attack_type = AttributeProperty(Ability.INT)
|
||||||
defense_type = AttributeProperty(Ability.CON)
|
defense_type = AttributeProperty(Ability.DEX)
|
||||||
damage_roll = AttributeProperty("1d8")
|
damage_roll = AttributeProperty("1d8")
|
||||||
|
|
||||||
|
|
||||||
|
class EvAdventureArmor(EvAdventureObject):
|
||||||
|
"""
|
||||||
|
Base class for all wearable Armors.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj_type = ObjType.ARMOR.value
|
||||||
|
inventory_use_slot = AttributeProperty(WieldLocation.BODY)
|
||||||
|
armor = AttributeProperty(11)
|
||||||
|
quality = AttributeProperty(3)
|
||||||
|
|
||||||
|
|
||||||
|
class EvAdventureShield(EvAdventureArmor):
|
||||||
|
"""
|
||||||
|
Base class for all Shields.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj_type = ObjType.SHIELD.value
|
||||||
|
inventory_use_slot = AttributeProperty(WieldLocation.SHIELD_HAND)
|
||||||
|
armor = AttributeProperty(1)
|
||||||
|
|
||||||
|
|
||||||
|
class EvAdventureHelmet(EvAdventureArmor):
|
||||||
|
"""
|
||||||
|
Base class for all Helmets.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj_type = ObjType.HELMET.value
|
||||||
|
inventory_use_slot = AttributeProperty(WieldLocation.HEAD)
|
||||||
|
armor = AttributeProperty(1)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,28 @@ EvAdventure rooms.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from evennia import AttributeProperty, DefaultRoom, TagProperty
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from evennia import AttributeProperty, DefaultCharacter, DefaultRoom, TagProperty
|
||||||
|
from evennia.utils.utils import inherits_from
|
||||||
|
|
||||||
|
_MAP_GRID = [
|
||||||
|
[" ", " ", " ", " ", " "],
|
||||||
|
[" ", " ", " ", " ", " "],
|
||||||
|
[" ", " ", "@", " ", " "],
|
||||||
|
[" ", " ", " ", " ", " "],
|
||||||
|
[" ", " ", " ", " ", " "],
|
||||||
|
]
|
||||||
|
_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, "\\"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class EvAdventureRoom(DefaultRoom):
|
class EvAdventureRoom(DefaultRoom):
|
||||||
|
|
@ -18,8 +39,39 @@ class EvAdventureRoom(DefaultRoom):
|
||||||
allow_pvp = False
|
allow_pvp = False
|
||||||
allow_death = False
|
allow_death = False
|
||||||
|
|
||||||
|
def format_appearance(self, appearance, looker, **kwargs):
|
||||||
|
"""Don't left-strip the appearance string"""
|
||||||
|
return appearance.rstrip()
|
||||||
|
|
||||||
class EvAdventurePvPRoom(DefaultRoom):
|
def get_display_header(self, looker, **kwargs):
|
||||||
|
"""
|
||||||
|
Display the current location as a mini-map.
|
||||||
|
"""
|
||||||
|
if not inherits_from(looker, DefaultCharacter):
|
||||||
|
# we don't need a map for npcs/mobs
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# build a map
|
||||||
|
map_grid = deepcopy(_MAP_GRID)
|
||||||
|
dx0, dy0 = 2, 2
|
||||||
|
map_grid[dy0][dx0] = "|w@|n"
|
||||||
|
for exi in self.exits:
|
||||||
|
dx, dy, symbol = _EXIT_GRID_SHIFT.get(exi.key, (None, None, None))
|
||||||
|
if symbol is None:
|
||||||
|
# we have a non-cardinal direction to go to - mark us blue to indicate this
|
||||||
|
map_grid[dy0][dx0] = "|b>|n"
|
||||||
|
continue
|
||||||
|
map_grid[dy0 + dy][dx0 + dx] = symbol
|
||||||
|
if exi.destination != self:
|
||||||
|
map_grid[dy0 + dy + dy][dx0 + dx + dx] = "X"
|
||||||
|
|
||||||
|
# Note that on the grid, dy is really going *downwards* (origo is
|
||||||
|
# in the top left), so we need to reverse the order at the end to mirror it
|
||||||
|
# vertically and have it come out right.
|
||||||
|
return " " + "\n ".join("".join(line) for line in reversed(map_grid))
|
||||||
|
|
||||||
|
|
||||||
|
class EvAdventurePvPRoom(EvAdventureRoom):
|
||||||
"""
|
"""
|
||||||
Room where PvP can happen, but noone gets killed.
|
Room where PvP can happen, but noone gets killed.
|
||||||
|
|
||||||
|
|
|
||||||
36
evennia/contrib/tutorials/evadventure/shops.py
Normal file
36
evennia/contrib/tutorials/evadventure/shops.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
"""
|
||||||
|
EvAdventure Shop system.
|
||||||
|
|
||||||
|
|
||||||
|
A shop is run by an NPC. It can provide one or more of several possible services:
|
||||||
|
|
||||||
|
- Buy from a pre-set list of (possibly randomized) items. Cost is based on the item's value,
|
||||||
|
adjusted by how stingy the shopkeeper is. When bought this way, the item is
|
||||||
|
generated on the fly and passed to the player character's inventory. Inventory files are
|
||||||
|
a list of prototypes, normally from a prototype-file. A random selection of items from each
|
||||||
|
inventory file is available.
|
||||||
|
- Sell items to the shop for a certain percent of their value. One could imagine being able
|
||||||
|
to buy back items again, but we will instead _destroy_ sold items, so as to remove them
|
||||||
|
from circulation. In-game we can say it's because the merchants collect the best stuff
|
||||||
|
to sell to collectors in the big city later. Each merchant buys a certain subset of items
|
||||||
|
based on their tags.
|
||||||
|
- Buy a service. For a cost, a certain action is performed for the character; this applies
|
||||||
|
immediately when bought. The most notable services are healing and converting coin to XP.
|
||||||
|
- Buy rumors - this is echoed to the player for a price. Different merchants could have
|
||||||
|
different rumors (or randomized ones).
|
||||||
|
- Quest - gain or hand in a quest for a merchant.
|
||||||
|
|
||||||
|
All shops are menu-driven. One starts talking to the npc and will then end up in their shop
|
||||||
|
interface.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.utils.evmenu import EvMenu
|
||||||
|
|
||||||
|
|
||||||
|
def start_npc_menu(caller, shopkeeper, **kwargs):
|
||||||
|
"""
|
||||||
|
Access function - start the NPC interaction/shop interface.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
@ -1367,6 +1367,20 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def format_appearance(self, appearance, looker, **kwargs):
|
||||||
|
"""
|
||||||
|
Final processing of the entire appearance string. Called by `return_appearance`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
appearance (str): The compiled appearance string.
|
||||||
|
looker (Object): Object doing the looking.
|
||||||
|
**kwargs: Arbitrary data for use when overriding.
|
||||||
|
Returns:
|
||||||
|
str: The final formatted output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return appearance.strip()
|
||||||
|
|
||||||
def return_appearance(self, looker, **kwargs):
|
def return_appearance(self, looker, **kwargs):
|
||||||
"""
|
"""
|
||||||
Main callback used by 'look' for the object to describe itself.
|
Main callback used by 'look' for the object to describe itself.
|
||||||
|
|
@ -1398,17 +1412,20 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
if not looker:
|
if not looker:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
# populate the appearance_template string. It's a good idea to strip it and
|
# populate the appearance_template string.
|
||||||
# let the client add any extra spaces instead.
|
return self.format_appearance(
|
||||||
return self.appearance_template.format(
|
self.appearance_template.format(
|
||||||
name=self.get_display_name(looker, **kwargs),
|
name=self.get_display_name(looker, **kwargs),
|
||||||
desc=self.get_display_desc(looker, **kwargs),
|
desc=self.get_display_desc(looker, **kwargs),
|
||||||
header=self.get_display_header(looker, **kwargs),
|
header=self.get_display_header(looker, **kwargs),
|
||||||
footer=self.get_display_footer(looker, **kwargs),
|
footer=self.get_display_footer(looker, **kwargs),
|
||||||
exits=self.get_display_exits(looker, **kwargs),
|
exits=self.get_display_exits(looker, **kwargs),
|
||||||
characters=self.get_display_characters(looker, **kwargs),
|
characters=self.get_display_characters(looker, **kwargs),
|
||||||
things=self.get_display_things(looker, **kwargs),
|
things=self.get_display_things(looker, **kwargs),
|
||||||
).strip()
|
),
|
||||||
|
looker,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Hook methods
|
# Hook methods
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue