Source: GirlManager.js

/**
 * Body parts throughout the whole game use these keys
 * @typedef GirlManager.bodyPart
 * @type 'Hands' | 'Feet' | 'Throat' | 'Tits' | 'Pussy' | 'Anal'
 */

/**
 * The GirlManager class lets you check and change anything related to the girls.
 * @class GirlManager
 */
class GirlManager {
    constructor() {
        this._girls = ['Queen', 'Suki', 'Esxea', 'Scarlett', 'Ardura'];
        /**
         * currentGirl is the currently selected girl. It's the girl displayed on the HUD.
         * @name GirlManager#currentGirl
         * @type {string}
         */
        this.currentGirl = 'Queen';
    }

    /**
     * Returns the gameData object for the girl
     * @method getGirl
     * @memberOf GirlManager
     * @instance
     * @param {string} girlID
     * @returns {object}
     */
    getGirl(girlID) {
        return gameData.character[girlID];
    }

    /**
     * Returns an array with the ids of the girls
     * @method getGirls
     * @instance
     * @memberOf GirlManager
     * @return {array<string>}
     */
    getGirls() {
        return this._girls;
    }

    /**
     * @method getGirlContainer
     * @memberOf GirlManager
     * @instance
     * @param {Phaser.Scene} context
     * @param {string} girlID
     * @param {object} [layers] - If you want to change any of the layers manually
     * @param {string} [layers.body]
     * @param {string} [layers.face]
     * @param {string} [layers.clothes]
     * @param {array} [layers.additional] - An optional array of textures that will be stacked on top of the finished image. This is for anything extra like cum
     * @param {boolean} [input] - If true, clicking the container will display the girl's image
     * @returns Phaser.GameObjects.Container
     */
    getGirlContainer(context, girlID, layers, input) {
        let girlObj = GAME.girl.getGirl(girlID);

        layers = layers || {};
        layers.body = layers.body || "Default";
        layers.face = layers.face || "Neutral";
        layers.clothes = layers.clothes || girlObj.Clothes;
        layers.additional = layers.additional || [];

        let container = context.add.container();

        container.add(context.add.image(0, 0, girlID + "-Body-" + layers.body).setOrigin(0.5, 1).setName('body'));
        container.add(context.add.image(0, 0, girlID + "-Face-" + layers.face).setOrigin(0.5, 1).setName('face'));
        container.add(context.add.image(0, 0, girlID + "-Clothes-" + layers.clothes).setOrigin(0.5, 1).setName('clothes').setTint(gameData.clothes[layers.clothes].Tint[0]));
        container.getByName('clothes').clothID = layers.clothes;

        for (let i in layers.additional) {
            container.add(context.add.image(0, 0, layers.additional[i]).setOrigin(0.5, 1).setName(layers.additional[i]));
        }

        if (input === true) {
            container.setInteractive({
                hitArea: container.getByName('body').getBounds(),
                hitAreaCallback: Phaser.Geom.Rectangle.Contains,
                useHandCursor: true
            })
                .on('pointerup', () => {
                    let imageLayers = [];
                    container.iterate((child) => {
                        if (child.visible === true) {
                            let tint = 0xFFFFFF;
                            if (child.hasOwnProperty('clothID')) {
                                tint = gameData.clothes[child.clothID].Tint[0];
                            }
                            imageLayers.push({Key: child.texture.key, Tint: tint});
                        }
                    });
                    GAME.viewImage(imageLayers);
                })
        }

        container.setNaked = function (boolean) {
            container.getByName('clothes').setVisible(!boolean);
            return container;
        };

        container.setBody = function (key) {
            container.getByName('body').setTexture(key);
            return container;
        };

        container.setFace = function (key) {
            container.getByName('face').setTexture(key);
            return container;
        };

        container.setClothes = function (clothID) {
            let girl = GAME.clothes.getAllClothes()[clothID].Girl;

            container.getByName('clothes').setTexture(girl + "-Clothes-" + clothID).setTint(gameData.clothes[clothID].Tint[0]);
            container.getByName('clothes').clothID = clothID;
            return container;
        };

        container.addAdditional = function (key) {
            container.add(context.add.image(0, 0, key).setOrigin(0.5, 1).setName('additional' + layers.additional.length + 1));
            layers.additional.push(key);
            return container;
        };

        container.removeAdditional = function (key) {
            container.remove(container.getByName(key), true);
            layers.additional.splice(layers.additional.indexOf(key), 1);
            return container;
        };

        container.removeAllAdditionals = function () {
            for (let i = layers.additional.length - 1; i >= 0; i--) {
                container.remove(container.getByName(layers.additional[i]), true);
                layers.additional.splice(i, 1);
            }
            return container;
        };

        container.setGirl = function (girlID) {
            let girlObj = GAME.girl.getGirl(girlID);

            container.removeAllAdditionals();

            container.iterate((child) => {
                child.setVisible(true);
            });

            container.setBody(girlID + "-Body-" + girlObj.Body);
            container.setFace(girlID + "-Face-Neutral");
            container.setClothes(girlObj.Clothes);
            return container;
        };

        return container;
    }

    /**
     * Returns the girl's clothes
     * @method getGirlClothes
     * @instance
     * @memberOf GirlManager
     * @param {string} girlID
     * @returns {string}
     */
    getGirlClothes(girlID) {
        return gameData.character[girlID].Clothes;
    }

    /**
     * Subtracts the amount from the girl's current stamina
     * @method loseStamina
     * @instance
     * @memberOf GirlManager
     * @param girl
     * @param amount
     */
    loseStamina(girl, amount) {
        if (amount > 0) {
            gameData.character[girl].Stamina -= amount;
            globalEvents.emit('loseStamina', {girl: girl, staminaAmount: -amount});
        }
    }

    /**
     * Adds the amount to the girl's current stamina
     * @method gainStamina
     * @instance
     * @memberOf GirlManager
     * @param girl
     * @param amount
     * @param [overMax=true] - If the added stamina should go over her maximum stamina amount
     */
    gainStamina(girl, amount, overMax) {
        if (overMax === undefined) {
            overMax = true;
        }
        if (amount > 0) {
            if (overMax === true) {
                gameData.character[girl].Stamina += amount;
            } else if (gameData.character[girl].Stamina < GAME.girl.getMaxStamina(girl)) {
                gameData.character[girl].Stamina += amount;
                if (gameData.character[girl].Stamina > GAME.girl.getMaxStamina(girl)) {
                    gameData.character[girl].Stamina = GAME.girl.getMaxStamina(girl);
                }
            }
            globalEvents.emit('gainStamina', {girl: girl, staminaAmount: amount});
        }
    };

    /**
     * Unlocks a girl. Checks to see if there is available space in the house
     * @method unlockGirl
     * @instance
     * @memberOf GirlManager
     * @param {string} girl
     * @param {boolean} force - Will raise the house capacity if it is maxed
     * @returns {boolean}
     */
    unlockGirl(girl, force) {
        if (GAME.checkMax() === true && gameData.character[girl].Unlocked === false) {
            gameData.houseCapacity += 1;
            gameData.houseUpgrades += 1;
            globalEvents.emit('refresh');
            return false;
        }

        if (gameData.character[girl].Unlocked === true) {
            return false;
        }
        gameData.character[girl].Unlocked = true;
        return true;
    }

    /**
     * Returns the total stamina for all the girls
     * @method getTotalStamina
     * @instance
     * @memberOf GirlManager
     * @returns {number}
     */
    getTotalStamina() {
        let total = 0;

        for (let i in gameData.character) {
            if (gameData.character[i].Unlocked === true) {
                total += gameData.character[i].Stamina;
            }
        }
        return parseFloat(total.toFixed(2));
    }

    /**
     * Returns the stamina of a girl
     * @method getStamina
     * @instance
     * @memberOf GirlManager
     * @param {String} [girl]
     * @return {number}
     */
    getStamina(girl) {
        return parseFloat(gameData.character[girl].Stamina.toFixed(2));
    }

    /**
     * Returns the limit of stamina a girl can have
     * @method getMaxStamina
     * @instance
     * @memberOf GirlManager
     * @param {String} girl
     */
    getMaxStamina(girl) {
        return parseFloat((gameData.character[girl].MaxStamina + Math.floor(GAME.girl.getStaminaGain(girl)) + gameData.character[girl].Bonus.Stamina).toFixed(2))
    };

    /**
     * @private
     * @method getStaminaGain
     * @instance
     * @memberOf GirlManager
     * @param girl
     * @returns {number}
     */
    getStaminaGain(girl) {
        return Math.sqrt(gameData.character[girl].GuysFucked) * STAMINA_GAIN;
    }

    /**
     * Returns the exp of a girl
     * @method getGirlExp
     * @instance
     * @memberOf GirlManager
     * @param {String} girl
     * @param {GirlManager.bodyPart} body
     * @returns {number}
     */
    getGirlExp(girl, body) {
        return gameData.character[girl][body];
    }

    /**
     * Returns the bonus object for the girl
     * @private
     * @method getGirlBonus
     * @memberOf GirlManager
     * @instance
     * @param {String} girl
     * @returns {object}
     */
    getGirlBonus(girl) {
        return gameData.character[girl].Bonus;
    }

    /**
     * Returns the girl's body part level
     * @method getGirlLevel
     * @memberOf GirlManager
     * @instance
     * @param {String} girl
     * @param {GirlManager.bodyPart} body
     * @param {boolean} [bonus=true] - Bonus is the extra levels you get from clothes/items/etc. Set this to false if you just want the raw level of the girl
     * @returns {number}
     */
    getGirlLevel(girl, body, bonus) {
        if (bonus === undefined) {
            bonus = true;
        }
        if (bonus === true) {
            return GAME.getLevel(this.getGirlExp(girl, body)) + this.getGirlBonus(girl)[body];
        } else {
            return GAME.getLevel(this.getGirlExp(girl, body));
        }
    }

    /**
     * Returns the total level of the girl. All of the body part levels combined
     * @method getTotalLevel
     * @memberOf GirlManager
     * @instance
     * @param {String} girl
     * @param {boolean} bonus - Bonus is the extra levels you get from clothes/items/etc. Set this to false if you just want the raw level of the girl
     */
    getTotalLevel(girl, bonus) {
        if (bonus === undefined) {
            bonus = true;
        }
        let total = 0;

        for (let i in skills) {
            if (gameData.body[skills[i]] !== false) {
                total += this.getGirlLevel(girl, skills[i], bonus);
            }
        }

        return total;
    }

    /**
     * Equips clothes based on the clothes ID
     * @method equipClothes
     * @memberOf GirlManager
     * @instance
     * @param {string} clothID
     */
    equipClothes(clothID) {
        if (gameData.clothes[clothID].Unlocked === false) {
            return null;
        }
        let girlName = GAME.clothes.getAllClothes()[clothID].Girl;
        let currentClothes = gameData.character[girlName].Clothes;
        let newClothes = clothID;

        if (GAME.girl.getTotalLevel(girlName, false) < GAME.clothes.getAllClothes()[newClothes].Level) {
            GAME.notify("She does not have a high enough total level to wear that!");
            return null;
        }

        for (let skill in skills) {
            if (GAME.clothes.getAllClothes()[currentClothes].Stats.hasOwnProperty(skills[skill])) {
                gameData.character[girlName].Bonus[skills[skill]] -= GAME.clothes.getAllClothes()[currentClothes].Stats[skills[skill]];
            }
            if (GAME.clothes.getAllClothes()[newClothes].Stats.hasOwnProperty(skills[skill])) {
                gameData.character[girlName].Bonus[skills[skill]] += GAME.clothes.getAllClothes()[newClothes].Stats[skills[skill]];
            }
        }
        if (GAME.clothes.getAllClothes()[currentClothes].Stats.hasOwnProperty("Stamina")) {
            gameData.character[girlName].Bonus.Stamina -= GAME.clothes.getAllClothes()[currentClothes].Stats.Stamina;
            GAME.girl.loseStamina(girlName, GAME.clothes.getAllClothes()[currentClothes].Stats.Stamina);
        }
        if (GAME.clothes.getAllClothes()[newClothes].Stats.hasOwnProperty("Stamina")) {
            gameData.character[girlName].Bonus.Stamina += GAME.clothes.getAllClothes()[newClothes].Stats.Stamina;
            GAME.girl.gainStamina(girlName, GAME.clothes.getAllClothes()[newClothes].Stats.Stamina);
        }
        if (GAME.clothes.getAllClothes()[currentClothes].Stats.hasOwnProperty("Recovery")) {
            gameData.character[girlName].Bonus.Recovery -= GAME.clothes.getAllClothes()[currentClothes].Stats.Recovery;
        }
        if (GAME.clothes.getAllClothes()[newClothes].Stats.hasOwnProperty("Recovery")) {
            gameData.character[girlName].Bonus.Recovery += GAME.clothes.getAllClothes()[newClothes].Stats.Recovery;
        }
        gameData.character[girlName].Clothes = newClothes;

        globalEvents.emit('refreshGirls');
    }

    /**
     * Increases the amount of guys a girl has fucked by the amount
     * The amount of guys a girl has fucked determines the stamina gain, so this is useful if you create a gang bang quest and want to reward the player with extra points
     * @method fuckGuys
     * @memberOf GirlManager
     * @instance
     * @param {string} girl
     * @param {number} amount
     */
    fuckGuys(girl, amount) {
        gameData.character[girl].GuysFucked += amount;
    }

    /**
     * Adds the exp to a girl's body part
     * @method gainExp
     * @memberOf GirlManager
     * @instance
     * @param {string} girl
     * @param {GirlManager.bodyPart} type
     * @param {number} exp
     */
    gainExp(girl, type, exp) {
        if (exp === 0) {
            return null;
        }

        let before = GAME.girl.getGirlLevel(girl, type, false);
        let after;

        gameData.character[girl][type] += exp;

        if (gameData.character[girl][type] > GAME.getExp(MAX_LEVEL)) {
            gameData.character[girl][type] = GAME.getExp(MAX_LEVEL);
        }

        after = GAME.girl.getGirlLevel(girl, type, false);

        if (before !== after) {
            globalEvents.emit('levelUp', {girl: girl, type: type});
        }

        globalEvents.emit('gainExp', exp);
    }

    /**
     * Splits the exp amongst the girls in the array
     * @method splitExp
     * @memberOf GirlManager
     * @instance
     * @param {Array<string>} girls - Array of girl ids
     * @param {number} totalExp - This number will be divided by the amount of girls and then by the amount of body parts
     * @param {Array<GirlManager.bodyPart>} bodyParts - Array of body parts
     */
    splitExp(girls, totalExp, bodyParts) {
        let eachGirlGets = totalExp / girls.length;
        let eachBodyPartGets = Math.ceil(eachGirlGets / bodyParts.length);

        for (let i in girls) {
            for (let j in bodyParts) {
                GAME.girl.gainExp(girls[i], bodyParts[j], eachBodyPartGets);
            }
        }
    }

    /**
     * Returns the girl's total recovery each turn of a battle
     * @method getRecovery
     * @memberOf GirlManager
     * @instance
     * @param {string} girlID
     * @returns {number}
     */
    getRecovery(girlID) {
        return 0.2 + gameData.character[girlID].Bonus.Recovery;
    }

    /**
     * Returns the body part that the player has chosen for that girl
     * @method getActiveBodyPart
     * @memberOf GirlManager
     * @instance
     * @param {string} girlID
     * @returns {GirlManager.bodyPart}
     */
    getActiveBodyPart(girlID) {
        return gameData.character[girlID].ChosenPart;
    }

    /**
     * Sets the body part for a girl
     * @method setActiveBodyPart
     * @memberOf GirlManager
     * @instance
     * @param {string} girlID
     * @param {GirlManager.bodyPart} bodyPart
     */
    setActiveBodyPart(girlID, bodyPart) {
        gameData.character[girlID].ChosenPart = bodyPart;
    }

    /**
     * Returns an array of body parts that are unlocked
     * @method getUnlockedBodyParts
     * @memberOf GirlManager
     * @instance
     * @returns {Array<GirlManager.bodyPart>}
     */
    getUnlockedBodyParts() {
        let array = [];

        for (let i in skills) {
            if (gameData.body[skills[i]] !== false) {
                array.push(skills[i]);
            }
        }

        return array;
    }

    /**
     * Sets the stamina for a girl, ignores all rules
     * @method setStamina
     * @memberOf GirlManager
     * @instance
     * @param {string} girlID
     * @param {number} amount
     */
    setStamina(girlID, amount) {
        gameData.character[girlID].Stamina = amount;
        globalEvents.emit('gainStamina', {girl: girlID, staminaAmount: 0});
    }

    /**
     * Checks if a girl is unlocked
     * @method isUnlocked
     * @memberOf GirlManager
     * @instance
     * @param {string} girlID
     * @returns {boolean}
     */
    isUnlocked(girlID) {
        return gameData.character[girlID].Unlocked;
    }
}