Added allies.

This commit is contained in:
raeleus 2025-05-17 09:17:21 -07:00
parent 1a6a605251
commit 8db353f486
4 changed files with 869 additions and 11 deletions

729
Input.js
View file

@ -1,4 +1,4 @@
const version = "Hashtag DnD v0.6.0"
const version = "Hashtag DnD v0.7.0"
const rollSynonyms = ["roll"]
const createSynonyms = ["create", "generate", "start", "begin", "setup", "party", "member", "new"]
const renameCharacterSynonyms = ["renamecharacter", "renameperson"]
@ -70,15 +70,20 @@ const showDaySynonyms = ["showday", "showdate", "day", "date"]
const setDaySynonyms = ["setday", "setdate"]
const encounterSynonyms = ["encounter", "startencounter"]
const showEnemiesSynonyms = ["showenemies", "enemies"]
const showAlliesSynonyms = ["showallies", "allies"]
const addEnemySynonyms = ["addenemy"]
const addAllySynonyms = ["addally"]
const removeEnemySynonyms = ["removeenemy"]
const removeAllySynonyms = ["removeally"]
const clearEnemiesSynonyms = ["clearenemies", "resetenemies", "removeenemies"]
const clearAlliesSynonyms = ["clearallies", "resetallies", "removeallies"]
const initiativeSynonyms = ["initiative"]
const setAcSynonyms = ["setac", "setarmorclass", "ac", "armorclass"]
const turnSynonyms = ["turn", "doturn", "taketurn"]
const fleeSynonyms = ["flee", "retreat", "runaway", "endcombat"]
const versionSynonyms = ["version", "ver", "showversion"]
const setupEnemySynonyms = ["setupenemy", "createenemy"]
const setupAllySynonyms = ["setupally", "createally"]
const setDamageSynonyms = ["setdamage"]
const setProficiencySynonyms = ["setproficiency", "setweaponproficiency"]
const healPartySynonyms = ["healparty", "healcharacters"]
@ -112,6 +117,12 @@ const modifier = (text) => {
else text = rawText
}
if (state.setupAllyStep != null) {
text = handleSetupAllyStep(text)
if (state.setupAllyStep != null) return { text }
else text = rawText
}
if (state.stragedyShopStep != null) {
text = handleStragedyShopStep(text)
if (state.stragedyShopStep != null) return { text }
@ -177,7 +188,7 @@ const modifier = (text) => {
return { text }
}
if (!found) found = processCommandSynonyms(command, commandName, helpSynonyms.concat(rollSynonyms, noteSynonyms, eraseNoteSynonyms, showNotesSynonyms, clearNotesSynonyms, showCharactersSynonyms, removeCharacterSynonyms, generateNameSynonyms, setDefaultDifficultySynonyms, showDefaultDifficultySynonyms, renameCharacterSynonyms, cloneCharacterSynonyms, createLocationSynonyms, showLocationsSynonyms, goToLocationSynonyms, removeLocationSynonyms, getLocationSynonyms, clearLocationsSynonyms, goNorthSynonyms, goSouthSynonyms, goEastSynonyms, goWestSynonyms, encounterSynonyms, showEnemiesSynonyms, addEnemySynonyms, removeEnemySynonyms, clearEnemiesSynonyms, initiativeSynonyms, turnSynonyms, fleeSynonyms, versionSynonyms, setupEnemySynonyms, healSynonyms, damageSynonyms, restSynonyms, addExperienceSynonyms, healPartySynonyms, blockSynonyms, repeatTurnSynonyms, lockpickSynonyms, memorySynonyms, resetSynonyms), function () {return true})
if (!found) found = processCommandSynonyms(command, commandName, helpSynonyms.concat(rollSynonyms, noteSynonyms, eraseNoteSynonyms, showNotesSynonyms, clearNotesSynonyms, showCharactersSynonyms, removeCharacterSynonyms, generateNameSynonyms, setDefaultDifficultySynonyms, showDefaultDifficultySynonyms, renameCharacterSynonyms, cloneCharacterSynonyms, createLocationSynonyms, showLocationsSynonyms, goToLocationSynonyms, removeLocationSynonyms, getLocationSynonyms, clearLocationsSynonyms, goNorthSynonyms, goSouthSynonyms, goEastSynonyms, goWestSynonyms, encounterSynonyms, showEnemiesSynonyms, showAlliesSynonyms, addEnemySynonyms, addAllySynonyms, removeEnemySynonyms, removeAllySynonyms, clearEnemiesSynonyms, clearAlliesSynonyms, initiativeSynonyms, turnSynonyms, fleeSynonyms, versionSynonyms, setupEnemySynonyms, setupAllySynonyms, healSynonyms, damageSynonyms, restSynonyms, addExperienceSynonyms, healPartySynonyms, blockSynonyms, repeatTurnSynonyms, lockpickSynonyms, memorySynonyms, resetSynonyms), function () {return true})
if (found == null) {
if (state.characterName == null) {
@ -262,13 +273,18 @@ const modifier = (text) => {
if (text == null) text = processCommandSynonyms(command, commandName, setAcSynonyms, doSetAc)
if (text == null) text = processCommandSynonyms(command, commandName, encounterSynonyms, doEncounter)
if (text == null) text = processCommandSynonyms(command, commandName, showEnemiesSynonyms, doShowEnemies)
if (text == null) text = processCommandSynonyms(command, commandName, showAlliesSynonyms, doShowAllies)
if (text == null) text = processCommandSynonyms(command, commandName, removeEnemySynonyms, doRemoveEnemy)
if (text == null) text = processCommandSynonyms(command, commandName, removeAllySynonyms, doRemoveAlly)
if (text == null) text = processCommandSynonyms(command, commandName, clearEnemiesSynonyms, doClearEnemies)
if (text == null) text = processCommandSynonyms(command, commandName, clearAlliesSynonyms, doClearAllies)
if (text == null) text = processCommandSynonyms(command, commandName, addEnemySynonyms, doAddEnemy)
if (text == null) text = processCommandSynonyms(command, commandName, addAllySynonyms, doAddAlly)
if (text == null) text = processCommandSynonyms(command, commandName, initiativeSynonyms, doInitiative)
if (text == null) text = processCommandSynonyms(command, commandName, fleeSynonyms, doFlee)
if (text == null) text = processCommandSynonyms(command, commandName, turnSynonyms, doTurn)
if (text == null) text = processCommandSynonyms(command, commandName, setupEnemySynonyms, doSetupEnemy)
if (text == null) text = processCommandSynonyms(command, commandName, setupAllySynonyms, doSetupAlly)
if (text == null) text = processCommandSynonyms(command, commandName, setDamageSynonyms, doSetDamage)
if (text == null) text = processCommandSynonyms(command, commandName, setProficiencySynonyms, doSetProficiency)
if (text == null) text = processCommandSynonyms(command, commandName, healPartySynonyms, doHealParty)
@ -1186,6 +1202,435 @@ function handleSetupEnemyStep(text) {
return text
}
function handleSetupAllyStep(text) {
state.show = "setupAlly"
if (/^\s*>.*says? ".*/.test(text)) {
text = text.replace(/^\s*>.*says? "/, "")
text = text.replace(/"\s*$/, "")
} else if (/^\s*>\s.*/.test(text)) {
text = text.replace(/\s*> /, "")
for (var i = 0; i < info.characters.length; i++) {
var matchString = info.characters[i] == "" ? "You " : `${info.characters[i]} `
if (text.startsWith(matchString)) {
text = text.replace(matchString, "")
break
}
}
text = text.replace(/\.?\s*$/, "")
} else {
text = text.replace(/^\s+/, "")
}
if (text.toLowerCase() == "q") {
state.setupAllyStep = null
return text
}
switch (state.setupAllyStep) {
case 0:
text = text.toLowerCase();
if (text.startsWith("y")) state.setupAllyStep = 100
else if (text.startsWith("n")) state.setupAllyStep++
break
case 1:
if (text.length > 0) {
state.tempAlly.name = text
state.setupAllyStep++
var allyMatches = state.allies.filter(x => x.name.toLowerCase() == state.tempAlly.name.toLowerCase() || x.name.toLowerCase() == `${state.tempAlly.name.toLowerCase()} a`)
if (allyMatches.length > 0) {
state.newAlly = false
state.tempAlly.health = allyMatches[0].health
state.tempAlly.ac = allyMatches[0].ac
state.tempAlly.hitModifier = allyMatches[0].hitModifier
state.tempAlly.damage = allyMatches[0].damage
state.tempAlly.initiative = allyMatches[0].initiative
state.tempAlly.spells = [...allyMatches[0].spells]
} else {
state.newAlly = true
}
}
return text
case 2:
if (text.length > 0) {
if (text.toLowerCase() == "default") {
state.setupAllyStep++
} else if (/^\d*d\d+((\+|-)\d+)?$/gi.test(text)) {
state.tempAlly.health = calculateRoll(text)
state.setupAllyStep++
} else if (!isNaN(text)) {
state.tempAlly.health = Math.max(0, parseInt(text))
state.setupAllyStep++
}
}
return text
case 3:
if (text.toLowerCase() == "default") {
state.setupAllyStep++
} else if (/^\d*d\d+((\+|-)\d+)?$/gi.test(text)) {
state.tempAlly.ac = calculateRoll(text)
state.setupAllyStep++
} else if (!isNaN(text)) {
state.tempAlly.ac = Math.max(0, parseInt(text))
state.setupAllyStep++
}
return text
case 4:
if (text.toLowerCase() == "default") {
state.setupAllyStep++
} else if (!isNaN(text)) {
state.tempAlly.hitModifier = Math.max(0, parseInt(text))
state.setupAllyStep++
}
return text
case 5:
if (text.toLowerCase() == "default") {
state.setupAllyStep++
} else if (/^\d*d\d+((\+|-)\d+)?$/gi.test(text)) {
state.tempAlly.damage = text
state.setupAllyStep++
} else if (!isNaN(text)) {
state.tempAlly.damage = Math.max(0, parseInt(text))
state.setupAllyStep++
}
return text
case 6:
if (text.toLowerCase() == "default") {
state.setupAllyStep++
} else if (/^\d*d\d+((\+|-)\d+)?$/gi.test(text)) {
state.tempAlly.initiative = calculateRoll(text)
state.setupAllyStep++
} else if (!isNaN(text)) {
state.tempAlly.initiative = Math.max(0, parseInt(text))
state.setupAllyStep++
}
return text
case 7:
if (text.toLowerCase() == "s") {
state.setupAllyStep = 500
} else if (text.toLowerCase() == "e") {
state.tempAlly.spells = []
} else if (text.length > 0) {
state.tempAlly.spells.push(text)
state.setupAllyStep++
}
return text
case 8:
if (text.toLowerCase() == "s") {
state.setupAllyStep = 500
}
else if (text.length > 0) {
state.tempAlly.spells.push(text)
}
return text
case 100:
if (/^\d+(\s.*)?$/gi.test(text)) {
state.setupAllyStep = 500
state.newAlly = true
var value = text.match(/^\d+/gi)[0]
var nameMatches = text.match(/(?<=\s).*/gi)
//name, health, ac, hitModifier, damage, initiative, ...spells
switch (parseInt(value)) {
case 1:
state.tempAlly = createAlly("Fighter", calculateRoll("1d6+12"), 18, 4, "1d8+4", "d20+2", "Javelin Throw1d6+4")
break
case 2:
state.tempAlly = createAlly("Cleric", calculateRoll("1d6+10"), 17, 3, "1d6+2", "d20", "Healing Word", "Sanctuary", "Guiding Bolt4d6")
break
case 3:
state.tempAlly = createAlly("Rogue", calculateRoll("1d6+10"), 15, 5, "2d6+3", "d20+5", "Sneak Attack3d6+3")
break
case 4:
state.tempAlly = createAlly("Ranger", calculateRoll("1d6+10"), 15, 4, "1d8+2", "d20+2", "Cure Wounds", "Hunter's Mark", "Ensaring Strike1d8+2")
break
case 5:
state.tempAlly = createAlly("Barbarian", calculateRoll("1d6+15"), 17, 3, "1d12+4", "d20+1", "Rage1d12+4")
break
case 6:
state.tempAlly = createAlly("Bard", calculateRoll("1d6+10"), 15, 3, "1d6", "d20", "Petrifying Bite1d4+1")
break
case 7:
state.tempAlly = createAlly("Druid", calculateRoll("1d6+10"), 16, 3, "1d6+1", "d20", "Poison Bite2d4+1")
break
case 8:
state.tempAlly = createAlly("Monk", calculateRoll("1d6+10"), 16, 5, "2d6+2", "d20+3", "Flurry of Blows 3d6+2")
break
case 9:
state.tempAlly = createAlly("Paladin", calculateRoll("1d6+10"), 16, 3, "1d8+2", "d20+1", "Searing Smite2d6+4")
break
case 10:
state.tempAlly = createAlly("Wizard", calculateRoll("1d6+8"), 14, 3, "1d6", "d20", "Ray of Frost1d8", "Mage Armor", "Ice Knife1d10+5")
break
case 11:
state.tempAlly = createAlly("Sorcerer", calculateRoll("1d6+8"), 14, 3, "1d6", "d20", "Sorcerous Burst1d8", "Chromatic Orb2d8", "Burning Hands1d10")
break
case 12:
state.tempAlly = createAlly("Warlock", calculateRoll("1d6+8"), 14, 3, "1d6", "d20", "Eldritch Blast1d8+5", "Chill Touch1d12", "Hex")
break
case 13:
state.tempAlly = createAlly("Artificer", calculateRoll("1d6+10"), 15, 3, "2d6", "d20+1", "Archanist's Fire2d6+5", "Acid Vial1d10")
break
case 14:
state.tempAlly = createAlly("Commoner", calculateRoll("1d8"), 10, 2, "1d4", "d20")
break
case 15:
state.tempAlly = createAlly("Bandit", calculateRoll("2d8+2"), 12, 3, "1d6+1", "d20+1")
break
case 16:
state.tempAlly = createAlly("Guard", calculateRoll("2d8+2"), 16, 3, "1d6+1", "d20+1")
break
case 17:
state.tempAlly = createAlly("Cultist", calculateRoll("2d8"), 12, 3, "1d6+1", "d20+1", "Dark Devotion")
break
case 18:
state.tempAlly = createAlly("Acolyte", calculateRoll("2d8"), 10, 2, "1d4", "d20", "Sacred Flame1d8", "Cure Wounds")
break
case 19:
state.tempAlly = createAlly("Apprentice", calculateRoll("3d8"), 10, 4, "1d10+2", "d20", "Burning Hands3d6")
break
case 20:
state.tempAlly = createAlly("Witch", calculateRoll("3d8+3"), 10, 3, "1d6+2", "d20", "Ray of Sickness2d8", "Tashas Hideous Laughter", "Invisibility", "Ray of Frost2d8")
break
case 21:
state.tempAlly = createAlly("Buccaneer", calculateRoll("8d8+24"), 14, 5, "1d6+3", "d20+2", "Invade")
break
case 22:
state.tempAlly = createAlly("Spy", calculateRoll("6d8"), 12, 4, "1d6+2", "d20+2", "Sneak Attack2d6+2")
break
case 23:
state.tempAlly = createAlly("Captain", calculateRoll("10d8+20"), 15, 5, "3d6+9", "initiative")
break
case 24:
state.tempAlly = createAlly("Charlatan", calculateRoll("8d8+8"), 15, 4, "1d6+2", "d20+2", "Charm Person", "Shatter3d8", "Thunderwave2d8", "Vicious Mockery1d4")
break
case 25:
state.tempAlly = createAlly("Berserker", calculateRoll("9d8+27"), 13, 5, "1d12+3", "d20+1")
break
case 26:
state.tempAlly = createAlly("Priest", calculateRoll("5d8+5"), 13, 2, "1d6", "d20", "Spirit Guardians3d8", "Spiritual Weapon1d8", "Guiding Bolt4d6", "Cure Wounds")
break
case 27:
state.tempAlly = createAlly("Knight", calculateRoll("8d8+16"), 18, 5, "4d6+6", "d20", "Leadership")
break
case 28:
state.tempAlly = createAlly("Archer", calculateRoll("10d8+30"), 16, 6, "2d8+8", "d20+4")
break
case 29:
state.tempAlly = createAlly("Warrior", calculateRoll("6d8+12"), 16, 6, "1d8+3", "d20+1")
break
case 30:
state.tempAlly = createAlly("Conjurer", calculateRoll("9d8"), 12, 5, "1d4+2", "d20+2", "Conjure Elemental", "Cloud Kill5d8", "Cloud of Daggers5d8", "Poison Spray1d12")
break
case 31:
state.tempAlly = createAlly("Mage", calculateRoll("9d8"), 12, 5, "1d4+2", "d20+2", "Greater Invisibility", "Ice Storm4d6", "Fireball8d6", "Magic Missile3d4+3")
break
case 32:
state.tempAlly = createAlly("Assassin", calculateRoll("12d8+24"), 15, 6, "2d6+6", "d20+3", "Sneak Attack6d6+6")
break
case 33:
state.tempAlly = createAlly("Evoker", calculateRoll("12d8+12"), 12, 3, "1d6-1", "d20+2", "Chain Lightning10d8", "Wall of Ice", "Counter Spell", "Shatter3d8", "Magic Missile6d4+6")
break
case 34:
state.tempAlly = createAlly("Necromancer", calculateRoll("12d8+12"), 12, 7, "2d4", "d20+2", "Circle of Death8d6", "Blight8d8", "Cloudkill5d8", "Animate Dead", "Chill Touch1d8")
break
case 35:
state.tempAlly = createAlly("Champion", calculateRoll("22d8+44"), 18, 9, "6d6+15", "d20+2", "Second Wind")
break
case 36:
state.tempAlly = createAlly("Warlord", calculateRoll("27d8+108"), 18, 9, "4d6+10", "d20+3", "Command Ally", "Frighten Foe")
break
case 37:
state.tempAlly = createAlly("Archmage", calculateRoll("18d8+18"), 12, 6, "1d4+2", "d20+2", "Time Stop", "Mind Blank", "Lightning Bolt8d6", "Cone of Cold8d8", "Shocking Grasp1d8")
break
case 38:
state.tempAlly = createAlly("Archdruid", calculateRoll("24d8+24"), 16, 6, "1d6+2", "d20+2", "Fire Storm7d10", "Sunbeam6d8", "Wall of Fire", "Beast Sense", "Conjure Animals")
break
case 39:
state.tempAlly = createAlly("Ape", calculateRoll("3d8+6"), 12, 5, "2d4+6", "d20+2", "Throw Rock2d6+3")
break
case 40:
state.tempAlly = createAlly("Badger", calculateRoll("1d4+3"), 11, 2, "1", "d20")
break
case 41:
state.tempAlly = createAlly("Bat", calculateRoll("1d4-1"), 12, 4, "1", "d20+2")
break
case 42:
state.tempAlly = createAlly("Black Bear", calculateRoll("3d8+6"), 11, 4, "2d6+4", "d20+1")
break
case 43:
state.tempAlly = createAlly("Boar", calculateRoll("2d8+4"), 11, 3, "1d6+1", "d20", "Gore2d6+1")
break
case 44:
state.tempAlly = createAlly("Brown Bear", calculateRoll("3d10+6"), 11, 5, "3d4+6", "d20+1", "Fire Storm7d10", "Sunbeam6d8", "Wall of Fire", "Beast Sense", "Conjure Animals")
break
case 45:
state.tempAlly = createAlly("Camel", calculateRoll("2d10+6"), 10, 4, "1d4+2", "d20-1")
break
case 46:
state.tempAlly = createAlly("Cat", calculateRoll("1d4"), 12, 4, "1", "d20+2")
break
case 47:
state.tempAlly = createAlly("Constrictor Snake", calculateRoll("2d10+2"), 13, 4, "1d8+2", "d20+2", "Constrict3d4")
break
case 48:
state.tempAlly = createAlly("Crab", calculateRoll("1d4+1"), 11, 2, "1", "d20")
break
case 49:
state.tempAlly = createAlly("Crocodile", calculateRoll("2d10+2"), 12, 4, "1d8+2", "d20")
break
case 50:
state.tempAlly = createAlly("Dire Wolf", calculateRoll("3d10+6"), 14, 5, "1d10+3", "d20+2")
break
case 51:
state.tempAlly = createAlly("Draft Horse", calculateRoll("2d10+4"), 10, 6, "1d4+4", "d20")
break
case 52:
state.tempAlly = createAlly("Elephant", calculateRoll("8d12+24"), 12, 8, "4d8+12", "d20-1", "Trample2d10+6")
break
case 53:
state.tempAlly = createAlly("Elk", calculateRoll("2d10+5"), 10, 5, "1d6+3", "d20")
break
case 54:
state.tempAlly = createAlly("Frog", calculateRoll("1d4-1"), 11, 3, "1", "d20+1")
break
case 55:
state.tempAlly = createAlly("Giant Badger", calculateRoll("2d8+6"), 13, 3, "2d4+1", "d20")
break
case 56:
state.tempAlly = createAlly("Giant Crab", calculateRoll("3d8"), 15, 3, "1d6+1", "d20+1")
break
case 57:
state.tempAlly = createAlly("Giant Goat", calculateRoll("3d10+3"), 11, 5, "1d6+3", "d20+1")
break
case 58:
state.tempAlly = createAlly("Giant Seahorse", calculateRoll("3d10"), 14, 4, "2d6+2", "d20+1", "Bubble Dash")
break
case 59:
state.tempAlly = createAlly("Giant Spider", calculateRoll("4d10+4"), 14, 5, "1d8+3", "d20+3", "Web")
break
case 60:
state.tempAlly = createAlly("Giant Weasel", calculateRoll("2d8"), 13, 5, "1d4+3", "d20+3")
break
case 61:
state.tempAlly = createAlly("Goat", calculateRoll("1d8"), 10, 2, "1", "d20")
break
case 62:
state.tempAlly = createAlly("Hawk", calculateRoll("1d4-1"), 13, 5, "1", "d20+3")
break
case 63:
state.tempAlly = createAlly("Imp", calculateRoll("6d4+6"), 13, 5, "3d6+3", "d20+3", "Invisibility")
break
case 64:
state.tempAlly = createAlly("Lion", calculateRoll("4d10"), 12, 5, "2d8+6", "d20+2", "Roar")
break
case 65:
state.tempAlly = createAlly("Lizard", calculateRoll("1d4"), 10, 2, "1", "d20")
break
case 66:
state.tempAlly = createAlly("Mastiff", calculateRoll("1d8+1"), 12, 3, "1d6+1", "d20+2")
break
case 67:
state.tempAlly = createAlly("Mule", calculateRoll("2d8+2"), 10, 4, "1d4+2", "d20")
break
case 68:
state.tempAlly = createAlly("Octopus", calculateRoll("1d6"), 12, 4, "1", "d20+2", "Ink Cloud")
break
case 69:
state.tempAlly = createAlly("Owl", calculateRoll("1"), 11, 3, "1", "d20+1")
break
case 70:
state.tempAlly = createAlly("Panther", calculateRoll("3d8"), 12, 4, "1d4+2", "d20+2")
break
case 71:
state.tempAlly = createAlly("Pony", calculateRoll("2d8+2"), 10, 4, "1d4+2", "d20")
break
case 72:
state.tempAlly = createAlly("Pseudodragon", calculateRoll("3d4+3"), 14, 4, "2d4+4", "d20+2", "String2d4+2")
break
case 73:
state.tempAlly = createAlly("Quasit", calculateRoll("10d4"), 13, 5, "1d4+3", "d20+3", "Shape Shift", "Scare", "Invisibility")
break
case 74:
state.tempAlly = createAlly("Rat", calculateRoll("1d4-1"), 10, 2, "1", "d20")
break
case 75:
state.tempAlly = createAlly("Raven", calculateRoll("1d4"), 12, 4, "1", "d20+2")
break
case 76:
state.tempAlly = createAlly("Reef Shark", calculateRoll("4d8+4"), 12, 4, "2d4+2")
break
case 77:
state.tempAlly = createAlly("Riding Horse", calculateRoll("2d10+2"), 11, 5, "1d8+3", "d20+1")
break
case 78:
state.tempAlly = createAlly("Scorpion", calculateRoll("1d4-1"), 13, 2, "1d6+1", "d20")
break
case 79:
state.tempAlly = createAlly("Skeleton", calculateRoll("2d8+4"), 13, 5, "1d6+3", "d20+3", "Shortbow1d6+3", "Sword1d6+3")
break
case 80:
state.tempAlly = createAlly("Slaad Tadpole", calculateRoll("3d4"), 12, 4, "1d6+2", "d20+2")
break
case 81:
state.tempAlly = createAlly("Sphinx of Wonder", calculateRoll("7d4+7"), 13, 5, "1d4+3", "d20+2")
break
case 82:
state.tempAlly = createAlly("Spider", calculateRoll("1d4-1"), 12, 4, "1", "d20+2")
break
case 83:
state.tempAlly = createAlly("Sprite", calculateRoll("4d4"), 15, 6, "1d4+4", "d20+4", "Enchanting Bow1d4", "Invisibility")
break
case 84:
state.tempAlly = createAlly("Tiger", calculateRoll("3d10+6"), 13, 5, "1d6+3", "d20+3")
break
case 85:
state.tempAlly = createAlly("Venomous Snake", calculateRoll("2d4"), 12, 4, "2d4+2", "d20+2")
break
case 86:
state.tempAlly = createAlly("Warhorse", calculateRoll("3d10+3"), 11, 6, "2d4+4", "d20+2")
break
case 87:
state.tempAlly = createAlly("Weasel", calculateRoll("1d4-1"), 13, 5, "1", "d20+3")
break
case 88:
state.tempAlly = createAlly("Wolf", calculateRoll("2d8+2"), 12, 4, "1d6+2", "d20+2")
break
case 89:
state.tempAlly = createAlly("Zombie", calculateRoll("2d8+6"), 8, 3, "1d6+1", "d20-2")
break
}
if (nameMatches != null) state.tempAlly.name = nameMatches[0]
}
return text
case 500:
state.show = null
state.setupAllyStep = null
var ally = createAlly(state.tempAlly.name, state.tempAlly.health, state.tempAlly.ac, state.tempAlly.hitModifier, state.tempAlly.damage, state.tempAlly.initiative)
ally.spells = [...state.tempAlly.spells]
var allyMatches = state.allies.filter(x => x.name.toLowerCase() == ally.name.toLowerCase() || x.name.toLowerCase() == `${ally.name.toLowerCase()} a`)
if (state.newAlly && allyMatches.length > 0) {
ally.name = getUniqueName(ally.name)
if (ally.name.endsWith("A")) {
allyMatches[0].name = ally.name
ally.name = ally.name.substring(0, ally.name.length - 1) + "B"
}
} else if (!state.newAlly) {
let removeIndex = state.allies.indexOf(allyMatches[0])
state.allies.splice(removeIndex, 1)
}
state.allies.push(ally)
break
}
return text
}
function resetTempCharacterSkills() {
state.tempCharacter.skills = [
{name: "Acrobatics", stat: "Dexterity", modifier: 0},
@ -1240,6 +1685,7 @@ function init() {
}
if (state.tempEnemy == null) state.tempEnemy = createEnemy("enemy", 10, 10, "2d6", 10)
if (state.tempAlly == null) state.tempAlly = createAlly("ally", 10, 10, "2d6", 10)
if (state.characters == null) state.characters = []
if (state.notes == null) state.notes = []
if (state.locations == null) state.locations = []
@ -1249,6 +1695,7 @@ function init() {
if (state.defaultDifficulty == null) state.defaultDifficulty = 10
if (state.day == null) state.day = 0
if (state.enemies == null) state.enemies = []
if (state.allies == null) state.allies = []
if (state.initiativeOrder == null) state.initiativeOrder = []
state.show = null
state.prefix = null
@ -1317,6 +1764,13 @@ function doSetupEnemy(command) {
return " "
}
function doSetupAlly(command) {
state.setupAllyStep = 0
state.tempAlly = createAlly("ally", 20, 10, 0, "2d6", 10)
state.show = "setupAlly"
return " "
}
function doMemory(command) {
var arg0 = getArgument(command, 0)
if (arg0 == null) {
@ -2494,6 +2948,14 @@ function doHeal(command) {
}
}
for (var ally of state.allies) {
if (ally.name.toLowerCase() == arg1.toLowerCase()) {
ally.health = Math.max(0, ally.health + healing)
state.show = "none"
return `\n[${toTitleCase(ally.name)} has been healed for ${healing} hp to a total of ${ally.health}]\n`
}
}
for (var character of state.characters) {
if (character.name.toLowerCase() == arg1.toLowerCase()) {
character.health += healing
@ -2504,7 +2966,7 @@ function doHeal(command) {
}
state.show = "none"
return `\n[Error: Could not find an enemy or character matching the name ${arg1}. Type #enemies or #characters to see a list]`
return `\n[Error: Could not find an enemy, ally, or character matching the name ${arg1}. Type #enemies, #allies, or #characters to see a list]`
}
}
@ -2568,6 +3030,14 @@ function doDamage(command) {
}
}
for (var ally of state.allies) {
if (ally.name.toLowerCase() == arg1.toLowerCase()) {
ally.health = Math.max(0, ally.health - damage)
state.show = "none"
return `\n[${toTitleCase(ally.name)} has been damaged for ${damage} hp with ${ally.health} remaining] ${ally.health == 0 ? " " + toTitleCase(ally.name) + " has been defeated!" : ""}\n`
}
}
for (var character of state.characters) {
if (character.name.toLowerCase() == arg1.toLowerCase()) {
character.health = Math.max(0, character.health - damage)
@ -2577,7 +3047,7 @@ function doDamage(command) {
}
state.show = "none"
return `\n[Error: Could not find an enemy matching the name ${arg1}. Type #enemies or #characters to see a list]`
return `\n[Error: Could not find an enemy, ally, or character matching the name ${arg1}. Type #enemies, #allies, or #characters to see a list]`
}
}
@ -2850,6 +3320,7 @@ function doAttack(command) {
}
var enemyString = ""
var allyString = ""
if (state.initiativeOrder.length > 0) {
var foundEnemy
@ -2868,6 +3339,23 @@ function doAttack(command) {
}
}
var foundAlly
if (foundEnemy == null) for (var ally of state.allies) {
if (targetText.toLowerCase().includes(ally.name.toLowerCase())) {
foundAlly = ally
break
}
}
if (foundAlly == null) {
var indexMatches = targetText.match(/(?<=ally\s*)\d+/gi)
if (indexMatches != null) {
foundAlly = state.allies[parseInt(indexMatches[0]) - 1]
targetText = targetText.replace(/ally\s*d+/gi, foundAlly.name)
}
}
var damage
if (/^\d*d\d+((\+|-)d+)?$/gi.test(character.damage)) damage = score == 20 ? calculateRoll(character.damage) + calculateRoll(character.damage) : calculateRoll(character.damage)
else damage = parseInt(character.damage)
@ -2894,6 +3382,22 @@ function doAttack(command) {
} else enemyString += ` ${toTitleCase(foundEnemy.name)} has ${foundEnemy.health} health remaining!`
}
}
if (foundAlly != null) {
if (usingDefaultDifficulty) targetRoll = foundAlly.ac
if (score == 20 || score + modifier >= targetRoll) {
if (score == 20) allyString += `\nCritical Damage: ${damage}\n`
else allyString += `\nDamage: ${damage}\n`
state.blockCharacter = foundAlly
state.blockPreviousHealth = foundAlly.health
foundAlly.health = Math.max(0, foundAlly.health - damage)
if (foundAlly.health == 0) {
allyString += ` ${toTitleCase(foundAlly.name)} has been defeated!`
} else allyString += ` ${toTitleCase(foundAlly.name)} has ${foundAlly.health} health remaining!`
}
}
}
var dieText = advantageText == "advantage" || advantageText == "disadvantage" ? `${advantageText}(${die1},${die2})` : die1
@ -2901,10 +3405,10 @@ function doAttack(command) {
state.show = "prefix"
if (targetRoll == 0) state.prefix = ""
else if (score == 20) state.prefix = `\n[Enemy AC: ${targetRoll} Attack roll: ${dieText}]\n`
else if (score == 1) state.prefix = `\n[Enemy AC: ${targetRoll} Attack roll: ${dieText}]\n`
else if (modifier != 0) state.prefix = `\n[Enemy AC: ${targetRoll} Attack roll: ${dieText}${modifier > 0 ? "+" + modifier : modifier}=${score + modifier}. ${score + modifier >= targetRoll ? "Success!" : "Failure!"}]\n`
else state.prefix = `\n[Enemy AC: ${targetRoll} Attack roll: ${dieText}. ${score >= targetRoll ? "Success!" : "Failure!"}]\n`
else if (score == 20) state.prefix = `\n[Target AC: ${targetRoll} Attack roll: ${dieText}]\n`
else if (score == 1) state.prefix = `\n[Target AC: ${targetRoll} Attack roll: ${dieText}]\n`
else if (modifier != 0) state.prefix = `\n[Target AC: ${targetRoll} Attack roll: ${dieText}${modifier > 0 ? "+" + modifier : modifier}=${score + modifier}. ${score + modifier >= targetRoll ? "Success!" : "Failure!"}]\n`
else state.prefix = `\n[Target AC: ${targetRoll} Attack roll: ${dieText}. ${score >= targetRoll ? "Success!" : "Failure!"}]\n`
var text
if (score + modifier >= targetRoll) text = `\n${toTitleCase(character.name)} successfully hit ${targetText}!`
@ -2914,6 +3418,7 @@ function doAttack(command) {
else if (score == 1) text += " Critical failure! The attack missed in a spectacular way!"
if (enemyString != null) text += enemyString
if (allyString != null) text += allyString
if (targetRoll > 0 && (score + modifier >= targetRoll || score == 20)) text += addXpToAll(Math.floor(state.autoXp * clamp(targetRoll, 1, 20) / 20))
return text + "\n"
@ -3252,6 +3757,11 @@ function doShowEnemies(command) {
return " "
}
function doShowAllies(command) {
state.show = "showAllies"
return " "
}
function doRemoveEnemy(command) {
var arg0 = getArgumentRemainder(command, 0)
if (arg0 == null) {
@ -3304,6 +3814,58 @@ function doRemoveEnemy(command) {
return `\n[The enemy ${toTitleCase(enemy.name)} has been removed]\n`
}
function doRemoveAlly(command) {
var arg0 = getArgumentRemainder(command, 0)
if (arg0 == null) {
state.show = "none"
return "\n[Error: Not enough parameters. See #help]\n"
}
if (/\d+\D+(\d+\D*)+/gi.test(arg0)) {
var list = arg0.split(/\D+/)
list.sort(function(a, b) {
return b - a;
});
var text = "\n"
list.forEach(x => {
var num = parseInt(x) - 1
if (num >= state.allies.length) {
state.show = "none"
return `\n[Error: Ally ${x} does not exist. See #showallies]\n`
}
var ally = state.allies[num]
state.allies.splice(num, 1)
var index = state.initiativeOrder.indexOf(ally)
if (index >= 0) state.initiativeOrder.splice(index, 1)
text += `[The ally ${toTitleCase(ally.name)} has been removed]\n`
})
state.show = "none"
return text
}
var ally
if (isNaN(arg0)) arg0 = state.allies.findIndex(x => x.name.toLowerCase() == arg0.toLowerCase())
else arg0--
if (arg0 == -1) {
state.show = "none"
return "\n[Error: Ally not found. See #showallies]\n"
} else if (arg0 >= state.allies.length || arg0 < 0) {
state.show = "none"
return "\n[Error: Location number out of bounds. See #showallies]\n"
} else {
ally = state.allies[arg0]
state.allies.splice(arg0, 1)
}
state.show = "none"
return `\n[The ally ${toTitleCase(ally.name)} has been removed]\n`
}
function doClearEnemies(command) {
var arg0 = getArgument(command, 0)
if (arg0 != null) {
@ -3317,6 +3879,19 @@ function doClearEnemies(command) {
return "\n[The enemies have been cleared]\n"
}
function doClearAllies(command) {
var arg0 = getArgument(command, 0)
if (arg0 != null) {
return doRemoveAlly(command)
}
state.allies = []
state.initiativeOrder = []
state.show = "none"
return "\n[The allies have been cleared]\n"
}
function doAddEnemy(command) {
var name = getArgument(command, 0)
if (name == null) {
@ -3406,6 +3981,95 @@ function doAddEnemy(command) {
return `[Enemy ${toTitleCase(enemy.name)} has been created]`
}
function doAddAlly(command) {
var name = getArgument(command, 0)
if (name == null) {
state.show = "none"
return "\n[Error: Not enough parameters. See #help]\n"
}
var health = getArgument(command, 1)
if (health == null) {
state.show = "none"
return "\n[Error: Not enough parameters. See #help]\n"
} else if (/^\d*d\d+((\+|-)\d+)?$/gi.test(health)) {
health = calculateRoll(health)
} else if (isNaN(health)) {
state.show = "none"
return "\n[Error: Expected a number. See #help]\n"
}
health = parseInt(health)
var ac = getArgument(command, 2)
if (ac == null) {
state.show = "none"
return "\n[Error: Not enough parameters. See #help]\n"
} else if (/^\d*d\d+((\+|-)\d+)?$/gi.test(ac)) {
ac = calculateRoll(ac)
} else if (isNaN(ac)) {
state.show = "none"
return "\n[Error: Expected a number. See #help]\n"
}
ac = parseInt(ac)
var hitModifier = getArgument(command, 3)
if (hitModifier == null) {
state.show = "none"
return "\n[Error: Not enough parameters. See #help]\n"
} else if (/^\d*d\d+((\+|-)\d+)?$/gi.test(hitModifier)) {
hitModifier = calculateRoll(hitModifier)
} else if (isNaN(hitModifier)) {
state.show = "none"
return "\n[Error: Expected a number. See #help]\n"
}
var damage = getArgument(command, 4)
if (damage == null) {
state.show = "none"
return "\n[Error: Not enough parameters. See #help]\n"
} else if (isNaN(damage) && !/^\d*d\d+((\+|-)\d+)?$/gi.test(damage)) {
state.show = "none"
return "\n[Error: Expected a number. See #help]\n"
}
var initiative = getArgument(command, 5)
if (initiative == null) {
state.show = "none"
return "\n[Error: Not enough parameters. See #help]\n"
} else if (/^\d*d\d+((\+|-)\d+)?$/gi.test(initiative)) {
initiative = calculateRoll(initiative)
} else if (isNaN(initiative)) {
state.show = "none"
return "\n[Error: Expected a number. See #help]\n"
}
initiative = parseInt(initiative)
var spells = []
var spell = null
var index = 6
do {
spell = getArgument(command, index++)
if (spell != null) spells.push(spell)
} while (spell != null)
var ally = createAlly(name, health, ac, hitModifier, damage, initiative)
ally.spells = spells
var allyMatches = state.allies.filter(x => x.name.toLowerCase() == ally.name.toLowerCase() || x.name.toLowerCase() == `${ally.name.toLowerCase()} a`)
if (allyMatches.length > 0) {
ally.name = getUniqueName(ally.name)
if (ally.name.endsWith("A")) {
allyMatches[0].name = ally.name
ally.name = ally.name.substring(0, ally.name.length - 1) + "B"
}
}
state.allies.push(ally)
state.show = "none"
return `[Ally ${toTitleCase(ally.name)} has been created]`
}
function doInitiative(command) {
for (character of state.characters) {
var stat = character.stats.find(element => element.name.toLowerCase() == "dexterity")
@ -3418,6 +4082,11 @@ function doInitiative(command) {
else enemy.calculatedInitiative = enemy.initiative
}
for (ally of state.allies) {
if (isNaN(ally.initiative)) ally.calculatedInitiative = calculateRoll(ally.initiative)
else ally.calculatedInitiative = ally.initiative
}
if (state.enemies.length == 0) {
state.show = "none"
return "\n[Error: No enemies! Type #addenemy or #encounter]\n"
@ -3479,6 +4148,15 @@ function doTurn(command) {
if (index >= 0) state.initiativeOrder.splice(index, 1)
}
var defeatedAllies = 0
for (var ally of state.allies) {
if (ally.health > 0) continue
defeatedAllies++
var index = state.initiativeOrder.findIndex(x => x.name.toLowerCase() == ally.name.toLowerCase())
if (index >= 0) state.initiativeOrder.splice(index, 1)
}
var defeatedCharacters = 0
for (var character of state.characters) {
if (character.health > 0) continue
@ -3519,6 +4197,7 @@ function doBlock(command) {
var character = state.characters.find(x => x.name.toLowerCase() == state.blockCharacter.name.toLowerCase())
if (character == null) character = state.enemies.find(x => x.name.toLowerCase() == state.blockCharacter.name.toLowerCase())
if (character == null) character = state.allies.find(x => x.name.toLowerCase() == state.blockCharacter.name.toLowerCase())
if (character == null) {
state.show = "none"
return "\n[Error: Character no longer exists. See #help]\n"
@ -4265,6 +4944,7 @@ function doCastSpell(command) {
var roll = advantage == "advantage" ? Math.max(roll1, roll2) : advantage == "disadvantage" ? Math.min(roll1, roll2) : roll1
var enemyString = ""
var allyString = ""
if (targetText != null && state.initiativeOrder.length > 0) {
var foundEnemy
@ -4283,6 +4963,23 @@ function doCastSpell(command) {
}
}
var foundAlly
if (foundEnemy == null) for (var ally of state.allies) {
if (targetText.toLowerCase().includes(ally.name.toLowerCase())) {
foundAlly = ally
break
}
}
if (foundAlly == null) {
var indexMatches = targetText.match(/(?<=ally\s*)\d+/gi)
if (indexMatches != null) {
foundAlly = state.allies[parseInt(indexMatches[0]) - 1]
targetText = targetText.replace(/ally\s*d+/gi, foundAlly.name)
}
}
var damage = roll == 20 ? calculateRoll("2d6") + calculateRoll("2d6") : calculateRoll("2d6")
var damageMatches = targetText.match(/\d*d\d+((\+|-)d+)?/gi)
@ -4305,6 +5002,20 @@ function doCastSpell(command) {
else enemyString += ` ${toTitleCase(foundEnemy.name)} has ${foundEnemy.health} health remaining!\n`
}
}
if (foundAlly != null) {
if (usingDefaultDifficulty) difficulty = foundAlly.ac
if (roll == 20 || roll + modifier >= difficulty) {
if (roll == 20) allyString += `\nCritical Damage: ${damage}\n`
else allyString += `\nDamage: ${damage}\n`
state.blockCharacter = foundAlly
state.blockPreviousHealth = foundAlly.health
foundAlly.health = Math.max(0, foundAlly.health - damage)
if (foundAlly.health == 0) allyString += ` ${toTitleCase(foundAlly.name)} has been defeated!\n`
else allyString += ` ${toTitleCase(foundAlly.name)} has ${foundAlly.health} health remaining!\n`
}
}
}
state.show = "prefix"
@ -4322,6 +5033,7 @@ function doCastSpell(command) {
else text += ` The spell ${targetText != null ? "misses" : "fails"}!`
if (enemyString != null) text += enemyString
if (allyString != null) text += allyString
if (difficulty > 0 && (roll + modifier >= difficulty || roll == 20)) text += addXpToAll(Math.floor(state.autoXp * clamp(difficulty, 1, 20) / 20))
return `\n${text}\n`
@ -4493,6 +5205,7 @@ function doReset(command) {
state.locations = []
state.location = null
state.enemies = null
state.allies = null
state.initiativeOrder = []
state.x = null
state.y = null

View file

@ -344,10 +344,13 @@ function executeTurn(activeCharacter) {
if (possessiveName == "Your") possessiveName = "your"
if (activeCharacter.className != null) {
//player
state.show = "none"
return `\n[It is ${possessiveName} turn]\n`
} else {
} else if (activeCharacter.ally == false) {
//enemy
var characters = state.characters.filter(x => x.health > 0)
characters.push(...state.allies.filter(x => x.health > 0))
var target = characters[getRandomInteger(0, characters.length - 1)]
var areWord = target.name == "You" ? "are" : "is"
var targetNameAdjustedCase = target.name == "You" ? "you" : toTitleCase(target.name)
@ -388,6 +391,49 @@ function executeTurn(activeCharacter) {
}
}
return text
} else {
//ally
var enemies = state.enemies.filter(x => x.health > 0)
var target = enemies[getRandomInteger(0, enemies.length - 1)]
var areWord = target.name == "You" ? "are" : "is"
var targetNameAdjustedCase = target.name == "You" ? "you" : toTitleCase(target.name)
var attack = calculateRoll(`1d20${activeCharacter.hitModifier > 0 ? "+" + activeCharacter.hitModifier : activeCharacter.hitModifier < 0 ? activeCharacter.hitModifier : ""}`)
var hit = attack >= target.ac
var text = `\n[It is ${possessiveName} turn]\n`
if (getRandomBoolean() || activeCharacter.spells.length == 0) {
if (hit) {
state.blockCharacter = target
state.blockPreviousHealth = target.health
var damage = isNaN(activeCharacter.damage) ? calculateRoll(activeCharacter.damage) : activeCharacter.damage
target.health = Math.max(target.health - damage, 0)
text += `\n[Enemy AC: ${target.ac} Attack roll: ${attack}]\n`
text += `${activeCharacterName} attacks ${targetNameAdjustedCase} for ${damage} damage!\n`
if (target.health == 0) text += ` ${toTitleCase(target.name)} ${areWord} unconscious! \n`
else text += ` ${toTitleCase(target.name)} ${areWord} at ${target.health} health.\n`
} else text += `${activeCharacterName} attacks ${targetNameAdjustedCase} but misses!\n`
} else {
var spell = activeCharacter.spells[getRandomInteger(0, activeCharacter.spells.length - 1)]
var diceMatches = spell.match(/(?<=^.*)\d*d\d+((\+|-)\d+)?$/gi)
if (diceMatches == null) text += `${activeCharacterName} casts spell ${spell}!`
else {
if (hit) {
var damage = calculateRoll(diceMatches[0])
var spell = spell.substring(0, spell.length - diceMatches[0].length)
target.health = Math.max(target.health - damage, 0)
text += `\n[Character AC: ${target.ac} Attack roll: ${attack}]\n`
text += `${activeCharacterName} casts spell ${spell} at ${targetNameAdjustedCase} for ${damage} damage!`
if (target.health == 0) text += ` ${toTitleCase(target.name)} ${areWord} unconscious!\n`
else text += ` ${toTitleCase(target.name)} ${areWord} at ${target.health} health.\n`
} else text += `${activeCharacterName} casts spell ${spell} at ${targetNameAdjustedCase} but misses!\n`
}
}
return text
}
}
@ -2775,11 +2821,26 @@ function createEnemy(name, health, ac, hitModifier, damage, initiative, ...spell
hitModifier: hitModifier,
damage: damage,
initiative: initiative,
spells: spells
spells: spells,
ally: false
}
return enemy
}
function createAlly(name, health, ac, hitModifier, damage, initiative, ...spells) {
var ally = {
name: name,
health: health,
ac: ac,
hitModifier: hitModifier,
damage: damage,
initiative: initiative,
spells: spells,
ally: true
}
return ally
}
function getUniqueName(name) {
const letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
var letterIndex = 0
@ -2807,6 +2868,11 @@ function createInitiativeOrder() {
state.initiativeOrder.push(enemy)
}
for (var ally of state.allies) {
if (ally.health <= 0) continue
state.initiativeOrder.push(ally)
}
state.initiativeOrder.sort(function(a, b) {
return b.calculatedInitiative - a.calculatedInitiative;
});

View file

@ -87,6 +87,54 @@ const modifier = (text) => {
break
}
break
case "setupAlly":
switch (state.setupAllyStep) {
case 0:
text += `***ALLY CREATION***\nWould you like to use a preset ally? (y/n/q to quit)\n`
break
case 1:
text += `What is the ally's name? This must be a unique name that has no duplicates in the current encounter. Typing the name of an existing ally will modify that ally's properties. Type q to quit.\n`
break
case 2:
text += `${!state.newAlly ? "Ally name already exists. You are now modifying the existing ally " + state.tempAlly.name + ". " : ""}What is the ally's health? This can be any positive integer or a dice roll (ie. 3d6+5). Type q to quit.${!state.newAlly ? " Type default to leave as current value " + state.tempAlly.health : ""}\n`
break
case 3:
text += `What is the ally's armor class (AC)? This can be any positive integer with 10 being easy and 20 being incredibly difficult. It can also be a dice roll (ie. 2d4+5). Type q to quit.${!state.newAlly ? " Type default to leave as current value " + state.tempAlly.ac : ""}\n`
break
case 4:
text += `What is the ally's hit modifier? This affects how accurate their attacks are. This can be any integer. 0 is normal accuracy. Type q to quit.${!state.newAlly ? " Type default to leave as current value " + state.tempAlly.hitModifier : ""}\n`
break
case 5:
text += `What is the ally's damage? This can be any positive integer or a dice roll (ie. 2d6+5). The dice roll is calculated at the time of each attack. Type q to quit.${!state.newAlly ? " Type default to leave as current value " + state.tempAlly.damage : ""}\n`
break
case 6:
text += `What is the ally's initiative? Initiative controls turn order. This can be any positive integer with higher numbers going first in battle. This can also be a dice roll (ie. 1d20+3). Type q to quit.${!state.newAlly ? " Type default to leave as current value " + state.tempAlly.initiative : ""}\n`
break
case 7:
text += `Enter the name of a spell that the ally knows. If it can target this spell at an enemy character, add a dice roll for the damage calculation after it (ie. Ray of Frost3d6+2). Type s to stop entering spells or type q to quit.${!state.newAlly ? " Type e to erase all current spells." : ""}\n`
break
case 8:
text += `Enter the name of another spell that the ally knows. If it can target this spell at an enemy character, add a dice roll for the damage calculation after it (ie. Ray of Frost3d6+2). Type s to stop entering spells or type q to quit.\n`
break
case 100:
text += `What ally preset will you choose?\nHeroes\n1. Fighter\n2. Cleric\n3. Rogue\n4. Ranger\n5. Barbarian\n6. Bard\n7. Druid\n8. Monk\n9. Paladin\n10. Wizard\n11. Sorcerer\n12. Warlock\n13. Artificer`
text += `\n\nHumanoid\n14. Commoner\n15. Bandit\n16. Guard\n17. Cultist\n18. Acolyte\n19. Apprentice\n20. Witch\n21. Buccaneer\n22. Spy\n123. Captain\n24. Bard\n25. Berserker\n26 Priest\n27. Knight\n28. Archer\n29. Warrior\n30. Conjurer\n31. Mage\n32. Assassin\n33. Evoker\n34. Necromancer\n35. Champion\n36. Warlord\n37. Archmage\n38. Archdruid`
text += `\n\nFamiliars\n39. Ape\n40. Badger\n41. Bat\n42. Black Bear\n43. Boar\n44. Brown Bear\n45. Camel\n46. Cat\n47. Constrictor Snake\n48. Crab\n49. Crocodile\n50. Dire Wolf\n51. Draft Horse\n52. Elephant\n53. Elk\n54. Frog\n55. Giant Badger\n56. Giant Crab\n57. Giant Goat\n58. Giant Seahorse\n59. Giant Spider\n60. Giant Weasel\n61. Goat\n62. Hawk\n63. Imp\n64. Lion\n65. Lizard\n66. Mastiff\n67. Mule\n68. Octopus\n69. Owl\n70. Panther\n71. Pony\n72. Pseudodragon\n73. Quasit\n74. Rat\n75. Raven\n76. Reef Shark\n77. Riding Horse\n78. Scorpion\n79. Skeleton\n80. Slaad Tadpole\n81. Sphinx of Wonder\n82. Spider\n83. Sprite\n84. Tiger\n85. Venomous Snake\n86. Warhorse\n87. Weasel\n88. Wolf\n89. Zombie`
text += `\n\nEnter the number or q to quit. If you want to rename the ally, add a space and type the name\n(ie. 25 Thuggish Zombie B)\n`
break
case 500:
var hashtag = `#addally "${state.tempAlly.name}" ${state.tempAlly.health} ${state.tempAlly.ac} ${state.tempAlly.hitModifier} ${state.tempAlly.damage} ${state.tempAlly.initiative}`
for (var spell of state.tempAlly.spells) {
hashtag += ` "${spell}"`
}
text += `${state.tempAlly.name} has been created.\nType #showallies to show the list of all allies.\nCopy and paste the following hashtag to create another identical ally like this:\n${hashtag}\n***********\n`
break;
case null:
text += `[Ally creation has been aborted!]\n`
break
}
break
case "stragedy":
text += handleStragedy()
break
@ -321,6 +369,20 @@ const modifier = (text) => {
}
}
text += "******************\n\n"
break
case "showAllies":
text += "*** ALLIES ***\n"
if (state.allies.length == 0) {
text += "There are no allies present here. Type #encounter to generate a scripted set or #addally to add your own\n"
} else {
var index = 0
for (var ally of state.allies) {
text += `${++index}. ${toTitleCase(ally.name)} (Health: ${ally.health} AC: ${ally.ac} Initiative: ${ally.initiative})\n`
}
}
text += "******************\n\n"
break
case "initiative":
@ -499,7 +561,9 @@ const modifier = (text) => {
text += "\n#addenemy name health ac hitModifier damage initiative spells"
text += "\n Adds the specified enemy to the list of enemies. health, ac, hitModifier, damage, and initiative can be numbers or dice rolls such as 3d6+5. Type the name in quotes if the name contains a space. The rest of the parameters can be a list of spells. Each spell must be typed in quotes if it has a space. If the spell does damage, write the name and damage roll in the following format: \"Ray of Frost5d10\""
text += "\n#removeenemy name or index"
text += "\n Removes the enemy as specified by the name or index. To delete multiple enemies, type the numbers with spaces or commas between them. This is safer than calling #removenote multiple times because the numbers shift as enemies are deleted. Quotes are not necessary."
text += "\n Removes the enemy as specified by the name or index. To delete multiple enemies, type the numbers with spaces or commas between them. This is safer than calling #removeenemy multiple times because the numbers shift as enemies are deleted. Quotes are not necessary."
text += "\n#clearenemies"
text += "\n Removes all enemies."
text += "\n#initiative"
text += "\n Assigns initiative to all characters and enemies. This begins combat."
text += "\n#turn"
@ -511,6 +575,18 @@ const modifier = (text) => {
text += "\n#flee (difficulty_class or automatic|effortless|easy|medium|hard|impossible)"
text += "\n Attempt to flee from combat. If the difficulty is not specified, the default difficulty will be used instead."
text += "\n\n--Allies--"
text += "\n#setupally"
text += "\nFollow prompts to create an ally from a template or completely from scratch. It will be added to the existing encounter if there is one already specified."
text += "\n#showallies"
text += "\n Shows the list of current allies."
text += "\n#addally name health ac hitModifier damage initiative spells"
text += "\n Adds the specified ally to the list of allies. health, ac, hitModifier, damage, and initiative can be numbers or dice rolls such as 3d6+5. Type the name in quotes if the name contains a space. The rest of the parameters can be a list of spells. Each spell must be typed in quotes if it has a space. If the spell does damage, write the name and damage roll in the following format: \"Ray of Frost5d10\""
text += "\n#removeally name or index"
text += "\n Removes the ally as specified by the name or index. To delete multiple allies, type the numbers with spaces or commas between them. This is safer than calling #removeally multiple times because the numbers shift as allies are deleted. Quotes are not necessary."
text += "\n#clearallies"
text += "\n Removes all allies."
text += "\n\n--Locations--"
text += "\n#createlocation [(x) (y) or (here|far) or (distance)] location_name"
text += "\n Creates a location at the given coordinates. The coordinates must be integers. If the coordinates are not provided, they are randomized within a range of 10 units from the party's current location. You can also use \"here\" to indicate that the location is at party's coordinates. \"far\" indicates that the coordinates will be randomly generated 50-100 units away. You may also just specify a distance. Multiple locations may exist at the same coordinates. A story card is created for the location. Quotes are not necessary."

View file

@ -19,6 +19,9 @@ Watch the [tutorial video](https://youtu.be/E5TYU7rDaBQ).
Hashtag-DnD has another new scenario! Check out the [VTOL-Knights Repository](https://github.com/raeleus/Hashtag-DnD/tree/VTOL-Knights)
v.0.7.0
* Added allies which are NPC characters that fight alongside the characters in encounters
v. 0.6.0
* Added Memory/Matchmaking Game
* Added Item Shop - Make sure to import the latest story cards: https://github.com/raeleus/Hashtag-DnD/blob/master/story-cards.json