objects/player/player.js

import Phaser from "phaser";

const Weapons = require('../weapons/weapon');
const dataManager = require('../data');
const Inventory = require('./inventory');

const Weapon = Weapons.Weapon;

/** 
 * @classdesc 
 * A class for representing the Player in the game world.
 * @memberof ClubCrawler.Objects
 * @extends Phaser.GameObjects.Image
 * @see {@link https://newdocs.phaser.io/docs/3.55.2/Phaser.GameObjects.Image Phaser.GameObjects.Image}
*/
class Player extends Phaser.GameObjects.Image {
    
    /**
     * Preloads assets needed for player. Currently only looks to 'images/discord-avatars.png' and 'atlas/discord-avatars.json' for player image data. Picks a random avatar from the {@link https://newdocs.phaser.io/docs/3.55.2/Phaser.Scene Phaser.Scene} each time the game is run.
     * 
     * See {@link https://newdocs.phaser.io/docs/3.55.2/Phaser.Loader.LoaderPlugin#atlas Phaser docs} for more info about atlases. 
     * 
     * @param {Phaser.Scene} config.scene - The {@link https://newdocs.phaser.io/docs/3.55.2/Phaser.Scene Phaser.Scene} doing the pre-loading.
     */
    static preload(config) {
        
        // load the texture atlas
        config.scene.load.atlas({
            key:'playerimages', 
            textureURL: 'images/discord-avatars.png', 
            atlasURL: 'atlas/discord-avatars.json'
        });
        Reticle.preload(config.scene);
    }
    /**
     * Constructs the player, gives it a physics body, selects a random Discord avatar to use as the image.
     * @param {Object} config - The configuration object
     * @param {Phaser.Scene} config.scene - The {@link https://newdocs.phaser.io/docs/3.55.2/Phaser.Scene Phaser.Scene} creating the Player
     * @param {number} config.x - The x location to place the player initially. If the map has a player spawn, this won't matter.
     * @param {number} config.y - The y location to place the player initially. If the map has a player spawn, this won't matter.
     * @param {number} [config.velocityIncrement=25] - The amount the player speed increases when a directional button is pressed
     * @param {number} [config.drag=100] - The amount to slow the player down when no directional is pressed
     */
    constructor(config) {

        // pick a random avatar
        let frameNames = config.scene.textures.get('playerimages').getFrameNames();
        let frameIndex = Math.floor(Math.random() * frameNames.length);
        let selectedFrame = frameNames[frameIndex]; 

        //call super
        super(config.scene, config.x, config.y, "playerimages", selectedFrame); 

        this.name = "Player";
        //add to scene
        config.scene.add.existing(this);
        if(config.scale) {
            this.setScale(config.scale, config.scale);
        }

        //make physics object
        config.scene.physics.add.existing(this);

        //set physics properties
        this.body.setMaxSpeed(config.maxSpeed ? config.maxSpeed : 100);
        this.body.setDrag(config.drag ? config.drag : 100, config.drag ? config.drag : 100);
        this.body.setBounce(0.7,0.7);

        dataManager.health = 0;
        //set game properties
        this.health = config.health ? config.health : 100;
        dataManager.changeHealth(this.health);
        this.velocityIncrement = config.velocityIncrement ? config.velocityIncrement : 25;

        //create reticle
        this.reticle = new Reticle({player:this, scene:config.scene, x:config.x, y:config.y});
        
        //create weapon
        this.weapon = new Weapon({
            scene:this.scene,
            wielder:this, 
            target:this.reticle
        });
        this.inventory = new Inventory({
            player: this,
            reticle: this.reticle,
            scene: this.scene
        })
        this.inventory.addItem(this.weapon);    
        if(dataManager.debug.on && dataManager.debug.player.construction) {
            dataManager.log(`player constructor? ${this.constructor.name} created, props ${Object.keys(this)}`)
        }
    }
    /**
     * Applies velocity to the player, such as in response to keyboard input
     * @param {string} direction - One of "e","w","s","n"
     */
    move(direction) {
        if(direction == "e") {
            this.body.setVelocityX(this.body.velocity.x + this.velocityIncrement);
        }
        else if(direction == "w") {
            this.body.setVelocityX(this.body.velocity.x - this.velocityIncrement);
        }
        else if(direction == "s") {
            this.body.setVelocityY(this.body.velocity.y + this.velocityIncrement);
        }
        else {
            this.body.setVelocityY(this.body.velocity.y - this.velocityIncrement);
        }
        // die if out of bounds
        if(!this.scene.mapManager.floors.getTileAtWorldXY(this.x,this.y)) {
            this.die();
        };
    }

    /**
     * Player death. Currently immediately stops the scene and starts gameover. Should instead unhook controls and fire a player death event.
     * @todo Set up a death animation, event handling
     * @fires ClubCrawler.Events.playerDied
     */
    die() {
        dataManager.emitter.emit('playerDied');
        //die animation
        this.scene.scene.stop('crawlergame');
        this.scene.scene.start('gameover');
    }
    takeDamage(amount) {
        dataManager.changeHealth(-amount, this);
        if(this.health <= 0) {
            this.die();
        }
    }
    heal(amount) {
        dataManager.changeHealth(amount, this)
    }
}

/**
 * @classdesc
 * 
 * The targetting reticle for aiming with the mouse.
 * 
 * @memberof ClubCrawler.Objects
 */
class Reticle extends Phaser.GameObjects.Image {


    /**
     * Description
     * @param {Object} config - The config object
     * @param {Phaser.Scene} config.scene - The {@link https://newdocs.phaser.io/docs/3.55.2/Phaser.Scene Phaser.Scene}
     * @param {number} [config.x = 0] - The x coordinate to start the reticle... will be updated when player moves
     * @param {number} [config.y = 0] - The y coordinate to start the reticle... updated when player moves 
     */
    constructor(config) {
        super(config.scene, config.x ? config.x : 0, config.y ? config.y : 0, 'reticle');
        this.scene.add.existing(this);
        // this.scene.physics.add.existing(this);
        
        /**
         * @type {ClubCrawler.Objects.Player}
         */
        this.player = config.player;

        /**
         * The offset from the player the mouse was at last time it moved, for updating position as player moves
         * @type {number}
         */
        this.offsetX = 0;
        /**
         * The offset from the player the mouse was at last time it moved, for updating position as player moves
         * @type {number}
         */
        this.offsetY = 0;

        this.scene.input.on('pointermove', function(pointer) {
            this.setX(pointer.worldX);
            this.setY(pointer.worldY);
            this.offsetX = this.x - this.player.x;
            this.offsetY = this.y - this.player.y;
        }, this)

    }
    /**
     * Called by Map Manager when player is placed
     */
    moveToPlayer() {
        this.setX(this.player.x);
        this.setY(this.player.y);
    }
    /**
     * Called by DungeonCrawlerGame each update to ensure position relative to player is the same
     */
    update() {
        // this.setX(this.scene.input.activePointer.worldX);
        // this.setY(this.scene.input.activePointer.worldY);
        // this.body.setVelocityX(this.player.body.velocity.x);
        // this.body.setVelocityY(this.player.body.velocity.y);
        this.setX(this.player.x + this.offsetX);
        this.setY(this.player.y + this.offsetY);
    }

}

module.exports = Player