diff --git a/deadsands/site_tools/campaign.py b/deadsands/site_tools/campaign.py index 9848b44..99b644d 100644 --- a/deadsands/site_tools/campaign.py +++ b/deadsands/site_tools/campaign.py @@ -60,6 +60,7 @@ def load(path=".", name='dnd_campaign', start_date='', backup=None, console=None campaign = defaultdict(str) campaign['start_date'] = default_date campaign['date'] = default_date + campaign['level'] = 1 if console: console.print(f"Loading campaign {name} from {path}...") diff --git a/deadsands/site_tools/shell/interactive_shell.py b/deadsands/site_tools/shell/interactive_shell.py index 2e85be3..b554005 100644 --- a/deadsands/site_tools/shell/interactive_shell.py +++ b/deadsands/site_tools/shell/interactive_shell.py @@ -40,8 +40,9 @@ class DMShell(BasePrompt): [ ("", " [?] Help "), ("", " [F2] Wild Magic Table "), - ("", " [F3] NPC"), - ("", " [F4] Calendar"), + ("", " [F3] Trinkets"), + ("", " [F4] NPC"), + ("", " [F5] Date"), ("", " [F8] Save"), ("", " [^Q] Quit "), ] @@ -62,10 +63,14 @@ class DMShell(BasePrompt): self.wmt() @self.key_bindings.add("f3") + def trinkets(event): + self.trinkets() + + @self.key_bindings.add("f4") def npc(event): self.npc() - @self.key_bindings.add("f4") + @self.key_bindings.add("f5") def date(event): self.date() @@ -73,8 +78,44 @@ class DMShell(BasePrompt): def save(event): self.save() + def _handler_date_season(self, *args): + self.console.print(self.cache['calendar'].season) + + def _handler_date_year(self, *args): + self.console.print(self.cache['calendar'].calendar) + + def _handler_date_inc(self, days): + offset = int(days or 1) * Day.length_in_seconds + self._campaign['date'] = self._campaign['date'] + offset + return self.date() + + def _handler_date_dec(self, days): + offset = int(days or 1) * Day.length_in_seconds + self._campaign['date'] = self._campaign['date'] - offset + return self.date() + + def _handler_date_set(self, new_date): + try: + self._campaign['date'] = campaign.string_to_date(new_date) + except ReckoningError as e: + self.console.error(str(e)) + self.console.error("Invalid date. Use numeric formats; see 'help date' for more.") + self.cache['calendar'] = TelisaranCalendar(today=self._campaign['date']) + return self.date() + + def _rolltable(self, source, frequency='default', die=20): + rt = RollTable( + [Path(f"{self.cache['table_sources_path']}/{source}").read_text()], + frequency=frequency, + die=die + ) + table = Table(*rt.rows[0]) + for row in rt.rows[1:]: + table.add_row(*row) + return table + @command(usage=""" - [title]Date[/title] + [title]DATE[/title] Work with the Telisaran calendar, including the current campaign date. @@ -121,31 +162,6 @@ class DMShell(BasePrompt): return handler(val) - def _handler_date_season(self, *args): - self.console.print(self.cache['calendar'].season) - - def _handler_date_year(self, *args): - self.console.print(self.cache['calendar'].calendar) - - def _handler_date_inc(self, days): - offset = int(days or 1) * Day.length_in_seconds - self._campaign['date'] = self._campaign['date'] + offset - return self.date() - - def _handler_date_dec(self, days): - offset = int(days or 1) * Day.length_in_seconds - self._campaign['date'] = self._campaign['date'] - offset - return self.date() - - def _handler_date_set(self, new_date): - try: - self._campaign['date'] = campaign.string_to_date(new_date) - except ReckoningError as e: - self.console.error(str(e)) - self.console.error("Invalid date. Use numeric formats; see 'help date' for more.") - self.cache['calendar'] = TelisaranCalendar(today=self._campaign['date']) - return self.date() - @command(usage=""" [title]Save[/title] @@ -304,13 +320,41 @@ class DMShell(BasePrompt): Generate a Wild Magic Table for resolving spell effects. """ if "wmt" not in self.cache: - rt = RollTable( - [Path(f"{self.cache['table_sources_path']}/{source}").read_text()], - frequency="default", - die=20, - ) - table = Table(*rt.expanded_rows[0]) - for row in rt.expanded_rows[1:]: - table.add_row(*row) - self.cache["wmt"] = table - self.console.print(self.cache["wmt"]) + self.cache['wmt'] = self._rolltable(source) + self.console.print(self.cache['wmt']) + + @command(usage=""" + [title]TRINKET TABLE[/title] + + [b]trinkets[/b] Generates a d20 random trinket table. + + [title]USAGE[/title] + + [link]> trinkets[/link] + + [title]CLI[/title] + + [link]roll-table \\ + sources/trinkets.yaml \\ + --frequency default --die 20[/link] + """) + def trinkets(self, parts=[], source="trinkets.yaml"): + self.console.print(self._rolltable(source)) + + @command(usage=""" + [title]LEVEL[/title] + + Get or set the current campaign's level. Used for generating loot tables. + + [title]USAGE[/title] + + [link]> level [LEVEL][/link] + + """) + def level(self, parts=[]): + if parts: + newlevel = int(parts[0]) + if newlevel > 20 or newlevel < 1: + self.console.error(f"Invalid level: {newlevel}. Levels must be between 1 and 20.") + self._campaign['level'] = newlevel + self.console.print(f"Party is currently at level {self._campaign['level']}.") diff --git a/deadsands/sources/trinkets.yaml b/deadsands/sources/trinkets.yaml index 1f1ad8d..f762b43 100644 --- a/deadsands/sources/trinkets.yaml +++ b/deadsands/sources/trinkets.yaml @@ -18,7 +18,7 @@ Trinket: - A petrified frog. - A twenty-sided die. - A cut yellow chrysanthemum that never dies. - - A palm-sized iron cage:: the door doesn't shut properly, as the tiny lock was broken from the inside. + - A palm-sized iron cage; the door doesn't shut properly, as the tiny lock was broken from the inside. - A blob of grey goo, slippy but safe to touch, kept in a ceramic pot. - A glowing blue-green line, six inches long, but with no discernible radius. - A pretty conch shell. @@ -36,7 +36,7 @@ Trinket: - A square of ironsilk sewn by the geargrubs of ancient Siclari. - An ivory knitting needle. - A peacock feather. - - A travel set of paints:: someone has used up all the black. + - A travel set of paints; someone has used up all the black. - A wig of short platinum-blonde hair. - A child's charm bracelet. - A small bar of orichalcum, a metal only mentioned in ancient literature. @@ -83,7 +83,7 @@ Trinket: - A wire circlet that bestows upon its wearer perfect posture. - A small hand-sized box covered with numbered buttons. - An empty whiskey tumbler that causes any liquid poured into it to become bourbon. - - A hunk of metal which appears to be several gears jammed together at unnatural and impossible angles:: attempting to turn it causes it to emit a horrible shrieking sound. + - A hunk of metal which appears to be several gears jammed together at unnatural and impossible angles; attempting to turn it causes it to emit a horrible shrieking sound. - A crystal prism that refracts shadow instead of light. - A smokeless and odorless candle. - A flat disc of layered metal and prismatic glass with a hole in the centre.