Source: GirlManager.js

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

/**
 * Function called after fucking a client
 * @callback GirlManager.passiveCallback
 * @param {string} girl
 * @param {object} client
 * @param {GirlManager.bodyPart} bodyPart
 */

/**
 * The GirlManager class lets you check and change anything related to the girls.
 * @class GirlManager
 */
class GirlManager {
    constructor() {
        this._girls = ['Queen', 'Suki', 'Esxea', 'Scarlett'];
        /**
         * currentGirl is the currently selected girl. It's the girl displayed on the HUD.
         * @name GirlManager#currentGirl
         * @type {string}
         */
        this.currentGirl = 'Queen';
        this._girlsAvatarCropSettings = {
            "Queen": {
                xOffset: 0,
                yOffset: 90,
                scale: 0.31,
                x: 0,
                y: 0,
                width: 530,
                height: 464
            },
            "Suki": {
                xOffset: 0,
                yOffset: 90,
                scale: 0.31,
                x: 0,
                y: 0,
                width: 530,
                height: 464
            },
            "Esxea": {
                xOffset: 0,
                yOffset: 50,
                scale: 0.31,
                x: 0,
                y: 0,
                width: 530,
                height: 600,
            },
            "Scarlett": {
                xOffset: -5,
                yOffset: 90,
                scale: 0.31,
                x: 0,
                y: 0,
                width: 530,
                height: 464,
            }
        };
        this._girlPassives = {
            "Queen": function (girl, client, bodyPart) {
                if (chance.bool({likelihood: 25}) === true) {
                    let stolenGold = chance.integer({min: client.Gold / 4, max: client.Gold / 2});
                    GAME.addGold(stolenGold);
                    GAME.notify('Queen stole ' + stolenGold + ' gold from the client!');
                }
            },
            "Suki": function (girl, client, bodyPart) {
                if (chance.bool({likelihood: 20}) === true) {
                    GAME.girl.gainExp(girl, bodyPart, GAME.client.getClientExp(client.ID));
                    GAME.notify('Suki gained double the experience from that job!');
                }
            },
            "Esxea": function (girl, client, bodyPart) {
                if (chance.bool({likelihood: 25}) === true) {
                    GAME.client.pushClient(client.ID);
                    GAME.notify('Esxea did so well the client stayed for more!');
                }
            },
            "Scarlett": function (girl, client, bodyPart) {
                if (chance.bool({likelihood: 25}) === true) {
                    GAME.girl.gainStamina('Scarlett', GAME.client.getClientObj()[client.ID].Stamina);
                    GAME.notify('Scarlett used no stamina when fucking ' + GAME.client.getClientObj()[client.ID].Name + '!');
                }
            }
        }
    }

    /**
     * Returns the gameData object for the girl
     * @private
     * @instance
     * @method getGirl
     * @memberOf GirlManager
     * @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;
    }

    /**
     * Returns a texture key
     * @method getGirl
     * @instance
     * @memberOf GirlManager
     * @param {string} girlID
     * @return {string}
     */
    getGirlImage(girlID) {
        return gameData.character[girlID].ID + '-Normal-' + gameData.character[girlID].Clothes;
    }

    /**
     * Returns the object used for cropping the girl's images in the girl containers
     * @private
     * @instance
     * @method getGirlAvatarCrop
     * @memberOf GirlManager
     * @param {string} girlID
     * @returns {object}
     */
    getGirlAvatarCrop(girlID) {
        return this._girlsAvatarCropSettings[girlID];
    }

    /**
     * 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) {
        gameData.character[girl].Stamina -= amount;
        globalEvents.emit('loseStamina', {girl: girl, amount: amount});
    }

    /**
     * Adds the amount to the girl's current stamina
     * @method gainStamina
     * @instance
     * @memberOf GirlManager
     * @param girl
     * @param amount
     */
    gainStamina(girl, amount) {
        gameData.character[girl].Stamina += amount;
        globalEvents.emit('gainStamina', {girl: girl, amount: amount});
    };

    /**
     * Unlocks a girl. Checks to see if there is available space in the house
     * @method unlockGirl
     * @instance
     * @memberOf GirlManager
     * @param {string} girl
     * @returns {boolean}
     */
    unlockGirl(girl) {
        if (GAME.checkMax() === true) {
            return false;
        } else {
            if (gameData.character[girl].Unlocked === true) {
                return false;
            }
            gameData.character[girl].Unlocked = true;
            globalEvents.emit('refresh');

            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
     * @memberOf Gameplay
     * @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 current passive function of a girl
     * @method getGirlPassive
     * @memberOf GirlManager
     * @instance
     * @param {string} girl
     * @returns {GirlManager.passiveCallback}
     */
    getGirlPassive(girl) {
        return this._girlPassives[girl];
    }

    /**
     * Overwrites the current passive function for the girl with a new function
     * @method setGirlPassive
     * @memberOf GirlManager
     * @instance
     * @param {string} girl
     * @param {GirlManager.passiveCallback} callback
     */
    setGirlPassive(girl, callback) {
        this._girlPassives[girl] = callback;
    }

    /**
     * Calls the girl's passive function
     * @method girlPassive
     * @memberOf GirlManager
     * @instance
     * @param {string} girl
     * @param {string} client
     * @param {GirlManager.bodyPart} bodyPart
     */
    girlPassive(girl, client, bodyPart) {
        client = GAME.client.getClientObj()[client];
        this._girlPassives[girl](girl, client, bodyPart);
    }

    /**
     * Opens up a menu where you choose a girl. Returns the girl's id or false if the user did not select any girl
     * @method chooseGirl
     * @memberOf GirlManager
     * @instance
     * @returns {Promise<string>}
     */
    chooseGirl() {
        return new Promise((resolve) => {
            game.scene.start('ChooseGirl', {pauseAllScenes: true});
            game.scene.getScene('ChooseGirl').events.once('shutdown', (scene, data) => {
                resolve(data.answer);
            })
        });
    }
}