Classes/Abilities

Player Classes

It may sound strange but there is no such thing as Player or NPC classes in the Ranvier engine. The engine simply has skills and how you decide to restrict the usage of those skills is up to you. There is an example implementation of player classes as well as class selection during character creation in the example bundles if you wish to implement them in your game.

Skills/Spells

Skills and Spells both are defined as Skills (see src/Skill.js). Spells are just skills with a different type. In this guide we'll implement 1 active skill and 1 passive skill to see a demo. You can see more complex examples abilities including heals, DoTs (damage over time), and defensive abilities in the bundle-example-classes bundle.

Skills are defined in the skills/ folder of a bundle:

Active Skill: lunge

This is a simple damage skill that will deal 250% of the player's weapon damage.

bundles/my-bundle/
  skills/
    lunge.js
'use strict';

// import necessary classes from core
const { Broadcast, Damage, SkillType } = require('ranvier');

// import custom lib from another bundle
const Combat = require('../../bundle-example-combat/lib/Combat');

// It's handy to define the different "tuning knobs" of skills right near the top all in one place so you can easily
// change them if you need to.
const damagePercent = 250;
const energyCost = 20;

function getDamage(player) {
  return Combat.calculateWeaponDamage(player) * (damagePercent / 100);
}

/**
 * Basic warrior attack
 */
module.exports = {
  // Friendly name of the skill, shown to the player on the skill list command.
  name: 'Lunge',

  // The type defines which of the ability managers you can find it in.
  // Either in state.SkillManager or state.SpellManager, respectively.
  type: SkillType.SKILL,

  /*
  If requiresTarget is true, the skill usage will fail if the player doesn't specify a target,
  unless you also add the `targetSelf: true` option, in which case if the player
  doesn't specify a target it will target themselves (for example, a healing spell).
  */
  requiresTarget: true,

  // If initiatesCombat is true, using the skill against a target will make the player
  // enter combat against them.
  initiatesCombat: true,

  /*
  The resource config defines the resource cost of the skill on use and is
  optional. Ranvier also supports multiple resource costs by defining an array
  with each entry in the array following the format of the single resource cost
  below.
  */
  resource: {
    // attribute to deduct the cost from
    attribute: 'energy',
    // amount to deduct
    cost: energyCost,
  },


  /*
  Cooldown let's you prevent immediate successive use of a skill by
  configuring the number of seconds between uses. This configuration will create
  a skill-specific cooldown of 6 seconds.
  */
  cooldown: 6,
  /*
  Cooldown can also be configured to be shared between multiple skills such
  that while any skill in the group is on cooldown no skills in the group may be
  used. It can be configured like so:

  cooldown: {
    length: 6,
    group: 'warrior-direct-attack',
  },

  In either case the core will throw a SkillErrors.CooldownError exception if
  execute() is called on a skill which cannot be used due to a cooldown.
  */

  /*
  The run method is where all the magic of skills happen and has a very similar layout to a
  command. A closure accepting GameState in 'state' and returning a function which,
  in this case takes the arguments to the skill, the player that executed the skill
  and the target of the skill
  */
  run: state => function (args, player, target) {
    // This is a simple damage skill so we'll create a new damage instance.
    // See the Attributes guide for more details
    const damage = new Damage('health', getDamage(player), player, this, {
      type: 'physical',
    });

    // Show some flashy effects to the player, target, and the other players in the room
    Broadcast.sayAt(player, '<red>You shift your feet and let loose a mighty attack!</red>');
    Broadcast.sayAtExcept(player.room, `<red>${player.name} lets loose a lunging attack on ${target.name}!</red>`, [player, target]);
    if (!target.isNpc) {
      Broadcast.sayAt(target, `<red>${player.name} lunges at you with a fierce attack!</red>`);
    }

    // apply the damage to the target
    damage.commit(target);
  },

  // the info function is used in `bundle-example-classes/commands/skill.js` to show details
  // about an ability to the player
  info: (player) => {
    return `Make a strong attack against your target dealing <bold>${damagePercent}%</bold> weapon damage.`;
  }
};

Passive Skill: Second Wind

This will be an example implementation of a "passive" skill: one that is always working in the background that the player doesn't type a command to use. The second wind passive ability is quite interesting: Once every 2 minutes, if the player's energy drops below 30% it will restore 50% of their max. To do this we'll need to create two parts: first, the skill file, and second the effect that will be applied to the player.

bundles/my-bundle/
  skills/
    secondwind.js
  effects/
    skill.secondwind.js

Skill file

'use strict';

const { SkillFlag, SkillType } = require('ranvier');

// Again, tuning knobs are at the top to make changing them easier
const interval = 2 * 60;
const threshold = 30;
const restorePercent = 50;

/**
 * Basic warrior passive
 */
module.exports = {
  name: 'Second Wind',
  type: SkillType.SKILL,
  // This 'flags' key is the first important part, we want to mark our skill as passive
  flags: [SkillFlag.PASSIVE],

  // 'effect' is the second most important, here we tell the skill what effect to apply
  // to the player
  effect: "skill.secondwind",

  // This is a passive skill but you can still configure its cooldown and manually
  // force the skill to enter a cooldown as we'll see when we build the effect
  cooldown: interval,

  // configureEffect allows the skill to modify the effect before it's applied to the
  // player
  configureEffect: effect => {
    // in this case we're customizing the default threshold and restorePercent of
    // the 'skill.secondwind' effect that we will build later
    effect.state = Object.assign(effect.state, {
      threshold,
      restorePercent,
    });

    return effect;
  },

  info: function (player) {
    return `Once every ${interval / 60} minutes, when dropping below ${threshold} energy, restore ${restorePercent}% of your max energy.`;
  }
};

Effect file

This will be a brief refresher on effects. See the Effect for more detail.

'use strict';

const { EffectFlag, Heal } = require('ranvier');

/**
 * Implementation effect for second wind skill
 */
module.exports = {
  config: {
    name: 'Second Wind',
    type: 'skill:secondwind'
  },
  flags: [EffectFlag.BUFF],
  listeners: {
    // we want to listen for any type the player takes damage to one of their attributes
    damaged: function (damage) {
      // ignore any damage that isn't to energy
      if (damage.attribute !== 'energy') {
        return;
      }

      // manually check our cooldown
      if (this.skill.onCooldown(this.target)) {
        return;
      }

      // don't do anything if they have more than 30% of their max energy. Note that
      // 'threshold' was configured by the skill's configureEffect function
      if ((this.target.getAttribute('energy') / this.target.getMaxAttribute('energy')) * 100 > this.state.threshold) {
        return;
      }

      Broadcast.sayAt(this.target, "<bold><yellow>You catch a second wind!</bold></yellow>");
      // create the Heal to heal the player's energy
      const amount = Math.floor(this.target.getMaxAttribute('energy') * (this.state.restorePercent / 100));
      const heal = new Heal('energy', amount, this.target, this.skill);
      heal.commit(this.target);

      // manually start the cooldown of the skill
      this.skill.cooldown(this.target);
    }
  }
};

Customizing Cooldowns

As demonstrated above a skill can be configured to have a cooldown length. The cooldown is implemented internally by an effect with the id cooldown. You can customize the details of this effect by creating your own effect with the id cooldown in a bundle which will override the default. See the Effects guide for more detail.