osrlib 2.3.0__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {osrlib-2.3.0 → osrlib-2.4.0}/PKG-INFO +1 -1
- {osrlib-2.3.0 → osrlib-2.4.0}/pyproject.toml +1 -1
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/ruleset.py +11 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/commands.py +6 -2
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/dungeon.py +4 -2
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/events.py +22 -6
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/exploration.py +6 -2
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/messages.py +3 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/LICENSE +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/LICENSE-OGL.md +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/README.md +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/__init__.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/__init__.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/abilities.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/alignment.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/character.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/classes.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/clock.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/combat.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/creature.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/dice.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/effects.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/events.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/items.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/monsters.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/npc.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/rng.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/spells.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/tables.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/treasure.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/core/validation.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/__init__.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/adventure.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/battle.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/content_pack.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/encounter.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/gates.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/interpreter.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/narrative.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/party.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/quests.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/session.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/stocking.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/triggers.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/crawl/views.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/LICENSE-OGL.md +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/__init__.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/abilities.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/classes.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/combat_tables.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/encounter_tables.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/equipment.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/languages.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/magic_items.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/monsters.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/spells.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/data/treasure.json +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/errors.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/persistence.py +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/py.typed +0 -0
- {osrlib-2.3.0 → osrlib-2.4.0}/src/osrlib/versioning.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "osrlib"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.4.0"
|
|
4
4
|
description = "B/X (1981 Basic/Expert) tabletop RPG rules engine for turn-based dungeon crawlers"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
# The wheel ships Open Game Content (osrlib/data/*.json and its LICENSE-OGL.md
|
|
@@ -263,3 +263,14 @@ class Ruleset(BaseModel):
|
|
|
263
263
|
in a ten-foot passage, and osrlib uses the lower number. Turn the flag off and there's
|
|
264
264
|
no cap, so every combatant may melee.
|
|
265
265
|
"""
|
|
266
|
+
|
|
267
|
+
failed_trap_removal_springs: bool = True
|
|
268
|
+
"""Spring a treasure trap on the thief who fails to remove it. An adaptation, on by default.
|
|
269
|
+
|
|
270
|
+
The SRD says a thief may try only once to remove a treasure trap, and it doesn't say what
|
|
271
|
+
a failure does. Turn the flag off and a failed
|
|
272
|
+
[`RemoveTreasureTrap`][osrlib.crawl.commands.RemoveTreasureTrap] leaves the trap armed.
|
|
273
|
+
The party still knows where it is. That thief can't try again, another thief in the
|
|
274
|
+
party can, and the trap still gets its 2-in-6 chance to spring when someone takes the
|
|
275
|
+
treasure.
|
|
276
|
+
"""
|
|
@@ -1030,7 +1030,10 @@ class RemoveTreasureTrap(Command):
|
|
|
1030
1030
|
isn't enough. A member must already have found the trap with
|
|
1031
1031
|
[`InspectTreasure`][osrlib.crawl.commands.InspectTreasure]. Only a member with thief
|
|
1032
1032
|
skills can try, and each member gets one attempt at each trap. A failed attempt springs
|
|
1033
|
-
the trap on the thief who tried
|
|
1033
|
+
the trap on the thief who tried, unless
|
|
1034
|
+
[`Ruleset.failed_trap_removal_springs`][osrlib.core.ruleset.Ruleset.failed_trap_removal_springs]
|
|
1035
|
+
is off. With the flag off, the trap stays armed and found, and another thief in the party
|
|
1036
|
+
can try. Once the trap is removed,
|
|
1034
1037
|
[`TakeTreasure`][osrlib.crawl.commands.TakeTreasure] empties the cache safely.
|
|
1035
1038
|
|
|
1036
1039
|
Modes:
|
|
@@ -1054,7 +1057,8 @@ class RemoveTreasureTrap(Command):
|
|
|
1054
1057
|
Events:
|
|
1055
1058
|
[`DetectionRolledEvent`][osrlib.crawl.events.DetectionRolledEvent] with the
|
|
1056
1059
|
skill roll, then a [`TrapEvent`][osrlib.crawl.events.TrapEvent]:
|
|
1057
|
-
`exploration.trap.removed` on success
|
|
1060
|
+
`exploration.trap.removed` on success. A failure gives `exploration.trap.sprung`, or
|
|
1061
|
+
`exploration.trap.removal_failed` when `failed_trap_removal_springs` is off.
|
|
1058
1062
|
The sprung trap resolves at once against the thief, or against every living
|
|
1059
1063
|
member when the trap catches the whole party. Each step is a separate event:
|
|
1060
1064
|
a [`SavingThrowRolledEvent`][osrlib.core.events.SavingThrowRolledEvent] when
|
|
@@ -1040,7 +1040,8 @@ class FeatureSpec(BaseModel):
|
|
|
1040
1040
|
party opens the cache. A thief finds it with
|
|
1041
1041
|
[`InspectTreasure`][osrlib.crawl.commands.InspectTreasure] and takes it out with
|
|
1042
1042
|
[`RemoveTreasureTrap`][osrlib.crawl.commands.RemoveTreasureTrap]. Each character gets one try at
|
|
1043
|
-
each
|
|
1043
|
+
each. A failed removal springs the trap, unless
|
|
1044
|
+
[`Ruleset.failed_trap_removal_springs`][osrlib.core.ruleset.Ruleset.failed_trap_removal_springs] is off."""
|
|
1044
1045
|
|
|
1045
1046
|
@model_validator(mode="after")
|
|
1046
1047
|
def _trap_kind_matches(self) -> FeatureSpec:
|
|
@@ -1893,7 +1894,8 @@ class DungeonState(BaseModel):
|
|
|
1893
1894
|
feature reference. Each character gets one try at each cache."""
|
|
1894
1895
|
removal_attempts: dict[str, list[str]] = {}
|
|
1895
1896
|
"""Which characters have tried to remove each treasure trap, as lists of character ids keyed by
|
|
1896
|
-
feature reference. Each character gets one try at each trap
|
|
1897
|
+
feature reference. Each character gets one try at each trap. A failed try springs the trap, unless
|
|
1898
|
+
[`Ruleset.failed_trap_removal_springs`][osrlib.core.ruleset.Ruleset.failed_trap_removal_springs] is off."""
|
|
1897
1899
|
lock_failures: dict[str, dict[str, int]] = {}
|
|
1898
1900
|
"""Which thieves failed to pick each lock, keyed by [`edge_ref`][osrlib.crawl.dungeon.edge_ref].
|
|
1899
1901
|
Each value maps a character id to the thief's level at the time. A thief who failed a lock can
|
|
@@ -448,7 +448,7 @@ class SearchCompletedEvent(Event):
|
|
|
448
448
|
|
|
449
449
|
|
|
450
450
|
class TrapEvent(Event):
|
|
451
|
-
"""A trap sprang, was found, was removed, or was known and didn't spring.
|
|
451
|
+
"""A trap sprang, was found, was removed, resisted removal, or was known and didn't spring.
|
|
452
452
|
|
|
453
453
|
Emitted by the commands that can set a trap off or look for one:
|
|
454
454
|
[`MoveParty`][osrlib.crawl.commands.MoveParty],
|
|
@@ -478,14 +478,29 @@ class TrapEvent(Event):
|
|
|
478
478
|
[`RemoveTreasureTrap`][osrlib.crawl.commands.RemoveTreasureTrap], because finding a
|
|
479
479
|
treasure trap doesn't disarm it. That is why `exploration.trap.safe`, a known trap
|
|
480
480
|
getting its chance and not springing, appears only for a treasure cache.
|
|
481
|
+
|
|
482
|
+
A thief who fails to remove a treasure trap springs it, unless
|
|
483
|
+
[`Ruleset.failed_trap_removal_springs`][osrlib.core.ruleset.Ruleset.failed_trap_removal_springs]
|
|
484
|
+
is off. With the flag off, the failure emits `exploration.trap.removal_failed` and the trap
|
|
485
|
+
stays armed and found.
|
|
481
486
|
"""
|
|
482
487
|
|
|
483
488
|
allowed_codes: ClassVar[frozenset[str]] = frozenset(
|
|
484
|
-
{
|
|
489
|
+
{
|
|
490
|
+
"exploration.trap.sprung",
|
|
491
|
+
"exploration.trap.safe",
|
|
492
|
+
"exploration.trap.found",
|
|
493
|
+
"exploration.trap.removed",
|
|
494
|
+
"exploration.trap.removal_failed",
|
|
495
|
+
}
|
|
485
496
|
)
|
|
486
497
|
"""`exploration.trap.sprung` when a trap goes off, `.found` when a search or inspection
|
|
487
|
-
reveals one, `.removed` when a thief disarms one,
|
|
488
|
-
|
|
498
|
+
reveals one, `.removed` when a thief disarms one, `.removal_failed` when a thief fails to
|
|
499
|
+
disarm one and it doesn't spring, and `.safe` when a trap the party already knows about got
|
|
500
|
+
its chance and didn't fire, which only a treasure trap does. A failed removal gives
|
|
501
|
+
`.removal_failed` only when
|
|
502
|
+
[`Ruleset.failed_trap_removal_springs`][osrlib.core.ruleset.Ruleset.failed_trap_removal_springs]
|
|
503
|
+
is off. Otherwise the trap springs."""
|
|
489
504
|
|
|
490
505
|
event_type: Literal["trap"] = "trap"
|
|
491
506
|
"""The event type, `trap`, which identifies this class in serialized form."""
|
|
@@ -501,8 +516,9 @@ class TrapEvent(Event):
|
|
|
501
516
|
tried to remove the trap. For a trap that springs when the party enters its area or opens its
|
|
502
517
|
door with [`OpenDoor`][osrlib.crawl.commands.OpenDoor], it's the first living member in
|
|
503
518
|
marching order. It's set even when the trap hits the whole party. For
|
|
504
|
-
`exploration.trap.removed`, and for a treasure trap
|
|
505
|
-
[`InspectTreasure`][osrlib.crawl.commands.InspectTreasure], it's the thief
|
|
519
|
+
`exploration.trap.removed` and `exploration.trap.removal_failed`, and for a treasure trap
|
|
520
|
+
found with [`InspectTreasure`][osrlib.crawl.commands.InspectTreasure], it's the thief who
|
|
521
|
+
tried."""
|
|
506
522
|
direction: str | None = None
|
|
507
523
|
"""The direction of the door a room trap was found through, from the searched cell, as a
|
|
508
524
|
[`Direction`][osrlib.crawl.dungeon.Direction] value. Set for that find alone: it is `None` on a
|
|
@@ -2452,9 +2452,13 @@ def _handle_remove_treasure_trap(session, command: RemoveTreasureTrap) -> tuple[
|
|
|
2452
2452
|
if result.passed:
|
|
2453
2453
|
state.removed_traps.append(ref)
|
|
2454
2454
|
events.append(TrapEvent(code="exploration.trap.removed", trap_ref=ref, character_id=member.id))
|
|
2455
|
+
elif not session.ruleset.failed_trap_removal_springs:
|
|
2456
|
+
# RAW says only "attempted once per trap". With failed_trap_removal_springs off, a
|
|
2457
|
+
# failure leaves the trap armed and found, and only this thief is barred from retrying.
|
|
2458
|
+
events.append(TrapEvent(code="exploration.trap.removal_failed", trap_ref=ref, character_id=member.id))
|
|
2455
2459
|
else:
|
|
2456
|
-
#
|
|
2457
|
-
# RAW says only "attempted once per trap".
|
|
2460
|
+
# With failed_trap_removal_springs on (the default), a failed removal springs the trap
|
|
2461
|
+
# on the thief, the classic reading. RAW says only "attempted once per trap".
|
|
2458
2462
|
state.sprung_traps.append(ref)
|
|
2459
2463
|
events.append(TrapEvent(code="exploration.trap.sprung", trap_ref=ref, character_id=member.id))
|
|
2460
2464
|
events.extend(_resolve_trap(session, feature.trap, triggerer=member))
|
|
@@ -296,6 +296,9 @@ _TEMPLATES: dict[str, Callable[[Any], str]] = {
|
|
|
296
296
|
"exploration.trap.removed": lambda event: (
|
|
297
297
|
(event.character_id or "The party") + f" removes the trap ({event.trap_ref})."
|
|
298
298
|
),
|
|
299
|
+
"exploration.trap.removal_failed": lambda event: (
|
|
300
|
+
(event.character_id or "The party") + f" fails to remove the trap ({event.trap_ref})."
|
|
301
|
+
),
|
|
299
302
|
"exploration.item.acquired": lambda event: (
|
|
300
303
|
f"{event.character_id} acquires "
|
|
301
304
|
+ (", ".join(event.item_ids) if event.item_ids else "")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|