Made simple map, start shops

This commit is contained in:
Griatch 2022-07-24 22:30:13 +02:00
parent b6533d0744
commit 2f28c8982d
5 changed files with 242 additions and 38 deletions

View file

@ -69,3 +69,20 @@ class WieldLocation(Enum):
# combat-related
OPTIMAL_DISTANCE = "optimal_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"

View file

@ -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
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.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):
@ -23,15 +35,23 @@ class EvAdventureObject(DefaultObject):
inventory_use_slot = AttributeProperty(WieldLocation.BACKPACK)
# how many inventory slots it uses (can be a fraction)
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)
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):
"""
@ -93,9 +113,30 @@ class EvAdventureObjectFiller(EvAdventureObject):
"""
obj_type = ObjType.QUEST.value # can't be sold
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):
"""
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)
uses = AttributeProperty(1)
@ -134,25 +175,13 @@ class EvAdventureConsumable(EvAdventureObject):
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:
"""
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"
inventory_use_slot = WieldLocation.WEAPON_HAND
attack_type = Ability.STR
@ -164,6 +193,23 @@ class 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):
"""
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)
quality = AttributeProperty(3)
attack_type = AttributeProperty(Ability.INT)
defense_type = AttributeProperty(Ability.CON)
defense_type = AttributeProperty(Ability.DEX)
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)

View file

@ -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):
@ -18,8 +39,39 @@ class EvAdventureRoom(DefaultRoom):
allow_pvp = 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.

View 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.
"""

View file

@ -1367,6 +1367,20 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
"""
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):
"""
Main callback used by 'look' for the object to describe itself.
@ -1398,17 +1412,20 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
if not looker:
return ""
# populate the appearance_template string. It's a good idea to strip it and
# let the client add any extra spaces instead.
return self.appearance_template.format(
name=self.get_display_name(looker, **kwargs),
desc=self.get_display_desc(looker, **kwargs),
header=self.get_display_header(looker, **kwargs),
footer=self.get_display_footer(looker, **kwargs),
exits=self.get_display_exits(looker, **kwargs),
characters=self.get_display_characters(looker, **kwargs),
things=self.get_display_things(looker, **kwargs),
).strip()
# populate the appearance_template string.
return self.format_appearance(
self.appearance_template.format(
name=self.get_display_name(looker, **kwargs),
desc=self.get_display_desc(looker, **kwargs),
header=self.get_display_header(looker, **kwargs),
footer=self.get_display_footer(looker, **kwargs),
exits=self.get_display_exits(looker, **kwargs),
characters=self.get_display_characters(looker, **kwargs),
things=self.get_display_things(looker, **kwargs),
),
looker,
**kwargs,
)
#
# Hook methods