models/character.js

import { Builder } from "../string_utils.js";
import { sentenceCase, startLowercase, traitsToString } from "../string_utils.js";
import { is, sum } from "../utils.js";
import Model from "./model.js";

/**
 * A player or NPC character.
 *
 * @class Character
 * @extends Model
 *
 * @constructor
 * @param [params] {Object} parameters
 */
class Character extends Model {
  constructor({
    name,
    gender = "male",
    age = 0,
    honorific,
    degree,
    clothing,
    possessions,
    traits = {},
    description = [],
    tags = new Set(),
  } = {}) {
    super({ tags });
    this.name = name;
    this.profession = null; // moves it up in prop iteration
    this.gender = gender;
    this.age = age;
    this.honorific = honorific;
    this.description = description;
    this.degree = degree;
    this.traits = traits;
    this.clothing = clothing;
    this.possessions = possessions;
  }
  /**
   * Is this character male?
   *
   * @property male
   * @return {Boolean} True if male, false if female.
   */
  get male() {
    return this.gender === "male";
  }
  /**
   * The total number of assigned trait points for this character is
   * referred to as the character's "points".
   *
   * @property points
   * @return {Number} The total number of trait points assigned to this character
   */
  get points() {
    return sum(Object.values(this.traits));
  }
  /**
   * Personal pronoun
   *
   *     character.gender
   *     => "female"
   *     character.personal
   *     => "she"
   *
   * property personal
   * @return {String} "he" or "she"
   */
  get personal() {
    return this.gender === "male" ? "he" : "she";
  }
  /**
   * Objective pronoun
   *
   *     character.gender
   *     => "female"
   *     character.objective
   *     => "her"
   *
   * @property objective
   * @return {String} "him" or "her"
   */
  get objective() {
    return this.gender === "male" ? "him" : "her";
  }
  /**
   * Reflexive pronoun (arguably redundant with the personal pronoun, may remove).
   *
   *     character.gender
   *     => "female"
   *     character.reflexive
   *     => "herself"
   *
   * @property reflexive
   * @return {String} "himself" or "herself"
   */
  get reflexive() {
    return this.gender === "male" ? "himself" : "herself";
  }
  /**
   * Possessive pronoun
   *
   *     character.gender
   *     => "female"
   *     character.possessive
   *     => "her"
   *
   * @property possessive
   * @return {String} "his" or "her"
   */
  get possessive() {
    return this.gender === "male" ? "his" : "her";
  }
  /**
   * Change a trait by a set number of points.
   *
   * @param traitName {String} The trait to change
   * @param value {Number} The amount to add or subtract. If the value is zero or less
   * after change, the trait will be removed.
   */
  changeTrait(traitName, value = 0) {
    this.traits[traitName] = this.traits[traitName] ?? 0;
    this.traits[traitName] += value;
    if (this.traits[traitName] <= 0) {
      delete this.traits[traitName];
    }
  }
  /**
   * Does this character have a trait?
   *
   * @method trait
   * @param traitName {String} The name of the trait
   * @return {Number}
   * the value of the trait, or 0 if the character does not have the trait
   */
  trait(traitName) {
    return this.traits[traitName] ?? 0;
  }
  #hasBag(name) {
    return is(this[name], "Object") && this[name].entries.length;
  }
  toString({ format = "long" } = {}) {
    // short, medium
    var b = new Builder();
    b.append("<p>");
    b.if(this.honorific, `${this.honorific} `);
    b.if(this.name, this.name.toString());
    b.if(this.degree, `, ${this.degree}`);
    b.if(!this.honorific && !!this.profession, ", " + this.profession);
    b.append(". ");
    if (!!this.status) {
      b.append(`${sentenceCase(this.status)} (would be ${this.age}). `);
    } else if (this.age) {
      b.append(`Age ${this.age}. `);
    }
    b.if(this.description.length, `${this.description.join(" ")}. `);
    b.if(this.#hasBag("clothing"), () => `Wearing ${startLowercase(this.clothing.toString())} `);
    b.if(Object.keys(this.traits).length, traitsToString(this.traits) + ". ");
    b.append("</p>");
    b.if(
      format === "long" && this.#hasBag("possessions"),
      () => `<p>Possessions: ${this.possessions.toString()}</p>`
    );
    return b.toString().replaceAll("..", ".").trim();
  }
}

export default Character;