checkpoint
This commit is contained in:
parent
54c63834c7
commit
ffc7be0f36
BIN
deadsands/content/images/tanos_edge_main_street.png
Normal file
BIN
deadsands/content/images/tanos_edge_main_street.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
|
@ -32,10 +32,13 @@ dnd-npcs = { file = "../../dnd-npcs/dist/dnd_npcs-0.2.0-py3-none-any.whl" }
|
|||
elethis-cipher= { git = "https://github.com/evilchili/elethis-cipher", branch = 'main' }
|
||||
#dnd-rolltable = { git = "https://github.com/evilchili/dnd-rolltable", branch = 'main' }
|
||||
dnd-rolltable = { file = "../../dnd-rolltable/dist/dnd_rolltable-1.1.9-py3-none-any.whl" }
|
||||
|
||||
dnd-calendar = { path = "../../dnd-calendar" }
|
||||
|
||||
prompt-toolkit = "^3.0.38"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
site = "site_tools.cli:app"
|
||||
site = "site_tools.cli:site_app"
|
||||
roll-table = "rolltable.cli:app"
|
||||
pelican = "site_tools.tasks:pelican_main"
|
||||
dmsh = "site_tools.cli:dmsh"
|
||||
|
|
|
@ -37,6 +37,11 @@ CONFIG.update(
|
|||
"production_host": "deadsands.froghat.club",
|
||||
# where to find roll table sources
|
||||
"table_sources_path": "sources",
|
||||
# where to store campaign state
|
||||
"campaign_save_path": '~/.dnd',
|
||||
"campaign_name": "deadsands",
|
||||
# campaign start date
|
||||
"campaign_start_date": "2.1125.5.25",
|
||||
}
|
||||
)
|
||||
|
||||
|
|
82
deadsands/site_tools/campaign.py
Normal file
82
deadsands/site_tools/campaign.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
import shutil
|
||||
import yaml
|
||||
|
||||
from telisar.reckoning import telisaran
|
||||
|
||||
|
||||
def _string_to_date(date):
|
||||
return telisaran.datetime.from_expression(f"on {date}", timeline={})
|
||||
|
||||
|
||||
def _date_to_string(date):
|
||||
return date.numeric
|
||||
|
||||
|
||||
def _rotate_backups(path, max_backups=10):
|
||||
|
||||
oldest = None
|
||||
if not path.exists():
|
||||
return oldest
|
||||
|
||||
# move file.000 to file.001, file.001 to file.002, etc...
|
||||
for i in range(max_backups - 2, -1, -1):
|
||||
source = Path(f"{path}.{i:03d}")
|
||||
target = Path(f"{path}.{i+1:03d}")
|
||||
if not source.exists():
|
||||
continue
|
||||
if oldest is None:
|
||||
oldest = i
|
||||
if i == max_backups:
|
||||
source.unlink()
|
||||
shutil.move(source, target)
|
||||
|
||||
return oldest
|
||||
|
||||
|
||||
def save(campaign, path='.', name='dnd_campaign'):
|
||||
savedir = Path(path).expanduser()
|
||||
savepath = savedir / f"{name}.yaml"
|
||||
|
||||
savedir.mkdir(exist_ok=True)
|
||||
backup_count = _rotate_backups(savepath)
|
||||
|
||||
if savepath.exists():
|
||||
target = Path(f"{savepath}.000")
|
||||
shutil.move(savepath, target)
|
||||
|
||||
campaign['date'] = _date_to_string(campaign['date'])
|
||||
campaign['start_date'] = _date_to_string(campaign['start_date'])
|
||||
savepath.write_text(yaml.safe_dump(dict(campaign)))
|
||||
return savepath, (backup_count or 0) + 2
|
||||
|
||||
|
||||
def load(path=".", name='dnd_campaign', start_date='', backup=None, console=None):
|
||||
ext = "" if backup is None else f".{backup:03d}"
|
||||
|
||||
default_date = _string_to_date(start_date)
|
||||
campaign = defaultdict(str)
|
||||
campaign['start_date'] = default_date
|
||||
campaign['date'] = default_date
|
||||
|
||||
if console:
|
||||
console.print(f"Loading campaign {name} from {path}...")
|
||||
try:
|
||||
target = Path(path).expanduser() / f"{name}.yaml{ext}"
|
||||
with open(target, 'rb') as f:
|
||||
loaded = yaml.safe_load(f)
|
||||
loaded['start_date'] = _string_to_date(loaded['start_date'])
|
||||
loaded['date'] = _string_to_date(loaded['date'])
|
||||
campaign.update(loaded)
|
||||
if console:
|
||||
console.print(f"Successfully loaded Campaign {name} from {target}!")
|
||||
return campaign
|
||||
except FileNotFoundError:
|
||||
console.print(f"No existing campaigns found in {path}.")
|
||||
return campaign
|
||||
except yaml.parser.ParserError as e:
|
||||
if console:
|
||||
console.print(f"{e}\nWill try an older backup.")
|
||||
return load(path, 0 if backup is None else backup+1)
|
|
@ -7,8 +7,10 @@ from rich.table import Table
|
|||
from rolltable.tables import RollTable
|
||||
|
||||
from site_tools.shell.base import BasePrompt, command
|
||||
from site_tools import campaign
|
||||
|
||||
from npc.generator.base import generate_npc
|
||||
from reckoning.calendar import TelisaranCalendar
|
||||
|
||||
BINDINGS = KeyBindings()
|
||||
|
||||
|
@ -23,12 +25,22 @@ class DMShell(BasePrompt):
|
|||
self._register_subshells()
|
||||
self._register_keybindings()
|
||||
|
||||
self.cache['campaign'] = campaign.load(
|
||||
path=self.cache['campaign_save_path'],
|
||||
name=self.cache['campaign_name'],
|
||||
start_date=self.cache['campaign_start_date'],
|
||||
console=self.console
|
||||
)
|
||||
self._campaign = self.cache['campaign']
|
||||
|
||||
def _register_keybindings(self):
|
||||
self._toolbar.extend(
|
||||
[
|
||||
("", " [?] Help "),
|
||||
("", " [F2] Wild Magic Table "),
|
||||
("", " [F3] NPC"),
|
||||
("", " [F4] Calendar"),
|
||||
("", " [F8] Save"),
|
||||
("", " [^Q] Quit "),
|
||||
]
|
||||
)
|
||||
|
@ -51,6 +63,61 @@ class DMShell(BasePrompt):
|
|||
def npc(event):
|
||||
self.npc()
|
||||
|
||||
@self.key_bindings.add("f4")
|
||||
def calendar(event):
|
||||
self.calendar()
|
||||
|
||||
@self.key_bindings.add("f8")
|
||||
def save(event):
|
||||
self.save()
|
||||
|
||||
@command(usage="""
|
||||
[title]Calendar[/title]
|
||||
|
||||
Print the Telisaran calendar, including the current date.
|
||||
|
||||
[title]calendar[/title]
|
||||
|
||||
[link]> calendar [season][/link]
|
||||
""", completer=WordCompleter(
|
||||
[
|
||||
'season',
|
||||
]
|
||||
))
|
||||
def calendar(self, parts=[]):
|
||||
|
||||
if not self.cache['calendar']:
|
||||
self.cache['calendar'] = TelisaranCalendar(today=self._campaign['start_date'])
|
||||
|
||||
if not parts:
|
||||
self.console.print(self.cache['calendar'].__doc__)
|
||||
self.console.print(f"Today is {self._campaign['date'].short}")
|
||||
return
|
||||
|
||||
if parts[0] == 'season':
|
||||
self.console.print(self.cache['calendar'].season)
|
||||
return
|
||||
|
||||
@command(usage="""
|
||||
[title]Save[/title]
|
||||
|
||||
Save the campaign state.
|
||||
|
||||
[title]USAGE[/title]
|
||||
|
||||
[link]> save[/link]
|
||||
""")
|
||||
def save(self, parts=[]):
|
||||
"""
|
||||
Save the campaign state.
|
||||
"""
|
||||
path, count = campaign.save(
|
||||
self.cache['campaign'],
|
||||
path=self.cache['campaign_save_path'],
|
||||
name=self.cache['campaign_name']
|
||||
)
|
||||
self.console.print(f"Saved {path}; {count} backups exist.")
|
||||
|
||||
@command(usage="""
|
||||
[title]NPC[/title]
|
||||
|
||||
|
@ -104,6 +171,7 @@ class DMShell(BasePrompt):
|
|||
"""
|
||||
Quit dmsh.
|
||||
"""
|
||||
self.save()
|
||||
try:
|
||||
get_app().exit()
|
||||
finally:
|
||||
|
|
Loading…
Reference in New Issue
Block a user