Axon allows you to customize specific editors (panels or views) to safely and reliably edit your world files within the game inside of a Ranvier
build MUD. Some customization will be needed to match the properties and the data types of the values of your properties that you want to edit. Axon comes with several reusable editors that make the most common editing really easy to implement.
Some Reusable editors:
- Input-text: Use for simple string sentences, setting values, or entityReferences
- Toggleable: Create a list of selections and toggle as many/few as you would like
- Select-one: Select a single value from a list of created values to assign to a property
- To Download Ranvier, see it's github.
- To see Ranvier's 'Getting Started' see here.
- Questions about Axon or Ranvier, join us on slack: https://ranviermud.slack.com/
Axon is designed to support the majority of the is to support the default entity properties that Ranvier
supports out of the box. It will be assumed that the developer will need to expand upon this module as it would be impossible to make a flexible module while making it specific enough for YOUR specific needs.
In addition to supplying the developer with some quick tools to change the default Ranvier
project, the project was designed to easily allow expansion of the editors. Adding additional properties to definitions and reusing template editors allows you to quickly template out new menus and adapt it to your own Mud.
Out of the box, Axon supports:
- Npc
- Item
- Quest
- Room
- Area
From the Ranvier
root directory:
npm run install-bundle https://github.com/nelsonsbrian/axon-olc.git
Open ranvier.json
and add axon-olc
to bundles
Navigate to axon-olc
directory at /bundles/axon-olc
and type npm install
.
Additional Resources: https://ranviermud.com/bundles/
Inside of the input-events/commands/
add the following code for OLC operation.
function loop() {
// OLC command bypass
if (player.otherInput) {
player.otherInput = false;
return;
} // end bypass
player.socket.emit("commands", player);
}
You are now all set up to use Axon-OLC
.
- Some opinionated decisions were made in the following areas, but the promise of
Axon
is to enable the user to easily add to or change any of the default code:- Colorization and formatting of the menus.
- Input validation - Most of the validation uses
@hapi/Joi
- Some commands are included, but aren't necessary for OLC functions (mlist, olist, etc). By default they aren't accessible to newly created players.
- A lot of string formating uses
sprintf
library.
- In effort to be modular, especially with the quests-bundle, a
QuestUtil
was made to help in provide the OLC components/options. If you were to want to expand on the quests-bundle, the recommendation would be to adddisplay
andconfigOptions
static methods to allQuestGoal
andQuestReward
. The stock quests-bundle comes withdisplay
static methods in itsQuestReward
class, seen here while theQuestGoal
class does not. - The bundle does not currently leverage the entityLoader feature of
Ranvier
. - Reach out on Slack with specific issues. @Spade
olc
- Shows a list of areas that have changes made it it, but not saved to disk. Useredit save
.
rlist [areaName]
- lists all the rooms in the specified area. No-arg defaults to current area of player.redit [roomEntityReference]
- Opens the editor for the room. No-arg and . defaults to current room. -roomEntityReference
has partial lookup. For example, a room with entityReference ofmagicForest:1
can be accessed by typingredit mag:1
as long as there are no other area's with that name higher in the alphabetical order. Note: selectingyes
to saving when exiting the editor will save the definition in memory, but not to the disk.redit save <areaName | .
- Saves the edited definitions of the specific area to disk.'.'
argument will select the area the player is currently in.
olist [areaName]
- lists all the items in the specified area. No-arg defaults to current area of player.oedit <itemEntityReference>
- Opens the editor for the specified item. If an an area is found, but not the id, a new item will be created for you to edit. The item will only be added to theItemFactory
if you choose to save the definition when exiting the editor. Note: selectingyes
to saving when exiting the editor will save the definition in memory, but not to the disk.oedit save <areaName | .
- Saves the edited definitions of the specific area to disk.'.'
argument will select the area the player is currently in.
mlist [areaName]
- lists all the npcs in the specified area. No-arg defaults to current area of player.medit <npcEntityReference>
- Opens the editor for the specified npc. If an an area is found, but not the id, a new npc will be created for you to edit. The npc will only be added to theMobFactory
if you choose to save the definition when exiting the editor. Note: selectingyes
to saving when exiting the editor will save the definition in memory, but not to the disk.medit save <areaName | .
- Saves the edited definitions of the specific area to disk.'.'
argument will select the area the player is currently in.
zlist
- lists all the areas in the game.zedit <areaId>
- Opens the editor for the specified area. Currently, you cannot create a new area using this OLC. StockAxon
does not come with any properties inArea
to edit, but the menu is created for you to add in content. Note: selectingyes
to saving when exiting the editor will save the definition in memory, but not to the disk.zedit save <areaName | .
- Saves the edited definitions of the specific area to disk.'.'
argument will select the area the player is currently in.
qlist [areaName]
- lists all the quests in the specified area. No-arg defaults to current area of player.qedit <questEntityReference>
- Opens the editor for the specified quest. If an an area is found, but not the id, a new quest will be created for you to edit. The quest will only be added to theQuestFactory
if you choose to save the definition when exiting the editor. Note: selectingyes
to saving when exiting the editor will save the definition in memory, but not to the disk.qedit save <areaName | .
- Saves the edited definitions of the specific area to disk.'.'
argument will select the area the player is currently in. *Note: TheQuestFactory
is not derived from theEntityFactory
and thusly uses a different scheme than the other entities. For example, a quest 'definition' is actually{ id: 300', areaName: 'Limbo', config: {...} }
.
The OLC is only available when the user is logged in and has been assigned to the role of Builder
or higher. When the player has the required role, a set of commands will be available for them to use.
The basic structure is a top-down, tree model. Where you have a root editor (room-editor
) and you have menues and submenus that nest inside of the root. Some editors are resuable between editing different sorts of entities (input-text
and select-one
) while some editors are tailored for the entity or definition being editing (room-exits
).
Similar to how the login menus work when you first log into the unmodified, base Ranvier
, Axon
utilizes input-events. During the normal course of the game, the player stays within the commands loop by emitting a commands
events on the player socket, player.socket.emit('commands, player)
.
For the OLC, we'll exit that loop and return to a more specific set of event loops. We'll emit which editor file (input-event) we want the user to be in.
An example would be player.socket.emit('room-editor', player, data)
room-editor
is the file name of theinput-event
to be called.player
is the currentPlayer
object.data
(inputConfig) is an object literal that holds all the information about the OLC process.
Inside the inputConfig
, there are some key properties that all the menus use. That is menuMap
and eventstack
.
_menuMap
- is a ES6 Javascript Map
that is structured with the input-event
as the key and any pertinent properties stored as values. Those values will be accessed in the following input-event
. It is structured this way so you can have a separate configuration for each input-event
fired that will be maintained until you go back
from that menu.
_eventStack
- is a simple string array that acts like a traditional stack data type. The FILO (First in last out) model allows the game to remember where the player is inside the nested structure. Since there are reusable editors, once you go back or exit a specific editor, the eventStack
is popped off to return to the previous menu player.socket.emit(eventStack.pop(), player, inputConfig)
.
options
options is an object array that holder the menu choices inside of each editor. The list is recalculated and repopulated each command inside the menu.
So putting it all together, we'll look at a common entry for more things you would want to edit:
module.exports = () => {
return {
event: state => (player, inputConfig) => {
const socket = player.socket;
const fileName = path.basename(__filename, '.js');
// Sets up the default properties to the inputConfig that gets
// passed to all of the other editors.
inputConfig = Object.assign({
menuMap: new Map(), // Stores information about each editor
eventStack: [], // Keeps track of the position in the editors
none: `<red>None</red>`,
}, inputConfig);
inputConfig.fileName = fileName;
let { eventStack, menuMap, def, none } = inputConfig;
let options = [];
options.push({
display: 'Title', // Display of the Option
displayValues: `<yellow>${def.title}</yellow>`, // What the value to be shown
key: '1', // The selector key, can be anything. If black, the option can't be selected
onSelect: (choice) => {
// Add the configuration to the next editor, 'input-text'. Those properties
// will be extracted inside of the 'input-text' resuable editor.
menuMap.set('input-text', {
text: def.title,
schema: Joi.string().min(1).max(75).required(),
displayProperty: 'Room Title',
onExit: choice.onExit,
});
eventStack.push(fileName); // add the current place to eventStack
player.socket.emit('input-text', player, inputConfig); // Emit the next editor to go to.
},
onExit: (optionConfig) => {
// When the 'input-text' is exited, this function will run, saving the result
// of the input collected inside of the 'input-text' editor.
def.title = optionConfig.text;
}
});
// Similar to above, this option, when selected with a 'Q', will fire the next
// editor, 'exit-olc' which hands saving or not saving what was edited.
options.push({
display: 'Quit',
displayValues: '',
key: 'q',
bottomMenu: true,
onSelect: () => {
eventStack.push(fileName);
player.socket.emit('exit-olc', player, inputConfig);
}
});
// Show menu compiles all of the options that were pushed into the 'options' array.
// It will display them and automatticaly wait for the next user input to select.
// one of the options.
// The show menu also will stylize the entire menu in a consistent manner and even,
// show important information a header bar.
DU.showMenu(player, inputConfig, options);
}
};
Here is the resulting code displayed in action:
With this next photo, the option Title
was selected with the 1
being inputed. That fires off the input-text
editor which automatically asks for a single text input as a response. The next input was 'The Winter Castle'
and that completes the input-text
and the title was changed to that new input. The editor was then exited with the 'q'
quit command and then the changes were saved typing in a 'y'
.
room-editor
item-editor
npc-editor
quest-editor
Allows for the single input text. Can be used for text, number, or anything else you want to validate for. Can search for entityReferences as well.
options.push({
display: "Name",
displayValues: `<yellow>${def.name}</yellow>`,
key: "1",
onSelect: choice => {
menuMap.set("input-text", {
text: def.name,
schema: Joi.string()
.min(1)
.max(75)
.required(),
displayProperty: "Room Name",
onExit: choice.onExit
});
eventStack.push(fileName);
player.socket.emit("input-text", player, inputConfig);
},
onExit: optionConfig => {
def.name = optionConfig.text;
}
});
Allows for multiple line input-text field that has additional user options to format and view your text before saving. Use for longer text blocks, mailing letters, help files, or forum posts.
options.push({
display: "Description",
displayValues: `\r\n` + DU.editorTextDisplayValue(def.description),
key: "2",
onSelect: choice => {
menuMap.set("editor-text", {
text: def.description,
params: {
min: 0,
max: 2100
},
displayProperty: "Description",
onExit: choice.onExit
});
eventStack.push(fileName);
player.socket.emit("editor-text", player, inputConfig);
},
onExit: (optionConfig, cmd) => {
if (cmd === "@s") {
def.description = optionConfig.text;
}
}
});
View:
Allows for the submission of multiple text inputs, making a list. Most commonly used in conjunction with lists of entityReferences of Npc
, Room
, or Item
.
const items = def.items || [];
options.push({
display: "Default Items",
displayValues: items.length || none,
key: "i",
onSelect: choice => {
menuMap.set("list-text", {
current: [...items], // Current List
displayProperty: choice.display,
// getCurrent gets passed each list item then passed to currentDisplay
getCurrent: er => state.ItemFactory.getDefinition(er),
currentDisplay: foundDef =>
foundDef
? `${foundDef.level ? `Lvl${foundDef.level}` : ""} ${foundDef.name ||
""}`
: "",
columns: 1,
showChoice: false,
schema: Joi.string().empty(""),
params: {
type: "entityReference",
entityType: "item" // Validate the text entries for valid item entityReferences
},
onExit: choice.onExit
});
eventStack.push(fileName);
player.socket.emit("list-text", player, inputConfig);
},
onExit: optionConfig => {
def.items = [...optionConfig.current]; // Update the list when exited.
}
});
View:
Allows for creating an array of primative types, most likely strings. Plug in any selections that you want to be able to be toggled.
const flags = def.metadata.flags || [];
options.push({
display: "Flags",
displayValues: flags.length ? [...flags].join(" ") : none,
key: "3",
onSelect: choice => {
menuMap.set("toggleable", {
current: new Set([...flags]),
selections: new Set([...Object.keys(Roomflag)]),
displayProperty: choice.display,
columns: 2,
minWidth: 10,
onExit: choice.onExit
});
eventStack.push(fileName);
player.socket.emit("toggleable", player, inputConfig);
},
onExit: optionConfig => {
def.metadata.flags = [...optionConfig.current];
}
});
View:
Allows for a single selection of preset values. The selections
property expects an array of objects with at least a property of display
defined in each object. The other properties are up to the user to define and the entire object that is selected is passed to the onExit()
function on the selection
property.
const itemTypeSelections = Object.keys(ItemType).map(type => {
return {
// Format your display to whatever you want it to look like.
display: `${cap(type.toLowerCase().replace("_", " "))}`,
// Add another property to be used as the 'selection' when returning from
// the 'select-one' editor.
type
};
});
options.push({
display: `Item Type`,
displayValues: def.type,
key: "T",
onSelect: choice => {
menuMap.set("select-one", {
text: def.type || "",
selections: itemTypeSelections,
columns: 2,
minWidth: 8,
displayProperty: `Item Type`,
required: true,
onExit: choice.onExit
});
eventStack.push(fileName);
player.socket.emit("select-one", player, inputConfig);
},
onExit: optionConfig => {
def.type = optionConfig.selection.type;
// optionConfig.selection.type corresponds to the `type` chosen from the `itemTypeSelections`.
}
});
View: