More dev tips: combinable items

Kln

I am still on my "survival horror" experiment. Today I present to you how to make items that can combine with other items.
Combinable objects are not components, in that they are not parts of a whole, but rather two wholes that can interact with each other

Step 1 - Prepare a template
N.B.: This template uses COUNTABLE as base, but it can work with a simple TAKEABLE as well.

const INVENTORY_ITEM = function(countableLocs) {
  const res = $.extend({}, COUNTABLE(countableLocs))

  res.combination = false;

  res.getListAlias = function(loc) {
    let itemAlias = (this.countAtLoc(loc) > 1) ? sentenceCase(this.pluralAlias) : sentenceCase(this.alias)
    return itemAlias + " (" + this.countAtLoc(loc) + ")";
  };

  return res;
}

Step 3 - Create a new command

commands.push(
  new Cmd('CombineWith', {
    //npcCmd:true,
    rules:[cmdRules.canManipulate, cmdRules.isHere],
    regex:/^(?:combine) (.+) (?:with) (.+)$/,
    objects:[
      //{special:'ignore'},
      {scope:parser.isPresent},
      //{special:'ignore'},
      {scope:parser.isPresent},
    ],
    script:function(objects) {
      const obj = objects[0][0]
      const obj2 = objects[1][0]
      
      // We check the entry of the first object, using the name of the second object as key. If the function does not find the entry or does not exist, it will return false. We check the second object just in case.
      let comboResult = false;
      
      if (obj.combination) {
        comboResult = obj.combination(obj2);
      }
      if (comboResult) {
        return world.SUCCESS;
      }
      
      if (obj2.combination) {
        comboResult = obj2.combination(obj);
      }
      if (comboResult) {
        return world.SUCCESS;
      }

      this.default({char:player, item:obj})
      return world.FAILED; 
    },
    defmsg: "These two items cannot be combined together",
  })
);

Step 4 Create your combinable items
N.B.: here, I go for the classic "green herb + red herb = powder" combo, but what happens during a combination can be anything, from modifying the base object to unlock a door or change a story flag.
N.B.2: notice that the combination function of the second object mirrors the one of the first object, in case the player combines the items in the "reverse" order.

createItem("greenHerb", INVENTORY_ITEM({"cellA": 1}), {
  loc: "cellA",
  alias: "green herb",
  examine: "A green herb with curative properties. It can be combined with other herbs, for a more powerful effect.",

  combination: function(otherItem) {
    switch (otherItem.name) {
      case "redHerb":
        if (this.countAtLoc(player.name) > 0) {
          this.takeFrom(player.name, 1);
        } else {
          this.takeFrom(player.loc, 1);
        }
        
        if (otherItem.countAtLoc(player.name) > 0) {
          otherItem.takeFrom(player.name, 1);
        } else {
          otherItem.takeFrom(player.loc, 1);
        }

        w["yellowPowder"].giveTo(player.name, 1);
        return true;
      default:
        return false;
    }
  }

})

createItem("redHerb", INVENTORY_ITEM({"cellA": 1}), {
  loc: "cellA",
  alias: "red herb",
  examine: "This red herb is poisonous if ingested alone. However, when combined to a green herb, it increases tremendously its curative properties.",

  combination: function(otherItem) {
    switch (otherItem) {
      case "greenHerb":
        if (this.countAtLoc(player.name) > 0) {
          this.takeFrom(player.name, 1);
        } else {
          this.takeFrom(player.loc, 1);
        }
        
        let otherItemObject = w[otherItem]
        if (otherItemObject.countAtLoc(player.name) > 0) {
          otherItemObject.takeFrom(player.name, 1);
        } else {
          otherItemObject.takeFrom(player.loc, 1);
        }
        w["yellowPowder"].giveTo(player.name, 1);
        return true;
      default:
        return false;
    }
  }

})

createItem("yellowPowder", INVENTORY_ITEM(), {
  alias: "yellow powder",
  examine: "This powder combines a green herb and a red herb. Ingesting it allows a full recovery.",

})

Step 5 - Test the command
combine green herb with red herb
Expected effect: Green herb and red herb should disappear from their location or be depleted by 1. A single yellow powder item appears in the player's inventory.


Kln

Here is an update. We are still combining items, but this time we want to use items that you want to count as individual objects, in case you want to implement an inventory limit of some kind.

The new template we will use:

const INVENTORY_ITEM = function() {
  const res = $.extend({}, TAKEABLE_DICTIONARY)

  res.combination = false;

  return res;
}

Here is how the combination functions will look like, in the items:

createItem("greenHerb1", INVENTORY_ITEM(), {
  loc: "cellA",
  alias: "green herb",
  rootName: "greenHerb", //Used for the combination
  examine: "A green herb with curative properties. It can be combined with other herbs, for a more powerful effect.",

  combination: function(otherItem) {
    if (!otherItem.rootName) {
      return false;
    }

    switch (otherItem.rootName) {
      case "redHerb":
        this.loc = false;
        otherItem.loc = false;
        //We clone yellowPowder to give an instance to the player. The name uses a timestamp to avoid duplicates
        cloneObject(w.yellowPowder, player.name, 'yellowPowder' + Date.now())
        return true;
      default:
        return false;
    }
  }

})

createItem("redHerb1", INVENTORY_ITEM(), {
  loc: "cellA",
  alias: "red herb",
  rootName: "redHerb",
  examine: "This red herb is poisonous if ingested alone. However, when combined to a green herb, it increases tremendously its curative properties.",

  combination: function(otherItem) {
    if (!otherItem.rootName) {
      return false;
    }

    switch (otherItem.rootName) {
      case "redHerb":
        this.loc = false;
        otherItem.loc = false;
        //We clone yellowPowder to give an instance to the player. The name uses a timestamp to avoid duplicates
        cloneObject(w.yellowPowder, player.name, 'yellowPowder' + Date.now())
        return true;
      default:
        return false;
    }
  }

})

createItem("yellowPowder", INVENTORY_ITEM(), {
  alias: "yellow powder",
  examine: function() {
    msg("This powder combines a green herb and a red herb. Ingesting it allows a full recovery. Its real name is: " + this.name)
  }

})

As you can notice, we use a "rootName" property to identify the possible combination, and we make use of the "clone" function to create the yellow powder from ex-nihilo. The herbs are also removed from the game, since they are destroyed during the making of the powder.

If you want to optimize further, you can create external functions, if you use a specific combo effect several times accross the game.


Will this go into a library or into Quest 6?


Kln

This is some custom code that I wrote, and thought I could share with the rest of the community, so it is up for Pix to implement it in the core framework of Quest 6.

However, if you are already coding with Quest 6, you can copy-paste the code into data.js, or in a new javascript file that you will add in settings.files


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums