Pre-release
AdventureJS Docs Downloads
Score: 0 Moves: 0
// Vessel.js
(function () {
  /*global adventurejs A*/

  /**
   * @ajspath adventurejs.Atom.Vessel
   * @augments adventurejs.Atom
   * @class adventurejs.Vessel
   * @ajsinternal
   * @ajsnavheading BaseClasses
   * @param {String} game_name Name of top level game instance that is scoped to window.
   * @param {String} name Instance name.
   * @summary Class added to an Aspect (aka aspects) to allow it to hold substances.
   * @tutorial Substances_Vessels
   * @classdesc
   * <p>
   * <strong>Vessel</strong> is a special class that
   * adds the ability to contain substances to
   * {@link adventurejs.Tangible|Tangible}
   * {@link adventurejs.Asset|Assets}.
   * Vessels must exist within
   * {@link adventurejs.Aspect|Aspects},
   * which exist within
   * {@link adventurejs.Tangible|Tangibles}.
   * In other words,
   * <code class="property">Tangible.Aspect.Vessel</code>,
   * or as a practical example: <code class="property">sink.aspects.in.vessel</code>.
   * </p>
   * <h3 class="examples">Example:</h3>
   * <pre class="display"><code class="language-javascript">MyGame.createAsset({
   *   class: "Bowl",
   *   name: "stone bowl",
   *   place: { on: "blood stained shrine" },
   *   descriptions:{look:"It's a stained, chipped stone bowl. ",}
   *   in:
   *   {
   *     vessel: {
   *       maxvolume: 500,
   *       volume: 350,
   *       substance_id: "viscous fluid",
   *     },
   *   },
   * });
   * </code></pre>
   * <p>
   * To learn more, see how to use
   * <a href="/doc/Substances_AboutSubstances.html">Substances</a>.
   * </p>
   **/
  class Vessel extends adventurejs.Atom {
    constructor(name, game_name, parent_id) {
      super(name, game_name);
      this.class = "Vessel";
      if ("string" === typeof parent_id && parent_id) {
        this.parent_id = parent_id;
      }

      this.list_in_room = true; // <- to contents
      this.list_in_examine = true; // <- to contents

      /**
       * Substance vessels are not automatically made
       * known with their parent. For example, picture a sealed
       * oil can with unidentified contents: the can will become
       * known as soon as the player enters a room with it; but
       * the can's contents should not become known until the
       * player opens it. Set this to true if you want a vessel
       * to become known with its parent, for example: water in
       * a drinking glass might be immediately apparent.
       * @var {Boolean} adventurejs.Vessel#know_with_parent
       * @default false
       */
      this.know_with_parent = false;

      /**
       * The knowability of substance vessels is indepenent from
       * the vessel's parent asset. For example, picture a sealed
       * oil can with unidentified contents: the can will become
       * known as soon as the player enters a room with it; but
       * the can's contents should not become known until the
       * player opens it.
       * @var {Boolean} adventurejs.Vessel#known
       * @default false
       */
      this.known = false;

      this.with_classes = []; // defined

      this.substance_id = "";
      this.can_only_contain_these_substances = []; // defined
      this.maxvolume = 0;
      this.can_overflow = true;
      this.volume = 0;
      this.mix_volume = 0;
      this.drain_id = "";

      this.temperature = this.game.settings.room_temperature;
      this.temperature_equilibrates = false; // TODO equilibrium calculations
      this.density = 1; // water // TODO function of temp

      //this.state = this.game.settings.states.LIQUID; // liquid is default
      // when state change occurs, swap substances

      // for drains
      this.is_drain = false;
      this.max_volume_of_flow_per_turn = -1;
      this.rate_of_flow = -1; // 0 to 1

      // for emitters
      this.is_emitter = false;
      this.is_emitting = false;
      this.target_id = "";

      this.reservoir = false;
    }

    get temperature() {
      return this.__temperature;
    }
    set temperature(temperature) {
      temperature = Number(temperature);
      if (isNaN(temperature) || "number" !== typeof temperature) {
        var msg =
          "Vessel.js > instance " +
          this.id +
          " received invalid temperature " +
          String(temperature);
        this.game.log("L1404", "error", "high", msg, "Vessel");
      } else {
        //this.game.log( "log", "high", "Vessel.js > instance " + this.id + " received valid temperature " + temperature, "Vessel" );
        this.__temperature = temperature;
      }
    }

    /**
     * @var {Getter} adventurejs.Vessel#can_drain
     * @default false
     */
    get can_drain() {
      // is this a drain?
      if (this.is_drain && !this.game.getAsset(this.parent_id).is.plugged) {
        return true;
      }

      // or does it have a separate drain asset?
      else if (this.drain_id) {
        var drain = this.game.getAsset(this.drain_id);
        if (drain && !drain.is.plugged) {
          return true;
        }
      }

      return false;
    }

    get parent_id() {
      return this.__parent_id;
    }
    set parent_id(id) {
      id = A.serialize(id);
      this.__parent_id = id;
    }

    get drain_id() {
      return this.__drain_id;
    }
    set drain_id(id) {
      id = A.serialize(id);
      this.__drain_id = id;
    }

    get substance_id() {
      if (this.volume > 0 || this.is_emitter) {
        return this.__substance_id;
      } else return "";
    }
    set substance_id(substance_id) {
      this.__substance_id = A.serialize(substance_id);
    }

    get maxvolume() {
      if (isNaN(this.__maxvolume)) return 0;
      return this.__maxvolume;
    }
    set maxvolume(volume) {
      if (Infinity === volume) {
        this.__maxvolume = Infinity;
        //this.__volume = Infinity;
        return;
      }

      this.__maxvolume = A.convertVolume.call(this, volume, this.parent_id);
    }

    get volume() {
      if (isNaN(this.__volume)) return 0;
      return this.__volume;
    }
    set volume(volume) {
      //console.warn( "volume: " + volume );
      if (true === this.is_drain) {
        this.__volume = 0;
        return;
      }
      if (Infinity === volume) {
        // infinite
        this.__volume = Infinity;
        this.__maxvolume = Infinity;
        return;
      }

      this.__volume = A.convertVolume.call(this, volume, this.parent_id);
    }

    get percent_of_maxvolume() {
      if (isFinite(this.maxvolume)) {
        return this.volume / this.maxvolume;
      } else {
        return 1;
      }
    }
    set percent_of_maxvolume(percent) {
      if (isFinite(this.maxvolume)) {
        this.volume = this.maxvolume * percent;
      } else {
        this.volume = this.maxvolume;
      }
    }

    get game_name() {
      return this.__game_name;
    }
    set game_name(game_name) {
      this.__game_name = game_name;
    }

    get game() {
      return window[this.game_name] || false;
    }

    get with_classes() {
      return this.__with_classes;
    }
    set with_classes(arr) {
      if (false === Array.isArray(this.__with_classes)) {
        this.__with_classes = [];
      }
      this.__with_classes = A.validateClassList(arr);
    }

    get can_only_contain_these_substances() {
      return this.__can_only_contain_these_substances;
    }
    set can_only_contain_these_substances(arr) {
      if (false === Array.isArray(this.__can_only_contain_these_substances)) {
        this.__can_only_contain_these_substances = [];
      }
      this.__can_only_contain_these_substances = A.validateAssetList(arr);
    }

    get max_volume_of_flow_per_turn() {
      if (isNaN(this.__max_volume_of_flow_per_turn)) return 0;
      return this.__max_volume_of_flow_per_turn;
    }
    set max_volume_of_flow_per_turn(volume) {
      if (Infinity === volume) {
        this.__max_volume_of_flow_per_turn = Infinity;
        return;
      }

      this.__max_volume_of_flow_per_turn = A.convertVolume.call(
        this,
        volume,
        this.parent_id
      );
    }

    get volume_of_flow_per_turn() {
      return this.max_volume_of_flow_per_turn * this.rate_of_flow;
    }

    get mass() {
      if (!this.substance) return 0;
      return this.volume * this.density;
    }

    get mix_mass() {
      if (!this.substance) return 0;
      return this.mix_volume * this.density;
    }

    get substance() {
      return this.game.getAsset(this.__substance_id);
    }

    get heat_capacity() {
      if (!this.substance) return 0;
      return this.specific_heat * this.mass;
    }

    /**
     * <strong>empty</strong> sets the volume of this vessel to 0.
     * @memberOf adventurejs.Vessel
     * @method adventurejs.Vessel#empty
     * @returns {number}
     */
    empty() {
      if (!this.is_emitting && Infinity !== this.volume) {
        this.volume = 0;
      }
      return this.volume;
    }

    /**
     * <strong>getVolume</strong> is called to get the current volume of this vessel.
     * Takes into consideration whether vessel is emitting.
     * @memberOf adventurejs.Vessel
     * @method adventurejs.Vessel#getVolume
     * @returns {number}
     */
    getVolume() {
      if (this.is_emitting) {
        return this.volume_of_flow_per_turn;
      } else return this.volume;
    }

    /**
     * <strong>setVolume</strong> is called to set a
     * vessel to a specified volume.
     * @memberOf adventurejs.Vessel
     * @method adventurejs.Vessel#setVolume
     * @param {number} volume The volume to set the vessel to.
     */
    setVolume(volume) {
      this.volume = volume;
      return this.volume;
    }

    /**
     * <strong>addVolume</strong> is called to raise the
     * vessel's volume by a specified amount.
     * @memberOf adventurejs.Vessel
     * @method adventurejs.Vessel#addVolume
     * @param {number} volume The volume to add to the vessel.
     */
    addVolume(volume) {
      if (Infinity !== this.volume) {
        volume = this.volume + volume;
        if (volume > this.maxvolume) volume = this.maxvolume;
        this.volume = volume;
      }
      return this.volume;
    }

    /**
     * <strong>addSubstance</strong> tries to add specified
     * volume of substance. If vessel already contains another
     * substance, will try to mixwith.
     * @memberOf adventurejs.Vessel
     * @method adventurejs.Vessel#addSubstance
     * @param {number} volume The volume to add to the vessel.
     * @param {String} substance_id The id of a substance to add to the vessel.
     */
    addSubstance(volume, substance_id) {
      if (isNaN(volume) || volume <= 0 || Infinity === this.volume) {
        return { volume: this.volume, substance: this.substance_id };
      }
      if (this.can_drain) {
        return { volume: 0, substance: "" };
      }
      if (this.volume <= 0) {
        this.substance_id = substance_id;
      } else if (this.volume > 0 && substance_id !== this.substance_id) {
        this.substance_id = this.mixwith(substance_id);
      }
      this.volume = Math.min(this.volume + volume, this.maxvolume);
      return { volume: this.volume, substance: this.substance_id };
    }

    /**
     * <strong>getSubstance</strong> is called to get the
     * substance of this vessel.
     * @memberOf adventurejs.Vessel
     * @method adventurejs.Vessel#getSubstance
     * @returns {number}
     */
    getSubstance() {
      return this.game.getAsset(this.substance_id);
      // return this.substance_id;
    }

    /**
     * <strong>mixwith</strong> checks for a substance mixwith.
     * @memberOf adventurejs.Vessel
     * @method adventurejs.Vessel#mixwith
     * @param {String} substance_id
     */
    mixwith(substance_id) {
      if (this.substance?.mixwith[substance_id]) {
        return this.substance.mixwith[substance_id];
      } else if (this.game.getAsset(substance_id)?.mixwith[this.substance_id]) {
        return this.game.getAsset(substance_id).mixwith[this.substance_id];
      }
      return this.substance_id;
    }

    /**
     * <strong>subtractVolume</strong> is called to reduce the
     * vessel's volume by a specified amount.
     * @memberOf adventurejs.Vessel
     * @method adventurejs.Vessel#subtractVolume
     * @param {number} volume The volume to subtract from the vessel.
     */
    subtractVolume(volume) {
      if (Infinity !== this.volume) {
        volume = this.volume - volume;
        if (0 > volume) volume = 0;
        this.volume = volume;
      }
      return this.volume;
    }
  }

  adventurejs.Vessel = Vessel;
})();