Skip to content

Commit 67a7bf7

Browse files
committed
add pick_lock skill and command
1 parent 5516c6e commit 67a7bf7

File tree

9 files changed

+137
-21
lines changed

9 files changed

+137
-21
lines changed

tale/accounts.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,13 @@ def _create_database(self) -> None:
109109
alignment integer NOT NULL,
110110
strength integer NOT NULL,
111111
dexterity integer NOT NULL,
112+
perception integer NOT NULL,
113+
intelligence integer NOT NULL,
112114
weapon_skills varchar NOT NULL,
113115
magic_skills varchar NOT NULL,
114-
skils varchar NOT NULL,
115-
combat_points integer NOT NULL,
116-
max_combat_points integer NOT NULL,
116+
skills varchar NOT NULL,
117+
action_points integer NOT NULL,
118+
max_action_points integer NOT NULL,
117119
magic_points integer NOT NULL,
118120
max_magic_points integer NOT NULL,
119121
FOREIGN KEY(account) REFERENCES Account(id)

tale/base.py

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -976,12 +976,14 @@ def __init__(self) -> None:
976976
self.race = "" # the name of the race of this creature
977977
self.strength = 3
978978
self.dexterity = 3
979+
self.perception = 3
980+
self.intelligence = 3
979981
self.unarmed_attack = Weapon(UnarmedAttack.FISTS.name, weapon_type=WeaponType.UNARMED)
980982
self.weapon_skills = {} # type: Dict[WeaponType, int] # weapon type -> skill level
981983
self.magic_skills = {} # type: Dict[MagicType, MagicSkill]
982984
self.skills = {} # type: Dict[str, int] # skill name -> skill level
983985
self.action_points = 0 # combat points
984-
self.max_combat_points = 5 # max combat points
986+
self.max_action_points = 5 # max combat points
985987
self.max_magic_points = 5 # max magic points
986988
self.magic_points = 0 # magic points
987989

@@ -1001,7 +1003,7 @@ def from_race(cls: type, race: builtins.str, gender: builtins.str='n') -> 'Stats
10011003
def set_stats_from_race(self) -> None:
10021004
# the stats that are static are always initialized from the races table
10031005
# we look it up via the name, not needed to store the actual Race object here
1004-
r = races.races[self.race]
1006+
r = races.races[self.race] # type: Race
10051007
self.bodytype = r.body
10061008
self.language = r.language
10071009
self.weight = r.mass
@@ -1028,9 +1030,9 @@ def replenish_combat_points(self, amount: int = None) -> None:
10281030
if amount:
10291031
self.action_points += amount
10301032
else:
1031-
self.action_points = self.max_combat_points
1032-
if self.action_points > self.max_combat_points:
1033-
self.action_points = self.max_combat_points
1033+
self.action_points = self.max_action_points
1034+
if self.action_points > self.max_action_points:
1035+
self.action_points = self.max_action_points
10341036

10351037
def replenish_magic_points(self, amount: int = None) -> None:
10361038
if amount:
@@ -1650,6 +1652,8 @@ def do_on_death(self) -> 'Container':
16501652
return remains
16511653

16521654
def hide(self, hide: bool = True):
1655+
""" Hide or reveal the living entity. """
1656+
16531657
if not hide:
16541658
self.hidden = False
16551659
self.hidden = False
@@ -1673,19 +1677,29 @@ def hide(self, hide: bool = True):
16731677
self.tell("You hide yourself.")
16741678
self.location.tell("%s hides" % self.title, exclude_living=self)
16751679

1676-
def search_hidden(self):
1680+
def search_hidden(self, silent: bool = False):
1681+
""" Search for hidden entities in the room.
1682+
For automatic searches (like when entering a room),
1683+
no fail messages will show, and no action points will be used."""
1684+
16771685
if self.stats.action_points < 1:
16781686
raise ActionRefused("You don't have enough action points to search.")
16791687

16801688
livings = self.location.livings
16811689

1682-
self.location.tell("%s searches for something in the room." % (self.title), exclude_living=self)
1690+
if not silent:
1691+
self.stats.action_points -= 1
1692+
self.location.tell("%s searches for something in the room." % (self.title), exclude_living=self)
16831693

16841694
if len(self.location.livings) == 1:
1685-
self.tell("You don't find anything.")
1695+
if not silent:
1696+
self.tell("You don't find anything.")
16861697
return
16871698

1688-
skillValue = self.stats.skills.get(SkillType.SEARCH, 0)
1699+
if silent:
1700+
skillValue = self.stats.perception * 5
1701+
else:
1702+
skillValue = self.stats.skills.get(SkillType.SEARCH, 0)
16891703

16901704
found = False
16911705

@@ -1698,9 +1712,31 @@ def search_hidden(self):
16981712
self.location.tell("%s reveals %s" % (self.title, living.title), exclude_living=self)
16991713
found = True
17001714

1701-
if not found:
1715+
if not found and not silent:
17021716
self.tell("You don't find anything.")
17031717

1718+
def pick_lock(self, door: 'Door'):
1719+
""" Pick a lock on an exit. """
1720+
if not door.locked:
1721+
raise ActionRefused("The door is not locked.")
1722+
1723+
if self.stats.action_points < 1:
1724+
raise ActionRefused("You don't have enough action points to pick the lock.")
1725+
1726+
self.stats.action_points -= 1
1727+
1728+
skillValue = self.stats.skills.get(SkillType.PICK_LOCK, 0)
1729+
1730+
if random.randint(1, 100) > skillValue:
1731+
self.tell("You fail to pick the lock.")
1732+
return
1733+
1734+
door.unlock()
1735+
1736+
self.tell("You successfully pick the lock.", evoke=True)
1737+
1738+
if not self.hidden:
1739+
self.location.tell("%s picks the lock on the door." % self.title, exclude_living=self)
17041740

17051741
class Container(Item):
17061742
"""
@@ -2044,6 +2080,33 @@ def insert(self, item: Union[Living, Item], actor: Optional[Living]) -> None:
20442080
else:
20452081
raise ActionRefused("You could try to lock the door with it instead.")
20462082
raise ActionRefused("The %s doesn't fit." % item.title)
2083+
2084+
def pick_lock(self, actor: Living) -> None:
2085+
if not self.locked:
2086+
raise ActionRefused("The door is not locked.")
2087+
2088+
if actor.stats.action_points < 1:
2089+
raise ActionRefused("You don't have enough action points to pick the lock.")
2090+
2091+
actor.stats.action_points -= 1
2092+
2093+
skillValue = actor.stats.skills.get(SkillType.PICK_LOCK, 0)
2094+
2095+
if random.randint(1, 100) > skillValue:
2096+
actor.tell("You fail to pick the lock.")
2097+
return
2098+
2099+
self.locked = False
2100+
self.opened = True
2101+
2102+
actor.tell("You successfully pick the %s." % (self.name), evoke=True, short_len=True)
2103+
if not actor.hidden:
2104+
actor.location.tell("%s picks the lock on the door." % actor.title, exclude_living=actor)
2105+
actor.tell_others("{Actor} picks the %s, and opens it." % (self.name), evoke=True, short_len=True)
2106+
if self.linked_door:
2107+
self.linked_door.locked = False
2108+
self.linked_door.opened = True
2109+
self.target.tell("The %s is unlocked and opened from the other side." % self.linked_door.name, evoke=False, short_len=True)
20472110

20482111

20492112
class Key(Item):

tale/cmds/normal.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,5 +1868,21 @@ def do_search_hidden(player: Player, parsed: base.ParseResult, ctx: util.Context
18681868

18691869
player.search_hidden()
18701870

1871+
@cmd("pick_lock")
1872+
def do_pick_lock(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
1873+
"""Pick a lock on a door."""
1874+
if len(parsed.args) < 1:
1875+
raise ParseError("You need to specify the door to pick")
1876+
try:
1877+
exit = str(parsed.args[0])
1878+
except ValueError as x:
1879+
raise ActionRefused(str(x))
1880+
if exit in player.location.exits:
1881+
door = player.location.exits[exit]
1882+
1883+
if not isinstance(door, base.Door) or not door.locked:
1884+
raise ActionRefused("You can't pick that")
1885+
1886+
door.pick_lock(player)
18711887

18721888

tale/cmds/spells.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def do_rejuvenate(player: Player, parsed: base.ParseResult, ctx: util.Context) -
112112
def do_hide(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
113113
""" Hide from view """
114114

115-
skillValue, spell = _check_spell_skill(player, MagicType.HIDE, "You don't know how the 'hide' spell.")
115+
skillValue, spell = _check_spell_skill(player, MagicType.HIDE, "You don't know the 'hide' spell.")
116116
level = _parse_level(player, parsed)
117117

118118
if not spell.check_cost(player.stats.magic_points, level):
@@ -135,7 +135,7 @@ def do_hide(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None
135135
def do_reveal(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
136136
""" Reveal hidden things. """
137137

138-
skillValue, spell = _check_spell_skill(player, MagicType.REVEAL, "You don't know how the 'reveal' spell.")
138+
skillValue, spell = _check_spell_skill(player, MagicType.REVEAL, "You don't know the 'reveal' spell.")
139139
level = _parse_level(player, parsed, 0)
140140

141141
if not spell.check_cost(player.stats.magic_points, level):

tale/skills/skills.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
class SkillType(Enum):
77

88
HIDE = 1
9-
SEARCH = 2
9+
SEARCH = 2
10+
PICK_LOCK = 3

tests/test_normal_commands.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
import tale
55
from tale import wearable
6-
from tale.base import Item, Location, ParseResult, Weapon, Wearable
6+
from tale.base import Door, Item, Location, ParseResult, Weapon, Wearable
77
from tale.cmds import normal
88
from tale.errors import ActionRefused, ParseError
99
from tale.llm.LivingNpc import LivingNpc
@@ -193,4 +193,36 @@ def test_search_hidden(self):
193193

194194
normal.do_search_hidden(self.test_player, ParseResult(verb='search', args=[]), self.context)
195195

196-
assert not test_npc.hidden
196+
assert not test_npc.hidden
197+
198+
def test_pick_lock(self):
199+
self.test_player.stats.skills[SkillType.PICK_LOCK] = 100
200+
self.test_player.stats.action_points = 1
201+
202+
hall = Location("hall")
203+
door = Door("north", hall, "a locked door", locked=True, opened=False)
204+
hall.add_exits([door])
205+
hall.insert(self.test_player, actor=None)
206+
207+
parse_result = ParseResult(verb='pick_lock', args=['north'])
208+
normal.do_pick_lock(self.test_player, parse_result, self.context)
209+
assert not door.locked
210+
211+
# Test failure
212+
213+
door.locked = True
214+
215+
self.test_player.stats.skills[SkillType.PICK_LOCK] = 0
216+
self.test_player.stats.action_points = 1
217+
218+
normal.do_pick_lock(self.test_player, parse_result, self.context)
219+
220+
assert door.locked
221+
222+
# Test no action points
223+
224+
self.test_player.stats.skills[SkillType.PICK_LOCK] = 100
225+
self.test_player.stats.action_points = 0
226+
227+
with pytest.raises(ActionRefused, match="You don't have enough action points to pick the lock."):
228+
normal.do_pick_lock(self.test_player, parse_result, self.context)

tests/test_player.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,8 @@ def test_dbcreate(self):
811811
self.assertEqual(races.BodySize.HUMAN_SIZED, account.stats.size)
812812
self.assertEqual("Edhellen", account.stats.language)
813813
self.assertEqual({}, account.stats.weapon_skills)
814+
self.assertEqual({}, account.stats.magic_skills)
815+
self.assertEqual({}, account.stats.skills)
814816
finally:
815817
dbfile.unlink()
816818

tests/test_spells.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def test_hide_fail(self):
226226

227227
def test_hide_refused(self):
228228
parse_result = ParseResult(verb='hide', args=[])
229-
with pytest.raises(ActionRefused, match="You don't know how the 'hide' spell."):
229+
with pytest.raises(ActionRefused, match="You don't know the 'hide' spell."):
230230
spells.do_hide(self.player, parse_result, None)
231231

232232
self.player.stats.magic_skills[MagicType.HIDE] = 10
@@ -283,7 +283,7 @@ def test_reveal_fail(self):
283283

284284
def test_reveal_refused(self):
285285
parse_result = ParseResult(verb='reveal', args=[])
286-
with pytest.raises(ActionRefused, match="You don't know how the 'reveal' spell."):
286+
with pytest.raises(ActionRefused, match="You don't know the 'reveal' spell."):
287287
spells.do_reveal(self.player, parse_result, None)
288288

289289
self.player.stats.magic_skills[MagicType.REVEAL] = 10

tests/test_stats.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def test_replenish_hp(self):
2020

2121
def test_replenish_combat_points(self):
2222
stats = Stats()
23-
stats.max_combat_points = 100
23+
stats.max_action_points = 100
2424
stats.action_points = 0
2525

2626
stats.replenish_combat_points(10)

0 commit comments

Comments
 (0)