rpgsheet

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 588466c2739d21bfdb8a70f2e2c5727900de4bc6
parent f61e77ab6fdaca800766af169431e801d0de94a7
Author: Skylar Hill <stellarskylark@posteo.net>
Date:   Thu,  2 Jun 2022 16:06:55 -0500

Various refactors and refinements

Diffstat:
Mrpgsheet.nimble | 2+-
Msrc/tui.nim | 136+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/utils.nim | 20+++++++++++++++++++-
Mtemplates/dnd5e.yaml | 76----------------------------------------------------------------------------
4 files changed, 77 insertions(+), 157 deletions(-)

diff --git a/rpgsheet.nimble b/rpgsheet.nimble @@ -1,6 +1,6 @@ # Package -version = "0.0.1" +version = "0.1.1" author = "Skylar Hill" description = "CLI/TUI application for TTRPG character sheets" license = "GPL-3.0-only" diff --git a/src/tui.nim b/src/tui.nim @@ -1,10 +1,11 @@ import + algorithm, illwill, os, - strutils, + std/wordwrap, strformat, + strutils, tables, - std/wordwrap, yaml import utils, verb @@ -29,18 +30,34 @@ proc exitProc() {.noconv.} = proc runTui*(sheet: YamlDocument): void = let tabs = sheet.root["tabs"] - var currentTabIndex = 0 + var tabIndex = 0 illwillInit(fullscreen=true) setControlCHook(exitProc) hideCursor() - var selectedWindowIndex = 0 + var selWinIndex = 0 var selectedByWindow = initTable[string, int]() var itemsByWindow = initTable[string, seq[string]]() var statusText = "" var descScroll = 0 + # Initialize tabs, windows, and expressions + for tab in tabs: + for name, windows in tab.fields.pairs: + for win in windows.elems: + itemsByWindow[win.content] = newSeqOfCap[string](100) + selectedByWindow[win.content] = 0 + + for name, content in sheet.root["expressions"].fields.pairs: + let win = content.getContent("window") + var working = itemsByWindow[win] + working.add(name.content) + itemsByWindow[win] = working + + for win in itemsByWindow.keys: + itemsbyWindow[win] = itemsByWindow[win].sorted + while true: var tb = newTerminalBuffer(terminalWidth(), terminalHeight()) var bb = newBoxBuffer(tb.width, tb.height) @@ -48,64 +65,40 @@ proc runTui*(sheet: YamlDocument): void = let descY = tb.height - 10 - var currentTabName: string - var currentTabWindows: seq[YamlNode] + var tabName: string + var tabWindows: seq[YamlNode] # There should only be one item, but because # fields is a Table, it's necessary to use an iterator # to get the name and content - for n, w in tabs[currentTabIndex].fields.pairs: - currentTabName = n.content - currentTabWindows = w.elems + for n, w in tabs[tabIndex].fields.pairs: + tabName = n.content + tabWindows = w.elems - let windowPositions = placeWindows(tb, currentTabWindows, descY) - - for win in currentTabWindows: - if win.content notin itemsByWindow: - itemsByWindow[win.content] = newSeqOfCap[string](100) + let windowPositions = placeWindows(tb, tabWindows, descY) + let win = tabWindows[selWinIndex].content var key = getKey() case key of Key.Q, Key.Escape: exitProc() of Key.ShiftJ: - if currentTabIndex < tabs.elems.high: - currentTabIndex += 1 - else: - currentTabIndex = tabs.elems.low - selectedWindowIndex = 0 + tabIndex = tabIndex.loopInc(tabs.elems) + selWinIndex = 0 of Key.ShiftK: - if currentTabIndex > tabs.elems.low: - currentTabIndex -= 1 - else: - currentTabIndex = tabs.elems.high - selectedWindowIndex = 0 + tabIndex = tabIndex.loopDec(tabs.elems) + selWinIndex = 0 of Key.L: - if selectedWindowIndex < currentTabWindows.high: - selectedWindowIndex += 1 - else: - selectedWindowIndex = currentTabWindows.low + selWinIndex = selwinIndex.loopInc(tabWindows) descScroll = 0 of Key.H: - if selectedWindowIndex > currentTabWindows.low: - selectedWindowIndex -= 1 - else: - selectedWindowIndex = currentTabWindows.high + selWinIndex = selwinIndex.loopDec(tabWindows) descScroll = 0 of Key.J: - let win = currentTabWindows[selectedWindowIndex].content - if selectedByWindow[win] < itemsByWindow[win].high: - selectedByWindow[win] = selectedByWindow[win] + 1 - else: - selectedByWindow[win] = itemsByWindow[win].low + selectedByWindow[win] = selectedByWindow[win].loopInc(itemsByWindow[win]) descScroll = 0 of Key.K: - let win = currentTabWindows[selectedWindowIndex].content - if selectedByWindow[win] > itemsByWindow[win].low: - selectedByWindow[win] = selectedByWindow[win] - 1 - else: - selectedByWindow[win] = itemsByWindow[win].high + selectedByWindow[win] = selectedByWindow[win].loopDec(itemsByWindow[win]) descScroll = 0 of Key.Enter: - let win = currentTabWindows[selectedWindowIndex].content let itemList = itemsByWindow[win] let action = itemList[selectedByWindow[win]] statusText = fmt"{fancyDisplay(action, 99)}: {doVerb(action, Roll, sheet)}" @@ -129,13 +122,13 @@ proc runTui*(sheet: YamlDocument): void = # Write current tab let tabX = title.len + 5 - let tabWidth = currentTabName.len + 3 + let tabWidth = tabName.len + 3 bb.drawRect( tabX, 0, tabX + tabWidth, 2, doubleStyle = true ) - tb.write(tabX + 2, 1, currentTabName) + tb.write(tabX + 2, 1, tabName.fancyDisplay(99)) # Write status line let statusX = tabX + tabWidth + 1 @@ -146,9 +139,9 @@ proc runTui*(sheet: YamlDocument): void = tb.write(statusX + 2, 1, statusText) # Write windows - for win in currentTabWindows: - let (x1, y1, x2, y2) = windowPositions[win.content] - if win == currentTabWindows[selectedWindowIndex]: + for window in tabWindows: + let (x1, y1, x2, y2) = windowPositions[window.content] + if window == tabWindows[selWinIndex]: sbb.drawRect(x1, y1, x2, y2) sbb.drawHorizLine(x1, x2, y1+2, doubleStyle=true) else: @@ -156,44 +149,29 @@ proc runTui*(sheet: YamlDocument): void = bb.drawHorizLine(x1, x2, y1+2, doubleStyle=true) let textWidth = x2 - x1 - 4 - tb.write(x1 + 2, y1+1, fancyDisplay(win.content, textWidth)) + tb.write(x1 + 2, y1+1, fancyDisplay(window.content, textWidth)) # Write expressions - for name, content in sheet.root["expressions"].fields.pairs: - if content.getContent("tab") != currentTabName: - continue - - let win = content.getContent("window") - if win == "": - continue - if win notin windowPositions: - continue - - if win notin selectedByWindow: - selectedByWindow[win] = 0 - - let (x1, y1, x2, _) = windowPositions[win] - let textWidth = x2 - x1 - 4 - let sel = selectedByWindow[win] - var currentItems = itemsByWindow[win] - if name.content notin currentItems: - currentItems.add(name.content) - if sel <= currentItems.high: - if name.content == currentItems[sel]: + for window in tabWindows: + for item in itemsByWindow[window.content]: + let itemWin = sheet.getExpression(item).getContent("window") + let (x1, y1, x2, _) = windowPositions[itemWin] + let textWidth = x2 - x1 - 4 + let sel = selectedByWindow[itemWin] + if item == itemsByWindow[itemWin][sel]: tb.setForegroundColor(fgBlue) - tb.write( - x1 + 2, - y1 + 3 + currentItems.find(name.content), - fancyDisplay(name.content, textWidth) - ) - tb.setForegroundColor(fgNone) - itemsbyWindow[win] = currentItems + tb.write( + x1 + 2, + y1 + 3 + itemsByWindow[itemWin].find(item), + fancyDisplay(item, textWidth) + ) + tb.setForegroundColor(fgNone) # Write description bb.drawRect(1, descY, tb.width - 1, tb.height - 1, doubleStyle = true) - let selWin = currentTabWindows[selectedWindowIndex].content + let selWin = tabWindows[selWinIndex].content let sel = itemsByWindow[selWin][selectedByWindow[selWin]] - let item = sheet.root["expressions"][sel] + let item = sheet.getExpression(sel) let desc = item.getContent("desc") let val = (item.getContent("dice") & " " & item.getContent("modifier")).strip let roll = diff --git a/src/utils.nim b/src/utils.nim @@ -1,5 +1,22 @@ import tables, strutils, yaml +let noCap = ["and", "of", "the", "as", "at", "but", "by", "for", "from", "if", "in", "on", "once", "onto", "or", "over", "so", "than", "that", "to", "up", "with", "when"] + +proc loopInc*(index: int, list: seq): int = + result = + if index < list.high: + index + 1 + else: list.low + +proc loopDec*(index: int, list: seq): int = + result = + if index > list.low: + index - 1 + else: list.high + +proc getExpression*(sheet: YamlDocument, exp: string): YamlNode = + sheet.root["expressions"][exp] + proc find*[T](list: seq[T], item: T): int = for i in 0..list.high: if list[i] == item: return i @@ -13,7 +30,8 @@ proc fancyDisplay*(exp: string, maxLen: int): string = exp[0..min(maxLen-1, exp.len-1)] result = result.replace("-", " ") for word in result.split(" "): - result = result.replace(word, word.capitalizeAscii) + if word notin noCap: + result = result.replace(word, word.capitalizeAscii) proc contains*(node: YamlNode, search: string): bool = for key in keys(node.fields): diff --git a/templates/dnd5e.yaml b/templates/dnd5e.yaml @@ -20,344 +20,268 @@ expressions: # Change "race" to your character's race race: desc: Description of your character's race here - tab: main window: features # Add as many more of these as you want. Make the key # like this; if you have Unarmored Defense, add an # unarmored-defense field. your-feature: desc: Your character's awesome feature - tab: main window: features # Set these equal to your stats max-health: modifier: 10 - tab: main window: stats current-health: modifier: 10 - tab: main window: stats temporary-health: modifier: 0 - tab: main window: stats speed: modifier: 30 - tab: main window: stats armor-class: modifier: 10 - tab: main window: stats proficiency: modifier: 2 - tab: main window: stats strength: modifier: 10 - tab: main window: stats dexterity: modifier: 10 - tab: main window: stats constitution: modifier: 10 - tab: main window: stats intelligence: modifier: 10 - tab: main window: stats wisdom: modifier: 10 - tab: main window: stats charisma: modifier: 10 - tab: main window: stats # Add your tool and language proficiencies here other-proficiencies: - tab: main window: stats desc: Common, Simple weapons # You probably don't want to change these STR: modifier: "(strength-10)/2" - tab: main window: ability-modifiers DEX: modifier: "(dexterity-10)/2" - tab: main window: ability-modifiers CON: modifier: "(constitution-10)/2" - tab: main window: ability-modifiers INT: modifier: "(intelligence-10)/2" - tab: main window: ability-modifiers WIS: modifier: "(wisdom-10)/2" - tab: main window: ability-modifiers CHA: modifier: "(charisma-10)/2" - tab: main window: ability-modifiers # Add "+proficiency" to the skills you are proficient in, # and "+(proficiency*2)" if you hvae expertise passive-perception: modifier: 10+WIS - tab: main window: skills strength-saving-throw: dice: d20 modifier: STR - tab: main window: skills dexterity-saving-throw: dice: d20 modifier: DEX - tab: main window: skills constitution-saving-throw: dice: d20 modifier: CON - tab: main window: skills intelligence-saving-throw: dice: d20 modifier: INT - tab: main window: skills wisdom-saving-throw: dice: d20 modifier: WIS - tab: main window: skills charisma-saving-throw: dice: d20 modifier: CHA - tab: main window: skills acrobatics: dice: d20 modifier: DEX - tab: main window: skills animal-handling: dice: d20 modifier: WIS - tab: main window: skills arcana: dice: d20 modifier: INT - tab: main window: skills athletics: dice: d20 modifier: STR - tab: main window: skills deception: dice: d20 modifier: CHA - tab: main window: skills history: dice: d20 modifier: INT - tab: main window: skills insight: dice: d20 modifier: WIS - tab: main window: skills intimidation: dice: d20 modifier: CHA - tab: main window: skills investigation: dice: d20 modifier: INT - tab: main window: skills medicine: dice: d20 modifier: WIS - tab: main window: skills nature: dice: d20 modifier: INT - tab: main window: skills perception: dice: d20 modifier: WIS - tab: main window: skills performance: dice: d20 modifier: CHA - tab: main window: skills persuasion: dice: d20 modifier: CHA - tab: main window: skills religion: dice: d20 modifier: INT - tab: main window: skills sleight-of-hand: dice: d20 modifier: DEX - tab: main window: skills stealth: dice: d20 modifier: DEX - tab: main window: skills survival: dice: d20 modifier: WIS - tab: main window: skills # Attacks; add two items for every way you have to attack, # one for the attack roll, and the second for damage unarmed-strike: dice: d20 modifier: STR+proficiency - tab: inventory-and-attacks window: attacks desc: A simple attack with the body unarmed-strike-damage: modifier: 1+STR - tab: inventory-and-attacks window: attacks # Inventory; add an item for whatever you happen to have common-clothes: desc: Simple, common clothes for a simple, common person. Nothing wrong with that. - tab: inventory-and-attacks window: equipment copper-pieces: modifier: 0 - tab: inventory-and-attacks window: wallet silver-pieces: modifier: 0 - tab: inventory-and-attacks window: wallet gold-pieces: modifier: 0 - tab: inventory-and-attacks window: wallet electrum-pieces: modifier: 0 - tab: inventory-and-attacks window: wallet platinum-pieces: modifier: 0 - tab: inventory-and-attacks window: wallet # You can remove these if you're not a caster first-level-spell-slots: modifier: 1 - tab: spells window: spell-slots second-level-spell-slots: modifier: 0 - tab: spells window: spell-slots third-level-spell-slots: modifier: 0 - tab: spells window: spell-slots fourth-level-spell-slots: modifier: 0 - tab: spells window: spell-slots fifth-level-spell-slots: modifier: 0 - tab: spells window: spell-slots sixth-level-spell-slots: modifier: 0 - tab: spells window: spell-slots seventh-level-spell-slots: modifier: 0 - tab: spells window: spell-slots eighth-level-spell-slots: modifier: 0 - tab: spells window: spell-slots ninth-level-spell-slots: modifier: 0 - tab: spells window: spell-slots first-level-slots-expended: modifier: 1 - tab: spells window: spell-slots second-level-slots-expended: modifier: 0 - tab: spells window: spell-slots third-level-slots-expended: modifier: 0 - tab: spells window: spell-slots fourth-level-slots-expended: modifier: 0 - tab: spells window: spell-slots fifth-level-slots-expended: modifier: 0 - tab: spells window: spell-slots sixth-level-slots-expended: modifier: 0 - tab: spells window: spell-slots seventh-level-slots-expended: modifier: 0 - tab: spells window: spell-slots eighth-level-slots-expended: modifier: 0 - tab: spells window: spell-slots ninth-level-slots-expended: modifier: 0 - tab: spells window: spell-slots spellcasting-ability: modifier: CHA # set this appropriately - tab: spells window: spellcasting-stats spell-save-dc: modifier: 8+spellcasting-ability+proficiency - tab: spells window: spellcasting-stats spell-attack-bonus: dice: 1d20 modifier: spellcasting-ability+proficiency - tab: spells window: spellcasting-stats prestidigitation: desc: "Cantrip. This spell is a minor magical trick that novice spellcasters use for practice. You create one of the following magical effects within range: 1) You create an instantaneous, harmless sensory effect, such as a shower of sparks, a puff of wind, faint musical notes, or an odd odor. 2) You instantaneously light or snuff out a candle, a torch, or a small campfire. 3) You instantaneously clean or soil an object no larger than 1 cubic foot. 4) You chill, warm, or flavor up to 1 cubic foot of nonliving material for 1 hour. 5) You make a color, a small mark, or a symbol appear on an object or a surface for 1 hour. 6) You create a nonmagical trinket or an illusory image that can fit in your hand and that lasts until the end of your next turn. If you cast this spell multiple times, you can have up to three of its non-instantaneous effects active at a time, and you can dismiss such an effect as an action." - tab: spells window: spells