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; } }