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' }
|
elethis-cipher= { git = "https://github.com/evilchili/elethis-cipher", branch = 'main' }
|
||||||
#dnd-rolltable = { git = "https://github.com/evilchili/dnd-rolltable", 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-rolltable = { file = "../../dnd-rolltable/dist/dnd_rolltable-1.1.9-py3-none-any.whl" }
|
||||||
|
|
||||||
|
dnd-calendar = { path = "../../dnd-calendar" }
|
||||||
|
|
||||||
prompt-toolkit = "^3.0.38"
|
prompt-toolkit = "^3.0.38"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
site = "site_tools.cli:app"
|
site = "site_tools.cli:site_app"
|
||||||
roll-table = "rolltable.cli:app"
|
roll-table = "rolltable.cli:app"
|
||||||
pelican = "site_tools.tasks:pelican_main"
|
pelican = "site_tools.tasks:pelican_main"
|
||||||
dmsh = "site_tools.cli:dmsh"
|
dmsh = "site_tools.cli:dmsh"
|
||||||
|
|
|
@ -37,6 +37,11 @@ CONFIG.update(
|
||||||
"production_host": "deadsands.froghat.club",
|
"production_host": "deadsands.froghat.club",
|
||||||
# where to find roll table sources
|
# where to find roll table sources
|
||||||
"table_sources_path": "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 rolltable.tables import RollTable
|
||||||
|
|
||||||
from site_tools.shell.base import BasePrompt, command
|
from site_tools.shell.base import BasePrompt, command
|
||||||
|
from site_tools import campaign
|
||||||
|
|
||||||
from npc.generator.base import generate_npc
|
from npc.generator.base import generate_npc
|
||||||
|
from reckoning.calendar import TelisaranCalendar
|
||||||
|
|
||||||
BINDINGS = KeyBindings()
|
BINDINGS = KeyBindings()
|
||||||
|
|
||||||
|
@ -23,12 +25,22 @@ class DMShell(BasePrompt):
|
||||||
self._register_subshells()
|
self._register_subshells()
|
||||||
self._register_keybindings()
|
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):
|
def _register_keybindings(self):
|
||||||
self._toolbar.extend(
|
self._toolbar.extend(
|
||||||
[
|
[
|
||||||
("", " [?] Help "),
|
("", " [?] Help "),
|
||||||
("", " [F2] Wild Magic Table "),
|
("", " [F2] Wild Magic Table "),
|
||||||
("", " [F3] NPC"),
|
("", " [F3] NPC"),
|
||||||
|
("", " [F4] Calendar"),
|
||||||
|
("", " [F8] Save"),
|
||||||
("", " [^Q] Quit "),
|
("", " [^Q] Quit "),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -51,6 +63,61 @@ class DMShell(BasePrompt):
|
||||||
def npc(event):
|
def npc(event):
|
||||||
self.npc()
|
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="""
|
@command(usage="""
|
||||||
[title]NPC[/title]
|
[title]NPC[/title]
|
||||||
|
|
||||||
|
@ -104,6 +171,7 @@ class DMShell(BasePrompt):
|
||||||
"""
|
"""
|
||||||
Quit dmsh.
|
Quit dmsh.
|
||||||
"""
|
"""
|
||||||
|
self.save()
|
||||||
try:
|
try:
|
||||||
get_app().exit()
|
get_app().exit()
|
||||||
finally:
|
finally:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user