package com.lucasdnd.ags.gameplay;
import java.util.ArrayList;
import java.util.Random;
import org.newdawn.slick.Font;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.util.pathfinding.Mover;
import org.newdawn.slick.util.pathfinding.Path;
import com.lucasdnd.ags.map.Map;
import com.lucasdnd.ags.map.OffsetUtil;
import com.lucasdnd.ags.system.GameSystem;
import com.lucasdnd.ags.system.ResourceLoader;
import com.lucasdnd.ags.ui.ViewGamesList;
import com.lucasdnd.ags.ui.popup.FloatingText;
public abstract class Character extends Entity implements Mover {
/** Random Movement Controllers */
protected int movementInterval = 0; // Something between 0,5 and 1,5 seconds.
protected int currentMovementInterval = 0; // When this reaches the movementInterval, the Entity moves around.
/** Movement */
protected float originalXPos; // When the Entity is moving to the Tile next to it, this is the xPos where it came from
protected float originalYPos; // When the Entity is moving to the Tile next to it, this is the yPos where it came from
protected boolean moving; // Indicates if this Entity is currently moving
// If true, it should not start a new movement until reaching its next destination Tile
protected Path path; // When the Entity is moving, it has a Path
protected int currentPathStep; // When the entity is moving, it has to know which is the current step in the Path
protected int targetXTile; // Target x tile
protected int targetYTile; // Target y tile
protected float speed; // Movement speed of this Entity
/** Target Asset Control */ // We do this so the Caracter stops the action if the Asset disappears
protected Asset targetAsset; // The Asset this Character wants to use
protected int targetAssetXTile; // The x tile of the target Asset the moment the Character started moving torwards it
protected int targetAssetYTile; // y of the above
protected int targetAssetFacingDirection; // FacingDirection of the above
/** Action Controllers */
protected int currentActionTimer; // How long it passed since the Character started performing the action
protected int maxActionTimer; // For how long the Character will perform the action
/** Floating Text */
protected FloatingText floatingText, floatingMoney; // The speech baloon
protected int chanceToTalk = 1; // Chance the Character has to speak something, out of 5
/** States */
protected int state = IDLE; // The state at which the Customer currently finds itself in
public static final int IDLE = 0;
public static final int RANDOMLY_MOVING = 1;
protected Character(int id, SpriteSheet spriteSheet, Font font, Font shadowFont) throws SlickException {
super(id, spriteSheet);
// Basic stuff
moving = false;
xSize = 1;
ySize = 1;
state = IDLE;
targetAsset = null;
canBeDisposed = false;
// Floating Text
floatingText = new FloatingText(font, shadowFont, xPos * 2, yPos * 2, xOffset * 2, yOffset * 2, this.image[0].getWidth(), this.image[0].getHeight(), 3000);
floatingMoney = new FloatingText(ResourceLoader.getInstance().tinyGreenFont, shadowFont, xPos * 2, yPos * 2, xOffset * 2, yOffset * 2 - 22f, this.image[0].getWidth(), this.image[0].getHeight(), 3000);
floatingMoney.setLocalYOffset(-100f);
}
/**
* Main Update Block
*
* @param container
* @param game
* @param delta
* @param input
* @param leftMouseClicked
* @param mouseX
* @param mouseY
* @param business
* @param tables
* @param shelfs
* @param viewGamesList
* @throws SlickException
*/
public void update(GameContainer container, StateBasedGame game, int delta, Input input,
boolean leftMouseClicked, int mouseX, int mouseY, Business business,
ArrayList<Table> tables, ArrayList<Shelf> shelfs, ViewGamesList viewGamesList) throws SlickException {
Map map = business.getStore().getMap();
depth = xTile + yTile;
this.updateFloatingTexts(container, game, delta);
// Literally moving
if(moving) {
this.updateMovement(map, delta);
}
}
/**
* Updates the Floating Texts
* @param container
* @param game
* @param delta
* @throws SlickException
*/
private void updateFloatingTexts(GameContainer container, StateBasedGame game, int delta) throws SlickException {
// Speech Text
floatingText.setxPos(xPos * GameSystem.globalScale * 2f);
floatingText.setyPos(yPos * GameSystem.globalScale * 2f);
floatingText.setxOffset(xOffset * GameSystem.globalScale * 2f);
floatingText.setyOffset(yOffset * GameSystem.globalScale * 2f);
floatingText.update(container, game, delta, 0f, 0f);
// Floating Money
floatingMoney.setxPos(xPos * GameSystem.globalScale * 2f);
floatingMoney.setyPos(yPos * GameSystem.globalScale * 2f);
floatingMoney.setxOffset(xOffset * GameSystem.globalScale * 2f);
floatingMoney.setyOffset(yOffset * GameSystem.globalScale * 2f);
floatingMoney.update(container, game, delta, 0f, 0f);
}
/**
* Literally moves the Character on the screen
* @param map
* @param delta
* @throws SlickException
*/
private void updateMovement(Map map, int delta) throws SlickException {
if (facingDirection == DOWN_RIGHT) {
xPos += speed * delta * Math.sin(Math.toRadians(116.5f));
yPos -= speed * delta * Math.cos(Math.toRadians(116.5f));
if(xPos - originalXPos >= (map.getTileWidth() / 2f) && yPos - originalYPos >= (map.getTileHeight() / 2f)) {
this.stopMoving(map);
}
} else if (facingDirection == UP_RIGHT) {
xPos += speed * delta * Math.sin(Math.toRadians(63.5f));
yPos -= speed * delta * Math.cos(Math.toRadians(63.5f));
if(xPos - originalXPos >= (map.getTileWidth() / 2f) && originalYPos - yPos >= (map.getTileHeight() / 2f)) {
this.stopMoving(map);
}
} else if (facingDirection == DOWN_LEFT) {
xPos += speed * delta * Math.sin(Math.toRadians(243.5f));
yPos -= speed * delta * Math.cos(Math.toRadians(243.5f));
if(originalXPos - xPos >= (map.getTileWidth() / 2f) && yPos - originalYPos >= (map.getTileHeight() / 2f)) {
this.stopMoving(map);
}
} else if (facingDirection == UP_LEFT) {
xPos += speed * delta * Math.sin(Math.toRadians(296.5f));
yPos -= speed * delta * Math.cos(Math.toRadians(296.5f));
if(originalXPos - xPos >= (map.getTileWidth() / 2f) && originalYPos - yPos >= (map.getTileHeight() / 2f)) {
this.stopMoving(map);
}
}
}
/**
* Setup the next Tile movement
* @throws SlickException
*/
public void setupNextMovementStep(Map map) throws SlickException {
// Save the current Pos
originalXPos = xPos;
originalYPos = yPos;
// Next Tile
targetXTile = path.getX(currentPathStep);
targetYTile = path.getY(currentPathStep);
// Map has been blocked during the movement
if(map.isBlocked(targetXTile, targetYTile)) {
this.stopMoving(map);
return;
}
// All good, let's move out
moving = true;
/**
* Based on the target Tiles, set the direction this Entity will be facing
* and update the Character's current Tile.
*
* Separating the two if blocks below, setting the property in the Pathfinder AND
* making another 4 movement directions would allow for diagonal movement.
*/
if(targetXTile > xTile && targetYTile == yTile) {
facingDirection = DOWN_RIGHT;
xTile++;
} else if (targetXTile < xTile && targetYTile == yTile) {
facingDirection = UP_LEFT;
xTile--;
} else if(targetYTile > yTile && targetXTile == xTile) {
facingDirection = DOWN_LEFT;
yTile++;
} else if (targetYTile < yTile && targetXTile == xTile) {
facingDirection = UP_RIGHT;
yTile--;
}
}
/**
* Finds a Path to an Asset, handles pathfinding exceptions
* @param asset
* @throws SlickException
*/
public void goToAsset(Asset asset, Map map, int nextState) throws SlickException {
// Let's make the Character go there
boolean pathIsValid = false;
Path p = null;
// Get a random usable tile from the Asset
int[] randomUsableTile = asset.getRandomUsableTile(map);
if(randomUsableTile != null) {
// Find the Path there
p = map.getPathFinder().findPath(this, this.getXTile(), this.getYTile(), randomUsableTile[0], randomUsableTile[1]);
if(p != null) {
pathIsValid = true;
}
}
// Go!
if(pathIsValid) {
// Change its state to the next state and make it go
state = nextState;
// Save info about the Target Asset
targetAsset = asset;
targetAssetXTile = asset.getXTile();
targetAssetYTile = asset.getYTile();
targetAssetFacingDirection = asset.getFacingDirection();
// Move!
this.startMoving(p, map);
return;
}
// Something wrong with the Path?
if((randomUsableTile != null) && this.getXTile() == randomUsableTile[0] && this.getYTile() == randomUsableTile[1]) {
// Already at the Target
state = nextState;
this.stopMoving(map);
} else {
// Couldn't find path
// Make the Character say it can't find a way to reach the Asset!
this.sayCantReachAsset(asset);
this.unlockAsset(asset);
state = IDLE;
}
}
/**
* Shows a Floating Text saying the Character can't find a path to the Asset
* @param asset
*/
private void sayCantReachAsset(Asset asset) {
String assetType = null;
if(asset instanceof Table) {
assetType = "table";
} else if(asset instanceof Shelf) {
assetType = "shelf";
}
floatingText.show("I can't reach the " + assetType);
}
/**
* Gives the Path to the Entity and tells it it should start moving
* @param targetXTile
* @param targetYTile
* @throws SlickException
* @throws NotAValidPathException
*/
public void startMoving(Path p, Map map) throws SlickException {
path = p;
currentPathStep = 1;
setupNextMovementStep(map);
}
/**
* Stops the movement, called when the Character arrives at a new Tile
* @throws SlickException
*/
public void stopMoving(Map map) throws SlickException {
map.snapToGrid(this);
moving = false;
// Do we still have more Steps to walk?
currentPathStep = currentPathStep + 1;
if(path != null && currentPathStep < path.getLength()) {
setupNextMovementStep(map);
} else {
this.finishMoving(map);
}
}
/**
* Finishes a Character movement (end of the Path)
* @param map
*/
public void finishMoving(Map map) throws SlickException {
path = null;
if(state == RANDOMLY_MOVING) {
state = IDLE;
}
}
/**
* Character is IDLE
* @throws SlickException
*/
protected void updateIdleState(Map map, int delta) throws SlickException {
// Increases the timer of the movementInterval
currentMovementInterval += delta;
// The timer limit has been reached!
// Reset it, recalculate the movementInterval and change the state to RANDOMLY_MOVING!
if(currentMovementInterval >= movementInterval) {
// Reset the Current Movement Interval
currentMovementInterval = 0;
movementInterval = new Random().nextInt(1000) + 500;
// Randomly move
this.setupRandomMovement(map);
}
}
/**
* Create a new Random Movement
* @param Map
* @throws SlickException
*/
protected void setupRandomMovement(Map map) throws SlickException {
Path p = null;
int randomMovementDirection = new Random().nextInt(4);
if (randomMovementDirection == Entity.DOWN_RIGHT) {
p = map.getPathFinder().findPath(this, this.getXTile(), this.getYTile(), (this.getXTile() + 1 == map.getWidth()) ? this.getXTile() - 1 : this.getXTile() + 1, this.getYTile());
} else if (randomMovementDirection == Entity.DOWN_LEFT) {
p = map.getPathFinder().findPath(this, this.getXTile(), this.getYTile(), this.getXTile(), (this.getYTile() + 1 == map.getHeight()) ? this.getYTile() - 1: this.getYTile() + 1);
} else if (randomMovementDirection == Entity.UP_RIGHT) {
p = map.getPathFinder().findPath(this, this.getXTile(), this.getYTile(), (this.getXTile() == 0) ? 1 : this.getXTile() - 1, this.getYTile());
} else if (randomMovementDirection == Entity.UP_LEFT) {
p = map.getPathFinder().findPath(this, this.getXTile(), this.getYTile(), this.getXTile(), (this.getYTile() == 0) ? 1 : this.getYTile() - 1);
}
if(p == null) {
state = IDLE;
} else {
state = RANDOMLY_MOVING;
this.startMoving(p, map);
}
}
/**
* Rendering
* @param container
* @param game
* @param g
* @param simulationSpeed
* @throws SlickException
*/
public void render(GameContainer container, StateBasedGame game, Graphics g, int simulationSpeed) throws SlickException {
if(path != null && simulationSpeed > 0) {
// If the Character is moving, we draw the Animation
animation[facingDirection].draw(
xPos + OffsetUtil.getPreciseOffset(xOffset),
yPos + OffsetUtil.getPreciseOffset(yOffset));
} else {
// Otherwise, draw the image of the Character standing still
animation[facingDirection].getImage(0).draw(
xPos + OffsetUtil.getPreciseOffset(xOffset),
yPos + OffsetUtil.getPreciseOffset(yOffset));
}
// Character playing sprites are rendered by the Table
// Floating Text Rendering is called from the MainState so it is rendered over other stuff
}
/**
* Releases this Character from an Asset it was using or was about to use
*/
protected abstract void unlockAsset(Asset asset);
public Path getPath() {
return path;
}
public void setPath(Path path) {
this.path = path;
}
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
this.speed = speed;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public boolean isMoving() {
return moving;
}
public void setMoving(boolean moving) {
this.moving = moving;
}
public FloatingText getFloatingText() {
return floatingText;
}
public void setFloatingText(FloatingText floatingText) {
this.floatingText = floatingText;
}
public Asset getTargetAsset() {
return targetAsset;
}
public void setTargetAsset(Asset targetAsset) {
this.targetAsset = targetAsset;
}
public FloatingText getFloatingMoney() {
return floatingMoney;
}
public void setFloatingMoney(FloatingText floatingMoney) {
this.floatingMoney = floatingMoney;
}
public int getCurrentPathStep() {
return currentPathStep;
}
public void setCurrentPathStep(int currentPathStep) {
this.currentPathStep = currentPathStep;
}
public float getOriginalXPos() {
return originalXPos;
}
public float getOriginalYPos() {
return originalYPos;
}
}