RPG Library: Limiting Magic - ThePix/QuestJS GitHub Wiki

Learning Spells

By default the player can learn any spell she can find. You can give the player a "maxSpellLevel" functon attribute to limit her to spells of a certain level. This example limits the player to spells of her level or below.

createItem("me", RPG_PLAYER(), {
  loc:"practice_room",
  regex:/^(me|myself|player)$/,
  maxSpellLevel:function() { return this.level },
  ...

You can give the player a custom "isSpellLearningAllowed" functon attribute if you want more control; it will be sent the spell and the source of the spell. It should return true if the spell can be learnt, or give a message and return false otherwise. This example stops the player learning spells of their opposed element.

createItem("me", RPG_PLAYER(), {
  loc:"practice_room",
  regex:/^(me|myself|player)$/,
  isSpellLearningAllowed:function(spell) { 
    if (spell.element && rpg.elements.opposed(spell.element) === player.element) {
      return falsemsg("Can't learn a spell from the element of " + spell.element)
    }
    return true
  },
  ...

Casting Spells

It is often desirable to limit how much magic can be expended. There are several approaches to this.

Unlimited Magic

More common in interactive fiction; the character is limited more by the applicability of the spell. An unlock spell is only useful while there are locks to unlock, so is limited anyway.

A perfectly reasonable solution is not to give the player powerful spells, if that works for your game. I think this works better for games with little of no character progression. If you start with a level one wizard, but by the end you are level ninety four, you are going to expect to be throwing around some pretty impressive spells. I would suggest that this works better with games with less combat. If the player can cast Mega-firestorm every turn, she is going to do so.

This is the default; nothing has to done to implement.

Power points or mana

The character has certain amount of magical energy; casting a spell expends some of that, the more powerful the spell, the more is used. When it is all used, no spells can be cast. This is common in many table-top RPGs, though not used in D&D.

There are four issues here - determining the maximum mana, testing if the player has enough mana for the spell, reducing the mana when the spell is cast and renewing mana.

I am going to skip over the first and just say the player has 10 mana; it very much depends on how you want your game. The second and third are accomplished by giving the player the built-in effect "Limited mana". The player then looks like this.

createItem("me", RPG_PLAYER(), {
  loc:"practice_room",
  regex:/^(me|myself|player)$/,
  health:100,
  mana:10,
  maxMana:10,
  activeEffects:['Limited mana'],
  offensiveBonus_spell:5,
  offensiveBonus:3,
  examine:"A regular spell-slinging guy.",
})

For the last, you need to decide in what situation the player gets her mana back. It could be touching the Great Orb, drinking a potion, whatever. This is the code:

  player.mana = player.maxMana

Fire and forget

Also known as Vancian magic, the character learns a set of spells at the start of the day. When a spell is cast, it is forgotten, and cannot be cast again until it is re-learnt. This is (or was until fourth edition?) the D&D approach.

You can use the "Fire and forget" effect to handle forgetting spells, and set "maxSpellLevel" and "maxSpellPoints" function attributes to limit what can be learnt. The "maxSpellLevel" attribute was discussed earlier. The "maxSpellPoints" determines the maximum number of levels of spells - that is, if you add the level of each known spell together, it cannot be greater than this.

createItem("me", RPG_PLAYER(), {
  loc:"practice_room",
  regex:/^(me|myself|player)$/,
  health:100,
  level:3,
  activeEffects:['Fire and forget'],
  maxSpellLevel:function() { return this.level },
  maxSpellPoints:function() { return 2 * this.level },
  offensiveBonus_spell:5,
  offensiveBonus:3,
  examine:"A regular spell-slinging guy.",
})

As the player is level 3, she could learn two level 3 spells, or six level 1 spells, or one each level 1, 2 and 3, etc.

You also need to limit when the player can learn her spell. One approach would be to have a spell font; the player goes to the spell font to learn spells, then heads into the wilderness.

Cooldown

After casting a spell, there is a cooldown time during which the spell - or possibly all spells - cannot be cast. This is common in computer RPGs, as the extra maths involved can be readily done by the computer and characters in computer RPGs rarely sleep.

This is going to work best with a very combat-orientated game. Outside of combat there is little to stop the player casting a spell, then just waiting for the cooldown to pass.

There are two approaches to cooldown; either an overall cooldown for the player or individual cooldown for each spell. The latter is significantly more complicated to implement, so I will be doing the former. You can use the built-in "Spell cooldown" effect, which prevents spells being cast for a number of turns equal to the spell level, but you do need to set up an event handler on the player.

createItem("me", RPG_PLAYER(), {
  loc:"practice_room",
  regex:/^(me|myself|player)$/,
  health:100,
  activeEffects:['Spell cooldown'],
  offensiveBonus_spell:5,
  offensiveBonus:3,
  examine:"A regular spell-slinging guy.",
})

You can add a custom "getSpellCooldownDelay" function to set your own cooldown delay This example, it lasts for twice the spell level, minus the player level.

createItem("me", RPG_PLAYER(), {
  loc:"practice_room",
  regex:/^(me|myself|player)$/,
  health:100,
  activeEffects:['Spell cooldown'],
  offensiveBonus_spell:5,
  offensiveBonus:3,
  getSpellCooldownDelay:function(skill) {
    return 2 * skill.level - this.level
  },
  examine:"A regular spell-slinging guy.",
})

You could readily modify this to affect skills rather than spells - and there are a built-in "Weapon attack cooldown" and "Natural attack cooldown" effects, and "getWeaponAttackCooldownDelay" and "getNaturalAttackCooldownDelay" function attributes. Your combat skills would than have a cooldown, while spells perhaps require mana.

Bad things may happen

A less common approach is to have a chance that something will go wrong with each spell cast; the more often the player casts a spell, the greater the chances of the impending apocalypse. Perhaps a better approach is to have low level spells free, but the powerful ones come with a significant risk. An issue is that the player can easily cheat - just save before casting, and if the world ends reload and try again. Is that a problem? You will need to decide that for yourself.

You could have an accumulator, and when the player has cast so many spells, the world ends, but this is in effect the same as having power points except there is no way to replenishment them.

Nevertheless, this is how to do it. First you need a new effect. Note that spells with no level do not have any adverse effect.

new Effect("Casting magic leads to the apocalypse", {
  modifyOutgoingAttack:function(attack) {
    if (!attack.skill) return
    if (!attack.skill instanceof Spell) return
    if (!attack.skill.level) return
    player.apocalypseAccumulator += attack.skill.level
    else if (player.apocalypseAccumulator > 100) {
      msg("The world shatters around you.")
      io.finish()
    }
    else if (player.apocalypseAccumulator > 70) {
      msg("The world seems to darken around you.")
    }
    else if (player.apocalypseAccumulator > 40) {
      msg("You get that feeling of someone walking across your grave.")
    }
  },
})

Then the player needs to be set up to have the effect.

createItem("me", RPG_PLAYER(), {
  loc:"practice_room",
  regex:/^(me|myself|player)$/,
  health:100,
  activeEffects:['Casting magic leads to the apocalypse'],
  apocalypseAccumulator:0,
  offensiveBonus_spell:5,
  offensiveBonus:3,
  examine:"A regular spell-slinging guy.",
})