const rollSynonyms = ["roll"] const createSynonyms = ["create", "generate", "start", "begin", "setup", "party", "member", "new"] const bioSynonyms = ["bio", "biography", "summary", "character", "charactersheet", "statsheet", "abilities", "showabilities"] const setClassSynonyms = ["setclass", "class"] const setSummarySynonyms = ["setsummary", "summary"] const trySynonyms = ["try", "tryto", "tries", "triesto", "attempt", "attemptto", "attemptsto", "do"] const setHealthSynonyms = ["sethealth"] const healSynonyms = ["heal", "mend", "restore"] const damageSynonyms = ["damage", "hurt", "harm", "injure"] const restSynonyms = ["rest", "longrest", "shortrest", "sleep", "nap"] const setExperienceSynonyms = ["setexperience", "setexp", "setxp", "setexperiencepoints"] const addExperienceSynonyms = ["addexperience", "addexp", "addxp", "addexperiencepoints", "experience", "exp", "xp", "experiencepoints"] const levelUpSynonyms = ["levelup", "level"] const setStatSynonyms = ["setstat", "setstatistic", "setattribute", "setability", "changestat", "changestatistic", "changeattribute", "changeability", "updatestat", "updatestatistic", "updateattribute", "updateability", "stat", "attribute", "ability"] const showStatsSynonym = ["showstats", "stats", "viewstats", "showabilities", "abilities", "viewabilities", "showstatistics", "statistics", "viewstatistics", "showattributes", "attributes", "viewattributes"] const removeStatSynonyms = ["removestat", "deletestat", "cancelstat", "removeability", "deleteability", "cancelAbility", "removestatistic", "deletestatistic", "cancelstatistic", "removeattribute", "deleteattribute", "cancelattribute"] const clearStatsSynonyms = ["clearstats", "clearabilities", "clearstatistics", "clearattributes"] const setSpellStatSynonyms = ["setspellstat", "setspellstatistic", "setspellability", "setspellcastingability", "changespellstat", "changespellstatistic", "changespellability", "changespellcastingability"] const setSkillSynonyms = ["setskill", "changeskill", "updateskill", "skill"] const showSkillsSynonyms = ["showskills", "skills"] const removeSkillSynonyms = ["removeskill", "deleteskill", "cancelskill"] const clearSkillsSynonyms = ["clearskills"] const checkSynonyms = ["check", "checkstat", "checkstatistic", "checkattribute", "checkability", "checkskill"] const showNotesSynonyms = ["notes", "shownotes", "viewnotes"] const noteSynonyms = ["note", "takenote"] const clearNotesSynonyms = ["clearnotes"] const eraseNoteSynonyms = ["erasenote", "removenote", "deletenote", "cancelnote"] const takeSynonyms = ["take", "steal", "get", "grab", "receive", "loot"] const buySynonyms = ["buy", "purchase", "barter", "trade", "swap", "exchange"] const sellSynonyms = ["sell", ] const dropSynonyms = ["remove", "discard", "drop", "leave", "dispose", "toss", "throw", "throwaway", "trash", "donate", "eat", "consume", "use", "drink"] const giveSynonyms = ["give", "handover", "hand", "gift"] const inventorySynonyms = ["inv", "inventory", "backpack", "gear", "showinv", "showinventory", "viewinventory", "viewinv"] const clearInventorySynonyms = ["clearinventory", "clearinv", "emptyinventory", "emptybackpack", "clearbackpack", "emptygear", "cleargear"] const learnSpellSynonyms = ["learnspell", "learnmagic", "learnincantation", "learnritual", "memorizespell", "memorizemagic", "memorizeincantation", "memorizeritual", "learnsspell", "learnsmagic", "learnsincantation", "learnsritual", "memorizesspell", "memorizesmagic", "memorizesincantation", "memorizesritual"] const forgetSpellSynonyms = ["forgetspell", "forgetmagic", "forgetincantation", "forgetritual", "forgetsspell", "forgetsmagic", "forgetsincantation", "forgetsritual", "deletespell", "deletemagic", "deleteincantation", "deleteritual", "deletesspell", "deletesmagic", "deletesincantation", "deletesritual", "cancelspell", "cancelmagic", "cancelincantation", "cancelritual", "cancelsspell", "cancelsmagic", "cancelsincantation", "cancelsritual", "removespell", "removemagic", "removeincantation", "removeritual", "removesspell", "removesmagic", "removesincantation", "removesritual"] const castSpellSynonyms = ["cast", "castspell", "castmagic", "castincantation", "castritual", "castsspell", "castsmagic", "castsincantation", "castsritual"] const clearSpellsSynonyms = ["clearspells", "clearmagic", "clearincantations", "clearrituals", "forgetallspells", "forgetallmagic", "forgetallincantation", "forgetallritual"] const spellbookSynonyms = ["spellbook", "spells", "listspells", "showspells", "spelllist", "spellcatalog", "spellinventory"] const resetSynonyms = ["reset", "cleandata", "cleardata", "resetdata", "resetsettings", "clearsettings", "profile"] const allSynonyms = ["all", "every", "each", "every one", "everyone"] const attackSynonyms = ["attack", "strike", "ambush", "assault", "fireat", "fireon"] const setMeleeStatSynonyms = ["setmeleestat", "setmeleestatistic", "setmeleeability", "changemeleestat", "changemeleestatistic", "changemeleeability"] const setrangedStatSynonyms = ["setrangedstat", "setrangedstatistic", "setrangedability", "changerangedstat", "changerangedstatistic", "changerangedability"] const showCharactersSynonyms = ["showcharacters", "showparty", "showteam", "characters", "party", "team"] const removeCharacterSynonyms = ["removecharacter", "deletecharacter", "erasecharacter", ""] const setAutoXpSynonyms = ["setautoxp", "autoxp"] const showAutoXpSynonyms = ["showautoxp"] const helpSynonyms = ["help"] const modifier = (text) => { init() const rawText = text if (state.createStep != null) { text = handleCreateStep(text) if (state.createStep != null) return { text } else text = rawText } if (state.initialized == null || !text.includes("#")) { state.initialized = true; return { text } } state.characterName = getCharacterName(rawText) text = sanitizeText(text) command = text.substring(text.search(/#/) + 1) var commandName = getCommandName(command).toLowerCase().replaceAll(/[^a-z0-9]*/gi, "") if (state.characterName == null || !hasCharacter(state.characterName)) { var found = processCommandSynonyms(command, commandName, createSynonyms, function () {return true}) if (state.characterName == null && found) { state.show = "none" text = `\n[Error: Character name not specified. Use the "do" or "say" modes. Alternatively, use "story" mode with the following format without quotes: "charactername #hashtag"]\n` return { text } } if (!found) found = processCommandSynonyms(command, commandName, helpSynonyms.concat(rollSynonyms, noteSynonyms, eraseNoteSynonyms, showNotesSynonyms, clearNotesSynonyms, showCharactersSynonyms, removeCharacterSynonyms, resetSynonyms), function () {return true}) if (found == null) { if (state.characterName == null) { state.show = "none" text = `\n[Error: Character name not specified. Use the "do" or "say" modes. Alternatively, use "story" mode with the following format without quotes: "charactername #hashtag"]\n` return { text } } else { state.show = "none" text = `\n[Error: Character ${state.characterName} does not exist. Type #setup to create this character.]\n` return { text } } } } text = processCommandSynonyms(command, commandName, rollSynonyms, doRoll) if (text == null) text = processCommandSynonyms(command, commandName, createSynonyms, doCreate) if (text == null) text = processCommandSynonyms(command, commandName, showCharactersSynonyms, doShowCharacters) if (text == null) text = processCommandSynonyms(command, commandName, removeCharacterSynonyms, doRemoveCharacter) if (text == null) text = processCommandSynonyms(command, commandName, bioSynonyms, doBio) if (text == null) text = processCommandSynonyms(command, commandName, setClassSynonyms, doSetClass) if (text == null) text = processCommandSynonyms(command, commandName, setSummarySynonyms, doSetSummary) if (text == null) text = processCommandSynonyms(command, commandName, setHealthSynonyms, doSetHealth) if (text == null) text = processCommandSynonyms(command, commandName, healSynonyms, doHeal) if (text == null) text = processCommandSynonyms(command, commandName, damageSynonyms, doDamage) if (text == null) text = processCommandSynonyms(command, commandName, restSynonyms, doRest) if (text == null) text = processCommandSynonyms(command, commandName, setExperienceSynonyms, doSetExperience) if (text == null) text = processCommandSynonyms(command, commandName, addExperienceSynonyms, doAddExperience) if (text == null) text = processCommandSynonyms(command, commandName, levelUpSynonyms, doLevelUp) if (text == null) text = processCommandSynonyms(command, commandName, showStatsSynonym, doShowStats) if (text == null) text = processCommandSynonyms(command, commandName, setStatSynonyms, doSetStat) if (text == null) text = processCommandSynonyms(command, commandName, setSpellStatSynonyms, doSetSpellStat) if (text == null) text = processCommandSynonyms(command, commandName, showSkillsSynonyms, doShowSkills) if (text == null) text = processCommandSynonyms(command, commandName, setSkillSynonyms, doSetSkill) if (text == null) text = processCommandSynonyms(command, commandName, checkSynonyms, doCheck) if (text == null) text = processCommandSynonyms(command, commandName, trySynonyms, doTry) if (text == null) text = processCommandSynonyms(command, commandName, showNotesSynonyms, doShowNotes) if (text == null) text = processCommandSynonyms(command, commandName, noteSynonyms, doNote) if (text == null) text = processCommandSynonyms(command, commandName, clearNotesSynonyms, doClearNotes) if (text == null) text = processCommandSynonyms(command, commandName, eraseNoteSynonyms, doEraseNote) if (text == null) text = processCommandSynonyms(command, commandName, takeSynonyms, doTake) if (text == null) text = processCommandSynonyms(command, commandName, dropSynonyms, doDrop) if (text == null) text = processCommandSynonyms(command, commandName, giveSynonyms, doGive) if (text == null) text = processCommandSynonyms(command, commandName, inventorySynonyms, doInventory) if (text == null) text = processCommandSynonyms(command, commandName, clearInventorySynonyms, doClearInventory) if (text == null) text = processCommandSynonyms(command, commandName, learnSpellSynonyms, doLearnSpell) if (text == null) text = processCommandSynonyms(command, commandName, forgetSpellSynonyms, doForgetSpell) if (text == null) text = processCommandSynonyms(command, commandName, castSpellSynonyms, doCastSpell) if (text == null) text = processCommandSynonyms(command, commandName, clearSpellsSynonyms, doClearSpells) if (text == null) text = processCommandSynonyms(command, commandName, spellbookSynonyms, doSpellbook) if (text == null) text = processCommandSynonyms(command, commandName, removeStatSynonyms, doRemoveStat) if (text == null) text = processCommandSynonyms(command, commandName, clearStatsSynonyms, doClearStats) if (text == null) text = processCommandSynonyms(command, commandName, removeSkillSynonyms, doRemoveSkill) if (text == null) text = processCommandSynonyms(command, commandName, clearSkillsSynonyms, doClearSkills) if (text == null) text = processCommandSynonyms(command, commandName, attackSynonyms, doAttack) if (text == null) text = processCommandSynonyms(command, commandName, setMeleeStatSynonyms, doSetMeleeStat) if (text == null) text = processCommandSynonyms(command, commandName, setrangedStatSynonyms, doSetRangedStat) if (text == null) text = processCommandSynonyms(command, commandName, buySynonyms, doBuy) if (text == null) text = processCommandSynonyms(command, commandName, sellSynonyms, doSell) if (text == null) text = processCommandSynonyms(command, commandName, resetSynonyms, doReset) if (text == null) text = processCommandSynonyms(command, commandName, setAutoXpSynonyms, doSetAutoXp) if (text == null) text = processCommandSynonyms(command, commandName, showAutoXpSynonyms, doShowAutoXp) if (text == null) text = processCommandSynonyms(command, commandName, helpSynonyms, doHelp) return { text } } function handleCreateStep(text) { state.show = "create" 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.createStep = null return text } switch (state.createStep) { case 0: text = text.toLowerCase(); if (text.startsWith("y")) state.createStep = 100 else if (text.startsWith("n")) state.createStep++ break case 1: if (text.length > 0) { state.tempCharacter.className = text state.createStep++ state.statDice = [] for (var i = 0; i < 6; i++) { var dice = [] for (var j = 0; j < 4; j++) { dice.push(parseInt(calculateRoll("d6"))) } dice.sort(function(a, b) { return b - a; }); dice.splice(3, 1) state.statDice.push(dice[0] + dice[1] + dice[2]) } state.statDice.sort(function(a, b) { return b - a }) } return text break case 2: if (text.length > 0) { var choices = text.split(/\D+/) choices = [...new Set(choices)]; if (choices.length != 6) break for (var i = 0; i < 6; i++) { const stat = { name: "temp", value: state.statDice[i] } switch (parseInt(choices[i])) { case 1: stat.name = "Strength" break case 2: stat.name = "Dexterity" break case 3: stat.name = "Constitution" break case 4: stat.name = "Intelligence" break case 5: stat.name = "Wisdom" break case 6: stat.name = "Charisma" break default: return text } state.tempCharacter.stats.push(stat) } state.createStep++ } return text case 3: if (text.length == 0) state.createStep++ if (!isNaN(text)) { switch (parseInt(text)) { case 1: state.tempCharacter.spellStat = "Intelligence" break case 2: state.tempCharacter.spellStat = "Wisdom" break case 3: state.tempCharacter.spellStat = "Charisma" break case 4: state.tempCharacter.spellStat = null } state.createStep++ } return text case 4: if (text.length > 0) { state.tempCharacter.summary = text state.createStep = 500 } return text case 100: if (!isNaN(text)) { state.createStep = 500 switch (parseInt(text)) { case 1: state.tempCharacter.className = "Fighter" state.tempCharacter.stats = [{name: "Strength", value: 16}, {name: "Dexterity", value: 9}, {name: "Constitution", value: 15}, {name: "Intelligence", value: 11}, {name: "Wisdom", value: 13}, {name: "Charisma", value: 14}] state.tempCharacter.inventory.push({name: "Greatsword", quantity: 1}, {name: "Javelin", quantity: 2}) state.tempCharacter.skills.find((element) => element.name == "Athletics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "History").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Perception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Persuasion").modifier = 2; state.tempCharacter.summary = "A skilled melee warrior specializing in weapons and armor." break case 2: state.tempCharacter.className = "Cleric" state.tempCharacter.stats = [{name: "Strength", value: 14}, {name: "Dexterity", value: 12}, {name: "Constitution", value: 14}, {name: "Intelligence", value: 11}, {name: "Wisdom", value: 18}, {name: "Charisma", value: 14}] state.tempCharacter.inventory.push({name: "Mace", quantity: 1}, {name: "Light Crossbow", quantity: 1}, {name: "Bolts", quantity: 10}) state.tempCharacter.spells = ["Spiritual Weapon", "Mass Healing Word"] state.tempCharacter.spellStat = "Wisdom" state.tempCharacter.skills.find((element) => element.name == "Insight").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Medicine").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Perception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Religion").modifier = 2; state.tempCharacter.summary = "A follower of a deity that can call on divine power." break case 3: state.tempCharacter.className = "Rogue" state.tempCharacter.stats = [{name: "Strength", value: 8}, {name: "Dexterity", value: 16}, {name: "Constitution", value: 12}, {name: "Intelligence", value: 13}, {name: "Wisdom", value: 10}, {name: "Charisma", value: 16}] state.tempCharacter.inventory.push({name: "Shortsword", quantity: 1}, {name: "Dagger", quantity: 1}, {name: "Hand Crossbow", quantity: 1}, {name: "Bolts", quantity: 10}) state.tempCharacter.skills.find((element) => element.name == "Acrobatics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Deception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Investigation").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Performance").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Sleight of Hand").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Stealth").modifier = 2; state.tempCharacter.summary = "An expert in stealth, subterfuge, and exploitation." break case 4: state.tempCharacter.className = "Ranger" state.tempCharacter.stats = [{name: "Strength", value: 12}, {name: "Dexterity", value: 17}, {name: "Constitution", value: 13}, {name: "Intelligence", value: 10}, {name: "Wisdom", value: 15}, {name: "Charisma", value: 8}] state.tempCharacter.inventory.push({name: "Shortsword", quantity: 1}, {name: "Longbow", quantity: 1}, {name: "Arrows", quantity: 20}) state.tempCharacter.skills.find((element) => element.name == "Animal Handling").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Athletics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Nature").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Perception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Stealth").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Survival").modifier = 2; state.tempCharacter.summary = "A talented hunter adept in tracking, survival, and animal handling." break case 5: state.tempCharacter.className = "Barbarian" state.tempCharacter.stats = [{name: "Strength", value: 17}, {name: "Dexterity", value: 13}, {name: "Constitution", value: 15}, {name: "Intelligence", value: 8}, {name: "Wisdom", value: 12}, {name: "Charisma", value: 10}] state.tempCharacter.inventory.push({name: "Greataxe", quantity: 1}, {name: "Javelin", quantity: 1}) state.tempCharacter.skills.find((element) => element.name == "Animal Handling").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Athletics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Intimidation").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Perception").modifier = 2; state.tempCharacter.summary = "Combat expert focused on brute strength and raw fury." break case 6: state.tempCharacter.className = "Bard" state.tempCharacter.stats = [{name: "Strength", value: 8}, {name: "Dexterity", value: 15}, {name: "Constitution", value: 14}, {name: "Intelligence", value: 13}, {name: "Wisdom", value: 10}, {name: "Charisma", value: 15}] state.tempCharacter.inventory.push({name: "Rapier", quantity: 1}, {name: "Lute", quantity: 1}) state.tempCharacter.spells = ["Vicious Mockery", "Charm Person", "Healing Word"] state.tempCharacter.spellStat = "Charisma" state.tempCharacter.skills.find((element) => element.name == "Acrobatics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Athletics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Deception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Perception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Performance").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Sleight of Hand").modifier = 2; state.tempCharacter.summary = "A musician that can transform song and word into magic." break case 7: state.tempCharacter.className = "Druid" state.tempCharacter.stats = [{name: "Strength", value: 11}, {name: "Dexterity", value: 13}, {name: "Constitution", value: 16}, {name: "Intelligence", value: 14}, {name: "Wisdom", value: 16}, {name: "Charisma", value: 9}] state.tempCharacter.spells = ["Druidcraft", "Animal Friendship", "Healing Word"] state.tempCharacter.spellStat = "Wisdom" state.tempCharacter.inventory.push({name: "Quarterstaff", quantity: 1}, {name: "Small Knife", quantity: 1}) state.tempCharacter.skills.find((element) => element.name == "Arcana").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "History").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Medicine").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Nature").modifier = 2; state.tempCharacter.summary = "Commands the natural world to cast spells and harness its power." break case 8: state.tempCharacter.className = "Monk" state.tempCharacter.stats = [{name: "Strength", value: 16}, {name: "Dexterity", value: 14}, {name: "Constitution", value: 14}, {name: "Intelligence", value: 8}, {name: "Wisdom", value: 17}, {name: "Charisma", value: 10}] state.tempCharacter.inventory.push({name: "Dart", quantity: 5}, {name: "Shortsword", quantity: 1}) state.tempCharacter.skills.find((element) => element.name == "Athletics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Deception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Sleight of Hand").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Stealth").modifier = 2; state.tempCharacter.summary = "A martial artist who has mastered melee and unarmed combat." break case 9: state.tempCharacter.className = "Paladin" state.tempCharacter.stats = [{name: "Strength", value: 16}, {name: "Dexterity", value: 9}, {name: "Constitution", value: 15}, {name: "Intelligence", value: 11}, {name: "Wisdom", value: 13}, {name: "Charisma", value: 14}] state.tempCharacter.spells = ["Thunderous Smite", "Divine Favor", "Cure Wounds"] state.tempCharacter.spellStat = "Charisma" state.tempCharacter.inventory.push({name: "Longsword", quantity: 1}, {name: "Javelin", quantity: 2}) state.tempCharacter.skills.find((element) => element.name == "Athletics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "History").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Insight").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Persuasion").modifier = 2; state.tempCharacter.summary = "A virtuous holy warrior with expertise in armor and mysticism." break case 10: state.tempCharacter.className = "Wizard" state.tempCharacter.stats = [{name: "Strength", value: 10}, {name: "Dexterity", value: 15}, {name: "Constitution", value: 14}, {name: "Intelligence", value: 16}, {name: "Wisdom", value: 12}, {name: "Charisma", value: 8}] state.tempCharacter.inventory.push({name: "Quarterstaff", quantity: 1}, {name: "Spellbook", quantity: 1}) state.tempCharacter.spells = ["Fire Bolt", "Mage Hand", "Magic Missile"] state.tempCharacter.spellStat = "Intelligence" state.tempCharacter.skills.find((element) => element.name == "Arcana").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Insight").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Investigation").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Perception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Religion").modifier = 2; state.tempCharacter.summary = "An expert in magic ability who found their power through arcane knowledge." break case 11: state.tempCharacter.className = "Sorcerer" state.tempCharacter.stats = [{name: "Strength", value: 8}, {name: "Dexterity", value: 16}, {name: "Constitution", value: 13}, {name: "Intelligence", value: 11}, {name: "Wisdom", value: 12}, {name: "Charisma", value: 15}] state.tempCharacter.inventory.push({name: "Dagger", quantity: 1}, {name: "Bag of Holding", quantity: 1}) state.tempCharacter.spells = ["Ray of Frost", "Minor Illusion", "Shield"] state.tempCharacter.spellStat = "Charisma" state.tempCharacter.skills.find((element) => element.name == "Arcana").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Intimidation").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Perception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Persuasion").modifier = 2; state.tempCharacter.summary = "A masterful spellcaster deriving their power from an innate source." break case 12: state.tempCharacter.className = "Warlock" state.tempCharacter.stats = [{name: "Strength", value: 9}, {name: "Dexterity", value: 13}, {name: "Constitution", value: 15}, {name: "Intelligence", value: 14}, {name: "Wisdom", value: 11}, {name: "Charisma", value: 16}] state.tempCharacter.spells = ["Eldritch Blast", "Witch Bolt", "Thunderwave"] state.tempCharacter.spellStat = "Charisma" state.tempCharacter.inventory.push({name: "Dagger", quantity: 1}, {name: "Orb", quantity: 1}) state.tempCharacter.skills.find((element) => element.name == "Arcana").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Deception").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "History").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Religion").modifier = 2; state.tempCharacter.summary = "A magic user granted ability by a pact with a powerful patron." break case 13: state.tempCharacter.className = "Artificer" state.tempCharacter.stats = [{name: "Strength", value: 10}, {name: "Dexterity", value: 14}, {name: "Constitution", value: 14}, {name: "Intelligence", value: 17}, {name: "Wisdom", value: 12}, {name: "Charisma", value: 8}] state.tempCharacter.inventory.push({name: "Shortsword", quantity: 1}, {name: "Hand Crossbow", quantity: 1}, {name: "Bolts", quantity: 20}) state.tempCharacter.skills.find((element) => element.name == "Acrobatics").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Performance").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Persuasion").modifier = 2; state.tempCharacter.skills.find((element) => element.name == "Arcana").modifier = 2; state.tempCharacter.summary = "An inventor and alchemist capable of imbuing objects with magic." break } } return text case 500: state.show = null state.createStep = null var character = getCharacter(state.tempCharacter.name) character.className = state.tempCharacter.className character.experience = 0 character.stats = [...state.tempCharacter.stats] character.inventory = [...state.tempCharacter.inventory] character.skills = [...state.tempCharacter.skills] character.spells = [...state.tempCharacter.spells] character.health = getHealthMax() character.spellStat = state.tempCharacter.spellStat character.meleeStat = state.tempCharacter.meleeStat character.rangedStat = state.tempCharacter.rangedStat character.summary = state.tempCharacter.summary break } return text } function resetTempCharacterSkills() { state.tempCharacter.skills = [ {name: "Acrobatics", stat: "Dexterity", modifier: 0}, {name: "Animal Handling", stat: "Wisdom", modifier: 0}, {name: "Arcana", stat: "Intelligence", modifier: 0}, {name: "Athletics", stat: "Strength", modifier: 0}, {name: "Deception", stat: "Charisma", modifier: 0}, {name: "History", stat: "Intelligence", modifier: 0}, {name: "Insight", stat: "Wisdom", modifier: 0}, {name: "Intimidation", stat: "Charisma", modifier: 0}, {name: "Investigation", stat: "Intelligence", modifier: 0}, {name: "Medicine", stat: "Wisdom", modifier: 0}, {name: "Nature", stat: "Intelligence", modifier: 0}, {name: "Perception", stat: "Wisdom", modifier: 0}, {name: "Performance", stat: "Charisma", modifier: 0}, {name: "Persuasion", stat: "Charisma", modifier: 0}, {name: "Religion", stat: "Intelligence", modifier: 0}, {name: "Sleight of Hand", stat: "Dexterity", modifier: 0}, {name: "Stealth", stat: "Dexterity", modifier: 0}, {name: "Survival", stat: "Wisdom", modifier: 0}, ] } function processCommandSynonyms(command, commandName, synonyms, func) { text = null synonyms.forEach(x => { if (commandName == x || commandName == x + "s") { text = func(command) } }) return text } function init() { if (state.tempCharacter == null) { state.tempCharacter = { name: "template", className: "adventurer", summary: "Template character not meant to be used.", inventory: [], spells: [], stats: [], spellStat: null, meleeStat: null, rangedStat: null, experience: 0, health: 10 } } if (state.characters == null) state.characters = [] if (state.notes == null) state.notes = [] if (state.autoXp == null) state.autoXp = 0 state.show = null state.prefix = null state.critical = null } function doRoll(command) { var rollType = searchArgument(command, /^(advantage)|(disadvantage)$/gi) if (rollType == null) rollType = "normal" var dice = searchArgument(command, /^.*\d.*$/gi) if (dice == null) dice = "d20" dice = formatRoll(dice) var roll = calculateRoll(dice) if (rollType == "advantage") roll = Math.max(roll, calculateRoll(dice)) if (rollType == "disadvantage") roll = Math.min(roll, calculateRoll(dice)) state.show = "none" var text = `\n[You roll a ${dice}` if (rollType != "normal") text += ` with ${rollType}` text += `. Score: ${roll}` if (roll == 20) text += " Critical Success!" else if (roll == 1) text += " Critical Failure!" text += "]\n" return text } function doCreate(command) { if (!hasCharacter(state.characterName)) createCharacter(state.characterName) var character = getCharacter() state.createStep = 0 state.tempCharacter.name = character.name resetTempCharacterSkills() state.tempCharacter.stats = [] state.tempCharacter.spells = [] state.tempCharacter.inventory = [{name: "Gold", quantity: 50}, {name: "Rope", quantity: 1}, {name: "Ration", quantity: 10}, {name: "Torch", quantity: 1}] state.spellStat = null state.tempCharacter.meleeStat = "Strength" state.tempCharacter.rangedStat = "Dexterity" state.show = "create" return " " } function doBio(command) { state.show = "bio" return " " } function doSetStat(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var arg1 = clamp(parseInt(getArgument(command, 1)), 1, 100) var possessiveName = getPossessiveName(character.name) const stat = { name: arg0, value: arg1 } var index = character.stats.findIndex((element) => element.name.toLowerCase() == stat.name.toLowerCase()) if (index == -1) { character.stats.push(stat) } else { var existingStat = character.stats[index] existingStat.value = parseInt(stat.value) } state.show = "none" return `\n[${possessiveName} ${arg0} stat is now ${arg1}.]\n` } function doSetSpellStat(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } character.spellStat = arg0 state.show = "none" return `\nSpellcasting Ability is set to ${arg0}\n` } function doSetMeleeStat(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } character.meleeStat = arg0 state.show = "none" return `\nMelee Ability is set to ${arg0}\n` } function doSetRangedStat(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } character.rangedStat = arg0 state.show = "none" return `\nRanged Ability is set to ${arg0}\n` } function doSetAutoXp(command) { var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } if (isNaN(arg0)) { state.show = "none" return "\n[Error: Expected a number. See #help]\n" } state.autoXp = Math.max(0, arg0) state.show = "none" return state.autoXp <= 0 ? `\n[Auto XP is disabled]\n` : `\n[Auto XP is set to ${state.autoXp}]\n` } function doShowAutoXp(command) { state.show = "none" return state.autoXp <= 0 ? `\n[Auto XP is disabled]\n` : `\n[Auto XP is set to ${state.autoXp}]\n` } function doSetSkill(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var arg1 = getArgument(command, 1) if (arg1 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var arg2 = getArgument(command, 2) if (arg2 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } arg2 = clamp(parseInt(arg2), 1, 100) var possessiveName = getPossessiveName(character.name) const skill = { name: arg0, stat: arg1, modifier: arg2 } var index = character.skills.findIndex((element) => element.name.toLowerCase() == skill.name.toLowerCase()) if (index == -1) { character.skills.push(skill) } else { var existingSkill = character.skills[index] existingSkill.modifier = parseInt(skill.modifier) } state.show = "none" return `\n[${possessiveName} ${arg0} skill is now ${arg1}.]\n` } function doSetExperience(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var possessiveName = getPossessiveName(character.name) character.experience = arg0 state.show = "none" return `\n[${possessiveName} experience is set to ${character.experience}]\n` } function doAddExperience(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } arg0 = parseInt(arg0) var possessiveName = getPossessiveName(character.name) var level = getLevel(character.experience) character.experience += arg0 var newLevel = getLevel(character.experience) if (newLevel > level) { state.show = "none" return `\n[${possessiveName} experience is increased to ${character.experience}. LEVEL UP! Level: ${newLevel}, Health Max: ${getHealthMax()}]\n` } state.show = "none" return `\n[${possessiveName} experience is increased to ${character.experience}.]\n` } function doLevelUp(command) { var character = getCharacter() var level = getLevel(character.experience) var experience = level >= levelSplits.length ? 0 : levelSplits[level] - character.experience return doAddExperience(`${command} ${experience}`) } function doSetClass(command) { var character = getCharacter() var arg0 = getArgumentRemainder(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var possessiveName = getPossessiveName(character.name) character.className = arg0 state.show = "none" return `\n[${possessiveName} class is set to "${character.className}".]\n` } function doSetSummary(command) { var character = getCharacter() var arg0 = getArgumentRemainder(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var possessiveName = getPossessiveName(character.name) character.summary = arg0 state.show = "none" return `\n[${possessiveName} summary is set.]\n` } function doSetHealth(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var possessiveName = getPossessiveName(character.name) character.health = arg0 character.health = clamp(character.health, 0, getHealthMax()) state.show = "none" return `\n[${possessiveName} health is set to ${character.health} health]\n` } function doHeal(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } arg0 = parseInt(arg0) var haveWord = character.name == "You" ? "have" : "has" var areWord = character.name == "You" ? "are" : "is" if (character.health >= getHealthMax()) { state.show = "none" return `\n[${character.name} ${areWord} at maximum health]\n` } character.health = character.health + arg0 character.health = clamp(character.health, 0, getHealthMax()) state.show = "none" return `\n[${character.name} ${haveWord} been healed by ${arg0} hp to ${character.health} health]\n` } function doDamage(command) { var character = getCharacter() var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var haveWord = character.name == "You" ? "have" : "has" character.health -= arg0 character.health = clamp(character.health, 0, getHealthMax()) state.show = "none" return `\n[${character.name} ${haveWord} been damaged for ${arg0} hp with ${character.health} remaining].${character.health == 0 ? " You are unconcious" : ""}\n` } function doRest(command) { var commandName = getCommandName(command) state.characters.forEach(function(character) { if (commandName.toLowerCase() == "shortrest") { var max = getHealthMax(character) character.health += Math.floor(max / 2) if (character.health > max) character.health = max } else { character.health = getHealthMax(character) } state.show = "none" }) return `\n[All characters have rested and feel rejuvinated]\n` } function doCheck(command) { const advantageNames = ["normal", "advantage", "disadvantage"] const difficultyNames = ["impossible", "extreme", "hard", "medium", "easy", "effortless"] const difficultyScores = [30, 25, 20, 15, 10, 5] var character = getCharacter() var arg0 = null if (character.stats.length > 0) arg0 = searchArgument(command, statsToOrPattern(character.stats)) if (arg0 == null && character.skills.length > 0) arg0 = searchArgument(command, statsToOrPattern(character.skills)) if (arg0 == null) arg0 = "Ability" arg0 = toTitleCase(arg0) var arg1 = searchArgument(command, arrayToOrPattern(advantageNames)) if (arg1 == null) arg1 = "normal" else arg1 = arg1.toLowerCase() const difficultyPatternNames = [...new Set(difficultyNames)] difficultyPatternNames.push("\\d+") var arg2 = searchArgument(command, arrayToOrPattern(difficultyPatternNames)) if (arg2 == null) arg2 = "easy" else arg2 = arg2.toLowerCase() var die1 = calculateRoll("1d20") var die2 = calculateRoll("1d20") var score = arg1 == "advantage" ? Math.max(die1, die2) : arg1 == "disadvantage" ? Math.min(die1, die2) : die1 var modifier = 0 var skill = character.skills.find(x => x.name.toLowerCase() == arg0.toLowerCase()) if (skill != null) { var stat = character.stats.find((element) => element.name.toLowerCase() == skill.stat.toLowerCase()) if (stat != null) modifier = skill.modifier + getModifier(stat.value) } else { var stat = character.stats.find((element) => element.name.toLowerCase() == arg0.toLowerCase()) if (stat != null) modifier = getModifier(stat.value) } var target = 15 if (/^\d+$/.test(arg2)) target = arg2 else { var targetIndex = difficultyNames.indexOf(arg2) if (targetIndex >= 0 && targetIndex < difficultyNames.length) target = difficultyScores[targetIndex] } state.show = "none" var dieText = arg1 == "advantage" || arg1 == "disadvantage" ? `${arg1}(${die1},${die2})` : die1 var text if (score == 20) text = `\n[${arg0} check: ${target}. roll: ${dieText}. Critical Success!]\n` else if (score == 1) text = `\n[${arg0} check: ${target}. roll: ${dieText}. Critical Failure!]\n` else if (modifier != 0) text = `\n[${arg0} check: ${target}. roll: ${dieText}${modifier > 0 ? "+" + modifier : modifier}=${score + modifier}. ${score + modifier >= target ? "Success!" : "Failure!"}]\n` else text = `\n[${arg0} check: ${target}. roll: ${dieText}. ${score >= target ? "Success!" : "Failure!"}]\n` return text } function doTry(command) { const advantageNames = ["normal", "advantage", "disadvantage"] const difficultyNames = ["impossible", "extreme", "hard", "medium", "easy", "effortless"] const difficultyScores = [30, 25, 20, 15, 10, 5] var character = getCharacter() var textIndex = 3 var failword = character.name == "You" ? "fail" : "fails" var arg0 = null if (character.stats.length > 0) arg0 = searchArgument(command, statsToOrPattern(character.stats)) if (arg0 == null && character.skills.length > 0) arg0 = searchArgument(command, statsToOrPattern(character.skills)) if (arg0 == null) { arg0 = "Ability" textIndex-- } arg0 = toTitleCase(arg0) var arg1 = searchArgument(command, arrayToOrPattern(advantageNames)) if (arg1 == null) { arg1 = "normal" textIndex-- } else arg1 = arg1.toLowerCase() const difficultyPatternNames = [...new Set(difficultyNames)] difficultyPatternNames.push("\\d+") var arg2 = searchArgument(command, arrayToOrPattern(difficultyPatternNames)) if (arg2 == null) { arg2 = "easy" textIndex-- } else arg2 = arg2.toLowerCase() var arg3 = getArgumentRemainder(command, textIndex) var toMatches = arg3.match(/^to\s+/gi) if (toMatches != null) arg3 = arg3.substring(toMatches[0].length) if (!/^.*(\.|!|\?)$/gi.test(arg3)) arg3 += "." var die1 = calculateRoll("1d20") var die2 = calculateRoll("1d20") var score = arg1 == "advantage" ? Math.max(die1, die2) : arg1 == "disadvantage" ? Math.min(die1, die2) : die1 var modifier = 0 var skill = character.skills.find(x => x.name.toLowerCase() == arg0.toLowerCase()) if (skill != null) { var stat = character.stats.find(x => x.name.toLowerCase() == skill.stat.toLowerCase()) if (stat != null) modifier = skill.modifier + getModifier(stat.value) } else { var stat = character.stats.find(x => x.name.toLowerCase() == arg0.toLowerCase()) if (stat != null) modifier = getModifier(stat.value) } var target = 15 if (/^\d+$/.test(arg2)) target = arg2 else { var targetIndex = difficultyNames.indexOf(arg2) if (targetIndex >= 0 && targetIndex < difficultyNames.length) target = difficultyScores[targetIndex] } var dieText = arg1 == "advantage" || arg1 == "disadvantage" ? `${arg1}(${die1},${die2})` : die1 state.show = "prefix" if (score == 20) state.prefix = `\n[${arg0} check: ${target}. roll: ${dieText}]\n` else if (score == 1) state.prefix = `\n[${arg0} check: ${target}. roll: ${dieText}]\n` else if (modifier != 0) state.prefix = `\n[${arg0} check: ${target}. roll: ${dieText}${modifier > 0 ? "+" + modifier : modifier}=${score + modifier}. ${score + modifier >= target ? "Success!" : "Failure!"}]\n` else state.prefix = `\n[${arg0} check: ${target}. roll: ${dieText}. ${score >= target ? "Success!" : "Failure!"}]\n` var text = `\n${character.name} ${score + modifier >= target ? "successfully" : failword + " to"} ${arg3}` if (score == 20) text += " Critical success! Your action was extremely effective." else if (score == 1) text += " Critical failure! There are dire consequences of your action." if (score + modifier >= target || score == 20) text += addXpToAll(Math.floor(state.autoXp * clamp(target, 1, 20) / 20)) + "\n" return text } function doAttack(command) { const advantageNames = ["normal", "advantage", "disadvantage"] const difficultyNames = ["impossible", "extreme", "hard", "medium", "easy", "effortless"] const difficultyScores = [30, 25, 20, 15, 10, 5] var character = getCharacter() var textIndex = 3 var missWord = character.name == "You" ? "miss" : "misses" var tryWord = character.name == "You" ? "try" : "tries" var statText = null statText = searchArgument(command, /ranged/gi) if (statText == null) { statText = character.meleeStat textIndex-- } else if (statText.toLowerCase() == "ranged") statText = character.rangedStat statText = toTitleCase(statText) var advantageText = searchArgument(command, arrayToOrPattern(advantageNames)) if (advantageText == null) { advantageText = "normal" textIndex-- } else advantageText = advantageText.toLowerCase() const difficultyPatternNames = [...new Set(difficultyNames)] difficultyPatternNames.push("\\d+") var difficultyText = searchArgument(command, arrayToOrPattern(difficultyPatternNames)) if (difficultyText == null) { difficultyText = "easy" textIndex-- } else difficultyText = difficultyText.toLowerCase() var targetText = getArgumentRemainder(command, textIndex) var toMatches = targetText.match(/^to\s+/gi) if (toMatches != null) targetText = targetText.substring(toMatches[0].length) if (!/^.*(\.|!|\?)$/gi.test(targetText)) targetText += "." var die1 = calculateRoll("1d20") var die2 = calculateRoll("1d20") var score = advantageText == "advantage" ? Math.max(die1, die2) : advantageText == "disadvantage" ? Math.min(die1, die2) : die1 var modifier = 0 var stat = character.stats.find(x => x.name.toLowerCase() == statText.toLowerCase()) if (stat != null) modifier = getModifier(stat.value) var targetRoll = 15 if (/^\d+$/.test(difficultyText)) targetRoll = difficultyText else { var targetIndex = difficultyNames.indexOf(difficultyText) if (targetIndex >= 0 && targetIndex < difficultyNames.length) targetRoll = difficultyScores[targetIndex] } var dieText = advantageText == "advantage" || advantageText == "disadvantage" ? `${advantageText}(${die1},${die2})` : die1 state.show = "prefix" 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` var text if (score + modifier >= targetRoll) text = `\n${character.name} successfully hit the ${targetText}!` else text = `\n${character.name} ${tryWord} to hit the ${targetText}. ${character.name} ${missWord}!` if (score == 20) text += " Critical success! Your attack is exceptionally damaging!" else if (score == 1) text += " Critical failure! Your attack missed in a spectacular way!" log(`targetRoll:${targetRoll}`) log(`autoXp:${state.autoXp}`) if (score + modifier >= targetRoll || score == 20) text += addXpToAll(Math.floor(state.autoXp * clamp(targetRoll, 1, 20) / 20)) return text + "\n" } function doNote(command) { var arg0 = getArgumentRemainder(command, 0) if (arg0 != null && arg0.length > 0) { state.notes.push(arg0) state.show = "none" return "\n[Note added successfully]\n" } else return doShowNotes(command) } function doShowNotes(command) { state.show = "showNotes" return " " } function doTake(command) { var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } const item = { quantity: isNaN(arg0) ? 1 : arg0, name: getArgumentRemainder(command, isNaN(arg0) ? 0 : 1).replace(/^((the)|(a)|(an))\s/, "").plural(true) } var commandName = getCommandName(command) var character = getCharacter() var haveWord = character.name == "You" ? "have" : "has" var displayItemName = item.name.plural(item.quantity == 1) if (item.quantity < 0) item.quantity = 1 var text = "\n" if (item.quantity == 1) text += `${character.name} ${commandName} the ${displayItemName}.\n` else text += `${character.name} ${commandName} ${item.quantity} ${displayItemName}.\n` var index = character.inventory.findIndex((element) => element.name.toLowerCase() == item.name.toLowerCase()) if (index == -1) { character.inventory.push(item) } else { var existingItem = character.inventory[index] existingItem.quantity = parseInt(existingItem.quantity) + parseInt(item.quantity) displayItemName = existingItem.name.plural(existingItem.quantity == 1) text += `${character.name} now ${haveWord} ${existingItem.quantity} ${displayItemName}.\n` } return text } function doDrop(command) { var character = getCharacter() var commandName = getCommandName(command) var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var characterNameAdjustedCase = character.name == "You" ? "you" : character.name var dontWord = character.name == "You" ? "don't" : "doesn't" var haveWord = character.name == "You" ? "have" : "has" var tryWord = character.name == "You" ? "try" : "tries" const item = { quantity: !isNaN(arg0) ? arg0 : allSynonyms.indexOf(arg0) > - 1 ? Number.MAX_SAFE_INTEGER : 1, name: getArgumentRemainder(command, isNaN(arg0) ? 0 : 1).replace(/^((the)|(a)|(an))\s/, "").plural(true) } var displayItemName = item.name.plural(item.quantity == 1) if (item.quantity < 0) item.quantity = 1 var text = "\n" var index = character.inventory.findIndex((element) => element.name.toLowerCase() == item.name.toLowerCase()) if (index == -1) { if (item.quantity == 1) text += `${character.name} ${tryWord} to ${commandName} the ${displayItemName}, but ${character.name} ${dontWord} have any.` else text += `${character.name} ${tryWord} to ${commandName} ${item.quantity == Number.MAX_SAFE_INTEGER ? arg0 : item.quantity} ${displayItemName}, but ${characterNameAdjustedCase} ${dontWord} have any.` } else { var existingItem = character.inventory[index] if (item.quantity == 1) text = `\n${character.name} ${commandName} the ${displayItemName}.\n` else if (item.quantity >= existingItem.quantity) text = `${character.name} ${commandName} all of the ${displayItemName}.` else text = `\n${character.name} ${commandName} ${item.quantity} ${displayItemName}.\n` existingItem.quantity -= item.quantity if (existingItem.quantity <= 0) { existingItem.quantity = 0 character.inventory.splice(index, 1) } if (existingItem.quantity > 0) { displayItemName = existingItem.name.plural(existingItem.quantity == 1) text += `${character.name} now ${haveWord} ${existingItem.quantity} ${displayItemName}.\n` } } return text } function doGive(command) { var character = getCharacter() var commandName = getCommandName(command) var arg0 = getArgument(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var arg1 = getArgument(command, 1) if (arg1 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var foundAll = allSynonyms.indexOf(arg1) > -1 const item = { quantity: !isNaN(arg1) ? arg1 : foundAll ? Number.MAX_SAFE_INTEGER : 1, name: getArgumentRemainder(command, isNaN(arg1) && !foundAll ? 1 : 2).replace(/^((the)|(a)|(an)|(of the))\s/, "").plural(true) } var otherCharacter = getCharacter(arg0) if (otherCharacter == null || otherCharacter.name == "You" && arg0.toLowerCase() != "you") { state.show = "none" return "\n[Error: Target character does not exist. See #characters]\n" } var characterNameAdjustedCase = character.name == "You" ? "you" : character.name var dontWord = character.name == "You" ? "don't" : "doesn't" var haveWord = character.name == "You" ? "have" : "has" var tryWord = character.name == "You" ? "try" : "tries" var otherHaveWord = otherCharacter.name == "You" ? "have" : "has" var displayItemName = item.name.plural(item.quantity == 1) var characterQuantityText = "" if (item.quantity < 0) item.quantity = 1 var text = "\n\n" var index = character.inventory.findIndex((element) => element.name.toLowerCase() == item.name.toLowerCase()) if (index == -1) { if (item.quantity == 1) text += `${character.name} ${tryWord} to ${commandName.plural(true)} the ${displayItemName}, but ${characterNameAdjustedCase} ${dontWord} have any.` else text += `${character.name} ${tryWord} to ${commandName.plural(true)} ${item.quantity == Number.MAX_SAFE_INTEGER ? arg0 : item.quantity} ${displayItemName}, but ${characterNameAdjustedCase} ${dontWord} have any.` return text + "\n\n" } else { var existingItem = character.inventory[index] if (item.quantity >= existingItem.quantity) { item.quantity = existingItem.quantity existingItem.quantity = 0 character.inventory.splice(index, 1) } else { existingItem.quantity -= item.quantity } if (existingItem.quantity > 0) { characterQuantityText = ` ${character.name} now ${haveWord} ${existingItem.quantity} ${existingItem.name.plural(existingItem.quantity == 1)}.` } else { characterQuantityText = ` ${character.name} ${dontWord} have any more.` } } if (item.quantity == 1) text += `${character.name} ${commandName.plural(character.name == "You")} ${otherCharacter.name} the ${displayItemName}.` else text += `${character.name} ${commandName.plural(character.name == "You")} ${otherCharacter.name} ${item.quantity} ${displayItemName}.` var otherIndex = otherCharacter.inventory.findIndex((element) => element.name.toLowerCase() == item.name.toLowerCase()) if (otherIndex == -1) { otherCharacter.inventory.push(item) } else { var existingItem = otherCharacter.inventory[otherIndex] existingItem.quantity = parseInt(existingItem.quantity) + parseInt(item.quantity) displayItemName = existingItem.name.plural(existingItem.quantity == 1) text += ` ${otherCharacter.name} now ${otherHaveWord} ${existingItem.quantity} ${displayItemName}.` } return text + characterQuantityText + "\n\n" } function doBuy(command) { var character = getCharacter() command = command.replaceAll(/\s+((for)|(with)|(the)|(a)|(an))\s+/g, " ") var args = [] args.push(getArgument(command, 0)) if (args[0] == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } args.push(getArgument(command, 1)) if (args[1] == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } args.push(getArgument(command, 2)) args.push(getArgument(command, 3)) var buyQuantity if (isNaN(args[0])) { buyQuantity = 1 } else { buyQuantity = args[0] args.splice(0, 1) } var buyName buyName = args[0] var sellQuantity if (isNaN(args[1])) { sellQuantity = 1 } else { sellQuantity = args[1] args.splice(1, 1) } var sellName = args[1] var characterNameAdjustedCase = character.name == "You" ? "you" : character.name var dontWord = character.name == "You" ? "don't" : "doesn't" var haveWord = character.name == "You" ? "have" : "has" var tryWord = character.name == "You" ? "try" : "tries" var tradeWord = character.name == "You" ? "trade" : "trades" var buyWord = character.name == "You" ? "buy" : "buys" var displayItemName = sellName.plural(sellQuantity == 1) var buyItemTotal = 0; var sellItemTotal = 0; if (sellQuantity < 0) sellQuantity = 1 var text = "\n\n" var index = character.inventory.findIndex((element) => element.name.toLowerCase() == sellName.toLowerCase()) if (index == -1) { if (sellQuantity == 1) text += `${character.name} ${tryWord} to trade the ${displayItemName}, but ${characterNameAdjustedCase} ${dontWord} have any.` else text += `${character.name} ${tryWord} to trade ${sellQuantity} ${displayItemName}, but ${characterNameAdjustedCase} ${dontWord} have any.` return text + "\n\n" } else { var existingItem = character.inventory[index] if (sellQuantity >= existingItem.quantity) { sellQuantity = existingItem.quantity existingItem.quantity = 0 character.inventory.splice(index, 1) } else { existingItem.quantity -= sellQuantity } sellItemTotal = existingItem.quantity } var suffix = `${buyQuantity} ${buyName.plural()}` if (buyQuantity == 1) suffix = `the ${buyName.plural(true)}` if (sellQuantity == 1) text += `${character.name} ${tradeWord} the ${displayItemName} for ${suffix}.` else text += `${character.name} ${tradeWord} ${sellQuantity} ${displayItemName} for ${suffix}.` index = character.inventory.findIndex((element) => element.name.toLowerCase() == buyName.toLowerCase()) if (index == -1) { character.inventory.push({name: buyName, quantity: buyQuantity}) buyItemTotal = buyQuantity } else { var existingItem = character.inventory[index] existingItem.quantity = parseInt(existingItem.quantity) + parseInt(buyQuantity) buyItemTotal = existingItem.quantity } text += ` ${character.name} now ${haveWord} ${sellItemTotal} ${sellName.plural(sellItemTotal == 1)} and ${buyItemTotal} ${buyName.plural(buyItemTotal == 1)}.` return text + "\n\n" } function doSell(command) { command = command.replace(/\s+((for)|(with))\s+/, " ") var args = [] args.push(getArgument(command, 0)) if (args[0] == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } args.push(getArgument(command, 1)) if (args[1] == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } args.push(getArgument(command, 2)) args.push(getArgument(command, 3)) var sellQuantity if (isNaN(args[0])) { sellQuantity = 1 } else { sellQuantity = args[0] args.splice(0, 1) } var sellName sellName = args[0] var buyQuantity if (isNaN(args[1])) { buyQuantity = 1 } else { buyQuantity = args[1] args.splice(1, 1) } var buyName = args[1] return doBuy(`buy ${buyQuantity} ${buyName} ${sellQuantity} ${sellName}`) } function doInventory(command) { state.show = "inventory" return " " } function doLearnSpell(command) { var arg0 = getArgumentRemainder(command, 0) if (arg0 == "") { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var character = getCharacter() var tryWord = character.name == "You" ? "try" : "tries" var found = character.spells.find((element) => element == arg0) if (found != null) return `\n[${character.name} ${tryWord} to learn the spell ${arg0}, but already knows it.]\n` character.spells.push(arg0) addStoryCard(arg0, "", "spell") return `\n${character.name} learned the spell ${arg0}.\n` } function doForgetSpell(command) { var character = getCharacter() var arg0 = getArgumentRemainder(command, 0) if (arg0 == "") { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var dontWord = character.name == "You" ? "don't" : "doesn't" var tryWord = character.name == "You" ? "try" : "tries" var found = character.spells.find((element) => element == arg0) if (found == null) return `\n[${character.name} ${tryWord} to forget the spell ${arg0}, but ${character.name} ${dontWord} even know it]\n` var index = character.spells.findIndex((element) => element.toLowerCase() == arg0.toLowerCase()) character.spells.splice(index, 1) return `\n[${character.name} forgot the spell ${arg0}]\n` } function doRemoveStat(command) { var character = getCharacter() var arg0 = getArgumentRemainder(command, 0) if (arg0 == "") { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var dontWord = character.name == "You" ? "don't" : "doesn't" var tryWord = character.name == "You" ? "try" : "tries" var found = character.stats.find((element) => element == arg0) if (found == null) return `\n[${character.name} ${tryWord} to remove the ability ${arg0}, but ${character.name} ${dontWord} even know it]\n` var index = character.stats.findIndex((element) => element.toLowerCase() == arg0.toLowerCase()) character.stats.splice(index, 1) return `\n[${character.name} removed the ability ${arg0}]\n` } function doRemoveSkill(command) { var character = getCharacter() var arg0 = getArgumentRemainder(command, 0) if (arg0 == "") { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var dontWord = character.name == "You" ? "don't" : "doesn't" var tryWord = character.name == "You" ? "try" : "tries" var found = character.skills.find((element) => element == arg0) if (found == null) return `\n[${character.name} ${tryWord} to remove the skill ${arg0}, but ${character.name} ${dontWord} even know it]\n` var index = character.skills.findIndex((element) => element.toLowerCase() == arg0.toLowerCase()) character.skills.splice(index, 1) return `\n[${character.name} removed the skill ${arg0}]\n` } function doCastSpell(command) { const advantageNames = ["normal", "advantage", "disadvantage"] const difficultyNames = ["impossible", "extreme", "hard", "medium", "easy", "effortless"] const difficultyScores = [30, 25, 20, 15, 10, 5] var character = getCharacter() const dontWord = character.name == "You" ? "don't" : "doesn't" const tryWord = character.name == "You" ? "try" : "tries" var spellIndex = 2; var advantage = searchArgument(command, arrayToOrPattern(advantageNames)) if (advantage == null) { advantage = "normal" spellIndex-- } const difficultyPatternNames = [...new Set(difficultyNames)] difficultyPatternNames.push("\\d+") var difficulty = searchArgument(command, arrayToOrPattern(difficultyPatternNames)) if (difficulty == null) { difficulty = "easy" spellIndex-- } var difficultyIndex = difficultyNames.indexOf(difficulty) if (difficultyIndex >= 0 && difficultyIndex < difficultyNames.length) { difficulty = difficultyScores[difficultyIndex] } var spell = getArgument(command, spellIndex) if (spell == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } var target = null var atWord = null var found = character.spells.find(x => x.toLowerCase() == spell.toLowerCase()) if (found != null) { target = getArgumentRemainder(command, spellIndex + 1) if (target != null) { target = target.trim() if (!/^((at)|(on))\s+.*/.test(target)) target = "at " + target } } else { var remainder = getArgumentRemainder(command, spellIndex) spell = remainder.replace(/\s+((at)|(on)).*/i, "").trim() found = character.spells.find(x => x.toLowerCase() == spell.toLowerCase()) target = remainder.replace(/^.*\s+(?=(at)|(on))/i, "").trim() } if (found == null) { state.show = "none" return `\n[${character.name} ${tryWord} to cast the spell ${spellName}, but ${character.name == "You" ? "you" : character.name} ${dontWord} know it.]\n` } var text = `${character.name} cast the spell ${spell}${advantage != "normal" ? " with " + advantage : ""}${target == null ? "" : " " + target}.` var modifier = 0 if (character.spellStat != null) { var stat = character.stats.find((element) => element.name.toLowerCase() == character.spellStat.toLowerCase()) if (stat != null) modifier = getModifier(stat.value) } var roll1 = calculateRoll("d20") var roll2 = calculateRoll("d20") var roll = advantage == "advantage" ? Math.max(roll1, roll2) : advantage == "disadvantage" ? Math.min(roll1, roll2) : roll1 state.show = "prefix" var dieText = advantage == "advantage" || advantage == "disadvantage" ? `${advantage}(${roll1},${roll2})` : roll1 if (roll == 20) state.prefix = `\n[Difficulty: ${difficulty}. Roll: ${dieText}. Critcal Success!]\n` else if (roll == 1) state.prefix = `\n[Difficulty: ${difficulty}. Roll: ${dieText}. Critcal Failure!]\n` else if (modifier != 0) state.prefix = `\n[Difficulty: ${difficulty}. Roll: ${dieText}${modifier > 0 ? "+" + modifier : modifier}=${roll + modifier}. ${roll + modifier >= difficulty ? "Success!" : "Failure!"}]\n` else state.prefix = `\n[Difficulty: ${difficulty}. Roll: ${dieText}. ${roll + modifier >= difficulty ? "Success!" : "Failure!"}]\n` if (roll == 20) text += ` Critical success!` else if (roll == 1) ` Critical failure! The spell ${target != null ? "misses" : "fails"} in a spectacular way.` else if (roll + modifier >= difficulty) text += ` The spell ${target != null ? "hits the target" : "is successful"}!` else text += ` The spell ${target != null ? "misses" : "fails"}!` if (roll + modifier >= difficulty || roll == 20) text += addXpToAll(Math.floor(state.autoXp * clamp(difficulty, 1, 20) / 20)) return `\n${text}\n` } function doShowCharacters(command) { state.show = "characters" return " " } function doSpellbook(command) { state.show = "spellbook" return " " } function doShowSkills(command) { state.show = "skills" return " " } function doShowStats(command) { state.show = "stats" return " " } function doClearNotes(command) { state.notes = [] state.show = "clearNotes" return " " } function doClearInventory(command) { var character = getCharacter() character.inventory = [] state.show = "clearInventory" return " " } function doEraseNote(command) { var arg0 = getArgument(command, 0) if (arg0 == null) arg0 = 1 arg0 = parseInt(arg0) - 1 if (arg0 >= state.notes.length) { state.show = "none" return "\n[Error: Note does not exist. Call #showNotes.]\n" } state.notes.splice(arg0, 1) state.show = "none" return `[Note #${arg0 + 1} removed]` } function doRemoveCharacter(command) { var arg0 = getArgumentRemainder(command, 0) if (arg0 == null) { state.show = "none" return "\n[Error: Not enough parameters. See #help]\n" } for (var i = 0; i < state.characters.length; i++) { var character = state.characters[i] if (character.name.toLowerCase() == arg0.toLowerCase()) { state.characters.splice(i, 1) state.show = "none" return `[Character ${character.name} removed.]` } } return `[Character ${arg0} was not found.]` } function doClearSpells(command) { var character = getCharacter() character.spells = [] state.show = "clearInventory" return " " } function doClearStats(command) { var character = getCharacter() character.stats = [] state.show = "clearStats" return " " } function doClearSkills(command) { var character = getCharacter() character.skills = [] state.show = "clearSkills" return " " } function doReset(command) { state.notes = [] state.characters = [] state.show = "reset" return " " } function doHelp(command) { state.show = "help" return " " } modifier(text)