8. Non-Player-Characters¶
Non-Player-Characters, or NPCs, is the common term for all active agents that are not controlled by players. NPCs could be anything from merchants and quest givers, to monsters and bosses. They could also be ‘flavor’ - townsfolk doing their chores, farmers tending their fields - there to make the world feel “more alive”.
In this lesson we will create the base class of EvAdventure NPCs based on the Knave ruleset. According to the Knave rules, NPCs have some simplified stats compared to the PC characters we designed earlier.
8.1. The NPC base class¶
Create a new module
evadventure/npcs.py.
1# in evadventure/npcs.py
2
3from evennia import DefaultCharacter, AttributeProperty
4
5from .characters import LivingMixin
6from .enums import Ability
7from .objects import get_bare_hands
8
9class EvAdventureNPC(LivingMixin, DefaultCharacter):
10 """Base class for NPCs"""
11
12 is_pc = False
13 hit_dice = AttributeProperty(default=1, autocreate=False)
14 armor = AttributeProperty(default=1, autocreate=False) # +10 to get armor defense
15 hp_multiplier = AttributeProperty(default=4, autocreate=False) # 4 default in Knave
16 hp = AttributeProperty(default=None, autocreate=False) # internal tracking, use .hp property
17 morale = AttributeProperty(default=9, autocreate=False)
18 allegiance = AttributeProperty(default=Ability.ALLEGIANCE_HOSTILE, autocreate=False)
19
20 weapon = AttributeProperty(default=get_bare_hands, autocreate=False) # instead of inventory
21 coins = AttributeProperty(default=1, autocreate=False) # coin loot
22
23 is_idle = AttributeProperty(default=False, autocreate=False)
24
25 @property
26 def strength(self):
27 return self.hit_dice
28
29 @property
30 def dexterity(self):
31 return self.hit_dice
32
33 @property
34 def constitution(self):
35 return self.hit_dice
36
37 @property
38 def intelligence(self):
39 return self.hit_dice
40
41 @property
42 def wisdom(self):
43 return self.hit_dice
44
45 @property
46 def charisma(self):
47 return self.hit_dice
48
49 @property
50 def hp_max(self):
51 return self.hit_dice * self.hp_multiplier
52
53 def at_object_creation(self):
54 """
55 Start with max health.
56
57 """
58 self.hp = self.hp_max
59 self.tags.add("npcs", category="group")
60
61
62class EvAdventureMob(EvAdventureNPC):
63 """
64 Mob(ile) NPC to be used for enemies.
65
66 """
Line 9: By use of multiple inheritance we use the
LinvingMixinwe created in the Character lesson. This includes a lot of useful methods, such as showing our ‘hurt level’, methods to use to heal, hooks to call when getting attacked, hurt and so on. We can re-use all of those in upcoming NPC subclasses.Line 12: The
is_pcis a quick and convenient way to check if this is, well, a PC or not. We will use it in the upcoming Combat base lesson.Line 13: The NPC is simplified in that all stats are just based on the
Hit dicenumber (see Lines 25-51). We storearmorand aweaponas direct Attributes on the class rather than bother implementing a full equipment system.Lines 17, 18: The
moraleandallegianceare Knave properties determining how likely the NPC is to flee in a combat situation and if they are hostile or friendly.Line 19: The
is_idleAttribute is a useful property. It should be available on all NPCs and will be used to disable AI entirely.Line 59: We make sure to tag NPCs. We may want to group different NPCs together later, for example to have all NPCs with the same tag respond if one of them is attacked.
We make an empty subclass EvAdventureMob. A ‘mob’ (short for ‘mobile’) is a common MUD term for NPCs that can move around on their own. We will in the future use this class to represent enemies in the game. We will get back to this class [in the lesson about adding AI][Beginner-Tutoroal-AI].
8.2. Testing¶
Create a new module
evadventure/tests/test_npcs.py
Not so much to test yet, but we will be using the same module to test other aspects of NPCs in the future, so let’s create it now.
# in evadventure/tests/test_npcs.py
from evennia import create_object
from evennia.utils.test_resources import EvenniaTest
from .. import npcs
class TestNPCBase(EvenniaTest):
"""Test the NPC base class"""
def test_npc_base(self):
npc = create_object(
npcs.EvAdventureNPC,
key="TestNPC",
attributes=[("hit_dice", 4)], # set hit_dice to 4
)
self.assertEqual(npc.hp_multiplier, 4)
self.assertEqual(npc.hp, 16)
self.assertEqual(npc.strength, 4)
self.assertEqual(npc.charisma, 4)
Nothing special here. Note how the create_object helper function takes attributes as a keyword. This is a list of tuples we use to set different values than the default ones to Attributes. We then check a few of the properties to make sure they return what we expect.
8.3. Conclusions¶
In Knave, an NPC is a simplified version of a Player Character. In other games and rule systems, they may be all but identical.
With the NPC class in place, we have enough to create a ‘test dummy’. Since it has no AI yet, it won’t fight back, but it will be enough to have something to hit when we test our combat in the upcoming lessons.