Source: rez_inventory.js

//-----------------------------------------------------------------------------
// Inventory
//-----------------------------------------------------------------------------

/**
 * Creates a new RezInventory
 * @class
 * @param {string} id assigned id
 * @param {object} attributes initial attributes
 * @description constructs @inventory game-objects
 */
function RezInventory(id, attributes) {
  this.id = id;
  this.game_object_type = "inventory";
  this.attributes = attributes;
  this.properties_to_archive = [];
  this.changed_attributes = [];
}

RezInventory.prototype = {
  __proto__: basic_object,
  constructor: RezInventory,

  /**
   * @function addItemHolderForSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @description adds an empty array to hold items for the given slot.
   */
  addSlot(slot_id) {
    const items = this.getAttribute("items");
    items[slot_id] = [];
  },

  /**
   * @function elementInitializer
   * @memberof RezInventory
   * @description called as part of the init process this creates the inital inventory slots
   */
  elementInitializer() {
    const slots = this.getAttribute("slots");
    for (const slot_id of slots) {
      this.addSlot(slot_id);
    }
  },

  /**
   * @function getItemsForSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @returns {array} contents of the specified slot
   */
  getItemsForSlot(slot_id) {
    this.validateSlot(slot_id);
    return this.getAttribute("items")[slot_id];
  },

  /**
   * @function slotIsOccupied
   * @memberof RezInventory
   * @param {string} slot_id
   * @returns {boolean} true if there is at least one item in the slot
   */
  slotIsOccupied(slot_id) {
    return this.countItemsForSlot(slot_id) > 0;
  },

  /**
   * @function getItemForSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @returns {string} id of first item in the slot
   */
  getItemForSlot(slot_id) {
    return this.getItemsForSlot(slot_id)[0];
  },

  /**
   * @function validateSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @returns {boolean} true if the given slot_id is part of this inventory
   */
  validateSlot(slot_id) {
    if(!this.getAttribute("slots").has(slot_id)) {
      throw `Inventory |${this.id}| does not have slot |${slot_id}|!`;
    };
  },

  /**
   * @function setSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {array} items array of item id's
   */
  setSlot(slot_id, item_ids) {
    this.validateSlot(slot_id);
    const items = this.getAttribute("items");
    items[slot_id] = item_ids;
  },

  /**
   * @function appendItemToSlot
   * @member RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @description appends the given item to the given slot
   */
  appendItemToSlot(slot_id, item_id) {
    this.getItemsForSlot(slot_id).push(item_id);
  },

  /**
   * @function appendItemsToSlot
   * @memberof RezInventory
   * @param {string|array} item_or_items either an item_id or array of item_id's to append to the slot
   * @description add either a single item_id or an array of item_ids to the slot
   */
  appendToSlot(slot_id, item_or_items) {
    if(typeof(item_or_items) === "string") {
      this.appendItemToSlot(slot_id, item_or_items);
    } else if(typeof(item_or_items) === "array") {
      item_or_items.forEach((item_id) => this.appendItemToSlot(slot_id, item_id));
    }
  },

  /**
   * @function setItemForSlot
   * @memberof RezInventory
   * @param {string} item_id
   * @description replaces any existing item content for the slot with this item
   */
  setItemForSlot(slot_id, item_id) {
    this.setSlot(slot_id, [item_id]);
  },

  /**
   * @function setItemsForSlot
   * @memberof RezInventory
   * @param {array} items array of item ids
   * @description replaces any existing item content for the slot with these items
   */
  setItemsForSlot(slot_id, items) {
    this.setSlot(slot_id, items);
  },

  /**
   * @function countItemsInSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @returns {integer} number of items in the given slot
   */
  countItemsInSlot(slot_id) {
    return this.getItemsForSlot(slot_id).length;
  },

  /**
   * @function slotContainsItem
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @returns {boolean} true if the item_id is in the slot
   */
  slotContainsItem(slot_id, item_id) {
    return this.getItemsForSlot(slot_id).some((an_item_id) => item_id === an_item_id);
  },

  /**
   * @function containsItem
   * @memberof RezInventory
   * @param {string} item_id
   * @returns {string|null} slot_id of the slot containing the item, or null if no slot contains it
   */
  containsItem(item_id) {
    for(const slot_id of this.getAttribute("slots")) {
      if(this.slotContainsItem(slot_id, item_id)) {
        return slot_id;
      }
    }
    return null;
  },

  /**
   * @function itemFitsInSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @returns {boolean} true if the item will fit with any other contents of the slot
   */
  itemFitsInSlot(slot_id, item_id) {
    const slot = $(slot_id);
    if(slot.has_capacity) {
      const used_capacity = this.getItemsForSlot(slot_id).reduce((amount, item_id) => {
        const item = $(item_id);
        return amount + item.size;
      });

      return used_capacity + item.size <= slot.capacity;
    } else {
      return true;
    }
  },

  /**
   * @function slotAcceptsItem
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @returns {boolean} true if the given item has a type that this slot accepts
   */
  slotAcceptsItem(slot_id, item_id) {
    this.validateSlot(slot_id);

    const slot = $(slot_id);
    const accepts = slot.getAttributeValue("accepts");
    const item = $(item_id);
    const type = item.getAttributeValue("type");

    return type === accepts;
  },

  /**
   * @function canAddItemForSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @returns {boolean} true if the slot accepts the item
   */
  canAddItemForSlot(slot_id, item_id) {
    const decision = new RezDecision("canItemForSlot");

    if (!this.slotAcceptsItem(slot_id, item_id)) {
      decision
        .no("slot doesn't take this kind of item")
        .setData("failed_on", "accepts");
    } else if (!this.itemFitsInSlot(slot_id, item_id)) {
      decision.no("does not fit").setData("failed_on", "capacity");
    } else if (this.owner != null) {
      const actor_decision = this.owner.checkItem(this.id, slot_id, item_id);
      if (actor_decision.result()) {
        decision.yes();
      } else {
        decision.no(actor_decision.reason()).setData("failed_on", "actor");
      }
    } else {
      decision.yes();
    }

    return decision;
  },

  /**
   * @function canRemoveItemFromSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @returns {object} RezDecision containing the result whether the item can be removed from the slot
   */
  canRemoveItemFromSlot(slot_id, item_id) {
    const decision = new RezDecision("canRemoveItemFromSlot");
    decision.default_yes();

    const item = $(item);
    decision.setData("inventory_id", this.id);
    decision.setData("slot_id", slot_id);
    item.canBeRemoved(item_decision);
    if(!item_decision.result) {
      return item_decision;
    }

    if(this.owner == null) {
      return decision;
    }

    this.owner.canRemoveItem(decision);
    return decision;
  },

  /**
   * @function addItemToSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @description adds the given item to the given slot, notifying inventory, slot & item and applying effects
   */
  addItemToSlot(slot_id, item_id) {
    const item = $t(item_id, "item");

    this.appendItemToSlot(slot_id, item_id);

    this.runEvent("insert", { slot_id: slot_id, item_id: item_id });

    const slot = $(slot_id);
    slot.runEvent("insert", { inventory_id: this.id, item_id: item_id });

    item.runEvent("insert", { inventory_id: this.id, slot_id: slot_id});

    this.applyEffects(slot_id, item_id);
  },

  /**
   * Determine whether effects should be applied to this inventory and the specified slot.
   *
   * @function shouldApplyEffects
   * @memberof RezInventory
   * @param {string} slot_id
   */
  shouldApplyEffects(slot_id) {
    if (this.owner) {
      if (this.apply_effects) {
        const slot = $(slot_id);
        return slot.apply_effects;
      } else {
        return false;
      }
    } else {
      // No owner object to apply the effect to
      return false;
    }
  },

  /**
   * @function applyEffects
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @returns {boolean} whether the effect was applied
   */
  applyEffects(slot_id, item_id) {
    if(!this.shouldApplyEffects(slot_id)) {
      return false;
    }

    const item = $(item_id);
    if (!item.hasAttribute("effects")) {
      // This item doesn't have any effects
      return false;
    }

    for (const effect_id of item.effects) {
      const effect = $t(effect_id, "effect");
      effect.apply(this.owner_id, slot_id, item_id);
    }

    return true;
  },

  /**
   * @function removeItemForSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   * @description removes the specified item from the specified inventory slot
   */
  removeItemFromSlot(slot_id, item_id) {
    const contents = this.getItemsForSlot(slot_id);
    if (!contents.includes(item_id)) {
      throw (
        "Attempt to remove item |" +
        item_id +
        "| from slot |" +
        slot_id +
        "| on inventory |" +
        this.id +
        "|. No such item found!"
      );
    }

    this.setItemsForSlot(slot_id, contents.filter((id) => {
      return id != item_id;
    }));

    const slot = $(slot_id);
    slot.runEvent("remove", { inventory: this.id, item: item_id });

    this.runEvent("remove", { slot: slot_id, item: item_id });

    this.removeEffects(slot_id, item_id);
  },

  /**
   * @function removeEffects
   * @memberof RezInventory
   * @param {string} slot_id
   * @param {string} item_id
   */
  removeEffects(slot_id, item_id) {
    if(!this.shouldApplyEffects(slot_id)) {
      return false;
    }

    const item = $(item_id);
    if (!item.hasAttribute("effects")) {
      return false;
    }

    for (const effect_id of item.effects) {
      const effect = $t(effect_id, "effect");
      effect.remove(this.owner_id, slot_id, item_id);
    }
  },

  /**
   * @function clearSlot
   * @memberof RezInventory
   * @param {string} slot_id
   * @description remove all items from give slot, removes any effects granted by those items
   */
  clearSlot(slot_id) {
    const items = this.getItemsForSlot(slot_id);
    items.forEach((item_id) => this.removeItemFromSlot(slot_id, item_id));
  }
};

window.RezInventory = RezInventory;