diff --git a/Input.js b/Input.js index aec3288..9a0d166 100644 --- a/Input.js +++ b/Input.js @@ -52,6 +52,12 @@ const showAutoXpSynonyms = ["showautoxp"] const setDefaultDifficultySynonyms = ["setdefaultdifficulty", "defaultdifficulty", "setdefaultdc", "defaultdc", "setdefaultac", "defaultac", "setdifficulty", "difficulty", "dc"] const showDefaultDifficultySynonyms = ["showdefaultdifficulty", "showdefaultdc", "showdefaultac"] const generateNameSynonyms = ["generatename", "name", "randomname", "makename", "createname"] +const createLocationSynonyms = ["createlocation", "makelocation", "generatelocation", "createplace", "makeplace", "generateplace", "createtown", "maketown", "generatetown", "createvillage", "makevillage", "generatevillage", "createcity", "makecity", "generatecity", "updatelocation", "updateplace", "updatetown", "updatevillage", "updatecity"] +const goToLocationSynonyms = ["gotolocation", "golocation", "movetolocation", "traveltolocation", "travellocation", "gotoplace", "goplace", "movetoplace", "traveltoplace", "travelplace", "gototown", "gotown", "movetotown", "traveltotown", "traveltown", "gotovillage", "govillage", "movetovillage", "traveltovillage", "travelvillage", "gotocity", "gocity", "movetocity", "traveltocity", "travelcity", "goto", "go", "moveto", "move", "travelto", "travel"] +const removeLocationSynonyms = ["removelocation", "deletelocation", "eraselocation", "removeplace", "deleteplace", "eraseplace", "removetown", "deletetown", "erasetown", "removevillage", "deletevillage", "erasevillage", "removecity", "deletecity", "erasecity"] +const showLocationsSynonyms = ["showlocations", "showplaces", "showtowns", "showvillages", "showcities", "locations", "places", "towns", "villages", "cities"] +const getLocationSynonyms = ["getlocation", "location", "getcoordinates", "coordinates", "getcoords", "coords", "showlocation"] +const clearLocationsSynonyms = ["clearlocations", "eraselocations", "deletelocations", "resetlocations"] const helpSynonyms = ["help"] const modifier = (text) => { @@ -84,7 +90,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, 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, resetSynonyms), function () {return true}) if (found == null) { if (state.characterName == null) { @@ -150,6 +156,12 @@ const modifier = (text) => { if (text == null) text = processCommandSynonyms(command, commandName, setDefaultDifficultySynonyms, doSetDefaultDifficulty) if (text == null) text = processCommandSynonyms(command, commandName, showDefaultDifficultySynonyms, doShowDefaultDifficulty) if (text == null) text = processCommandSynonyms(command, commandName, generateNameSynonyms, doGenerateName) + if (text == null) text = processCommandSynonyms(command, commandName, createLocationSynonyms, doCreateLocation) + if (text == null) text = processCommandSynonyms(command, commandName, goToLocationSynonyms, doGoToLocation) + if (text == null) text = processCommandSynonyms(command, commandName, clearLocationsSynonyms, doClearLocations) + if (text == null) text = processCommandSynonyms(command, commandName, removeLocationSynonyms, doRemoveLocation) + if (text == null) text = processCommandSynonyms(command, commandName, showLocationsSynonyms, doShowLocations) + if (text == null) text = processCommandSynonyms(command, commandName, getLocationSynonyms, doGetLocation) if (text == null) text = processCommandSynonyms(command, commandName, renameCharacterSynonyms, doRenameCharacter) if (text == null) text = processCommandSynonyms(command, commandName, cloneCharacterSynonyms, doCloneCharacter) if (text == null) text = processCommandSynonyms(command, commandName, helpSynonyms, doHelp) @@ -518,6 +530,9 @@ function init() { } if (state.characters == null) state.characters = [] if (state.notes == null) state.notes = [] + if (state.locations == null) state.locations = [] + if (state.x == null) state.x = 0 + if (state.y == null) state.y = 0 if (state.autoXp == null) state.autoXp = 0 if (state.defaultDifficulty == null) state.defaultDifficulty = 10 state.show = null @@ -1163,6 +1178,134 @@ function doShowNotes(command) { return " " } +function doCreateLocation(command) { + var locationArgIndex = 2 + + var arg0 = getArgument(command, 0) + var arg1 = getArgument(command, 1) + if (arg0 == null || isNaN(arg0)) { + arg0 = null + arg1 = null + locationArgIndex = 0 + } + + if (arg0 != null && (arg1 == null || isNaN(arg1))) { + arg1 = null + locationArgIndex = 1 + } + + var arg2 = getArgumentRemainder(command, locationArgIndex) + if (arg2 == null) { + state.show = "none" + return "\n[Error: Not enough parameters. See #help]\n" + } + + var location = createLocation(arg0, arg1, arg2) + + state.show = "none" + return `\n[Location ${toTitleCase(arg2)} has been created at (${location.x},${location.y})]\n` +} + +function doGoToLocation(command) { + var character = getCharacter() + var characterName = character == null ? "You" : character.name + var possessiveName = getPossessiveName(characterName) + var travelWord = characterName == "You" ? "travel" : "travels" + var locationArgIndex = 2 + + var arg0 = getArgument(command, 0) + var arg1 = getArgument(command, 1) + + if (arg0 == null || isNaN(arg0)) { + arg0 = state.x + arg1 = state.y + locationArgIndex = 0 + } + + if (arg0 != null && (arg1 == null || isNaN(arg1))) { + arg1 = state.y + locationArgIndex = 1 + } + + var distance = 0 + var location + var locationName = getArgumentRemainder(command, locationArgIndex) + if (locationName == null && locationArgIndex == 0) { + state.show = none + return "\n[Error: Not enough parameters. See #help]\n" + } + + if (locationName == null) { + var index = state.locations.findIndex(x => x.x == arg0 && x.y == arg1) + if (index != -1) location = state.locations[index] + } else { + var index = state.locations.findIndex(x => x.name.toLowerCase() == locationName.toLowerCase()) + if (index != -1) location = state.locations[index] + else location = createLocation(arg0, arg1, locationName) + } + + if (location == null) { + distance = pointDistance(state.x, state.y, arg0, arg1) + state.x = arg0 + state.y = arg1 + } else { + distance = pointDistance(state.x, state.y, location.x, location.y) + state.x = location.x + state.y = location.y + state.location = location.name + } + distance = distance.toFixed(1) + + state.show = "none" + if (location == null) return `\n${characterName} ${travelWord} ${distance > 0 ? distance + " units " : ""} to (${arg0},${arg1})` + if (state.characters.length > 1) return `\n${possessiveName} party travels ${distance > 0 ? distance + " units " : ""}to ${toTitleCase(location.name)} at (${location.x},${location.y})\n` + return `\n${characterName} ${travelWord} ${distance > 0 ? distance + " units " : ""}to ${toTitleCase(location.name)} at (${location.x},${location.y})\n` +} + +function doGetLocation(command) { + state.show = "none" + return `\n[You are at ${state.location == null ? "" : "the location " + state.location + " "}(${state.x},${state.y})]` +} + +function doClearLocations(command) { + state.locations = [] + state.location = null + + state.show = "none" + return "\n[The locations have been cleared]\n" +} + +function doRemoveLocation(command) { + var arg0 = getArgumentRemainder(command, 0) + if (arg0 == null) { + state.show = "none" + return "\n[Error: Not enough parameters. See #help]\n" + } + + var location + if (isNaN(arg0)) arg0 = state.locations.findIndex(x => x.name.toLowerCase() == arg0.toLowerCase()) + else arg0-- + + if (arg0 == -1) { + state.show = "none" + return "\n[Error: Location not found. See #showlocations]\n" + } else if (arg0 >= state.locations.length || arg0 < 0) { + state.show = "none" + return "\n[Error: Location number out of bounds. See #showlocations]\n" + } else { + location = state.locations[arg0] + state.locations.splice(arg0, 1) + } + + state.show = "none" + return `\n[The location ${toTitleCase(location.name)} has been removed]\n` +} + +function doShowLocations(command) { + state.show = "locations" + return " " +} + function doTake(command) { var arg0 = getArgument(command, 0) if (arg0 == null) { @@ -1794,6 +1937,10 @@ function doClearSkills(command) { function doReset(command) { state.notes = [] state.characters = [] + state.locations = [] + state.location = null + state.x = 0 + state.y = 0 state.defaultDifficulty = null state.autoXp = null diff --git a/Library.js b/Library.js index da8e655..fe13303 100644 --- a/Library.js +++ b/Library.js @@ -17,6 +17,52 @@ function shuffle(array, seed) { } } +function pointDistance(x1, y1, x2, y2) { + return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) +} + +function rotate(cx, cy, x, y, angle) { + var radians = (Math.PI / 180) * angle + var cos = Math.cos(radians) + var sin = Math.sin(radians) + var nx = (cos * (x - cx)) + (sin * (y - cy)) + cx + var ny = (cos * (y - cy)) - (sin * (x - cx)) + cy + return [nx, ny]; +} + +function createLocation(x, y, name) { + if (x == null || y == null) { + var cx = x == null ? state.x : x + var cy = y == null ? state.y : y + var coords = rotate(cx, cy, getRandomInteger(-10, 10) + cx, cy, Math.random() * 360) + x = coords[0] + y = coords[1] + } + + x = Math.round(x) + y = Math.round(y) + + var existingLocationIndex = state.locations.findIndex(element => element.name.toLowerCase() == name.toLowerCase()) + var location + if (existingLocationIndex == -1) { + location = { + x: x, + y: y, + name: name + } + state.locations.push(location) + } else { + location = state.locations[existingLocationIndex] + location.x = x + location.y = y + location.name = name + } + + addStoryCard(location.name, "", "location") + + return location +} + function sanitizeText(text) { if (/^\s*>.*says? ".*/.test(text)) { text = text.replace(/^\s*>\s/, "") @@ -175,7 +221,7 @@ function calculateRoll(rolltext) { function getCharacter(characterName) { if (characterName == null) characterName = state.characterName if (characterName == null) return null - return state.characters.find((element) => element.name.toLowerCase() == characterName.toLowerCase()) + return state.characters.find(element => element.name.toLowerCase() == characterName.toLowerCase()) } function hasCharacter(characterName) { diff --git a/Output.js b/Output.js index f84047f..70d4a4d 100644 --- a/Output.js +++ b/Output.js @@ -173,6 +173,20 @@ const modifier = (text) => { } text += "******************\n\n" break + case "locations": + text += `Player location: ${state.location == null ? "" : state.location + " "}(${state.x},${state.y})\n` + text += `*** LOCATIONS ***\n` + if (state.locations.length > 0) { + var index = 0 + state.locations.forEach(function(location) { + var distance = pointDistance(state.x, state.y, location.x, location.y).toFixed(1) + text += `${++index}. ${toTitleCase(location.name)} (${location.x},${location.y}) Distance: ${distance}\n` + }) + } else { + text += `No locations have been discovered!` + } + text += "******************\n\n" + break case "none": text += " " break @@ -305,6 +319,16 @@ const modifier = (text) => { text += "\n#spellbook" text += "\n Shows the list of spells that the character has learned." + text += "\n\n--Locations--" + text += "\n#createlocation (x) (y) location_name" + text += "\n Creates a location at the given coordinates. If the coordinates are not provided, they are randomized within a range of 10 units from the player's current location. Multiple locations may exist at the same coordinates. A story card is created for the location. Quotes are not necessary." + text += "\n#goto (x) (y) or (location_name)" + text += "\n Makes the characters travel to the location specified by the coordinates or location_name. You must provide at least one or the other. If the location does not exist, it is created at your current coordinates. If you only specify coordinates, you will go to the first location at those coordinates. Quotes are not necessary." + text += "\n#getlocation" + text += "\n Returns the coordinates that the player's party is at. It will also list a location if a location was specified when using #goto." + text += "\n#removelocation location_name or location_number" + text += "\n Removes the specified location or location number as listed in #showlocations. Quotes are not necessary." + text += "\n\n--Danger Zone--" text += "\n#reset" text += "\n Removes all characters and changes all settings to their defaults. Use with caution!" diff --git a/README.md b/README.md index 554a116..097e7eb 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Personalized note system that does not take up context space
See the [user guide here](https://github.com/raeleus/Hashtag-DnD/wiki). v. 0.1.0 +* Added `#createlocation`, `#goto`, `#removelocation`, `#clearlocations`, `#getlocation` and `#showlocations` to enable travelling. * Added `#renameitem` to rename an existing item * Added `#renamecharacter` to rename an existing character * Added `#clonecharacter` to copy an existing character