package com.jonathan.survivor.entity;
import com.esotericsoftware.spine.Skeleton;
import com.jonathan.survivor.Assets;
import com.jonathan.survivor.entity.Human.State;
import com.jonathan.survivor.entity.InteractiveObject.InteractiveState;
import com.jonathan.survivor.inventory.Axe;
import com.jonathan.survivor.inventory.Bullet;
import com.jonathan.survivor.inventory.Inventory;
import com.jonathan.survivor.inventory.Loadout;
import com.jonathan.survivor.inventory.MeleeWeapon;
import com.jonathan.survivor.inventory.RangedWeapon;
import com.jonathan.survivor.inventory.Rifle;
import com.jonathan.survivor.math.Line;
import com.jonathan.survivor.math.Rectangle;
import com.jonathan.survivor.math.Vector2;
public class Player extends Human
{
/** Stores the width and height of the player's rectangle collider in world units. */
public static final float COLLIDER_WIDTH = 1.56f;
public static final float COLLIDER_HEIGHT = 2.5f;
/** Holds the player's default health. */
public static final float DEFAULT_HEALTH = 100;
/** Stores the maximum walk speed of the player in the horizontal direction. */
public static final float MAX_WALK_SPEED = 6f;
/** Stores the jump speed of the player in the vertical direction when in EXPLORING mode. */
public static final float EXPLORATION_JUMP_SPEED = 10.7f;
/** Stores the jump speed of the player in the vertical direction when in COMBAT mode. */
public static final float COMBAT_JUMP_SPEED = 18.0f;
/** Stores the downwards speed at which the player falls through a TerrainLayer. */
public static final float FALL_SPEED = -5;
/** Holds the amount of damage delivered to a zombie when it is stomped on the head by the player. */
private static final float HEAD_STOMP_DAMAGE = 25;
/** Stores the speed at which the player jumps after hitting the zombie's head. */
private static final float HEAD_STOMP_JUMP_SPEED = 15;
/** Holds the amount of time that the player is invulnerable when hit. */
public static final float INVULNERABLE_TIME = 3f;
/** Stores the player's loadout, containing the player's active weapons. */
private Loadout loadout;
/** Holds the player's inventory, which contains all of the player's collected items. */
private Inventory inventory;
/** Holds the zombie that the player will fight once he enters combat mode. Convenience member variable to avoid large work-arounds. */
private Zombie zombieToFight;
/** Stores the PlayerListener instance where methods are delegated upon player events. */
private PlayerListener playerListener;
/** Creates a player whose bottom-center is at position (0, 0). */
public Player()
{
this(0, 0);
}
/** Creates a player whose bottom-center is at position (x, y). */
public Player(float x, float y)
{
super(x, y, COLLIDER_WIDTH, COLLIDER_HEIGHT);
//Creates the Spine skeleton for the player using the PlayerSkeletonData.
setSkeleton(new Skeleton(Assets.instance.playerSkeletonData));
//The player is always in exploration mode by default and not in fighting mode.
setMode(Mode.EXPLORING);
//Set the player to SPAWN state once instantiated.
setState(State.SPAWN);
//Sets the player's walking speed to default.
setWalkSpeed(MAX_WALK_SPEED);
//Gives the player default health when instantiated.
setHealth(DEFAULT_HEALTH);
}
/** Updates the player's internal game logic. */
@Override
public void update(float deltaTime)
{
//Updates the player's game logic, such as its stateTime and its collider's position.
super.update(deltaTime);
}
/** Makes the player jump. */
public void jump()
{
//If the player is already jumping, don't make him jump again. Or, if his y-velocity is non-zero, he is in the air. Thus, he can't jump. Also,
//if he is dead, he can't jump. Therefore, return the method.
if(getState() == State.JUMP || getVelocity().y != 0 || isDead())
return;
//If the player is in exploration mode, use a different jump speed.
if(getMode() == Mode.EXPLORING)
{
//Set the y-velocity of the player to the desired jump speed.
setVelocity(0, EXPLORATION_JUMP_SPEED);
//Set the state of the player to his jumping state, telling the world he is jumping.
setState(State.JUMP);
//Move the player up a terrain cell. Lets the player keep track of which cell he is in for the terrain level.
getTerrainCell().moveUp();
}
//Else, if the player is in COMBAT mode
else if(getMode() == Mode.COMBAT )
{
//Set the y-velocity of the player to the desired jump speed.
setVelocity(0, COMBAT_JUMP_SPEED);
//Set the state of the player to his jumping state, telling the world he is jumping.
setState(State.JUMP);
}
//Make the player lose his target so that he stops walking to a specific GameObject after jumping.
loseTarget();
}
/** Makes the player fall through one layer. */
public void fall()
{
//Make the player fall at his fall-speed in the vertical direction.
setVelocity(0, FALL_SPEED);
//Tell the player he is falling
setState(State.FALL);
//Move the player down a terrain cell.
getTerrainCell().moveDown();
//Make the player lose his target so that he stops walking to a specific GameObject after falling.
loseTarget();
}
/** Makes the player start chopping a tree */
public void chopTree()
{
//If the player has a melee weapon equipped
if(loadout.getMeleeWeapon() != null)
{
//Start chopping the tree.
setState(State.CHOP_TREE);
}
}
/** Makes the player swing his melee weapon if he has one. */
public void melee()
{
//If the player is dead, he can't melee. Therefore, return the method.
if(isDead())
return;
//If the player is in combat mode
if(getMode() == Mode.COMBAT)
{
//If the player has a melee weapon and is not meleeing already or jumping or being hit
if(hasMeleeWeapon() && getState() != State.MELEE && getState() != State.JUMP && getState() != State.HIT)
{
//Switch to MELEE state so that the MELEE animation plays.
setState(State.MELEE);
}
}
}
/** Makes the player start to charge his gun. Call only once, when the player starts charging his ranged weapon. */
public void charge()
{
//If the player doesn't have a ranged weapon equipped, or doesn't have bullets in his inventory, he can't charge his ranged weapon. Therefore, return this method.
if(!hasRangedWeapon() || !hasBullets())
return;
//Set the player to CHARGE_START state, telling the player to pull out his gun before charging it. When done, the player will switch to CHARGE state.
setState(State.CHARGE_START);
}
/** Makes the player fire his ranged weapon */
public void fire()
{
//If the player does not have a ranged weapon, or if he is dead, he cannot fire a gun. Therefore, return this method.
if(!hasRangedWeapon() || isDead())
return;
//If the player is in CHARGE_START state, the player is trying to fire his gun before he pulled it out. This cannot be done
if(getState() == State.CHARGE_START)
{
//Thus, set the player back to IDLE state.
setState(State.IDLE);
}
//Else, if the player is not in CHARGE_STATE, but is in CHARGE state, he can fire his ranged weapon.
else if(getState() == State.CHARGE)
{
//Fire the player's ranged weapon at the zombie that he's fighting. If the weapon's crosshair does not hit the zombie, the method returns.
fireWeapon(getZombieToFight());
}
}
/** Deals damage to the zombie with the player's melee weapon. Only deals damage if the player's melee weapon is colliding with the given zombie. */
public void meleeHit(Zombie zombie)
{
//If the zombie is invulnerable, he can't get hit. Also, if the melee weapon is not intersecting the zombie, the zombie is not being touched by the weapon.
//Therefore, return the method.
if(zombie.isInvulnerable() || !getMeleeWeaponCollider().intersects(zombie.getCollider()))
return;
//Deals damage to the zombie according to the meleeWeapon's damage value.
zombie.takeDamage(loadout.getMeleeWeapon().getDamage());
//Checks if the zombie is dead. If so, play the KO animation.
checkDead(zombie);
}
/** Fire the player's currently equipped ranged weapon at the given zombie. */
private void fireWeapon(Zombie zombie)
{
//If the weapon's crosshair intersects with the zombie's collider, the weapon has a change of hitting the zombie. The player must also have bullets.
if(getCrosshair().intersects(zombie.getCollider()) && hasBullets())
{
//Computes a random value between 0.9 and 1 which will dictate if the player's ranged weapon misses.
float rand = 0.75f + (float)Math.random()*0.25f;
//If the random number is less than the percent charge completion of the player's weapon, odds are in the player's favour. The bullet has hit the zombie.
if(rand < getChargeCompletion())
{
//Deal damage to the zombie according to the strength of the player's ranged weapon.
zombie.takeDamage(getRangedWeapon().getDamage());
//Checks if the zombie is dead. If so, play the KO animation.
checkDead(zombie);
}
}
//Use one bullet in the player's inventory.
useBullets(1);
//Tell the player that he is firing his gun, making his FIRE animation play. The player had to be in CHARGE state to call getChargeCompletion().
setState(State.FIRE);
}
/** Called when the player has hit the tree stored as his target. */
public void hitTree()
{
//Retrieve the hit tree, which is the player's target.
Tree tree = (Tree)getTarget();
//Tell the tree it was hit so that it plays the correct animation.
tree.setInteractiveState(InteractiveState.HIT);
//Make the player's melee weapon deal damage to the tree
loadout.getMeleeWeapon().hit(tree);
//If the tree has been scavenged after dealing damage to it in the above statement
if(tree.getInteractiveState() == InteractiveState.SCAVENGED)
{
//Tell the player to lose his target of the tree, since it has been destroyed
loseTarget();
//Set the player back to IDLE state.
setState(State.IDLE);
//Tell the world to spawn items at the destroyed tree's position.
playerListener.scavengedObject(tree);
}
}
/** Called when the player hits a zombie's head. Deals damage to this zombie and changes its state. */
public void hitHead(Zombie zombie)
{
//If the zombie is invulnerable, he can't get hit. Therefore, return the method.
if(zombie.isInvulnerable())
return;
//Sets the y-velocity of the player to slightly jump after hitting the zombie's head.
setVelocityY(HEAD_STOMP_JUMP_SPEED);
//Tell the player to play his double jump animation.
setState(State.DOUBLE_JUMP);
//Deals damage to the zombie according to the head stomp constant.
zombie.takeDamage(HEAD_STOMP_DAMAGE);
//Checks if the zombie is dead. If so, play the KO animation.
checkDead(zombie);
}
/** Checks if the zombie is dead. If so, plays the KO animation. */
private void checkDead(Zombie zombie)
{
//If the zombie is dead
if(zombie.isDead())
{
//Tell the world to play the KO animation.
playerListener.playKoAnimation();
}
}
/** Uses a given amount of bullets inside the player's inventory. */
private void useBullets(int quantity)
{
//Remove the given amount of bullets from the player's inventory.
inventory.addItem(Bullet.class, -quantity);
}
/** Returns true if the player has bullets in his inventory. */
public boolean hasBullets()
{
//If the value for the Bullet key inside the inventory is not null, the player has bullets in his inventory.
return inventory.getItemMap().get(Bullet.class) != null;
}
/** Returns a float between 0 and 1 representing the charge completion of the player's ranged weapon. 1 means that the weapon is done charging completely. */
public float getChargeCompletion()
{
//If the player is not charging his gun, this method will return incorrect values based on the stateTime since the player is not charging his ranged weapon.
if(getState() != State.CHARGE)
return 0;
//Returns a percent completion of the player charging his gun. Computes a normalized value between 0 and 1, where 1 means that the gun has finished charging.
float chargeRate = getStateTime() / getRangedWeapon().getChargeTime();
//Cap the charge rate at 1.
chargeRate = (chargeRate > 1)? 1:chargeRate;
//Returns the charge completion of the player's ranged weapon.
return chargeRate;
}
/** Regenerates the player to default health. */
public void regenerate()
{
//Give the player his default health back.
setHealth(DEFAULT_HEALTH);
}
/** Overrides the takeDamage() method to take note of when the player dies, in order to display the KO animation. */
@Override
public void takeDamage(float amount)
{
//Deal damage to the player
super.takeDamage(amount);
//If the player died while taking damage
if(getState() == State.DEAD)
{
//Tell the World instance to play the KO animation. When the animation is done, the "Game Over" screen will be displayed.
playerListener.playKoAnimation();
}
}
/** Makes the player lose all of the items in his inventory. */
public void loseLoot()
{
//Clears the player's inventory.
inventory.clear();
//Clear the player's loadout to ensure that he loses all his weapons.
loadout.clear();
}
/** Called when the player loses his target. */
@Override
public void loseTarget()
{
//Store the player's old target
GameObject oldTarget = getTarget();
//If it isn't null
if(oldTarget != null)
{
//If it is an Interactive GameObject
if(oldTarget instanceof InteractiveObject)
{
//Untarget the old target.
((InteractiveObject) oldTarget).untargetted();
}
//Else, if a zombie target was lost
else if(oldTarget instanceof Zombie)
{
//Tell the zombie that it is no longer targetted.
((Zombie) oldTarget).setTargetted(false);
}
}
//Lose the target.
super.loseTarget();
}
/** Returns true if the player has won the game. */
public boolean didWin()
{
//If the player is in TELEPORT state, he has won the game.
return getState() == State.TELEPORT;
}
/** Override the canTarget method as always returning false since the Player can never be targetted. */
@Override
public boolean canTarget()
{
//The player can never be targetted.
return false;
}
/** Returns the melee weapon that the player has equipped. */
public MeleeWeapon getMeleeWeapon()
{
//Returns the melee weapon stored in the player's loadout, which is the one that the player has equipped.
return getLoadout().getMeleeWeapon();
}
/** Returns the ranged weapon that the player has equipped. */
public RangedWeapon getRangedWeapon()
{
//Returns the ranged weapon stored in the player's loadout, which is the one that the player has equipped.
return getLoadout().getRangedWeapon();
}
/** Returns true if the player has a melee weapon equipped. */
public boolean hasMeleeWeapon()
{
//Returns true if the melee weapon in the player's loadout is not null.
return loadout.getMeleeWeapon() != null;
}
/** Returns true if the player has a ranged weapon equipped. */
public boolean hasRangedWeapon()
{
//Returns true if the ranged weapon in the player's loadout is not null. If not, the player has a ranged weapon equipped.
return loadout.getRangedWeapon() != null;
}
/** Returns true if the player has his ranged weapon out and visible. */
public boolean hasRangedWeaponOut()
{
//Returns true if the player is in any state which requires his ranged weapon to be visible.
return getState() == State.CHARGE_START || getState() == State.CHARGE || getState() == State.FIRE;
}
/** Returns the Collider of the player's melee weapon. Allows to test if the player has hit a zombie with his weapon. */
public Rectangle getMeleeWeaponCollider()
{
//Returns the collider attached to the player's melee weapon.
return loadout.getMeleeWeapon().getCollider();
}
/** Returns the crosshair line, which dictates where the player's ranged weapon will fire and where the bullet will travel. */
public Line getCrosshair()
{
//Returns the crosshair point of the ranged weapon the player has equipped.
return getRangedWeapon().getCrosshair();
}
/** Returns the position where the weapon crosshair should be placed on the player in world units. This is usually the tip of the player's ranged weapon. */
public Vector2 getCrosshairPoint()
{
//Returns the crosshair point of the ranged weapon the player has equipped.
return getRangedWeapon().getCrosshairPoint();
}
/** Makes the player invulnerable from attacks for a given amount of seconds. */
@Override
public void makeInvulnerable()
{
//Makes the player invulnerable for a given amount of seconds from the stored constant.
setInvulnerabilityTime(INVULNERABLE_TIME);
}
/** Retrieves the player's loadout containing the player's weapons. */
public Loadout getLoadout() {
return loadout;
}
/** Sets the player's loadout. */
public void setLoadout(Loadout loadout) {
this.loadout = loadout;
}
/** Gets the loadout which stores the items held by the player. */
public Inventory getInventory() {
return inventory;
}
/** Sets the loadout which stores the items held by the player. */
public void setInventory(Inventory inventory) {
this.inventory = inventory;
}
/** Returns the zombie that the player should fight once he enters combat mode. Set when the player collides with a zombie. */
public Zombie getZombieToFight() {
return zombieToFight;
}
/** Sets the zombie the player should fight once he enters combat mode. Set when the player collides with a zombie. */
public void setZombieToFight(Zombie zombieToFight) {
this.zombieToFight = zombieToFight;
}
/** Sets the given listener to have its methods delegated by the player instance. */
public void setListener(PlayerListener listener)
{
playerListener = listener;
}
}