The JavaScript code below implements a simple turn-based combat engine for a Japanese-style RPG in JavaScript. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript. Or, for an online version, visit https://thiscouldbebetter.neocities.org/RPGCombatEngine/Source/RPGCombatEngine.html. It’s not really finished yet, but as usual, I figured I’d put it out here before I lose interest for the time being. Use the W, A, S, D, and Enter keys to navigate the menus and choose targets for attacks.

UPDATE 2017/01/30: I have updated the code to more closely match my current standards. The arrow keys are now used instead of WASD. The new version allows for the drawing of images for player characters, but will fall back to simple shapes if no image files are present at “../Media/Images/Agents.png”. A sample image is included below.

UPDATE 2017/02/05: I have made a few tweaks to the graphics, including representing the combatants with simple images, provided that those images are present in the “../Media/Images/” directory.
UPDATE 2018/03/01: I have split this program into classes and uploaded it to the URL “https://github.com/thiscouldbebetter/RPGCombatEngine“.



<html>
<body>
<script type="text/javascript">
// main
function main()
{
var universe = new DemoData().universe();
Globals.Instance.initialize
(
100, // millisecondsPerTimerTick
new Display
(
new Coords(300, 225),
8, // fontHeightInPixels
"LightGray", // colorFore
"Black" // colorBack
),
universe
);
}
// extensions
function ArrayExtensions()
{
// extension class
}
{
Array.prototype.addLookups = function(keyName)
{
for (var i = 0; i < this.length; i++)
{
var item = this[i];
this[item[keyName]] = item;
}
return this;
}
}
// classes
function Action()
{
this.status = ActionStatus.Instances.None;
this.parameters = [];
}
{
// instance methods
Action.prototype.defn = function(agent)
{
var returnValue = agent.defn().actionDefns[this.defnName];
if (returnValue == null)
{
returnValue = Globals.Instance.universe.actionDefns[this.defnName];
}
return returnValue;
}
Action.prototype.target = function()
{
return this.parameters["Target"];
}
Action.prototype.target_Set = function(valueToSet)
{
this.parameters["Target"] = valueToSet;
}
Action.prototype.updateEncounterAndAgentForTimerTick = function(encounter, agent)
{
var display = Globals.Instance.display;
if (this.status == ActionStatus.Instances.None)
{
this.status = ActionStatus.Instances.AwaitingActionDefn;
var actionDefns = agent.defn().actionDefns;
var updateEncounter = function()
{
var encounter = Globals.Instance.universe.encounter;
var agent = encounter.agentCurrent;
var action = agent.action;
action.defnName = this.text; // hack
var actionDefn = action.defn(agent);
if (actionDefn.requiresTarget == true)
{
actionStatusNext = ActionStatus.Instances.AwaitingTarget;
}
else
{
actionStatusNext = ActionStatus.Instances.Complete;
}
action.status = actionStatusNext;
}
var panes = encounter.defn().panes;
var menuForActionDefns = new Menu
(
"Actions",
panes["Menu_Player"].pos, // pos
new Coords(0, 8), // spacing
null, // updateEncounter
null, // menuable
Menu.menuablesToMenus
(
actionDefns,
[ "name" ], // bindingPathsForMenuText
updateEncounter
),
0 // indexOfChildSelected
);
encounter.entitiesToSpawn.push(menuForActionDefns);
}
else if (this.status == ActionStatus.Instances.AwaitingActionDefn)
{
// do nothing
}
else if (this.status == ActionStatus.Instances.AwaitingTarget)
{
var intelligence = encounter.partyCurrent().intelligence;
intelligence.decideAction(this);
}
else if (this.status == ActionStatus.Instances.Running)
{
var actionDefn = this.defn(agent);
actionDefn.perform(encounter, agent, this);
}
else if (this.status == ActionStatus.Instances.Complete)
{
encounter.entitiesToRemove.push(agent.action);
agent.action = null;
agent.hasMovedThisTurn = true;
encounter.agentCurrentAdvance();
}
}
}
function ActionDefn
(
name,
requiresTarget,
perform,
toMenu
)
{
this.name = name;
this.requiresTarget = requiresTarget;
this.perform = perform;
this.toMenu = toMenu;
}
function ActionStatus(name)
{
this.name = name;
}
{
ActionStatus.Instances = new ActionStatus_Instances();
function ActionStatus_Instances()
{
this.None = new ActionStatus("None");
this.AwaitingActionDefn = new ActionStatus("AwaitingActionDefn");
this.AwaitingTarget = new ActionStatus("AwaitingTarget");
this.Running = new ActionStatus("Running");
this.Complete = new ActionStatus("Complete");
}
}
function Agent(name, defnName, pos, itemsEquipped, itemsInInventory)
{
this.name = name;
this.defnName = defnName;
this.pos = pos;
this.itemsEquipped = itemsEquipped;
this.itemsInInventory = itemsInInventory;
this.action = null;
this.effects = [];
this.hasMovedThisTurn = false;
}
{
Agent.prototype.defn = function()
{
return Globals.Instance.universe.agentDefns[this.defnName];
}
Agent.prototype.initializeForEncounter = function(encounter)
{
var defn = this.defn();
this.integrity = defn.integrityMax;
this.energy = defn.energyMax;
this.hasMovedThisTurn = false;
if (this.itemsEquipped != null)
{
for (var i = 0; i < this.itemsEquipped.length; i++)
{
var item = this.itemsEquipped[i];
var itemDefn = item.defn();
var categoryNames = itemDefn.categoryNames;
for (var c = 0; c < categoryNames.length; c++)
{
var categoryName = categoryNames[c];
this.itemsEquipped[categoryName] = item;
}
}
}
}
Agent.prototype.updateEncounterForTimerTick = function(encounter)
{
if (encounter.agentCurrent == this)
{
if (this.action == null)
{
this.action = new Action();
}
this.action.updateEncounterAndAgentForTimerTick
(
encounter,
this
);
}
}
// menuable
Agent.prototype.toMenu = function()
{
var universe = Globals.Instance.universe;
var encounter = universe.encounter;
var panes = encounter.defn().panes;
var agentDefn = this.defn();
var spellDefns = agentDefn.spellDefns;
var textForAgent;
if (this.name == null) // hack
{
textForAgent = agentDefn.name;
}
else
{
textForAgent = this.name;
}
textForAgent +=
" ("
+ "H:" + this.integrity + "/" + agentDefn.integrityMax;
if (agentDefn.energyMax > 0)
{
textForAgent +=
" E:" + this.energy + "/" + agentDefn.energyMax
}
textForAgent += ")";
var returnMenu = new Menu
(
textForAgent,
panes["Menu_Player"].pos, // pos
new Coords(0, 8), // spacing
this, // menuable
null, // updateEncounter
null, // children
0 // indexOfChildSelected
);
return returnMenu;
}
// drawable
Agent.prototype.drawToDisplay = function(display)
{
var agent = this;
var agentDefn = agent.defn();
agentDefn.visual.drawToDisplayForDrawable(display, this);
var encounter = Globals.Instance.universe.encounter;
var agentCurrent = encounter.agentCurrent;
if (agent == agentCurrent)
{
var arrowSizeInPixels = agentDefn.sizeInPixels;
display.drawArrow
(
agent.pos,
arrowSizeInPixels
);
var action = agent.action;
if (action != null)
{
var actionTarget = action.target();
if (actionTarget != null)
{
display.drawArrow
(
actionTarget.pos,
arrowSizeInPixels
);
}
}
}
}
}
function AgentDefn
(
name,
visual,
sizeInPixels,
integrityMax,
energyMax,
initiativeRange,
actionDefns,
spellDefns
)
{
this.name = name;
this.visual = visual;
this.sizeInPixels = sizeInPixels;
this.integrityMax = integrityMax;
this.energyMax = energyMax;
this.initiativeRange = initiativeRange;
this.actionDefns = actionDefns;
this.spellDefns = spellDefns;
this.actionDefns.addLookups("name");
if (this.spellDefns != null)
{
this.spellDefns.addLookups("name");
}
}
function Category(name)
{
this.name = name;
}
function Coords(x, y)
{
this.x = x;
this.y = y;
}
{
// instances
Coords.Instances = new Coords_Instances();
function Coords_Instances()
{
this.Zeroes = new Coords(0, 0, 0);
}
// methods
Coords.prototype.add = function(other)
{
this.x += other.x;
this.y += other.y;
return this;
}
Coords.prototype.clone = function()
{
return new Coords(this.x, this.y);
}
Coords.prototype.divideScalar = function(scalar)
{
this.x /= scalar;
this.y /= scalar;
return this;
}
Coords.prototype.magnitude = function()
{
return Math.sqrt(this.x * this.x + this.y * this.y);
}
Coords.prototype.multiply = function(other)
{
this.x *= other.x;
this.y *= other.y;
return this;
}
Coords.prototype.multiplyScalar = function(scalar)
{
this.x *= scalar;
this.y *= scalar;
return this;
}
Coords.prototype.overwriteWith = function(other)
{
this.x = other.x;
this.y = other.y;
return this;
}
Coords.prototype.subtract = function(other)
{
this.x -= other.x;
this.y -= other.y;
return this;
}
}
function Display(sizeInPixels, fontHeightInPixels, colorFore, colorBack)
{
this.sizeInPixels = sizeInPixels;
this.fontHeightInPixels = fontHeightInPixels;
this.colorFore = colorFore;
this.colorBack = colorBack;
}
{
Display.prototype.clear = function()
{
this.drawRectangle
(
Coords.Instances.Zeroes, this.sizeInPixels,
this.colorBack, this.colorFore
);
}
Display.prototype.drawArrow = function(pos, size)
{
this.graphics.strokeStyle = this.colorFore;
this.graphics.beginPath();
this.graphics.moveTo(pos.x, pos.y + size.y / 2);
this.graphics.lineTo(pos.x - size.x, pos.y);
this.graphics.lineTo(pos.x - size.x, pos.y + size.y);
this.graphics.closePath();
this.graphics.stroke();
}
Display.prototype.drawImage = function(systemImage, pos, size)
{
if (size == null)
{
this.graphics.drawImage
(
systemImage,
pos.x, pos.y
);
}
else
{
this.graphics.drawImage
(
systemImage,
pos.x, pos.y,
size.x, size.y
);
}
}
Display.prototype.drawImageSlice = function
(
systemImage, sliceOffset, sliceSize, drawPos, drawSize
)
{
if (drawSize == null)
{
this.graphics.drawImage
(
systemImage,
sliceOffset.x, sliceOffset.y,
sliceSize.x, sliceSize.y,
drawPos.x, drawPos.y,
sliceSize.x, sliceSize.y // drawSize
);
}
else
{
this.graphics.drawImage
(
systemImage,
sliceOffset.x, sliceOffset.y,
sliceSize.x, sliceSize.y,
drawPos.x, drawPos.y,
drawSize.x, drawSize.y
);
}
}
Display.prototype.drawRectangle = function(pos, size, colorFill, colorBorder)
{
if (colorFill != null)
{
this.graphics.fillStyle = colorFill;
this.graphics.fillRect
(
pos.x, pos.y, size.x, size.y
);
}
if (colorBorder != null)
{
this.graphics.strokeStyle = colorBorder;
this.graphics.strokeRect
(
pos.x, pos.y, size.x, size.y
);
}
}
Display.prototype.drawText = function(textToDraw, pos, colorFill, colorBorder)
{
var textToDrawAsLines = textToDraw.split("\n");
if (colorFill == null)
{
colorFill = this.colorFore;
}
this.graphics.fillStyle = colorFill;
if (colorBorder != null)
{
this.graphics.strokeStyle = colorBorder;
}
for (var i = 0; i < textToDrawAsLines.length; i++)
{
var textLine = textToDrawAsLines[i];
if (colorBorder != null)
{
this.graphics.strokeText
(
textLine,
pos.x + 2, // hack
pos.y + this.fontHeightInPixels * (i + 1)
);
}
this.graphics.fillText
(
textLine,
pos.x + 2, // hack
pos.y + this.fontHeightInPixels * (i + 1)
);
}
}
Display.prototype.initialize = function()
{
var canvas = document.createElement("canvas");
canvas.width = this.sizeInPixels.x;
canvas.height = this.sizeInPixels.y;
this.graphics = canvas.getContext("2d");
this.graphics.font = "" + this.fontHeightInPixels + "px sans-serif";
document.body.appendChild(canvas);
}
}
function Effect(defnName)
{
this.defnName = defnName;
this.turnApplied = turnApplied;
}
{
Effect.prototype.defn = function()
{
return Globals.Instance.universe.effectDefns[this.defnName];
}
}
function EffectDefn(name, apply)
{
this.name = name;
this.apply = apply;
}
function Empty(pos)
{
this.pos = pos;
}
function Encounter(defnName, parties)
{
this.defnName = defnName;
this.parties = parties;
this.entities = [];
this.entitiesToSpawn = [];
this.entitiesToRemove = [];
this.entitiesToSpawn = this.entitiesToSpawn.concat(this.parties);
this.agentCurrent = null;
}
{
Encounter.prototype.agentCurrentAdvance = function()
{
var agentNext = null;
for (var i = 0; i < this.agentsAll.length; i++)
{
var agent = this.agentsAll[i];
if (agent.hasMovedThisTurn == false)
{
agentNext = agent;
haveAllAgentsMovedThisTurn = false;
break;
}
}
if (haveAllAgentsMovedThisTurn == true)
{
for (var i = 0; i < this.agentsAll.length; i++)
{
var agent = this.agentsAll[i];
agent.hasMovedThisTurn = false;
}
agentNext = this.agentsAll[0];
}
this.agentCurrent = agentNext;
}
Encounter.prototype.defn = function()
{
return Globals.Instance.universe.encounterDefns[this.defnName];
}
Encounter.prototype.initialize = function()
{
this.agentsAll = [];
for (var p = 0; p < this.parties.length; p++)
{
var party = this.parties[p];
var partyAgents = party.agents;
this.agentsAll = this.agentsAll.concat(partyAgents);
}
this.agentCurrentAdvance();
}
Encounter.prototype.partyCurrent = function()
{
return (this.agentCurrent.party);
}
Encounter.prototype.updateForTimerTick = function()
{
for (var i = 0; i < this.entitiesToSpawn.length; i++)
{
var entity = this.entitiesToSpawn[i];
if (entity.initializeForEncounter != null)
{
entity.initializeForEncounter(this);
}
this.entities.push(entity);
}
this.entitiesToSpawn.length = 0;
for (var i = 0; i < this.entities.length; i++)
{
var entity = this.entities[i];
entity.updateEncounterForTimerTick(this);
}
for (var i = 0; i < this.entitiesToRemove.length; i++)
{
var entity = this.entitiesToRemove[i];
this.entities.splice
(
this.entities.indexOf(entity),
1
);
}
this.entitiesToRemove.length = 0;
this.updateForTimerTick_WinOrLose();
this.drawToDisplay(Globals.Instance.display);
}
Encounter.prototype.updateForTimerTick_WinOrLose = function()
{
for (var p = 0; p < this.parties.length; p++)
{
var party = this.parties[p];
var partyAgents = party.agents;
var areAnyAgentsInPartyAlive = false;
for (var a = 0; a < partyAgents.length; a++)
{
var agent = partyAgents[a];
if (agent.integrity > 0)
{
areAnyAgentsInPartyAlive = true;
break;
}
}
if (areAnyAgentsInPartyAlive == false)
{
if (p == 0)
{
document.write("You lose!");
}
else
{
document.write("You win!");
}
}
}
}
// drawable
Encounter.prototype.drawToDisplay = function(display)
{
var encounter = this;
display.clear();
var encounterDefn = encounter.defn();
var panes = encounterDefn.panes;
for (var p = 0; p < panes.length; p++)
{
var pane = panes[p];
pane.drawToDisplay(display);
}
var entities = encounter.entities;
for (var i = 0; i < entities.length; i++)
{
var entity = entities[i];
if (entity.drawToDisplay != null)
{
entity.drawToDisplay(display);
}
}
}
}
function EncounterDefn(name, panes)
{
this.name = name;
this.panes = panes;
this.panes.addLookups("name");
}
function Globals()
{}
{
Globals.Instance = new Globals();
Globals.prototype.handleEventTimerTick = function()
{
this.universe.updateForTimerTick();
this.inputHelper.updateForTimerTick();
}
Globals.prototype.initialize = function
(
millisecondsPerTimerTick,
display,
universe
)
{
this.display = display;
this.display.initialize();
this.universe = universe;
this.universe.initialize();
this.inputHelper = new InputHelper();
this.inputHelper.initialize();
this.timer = setInterval
(
this.handleEventTimerTick.bind(this),
millisecondsPerTimerTick
);
}
}
function Image(name, sourcePath)
{
this.name = name;
this.sourcePath = sourcePath;
this.systemImage = document.createElement("img");
this.systemImage.src = this.sourcePath;
}
function ImageLibrary(images)
{
this.images = images;
this.images.addLookups("name");
}
function InputHelper()
{}
{
InputHelper.prototype.initialize = function()
{
document.body.onkeydown = this.handleEventKeyDown.bind(this);
document.body.onkeyup = this.handleEventKeyUp.bind(this);
}
InputHelper.prototype.updateForTimerTick = function()
{
this.keyPressed = null;
}
// events
InputHelper.prototype.handleEventKeyDown = function(e)
{
this.keyPressed = e.key;
}
InputHelper.prototype.handleEventKeyUp = function(e)
{
// todo
}
}
function IntelligenceHuman()
{}
{
IntelligenceHuman.prototype.decideAction = function(action)
{
var encounter = Globals.Instance.universe.encounter;
var agent = encounter.agentCurrent;
var target = action.target();
if (target == null)
{
target = encounter.parties[1].agents[0];
action.target_Set(target);
action.parameters["EmptyForPosToReturnTo"] = new Empty(agent.pos.clone());
}
var inputHelper = Globals.Instance.inputHelper;
var keyPressed = inputHelper.keyPressed;
if (keyPressed == "Enter")
{
action.status = ActionStatus.Instances.Running;
}
else
{
var partyTargeted = target.party;
var agentsInPartyTargeted = partyTargeted.agents;
if (keyPressed == "ArrowLeft")
{
partyToTarget = encounter.parties[1];
if (partyToTarget != partyTargeted)
{
target = partyToTarget.agents[0];
}
}
else if (keyPressed == "ArrowRight")
{
partyToTarget = encounter.parties[0];
if (partyToTarget != partyTargeted)
{
target = partyToTarget.agents[0];
}
}
else if (keyPressed == "ArrowDown")
{
var indexOfAgentToTarget = agentsInPartyTargeted.indexOf(target) + 1;
if (indexOfAgentToTarget >= agentsInPartyTargeted.length)
{
indexOfAgentToTarget = 0;
}
target = agentsInPartyTargeted[indexOfAgentToTarget];
}
else if (keyPressed == "ArrowUp")
{
var indexOfAgentToTarget = agentsInPartyTargeted.indexOf(target) - 1;
if (indexOfAgentToTarget < 0)
{
indexOfAgentToTarget = agentsInPartyTargeted.length - 1;
}
target = agentsInPartyTargeted[indexOfAgentToTarget];
}
action.target_Set(target);
}
}
}
function IntelligenceMachine()
{}
{
IntelligenceMachine.prototype.decideAction = function()
{
// todo
}
}
function Item(defnName)
{
this.defnName = defnName;
}
{
Item.prototype.apply = function(agent, target)
{
this.defn().apply(this, agent, target);
}
Item.prototype.defn = function()
{
return Globals.Instance.universe.itemDefns[this.defnName];
}
}
function ItemDefn(name, categoryNames, apply)
{
this.name = name;
this.categoryNames = categoryNames;
this.apply = apply;
}
function Menu
(
text,
pos,
spacing,
menuable,
updateEncounter,
children,
indexOfChildSelected
)
{
this.text = text;
this.pos = pos;
this.spacing = spacing;
this.menuable = menuable;
this.updateEncounter = updateEncounter;
this.children = children;
this.indexOfChildSelected = indexOfChildSelected;
}
{
Menu.menuablesToMenus = function(menuables, bindingPathsForMenuText, updateEncounter)
{
var returnValues = [];
for (var i = 0; i < menuables.length; i++)
{
var menuable = menuables[i];
var menuableAsMenu;
if (menuable.toMenu != null)
{
menuableAsMenu = menuable.toMenu();
}
else
{
var menuText = "";
for (var f = 0; f < bindingPathsForMenuText.length; f++)
{
var bindingPathForMenuText = bindingPathsForMenuText[f];
var bindingPathElements = bindingPathForMenuText.split(".");
var valueCurrent = menuable;
for (var g = 0; g < bindingPathElements.length; g++)
{
var bindingPathElement = bindingPathElements[g];
if (bindingPathElement.indexOf("()") == -1)
{
valueCurrent = valueCurrent[bindingPathElement];
}
else
{
bindingPathElement = bindingPathElement.substr(0, bindingPathElement.length - "()".length);
var method = valueCurrent[bindingPathElement];
valueCurrent = method.call(valueCurrent);
}
}
menuText += valueCurrent;
}
menuableAsMenu = new Menu
(
menuText,
new Coords(0, 0), // pos
new Coords(0, 8), // spacing
menuable, // menuable
updateEncounter,
null // children
);
}
returnValues.push(menuableAsMenu);
}
return returnValues;
}
// instance methods
Menu.prototype.childSelected = function()
{
return (this.indexOfChildSelected == null ? null : this.children[this.indexOfChildSelected]);
}
Menu.prototype.indexOfChildSelectedAdd = function(valueToAdd)
{
this.indexOfChildSelected += valueToAdd;
if (this.indexOfChildSelected < 0)
{
this.indexOfChildSelected = this.children.length - 1;
}
else if (this.indexOfChildSelected >= this.children.length)
{
this.indexOfChildSelected = 0;
}
}
Menu.prototype.updateEncounterForTimerTick = function(encounter)
{
if (this.isLocked == true)
{
return;
}
var inputHelper = Globals.Instance.inputHelper;
var keyPressed = inputHelper.keyPressed;
if (keyPressed == "ArrowLeft")
{
// todo - back
}
else if (keyPressed == "Enter" || keyPressed == "ArrowRight")
{
var encounter = Globals.Instance.universe.encounter;
var childSelected = this.childSelected();
this.isLocked = true;
if (childSelected.children == null)
{
childSelected.updateEncounter(encounter);
}
else
{
encounter.entitiesToRemove.push(this);
encounter.entitiesToSpawn.push(childSelected);
}
}
else if (keyPressed == "ArrowDown")
{
this.indexOfChildSelectedAdd(1);
}
else if (keyPressed == "ArrowUp")
{
this.indexOfChildSelectedAdd(-1);
}
}
// drawable
Menu.prototype.drawToDisplay = function(display)
{
var menu = this;
var pos = menu.pos;
var spacing = menu.spacing;
var arrowSize = new Coords(8, 8);
var drawPos = pos.clone().add
(
new Coords(arrowSize.x + 2, spacing.y / 2)
);
var children = menu.children;
if (children != null)
{
for (var i = 0; i < children.length; i++)
{
var child = children[i];
var displayText = child.text;
display.drawText
(
displayText,
drawPos
);
drawPos.y += arrowSize.y / 3; // hack
if (i == menu.indexOfChildSelected)
{
display.drawArrow
(
drawPos, // pos - hack
arrowSize // size
);
}
drawPos.y -= arrowSize.y / 3; // hack
drawPos.add(spacing);
} // end for
}
}
}
function Message(text, pos)
{
this.text = text;
this.pos = pos;
this.ticksToLive = 100;
}
function NumberHelper()
{
// static class
}
{
NumberHelper.trimValueToMinAndMax = function(value, min, max)
{
if (value < min)
{
value = min;
}
else if (value > max)
{
value = max;
}
return value;
}
}
function Pane(name, pos, size)
{
this.name = name;
this.pos = pos;
this.size = size;
}
{
// drawable
Pane.prototype.drawToDisplay = function(display)
{
display.drawRectangle
(
this.pos, this.size,
display.colorBack,
display.colorFore
);
}
}
function Party(name, intelligence, agents)
{
this.name = name;
this.intelligence = intelligence;
this.agents = agents;
this.agentIndexCurrent = null;
for (var i = 0; i < this.agents.length; i++)
{
var agent = this.agents[i];
agent.party = this;
}
}
{
Party.prototype.agentCurrent = function()
{
return (this.agentIndexCurrent == null ? null : this.agents[this.agentIndexCurrent]);
}
Party.prototype.agentCurrentAdvance = function()
{
if (this.agentIndexCurrent == null)
{
this.agentIndexCurrent = 0;
}
else
{
this.agentIndexCurrent++;
if (this.agentIndexCurrent >= this.agents.length)
{
this.agentIndexCurrent = 0;
}
}
}
Party.prototype.initializeForEncounter = function(encounter)
{
for (var i = 0; i < this.agents.length; i++)
{
var agent = this.agents[i];
encounter.entitiesToSpawn.push(agent);
}
}
Party.prototype.updateEncounterForTimerTick = function(encounter)
{
// do nothing
}
// drawable
Party.prototype.drawToDisplay = function(display)
{
var panes = Globals.Instance.universe.encounter.defn().panes;
var paneForStatus = panes["Status_" + this.name];
var menuForParty = new Menu
(
"Party",
paneForStatus.pos,
new Coords(0, 8), // spacing
null, // updateEncounter
this, // menuable
Menu.menuablesToMenus
(
this.agents,
[ "name", "integrity", "defn().integrityMax" ], // bindingPathsForDisplayText
null // updateEncounter
)
)
menuForParty.drawToDisplay(display);
}
}
function Range(min, max)
{
this.min = min;
this.max = max;
this.size = this.max - this.min;
}
{
Range.prototype.randomNumberInRange = function()
{
return
this.min
+ Math.floor
(
Math.random()
* this.size
);
}
}
function SpellDefn(name, apply)
{
this.name = name;
this.apply = apply;
}
function Universe
(
name,
imageLibrary,
actionDefns,
agentDefns,
effectDefns,
encounterDefns,
itemDefns,
encounter
)
{
this.name = name;
this.imageLibrary = imageLibrary;
this.actionDefns = actionDefns;
this.agentDefns = agentDefns;
this.effectDefns = effectDefns;
this.encounterDefns = encounterDefns;
this.itemDefns = itemDefns;
this.encounter = encounter;
this.agentDefns.addLookups("name");
this.effectDefns.addLookups("name");
this.encounterDefns.addLookups("name");
this.itemDefns.addLookups("name");
}
{
Universe.prototype.initialize = function()
{
this.encounter.initialize();
}
Universe.prototype.updateForTimerTick = function()
{
this.encounter.updateForTimerTick();
}
}
function VisualDynamic(drawToDisplayForDrawable)
{
this.drawToDisplayForDrawable = drawToDisplayForDrawable;
}
{
VisualDynamic.prototype.drawToDisplayForDrawable = function
(
display, drawable
)
{
this.drawToDisplayForDrawable.call(this, display, drawable);
}
}
function VisualFallthrough(children)
{
this.children = children;
}
{
VisualFallthrough.prototype.drawToDisplayForDrawable = function
(
display, drawable
)
{
for (var i = 0; i < this.children.length; i++)
{
var child = this.children[i];
try
{
child.drawToDisplayForDrawable(display, drawable);
break;
}
catch (err)
{
// do nothing
var todo = 1;
}
}
}
}
function VisualGroup(children)
{
this.children = children;
}
{
VisualGroup.prototype.drawToDisplayForDrawable = function
(
display, drawable
)
{
for (var i = 0; i < this.children.length; i++)
{
var child = this.children[i];
child.drawToDisplayForDrawable(display, drawable);
}
}
}
function VisualImage(imageName)
{
this.imageName = imageName;
}
{
VisualImage.prototype.drawToDisplayForDrawable = function
(
display, drawable
)
{
var imageLibrary = Globals.Instance.universe.imageLibrary;
var image = imageLibrary.images[this.imageName];
display.drawImage(image.systemImage, drawable.pos);
}
}
function VisualImageSlice(imageName, offset, size)
{
this.imageName = imageName;
this.offset = offset;
this.size = size;
}
{
VisualImageSlice.prototype.drawToDisplayForDrawable = function
(
display, drawable
)
{
var imageLibrary = Globals.Instance.universe.imageLibrary;
var image = imageLibrary.images[this.imageName];
display.drawImageSlice
(
image.systemImage, this.offset, this.size, drawable.pos
);
}
}
function VisualRectangle(size, colorFill, colorBorder)
{
this.size = size;
this.colorFill = colorFill;
this.colorBorder = colorBorder;
}
{
VisualRectangle.prototype.drawToDisplayForDrawable = function
(
display, drawable
)
{
display.drawRectangle
(
drawable.pos, this.size,
this.colorFill, this.colorBorder
);
}
}
function VisualText(text, colorFill)
{
this.text = text;
this.colorFill = colorFill;
}
{
VisualText.prototype.drawToDisplayForDrawable = function(display, drawable)
{
display.drawText
(
this.text,
pos,
this.colorFill
);
}
}
// demo
function DemoData()
{
// do nothing
}
{
DemoData.prototype.universe = function()
{
var imageDirectory = "../Media/Images/";
var imageLibrary = new ImageLibrary
([
new Image("Agents", imageDirectory + "Agents.png"),
new Image("Ogre", imageDirectory + "Ogre.png"),
]);
var effectDefns = [];
var categories =
[
new Category("Consumable"),
new Category("Weapon"),
].addLookups("name");
var itemDefns = this.universe_1_ItemDefns(categories);
var actionDefnsCommon = this.universe_2_ActionDefns();
var agentDefns = this.universe_3_AgentDefns(actionDefnsCommon);
var encounterDefns =
[
new EncounterDefn
(
"Default",
// panes
[
new Pane("Field_Other", new Coords(0, 0), new Coords(225, 150)),
new Pane("Field_Player", new Coords(225, 0), new Coords(75, 150)),
new Pane("Status_Other", new Coords(0, 150), new Coords(100, 75)),
new Pane("Status_Player", new Coords(100, 150), new Coords(125, 75)),
new Pane("Menu_Player", new Coords(225, 150), new Coords(75, 75)),
]
),
].addLookups("name");
var encounter = this.universe_4_Encounter(encounterDefns, agentDefns, itemDefns);
var universe = new Universe
(
"Universe0",
imageLibrary,
actionDefnsCommon,
agentDefns,
effectDefns,
encounterDefns,
itemDefns,
encounter
);
return universe;
} // end method
DemoData.prototype.universe_1_ItemDefns = function(categories)
{
var itemDefns =
[
// consumables
new ItemDefn
(
"Heal Potion",
[ categories["Consumable"].name ],
// apply
function(item, agent, target)
{
var items = agent.itemsInInventory;
items.splice
(
items.indexOf(item),
1
);
target.integrity += 20;
}
),
new ItemDefn
(
"Energy Potion",
[ categories["Consumable"].name ],
// apply
function(item, agent, target)
{
var items = agent.itemsInInventory;
items.splice
(
items.indexOf(item),
1
);
target.energy += 20;
}
),
// weapons
new ItemDefn
(
"Dagger",
[ categories["Weapon"].name ],
// apply
function(item, agent, target)
{
target.integrity -= 2;
}
),
new ItemDefn
(
"Mace",
[ categories["Weapon"].name ],
// apply
function(item, agent, target)
{
target.integrity -= 6;
}
),
new ItemDefn
(
"Sword",
[ categories["Weapon"].name ],
// apply
function(item, agent, target)
{
target.integrity -= 10;
}
),
];
itemDefns.addLookups("name");
return itemDefns;
}
DemoData.prototype.universe_2_ActionDefns = function()
{
var actionDefnsCommon =
[
new ActionDefn
(
"Attack",
true, // requiresTarget
function(encounter, agent, action)
{
var target = action.target();
var displacementFromAgentToTarget = target.pos.clone().subtract
(
agent.pos
);
var distanceToTarget = displacementFromAgentToTarget.magnitude();
var distanceToMovePerTick = 16;
if (distanceToTarget > distanceToMovePerTick)
{
var displacementToMove = displacementFromAgentToTarget.divideScalar
(
distanceToTarget
).multiplyScalar
(
distanceToMovePerTick
);
agent.pos.add(displacementToMove);
}
else
{
var emptyToReturnTo = action.parameters["EmptyForPosToReturnTo"];
if (target.integrity == null)
{
agent.pos.overwriteWith(emptyToReturnTo.pos);
action.target_Set(null);
action.status = ActionStatus.Instances.Complete;
}
else
{
var weaponEquipped = agent.itemsEquipped["Weapon"];
if (weaponEquipped != null)
{
weaponEquipped.apply
(
agent,
target
);
}
action.target_Set(emptyToReturnTo);
}
}
},
null // toMenu
),
new ActionDefn
(
"Defend",
false, // requiresTarget
function(encounter, agent, action)
{
action.status = ActionStatus.Instances.Complete;
},
null // toMenu
),
new ActionDefn
(
"Magic",
true, // requiresTarget
// updateEncounter
function(encounter, agent, action)
{
action.status = ActionStatus.Instances.Complete;
},
// toMenu
function()
{
var universe = Globals.Instance.universe;
var encounter = universe.encounter;
var panes = encounter.defn().panes;
var agent = encounter.agentCurrent;
var spellDefns = agent.defn().spellDefns;
var returnMenu = new Menu
(
"Magic",
panes["Menu_Player"].pos, // pos
new Coords(0, 8), // spacing
null, // menuable
null, // updateEncounter
Menu.menuablesToMenus
(
spellDefns,
[ "name" ], // bindingPathsForMenuText
function (encounter)
{
var spellDefn = this.menuable;
var agent = encounter.agentCurrent;
var action = agent.action;
action.parameters["SpellDefn"] = spellDefn;
action.defnName = "Spell";
action.status = ActionStatus.Instances.AwaitingTarget;
}
),
0 // indexOfChildSelected
);
return returnMenu;
}
),
new ActionDefn
(
"Spell",
true, // requiresTarget
function(encounter, agent, action)
{
var target = action.target();
var displacementFromAgentToTarget = target.pos.clone().subtract
(
agent.pos
);
var distanceToTarget = displacementFromAgentToTarget.magnitude();
var distanceToMovePerTick = 16;
if (distanceToTarget > distanceToMovePerTick)
{
var displacementToMove = displacementFromAgentToTarget.divideScalar
(
distanceToTarget
).multiplyScalar
(
distanceToMovePerTick
);
agent.pos.add(displacementToMove);
}
else
{
var emptyToReturnTo = action.parameters["EmptyForPosToReturnTo"];
if (target.integrity == null)
{
agent.pos.overwriteWith(emptyToReturnTo.pos);
action.target_Set(null);
action.status = ActionStatus.Instances.Complete;
}
else
{
var spellDefn = action.parameters["SpellDefn"];
spellDefn.apply
(
agent,
target
);
action.target_Set(emptyToReturnTo);
}
}
},
null // toMenu
),
new ActionDefn
(
"Item",
false, // requiresTarget
function(encounter, agent, action)
{
action.status = ActionStatus.Instances.Complete;
},
// toMenu
function()
{
var universe = Globals.Instance.universe;
var encounter = universe.encounter;
var panes = encounter.defn().panes;
var agent = encounter.agentCurrent;
var items = agent.itemsInInventory;
var returnMenu = new Menu
(
"Items",
panes["Menu_Player"].pos, // pos
new Coords(0, 8), // spacing
null, // menuable
null, // updateEncounter
Menu.menuablesToMenus
(
items,
[ "defn().name" ], // bindingPathsForMenuText
function (encounter)
{
var item = this.menuable;
var agentCurrent = encounter.agentCurrent;
var target = agentCurrent.action.target();
item.apply(agentCurrent, target);
}
),
0 // indexOfChildSelected
);
return returnMenu;
}
),
new ActionDefn
(
"Wait",
false, // requiresTarget
function(encounter, agent, action)
{
encounter.agentCurrentAdvance();
},
null // toMenu
),
new ActionDefn
(
"Run",
false, // requiresTarget
function(encounter, agent, action)
{
// todo
action.status = ActionStatus.Instances.Complete;
},
null // toMenu
),
];
actionDefnsCommon.addLookups("name");
return actionDefnsCommon;
}
DemoData.prototype.universe_3_AgentDefns = function(actionDefnsCommon)
{
var agentSizeInPixelsStandard = new Coords(24, 24);
var visualAgentLabel = new VisualDynamic
(
function drawToDisplayForDrawable(display, drawable)
{
var agent = drawable;
var agentDefn = agent.defn();
var agentText = "\n";
if (agent.name != null)
{
agentText += agent.name + "\n";
}
agentText += agentDefn.name;
display.drawText
(
agentText, agent.pos,
display.colorFore, display.colorBack
);
}
);
var agentDefns =
[
new AgentDefn
(
"Mage",
new VisualGroup
([
new VisualFallthrough
([
new VisualImageSlice
(
"Agents",
new Coords(0, 0).multiply
(
agentSizeInPixelsStandard
),
agentSizeInPixelsStandard
),
new VisualRectangle
(
agentSizeInPixelsStandard,
null, // colorFill
"LightGray"
),
]),
visualAgentLabel,
]),
agentSizeInPixelsStandard,
10, //integrityMax,
20, //energyMax,
new Range(1, 10), // initiativeRange
//actionDefns
[
actionDefnsCommon["Attack"],
actionDefnsCommon["Magic"],
actionDefnsCommon["Item"],
actionDefnsCommon["Wait"],
],
// spellDefns
[
new SpellDefn
(
"Fire",
// apply
function(agent, target)
{
agent.energy -= 5;
target.integrity -= 20;
}
),
]
),
new AgentDefn
(
"Priest",
new VisualGroup
([
new VisualFallthrough
([
new VisualImageSlice
(
"Agents",
new Coords(1, 0).multiply
(
agentSizeInPixelsStandard
),
agentSizeInPixelsStandard
),
new VisualRectangle
(
agentSizeInPixelsStandard,
null, // colorFill
"LightGray"
),
]),
visualAgentLabel,
]),
agentSizeInPixelsStandard,
20, //integrityMax,
20, //energyMax,
new Range(1, 10), // initiativeRange
//actionDefns
[
actionDefnsCommon["Attack"],
actionDefnsCommon["Magic"],
actionDefnsCommon["Item"],
actionDefnsCommon["Wait"],
],
// spellDefns
[
new SpellDefn
(
"Heal",
// apply
function(agent, target)
{
agent.energy += 5;
var integrityToHeal = 20;
target.integrity = NumberHelper.trimValueToMinAndMax
(
target.integrity + integrityToHeal,
0, // min
target.defn().integrityMax // max
);
}
),
]
),
new AgentDefn
(
"Warrior",
new VisualGroup
([
new VisualFallthrough
([
new VisualImageSlice
(
"Agents",
new Coords(2, 0).multiply
(
agentSizeInPixelsStandard
),
agentSizeInPixelsStandard
),
new VisualRectangle
(
agentSizeInPixelsStandard,
null, // colorFill
"LightGray"
),
]),
visualAgentLabel,
]),
agentSizeInPixelsStandard,
30, //integrityMax,
0, //energyMax,
new Range(1, 10), // initiativeRange
//actionDefns
[
actionDefnsCommon["Attack"],
actionDefnsCommon["Item"],
actionDefnsCommon["Wait"],
],
null // spellDefns
),
new AgentDefn
(
"Ogre",
new VisualGroup
([
new VisualFallthrough
([
new VisualImage("Ogre"),
new VisualRectangle
(
new Coords(40, 40),
null, // colorFill
"LightGray"
),
]),
visualAgentLabel,
]),
new Coords(40, 40), // size
50, //integrityMax,
0, //energyMax,
new Range(1, 10), // initiativeRange
//actionDefns
[
actionDefnsCommon["Attack"],
],
null // spellDefns
),
];
agentDefns.addLookups("name");
return agentDefns;
}
DemoData.prototype.universe_4_Encounter = function(encounterDefns, agentDefns, itemDefns)
{
var encounter = new Encounter
(
encounterDefns["Default"].name,
// parties
[
new Party
(
"Player",
new IntelligenceHuman(),
[
new Agent
(
"One",
agentDefns["Mage"].name,
new Coords(256, 20),
// itemsEquipped
[
new Item(itemDefns["Dagger"].name)
],
// itemsInInventory
[
new Item(itemDefns["Energy Potion"].name),
new Item(itemDefns["Energy Potion"].name),
new Item(itemDefns["Energy Potion"].name),
]
),
new Agent
(
"Two",
agentDefns["Priest"].name,
new Coords(256, 50),
// itemsEquipped
[
new Item(itemDefns["Mace"].name)
],
[
new Item(itemDefns["Heal Potion"].name),
new Item(itemDefns["Heal Potion"].name),
new Item(itemDefns["Heal Potion"].name),
] // itemsInInventory
),
new Agent
(
"Three",
agentDefns["Warrior"].name,
new Coords(256, 80),
// itemsEquipped
[
new Item(itemDefns["Sword"].name)
],
[] // itemsInInventory
),
new Agent
(
"Four",
agentDefns["Mage"].name,
new Coords(256, 110),
// itemsEquipped
[
new Item(itemDefns["Dagger"].name)
],
[] // itemsInInventory
),
]
),
new Party
(
"Other",
new IntelligenceHuman(),
[
new Agent
(
null, // name
agentDefns["Ogre"].name,
new Coords(90, 20),
null, // itemsEquipped
null // itemsInInventory
),
new Agent
(
null, // name
agentDefns["Ogre"].name,
new Coords(30, 50),
null, // itemsEquipped
null // itemsInInventory
),
new Agent
(
null, // name
agentDefns["Ogre"].name,
new Coords(90, 80),
null, // itemsEquipped
null // itemsInInventory
),
new Agent
(
null, // name
agentDefns["Ogre"].name,
new Coords(150, 50),
null, // itemsEquipped
null // itemsInInventory
),
]
),
]
);
return encounter;
}
} // end class DemoData
// run
main();
</script>
</body>
</html>
Programming for the Beginner (on Amazon)
github.com/thiscouldbebetter




Reminds me of Final Fantasy.