/******************************************************************************* * Copyright (c) 2015 - 2017 * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *******************************************************************************/ package jsettlers.logic.movable; import jsettlers.algorithms.fogofwar.IViewDistancable; import jsettlers.algorithms.path.IPathCalculatable; import jsettlers.algorithms.path.Path; import jsettlers.common.mapobject.EMapObjectType; import jsettlers.common.material.EMaterialType; import jsettlers.common.material.ESearchType; import jsettlers.common.movable.EDirection; import jsettlers.common.movable.EMovableAction; import jsettlers.common.movable.EMovableType; import jsettlers.logic.movable.interfaces.ILogicMovable; import jsettlers.common.position.ShortPoint2D; import jsettlers.common.selectable.ESelectionType; import jsettlers.graphics.messages.SimpleMessage; import jsettlers.input.IGuiMovable; import jsettlers.logic.buildings.military.IBuildingOccupyableMovable; import jsettlers.logic.buildings.military.IOccupyableBuilding; import jsettlers.logic.constants.Constants; import jsettlers.logic.constants.MatchConstants; import jsettlers.logic.movable.interfaces.AbstractMovableGrid; import jsettlers.logic.movable.interfaces.IAttackable; import jsettlers.logic.movable.interfaces.IAttackableMovable; import jsettlers.logic.movable.interfaces.IDebugable; import jsettlers.logic.movable.strategies.FleeStrategy; import jsettlers.logic.movable.strategies.soldiers.SoldierStrategy; import jsettlers.logic.player.Player; import jsettlers.logic.timer.IScheduledTimerable; import jsettlers.logic.timer.RescheduleTimer; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.HashMap; import java.util.concurrent.ConcurrentLinkedQueue; /** * Central Movable class of JSettlers. * * @author Andreas Eberle * */ public final class Movable implements ILogicMovable { private static final long serialVersionUID = 2472076796407425256L; private static final HashMap<Integer, ILogicMovable> movablesByID = new HashMap<Integer, ILogicMovable>(); private static final ConcurrentLinkedQueue<ILogicMovable> allMovables = new ConcurrentLinkedQueue<ILogicMovable>(); private static int nextID = Integer.MIN_VALUE; protected final AbstractMovableGrid grid; private final int id; private EMovableState state = EMovableState.DOING_NOTHING; private EMovableType movableType; private MovableStrategy strategy; private final Player player; private EMaterialType materialType = EMaterialType.NO_MATERIAL; private EMovableAction movableAction = EMovableAction.NO_ACTION; private EDirection direction; private int animationStartTime; private short animationDuration; private ShortPoint2D position; private ShortPoint2D requestedTargetPosition = null; private Path path; private float health; private boolean visible = true; private boolean enableNothingToDo = true; private ILogicMovable pushedFrom; private boolean isRightstep = false; private int flockDelay = 700; private EMaterialType takeDropMaterial; private transient boolean selected = false; private transient boolean soundPlayed = false; public Movable(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) { this.grid = grid; this.position = position; this.player = player; this.strategy = MovableStrategy.getStrategy(this, movableType); this.movableType = movableType; this.health = movableType.getHealth(); this.direction = EDirection.VALUES[MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS)]; RescheduleTimer.add(this, Constants.MOVABLE_INTERRUPT_PERIOD); this.id = nextID++; movablesByID.put(this.id, this); allMovables.offer(this); grid.enterPosition(position, this, true); } /** * This method overrides the standard deserialize method to restore the movablesByID map and the nextID. * * @param ois * @throws IOException * @throws ClassNotFoundException */ private final void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); movablesByID.put(this.id, this); allMovables.add(this); nextID = Math.max(nextID, this.id + 1); } /** * Tests if this movable can receive moveTo requests and if so, directs it to go to the given position. * * @param targetPosition */ public final void moveTo(ShortPoint2D targetPosition) { if (movableType.isPlayerControllable() && strategy.canBeControlledByPlayer() && !alreadyWalkingToPosition(targetPosition)) { this.requestedTargetPosition = targetPosition; } } private boolean alreadyWalkingToPosition(ShortPoint2D targetPosition) { return this.state == EMovableState.PATHING && this.path.getTargetPos().equals(targetPosition); } public void leavePosition() { if (state != EMovableState.DOING_NOTHING || !enableNothingToDo) { return; } int offset = MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS); for (int i = 0; i < EDirection.NUMBER_OF_DIRECTIONS; i++) { EDirection currDir = EDirection.VALUES[(i + offset) % EDirection.NUMBER_OF_DIRECTIONS]; if (goInDirection(currDir, EGoInDirectionMode.GO_IF_ALLOWED_AND_FREE)) { break; } else { ILogicMovable movableAtPos = grid.getMovableAt(currDir.getNextTileX(position.x), currDir.getNextTileY(position.y)); if (movableAtPos != null) { movableAtPos.push(this); } } } } @Override public int timerEvent() { if (state == EMovableState.DEAD) { return -1; } switch (state) { // ensure animation is finished, if not, reschedule case GOING_SINGLE_STEP: case PLAYING_ACTION: case TAKE: case DROP: case PATHING: case WAITING: int remainingAnimationTime = animationStartTime + animationDuration - MatchConstants.clock().getTime(); if (remainingAnimationTime > 0) { return remainingAnimationTime; } break; default: break; } switch (state) { case TAKE: case DROP: if (this.movableAction != EMovableAction.RAISE_UP) { break; } // TAKE and DROP are finished if we get here and we the action is RAISE_UP, otherwise continue with second part. case WAITING: case GOING_SINGLE_STEP: case PLAYING_ACTION: state = EMovableState.DOING_NOTHING; // the action is finished, as the time passed movableAction = EMovableAction.NO_ACTION; case PATHING: case DOING_NOTHING: if (visible) { checkPlayerOfCurrentPosition(); } break; default: break; } if (requestedTargetPosition != null) { if (strategy.canBeControlledByPlayer()) { switch (state) { case PATHING: // if we're currently pathing, stop former pathing and calculate a new path setState(EMovableState.DOING_NOTHING); this.movableAction = EMovableAction.NO_ACTION; this.path = null; case DOING_NOTHING: ShortPoint2D oldTargetPos = path != null ? path.getTargetPos() : null; ShortPoint2D oldPos = position; boolean foundPath = goToPos(requestedTargetPosition); // progress is reset in here requestedTargetPosition = null; if (foundPath) { this.strategy.moveToPathSet(oldPos, oldTargetPos, path.getTargetPos()); return animationDuration; // we already follow the path and initiated the walking } else { break; } default: break; } } else { requestedTargetPosition = null; } } switch (state) { case GOING_SINGLE_STEP: case PLAYING_ACTION: setState(EMovableState.DOING_NOTHING); this.movableAction = EMovableAction.NO_ACTION; break; case PATHING: pathingAction(); break; case TAKE: grid.takeMaterial(position, takeDropMaterial); setMaterial(takeDropMaterial); playAnimation(EMovableAction.RAISE_UP, Constants.MOVABLE_BEND_DURATION); strategy.tookMaterial(); break; case DROP: if (takeDropMaterial != null && takeDropMaterial.isDroppable()) { boolean offerMaterial = strategy.droppingMaterial(); grid.dropMaterial(position, takeDropMaterial, offerMaterial, false); } setMaterial(EMaterialType.NO_MATERIAL); playAnimation(EMovableAction.RAISE_UP, Constants.MOVABLE_BEND_DURATION); break; default: break; } if (state == EMovableState.DOING_NOTHING) { // if movable is currently doing nothing strategy.action(); // let the strategy work if (state == EMovableState.DOING_NOTHING) { // if movable is still doing nothing after strategy, consider doingNothingAction() if (visible && enableNothingToDo) { return doingNothingAction(); } else { return Constants.MOVABLE_INTERRUPT_PERIOD; } } } return animationDuration; } private void pathingAction() { if (path == null || !path.hasNextStep() || !strategy.checkPathStepPreconditions(path.getTargetPos(), path.getStep())) { // if path is finished, or canceled by strategy return from here setState(EMovableState.DOING_NOTHING); movableAction = EMovableAction.NO_ACTION; path = null; return; } ILogicMovable blockingMovable = grid.getMovableAt(path.nextX(), path.nextY()); if (blockingMovable == null) { // if we can go on to the next step if (grid.isValidNextPathPosition(this, path.getNextPos(), path.getTargetPos())) { // next position is valid goSinglePathStep(); } else { // next position is invalid movableAction = EMovableAction.NO_ACTION; animationDuration = Constants.MOVABLE_INTERRUPT_PERIOD; // recheck shortly Path newPath = grid.calculatePathTo(this, path.getTargetPos()); // try to find a new path if (newPath == null) { // no path found setState(EMovableState.DOING_NOTHING); strategy.pathAborted(path.getTargetPos()); // inform strategy path = null; } else { this.path = newPath; // continue with new path if (grid.hasNoMovableAt(path.nextX(), path.nextY())) { // path is valid, but maybe blocked (leaving blocked area) goSinglePathStep(); } } } } else { // step not possible, so try it next time movableAction = EMovableAction.NO_ACTION; boolean pushedSuccessfully = blockingMovable.push(this); if (!pushedSuccessfully) { path = strategy.findWayAroundObstacle(position, path); animationDuration = Constants.MOVABLE_INTERRUPT_PERIOD; // recheck shortly } else if (movableAction == EMovableAction.NO_ACTION) { animationDuration = Constants.MOVABLE_INTERRUPT_PERIOD; // recheck shortly } // else: push initiated our next step } } @Override public void goSinglePathStep() { initGoingSingleStep(path.getNextPos()); path.goToNextStep(); } @Override public ShortPoint2D getPosition() { return position; } @Override public ILogicMovable getPushedFrom() { return pushedFrom; } private void initGoingSingleStep(ShortPoint2D position) { direction = EDirection.getDirection(this.position, position); playAnimation(EMovableAction.WALKING, movableType.getStepDurationMs()); grid.leavePosition(this.position, this); grid.enterPosition(position, this, false); this.position = position; isRightstep = !isRightstep; } private int doingNothingAction() { if (grid.isBlockedOrProtected(position.x, position.y)) { Path newPath = grid.searchDijkstra(this, position.x, position.y, (short) 50, ESearchType.NON_BLOCKED_OR_PROTECTED); if (newPath == null) { kill(); return -1; } else { followPath(newPath); return animationDuration; } } else { if (flockToDecentralize()) { return animationDuration; } else { int turnDirection = MatchConstants.random().nextInt(-8, 8); if (Math.abs(turnDirection) <= 1) { lookInDirection(direction.getNeighbor(turnDirection)); } } return flockDelay; } } /** * Tries to walk the movable into a position where it has a minimum distance to others. * * @return true if the movable moves to flock, false if no flocking is required. */ private boolean flockToDecentralize() { ShortPoint2D decentVector = grid.calcDecentralizeVector(position.x, position.y); EDirection randomDirection = direction.getNeighbor(MatchConstants.random().nextInt(-1, 1)); int dx = randomDirection.gridDeltaX + decentVector.x; int dy = randomDirection.gridDeltaY + decentVector.y; if (ShortPoint2D.getOnGridDist(dx, dy) >= 2) { flockDelay = Math.max(flockDelay - 100, 500); return this.goInDirection(EDirection.getApproxDirection(0, 0, dx, dy), EGoInDirectionMode.GO_IF_ALLOWED_AND_FREE); } else { flockDelay = Math.min(flockDelay + 100, 1000); return false; } } /** * A call to this method indicates this movable that it shall leave it's position to free the position for another movable. * * @param pushingMovable * The movable pushing at this movable. This should be the movable that want's to get the position! * @return true if this movable will move out of it's way in the near future <br> * false if this movable doesn't move. */ @Override public boolean push(ILogicMovable pushingMovable) { if (state == EMovableState.DEAD) { return false; } switch (state) { case DOING_NOTHING: if (!enableNothingToDo) { // don't go to random direction if movable shouldn't do something in DOING_NOTHING return false; } if (goToRandomDirection(pushingMovable)) { // try to find free direction return true; // if we found a free direction, go there and tell the pushing one we'll move } else { // if we didn't find a direction, check if it's possible to exchange positions if (pushingMovable.getPath() == null || !pushingMovable.getPath().hasNextStep()) { return false; // the other movable just pushed to get space, we can't do anything for it here. } else if (pushingMovable.getMovableType().isPlayerControllable() || strategy.isValidPosition(pushingMovable.getPos())) { // exchange positions EDirection directionToPushing = EDirection.getDirection(position, pushingMovable.getPos()); pushingMovable.goSinglePathStep(); // if no free direction found, exchange the positions of the movables goInDirection(directionToPushing, EGoInDirectionMode.GO_IF_ALLOWED_WAIT_TILL_FREE); return true; } else { // exchange not possible, as the location is not valid. return false; } } case PATHING: if (path == null || pushingMovable.getPath() == null || !pushingMovable.getPath().hasNextStep()) { return false; // the other movable just pushed to get space, so we can't do anything for it in this state. } if (animationStartTime + animationDuration <= MatchConstants.clock().getTime() && this.path.hasNextStep()) { ShortPoint2D nextPos = path.getNextPos(); if (pushingMovable.getPosition() == nextPos) { // two movables going in opposite direction and wanting to exchange positions pushingMovable.goSinglePathStep(); this.goSinglePathStep(); } else { if (grid.hasNoMovableAt(nextPos.x, nextPos.y)) { // this movable isn't blocked, so just let it's pathingAction() handle this } else if (pushedFrom == null) { try { this.pushedFrom = pushingMovable; return grid.getMovableAt(nextPos.x, nextPos.y).push(this); } finally { this.pushedFrom = null; } } else { while (pushingMovable != this) { pushingMovable.goSinglePathStep(); pushingMovable = pushingMovable.getPushedFrom(); } this.goSinglePathStep(); } } } return true; case GOING_SINGLE_STEP: case PLAYING_ACTION: case TAKE: case DROP: case WAITING: return false; // we can't do anything case DEBUG_STATE: return false; default: assert false : "got pushed in unhandled state: " + state; return false; } } @Override public Path getPath() { return path; } public boolean isProbablyPushable(ILogicMovable pushingMovable) { switch (state) { case DOING_NOTHING: return true; case PATHING: return path != null && pushingMovable.getPath() != null && pushingMovable.getPath().hasNextStep(); default: return false; } } private boolean goToRandomDirection(ILogicMovable pushingMovable) { int offset = MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS); EDirection pushedFromDir = EDirection.getDirection(this.getPos(), pushingMovable.getPos()); for (int i = 0; i < EDirection.NUMBER_OF_DIRECTIONS; i++) { EDirection currDir = EDirection.VALUES[(i + offset) % EDirection.NUMBER_OF_DIRECTIONS]; if (currDir != pushedFromDir && goInDirection(currDir, EGoInDirectionMode.GO_IF_ALLOWED_AND_FREE)) { return true; } } return false; } /** * Sets the material this movable is carrying to the given one. * * @param materialType * @return {@link EMaterialType} that has been set before. */ final EMaterialType setMaterial(EMaterialType materialType) { assert materialType != null : "MaterialType may not be null"; EMaterialType former = this.materialType; this.materialType = materialType; return former; } /** * Lets this movable execute the given action with given duration. * * @param movableAction * action to be animated. * @param duration * duration the animation should last (in seconds). // TODO change to milliseconds */ final void playAction(EMovableAction movableAction, float duration) { assert state == EMovableState.DOING_NOTHING : "can't do playAction() if state isn't DOING_NOTHING. curr state: " + state; playAnimation(movableAction, (short) (duration * 1000)); setState(EMovableState.PLAYING_ACTION); this.soundPlayed = false; } private void playAnimation(EMovableAction movableAction, short duration) { this.animationStartTime = MatchConstants.clock().getTime(); this.animationDuration = duration; this.movableAction = movableAction; } /** * * @param materialToTake * @return true if the animation will be executed. */ final boolean take(EMaterialType materialToTake, boolean takeFromMap) { if (!takeFromMap || grid.canTakeMaterial(position, materialToTake)) { this.takeDropMaterial = materialToTake; playAnimation(EMovableAction.BEND_DOWN, Constants.MOVABLE_BEND_DURATION); setState(EMovableState.TAKE); return true; } else { return false; } } final void drop(EMaterialType materialToDrop) { this.takeDropMaterial = materialToDrop; playAnimation(EMovableAction.BEND_DOWN, Constants.MOVABLE_BEND_DURATION); setState(EMovableState.DROP); } /** * * @param sleepTime * time to sleep in milliseconds */ final void sleep(short sleepTime) { assert state == EMovableState.DOING_NOTHING : "can't do sleep() if state isn't DOING_NOTHING. curr state: " + state; playAnimation(EMovableAction.NO_ACTION, sleepTime); setState(EMovableState.WAITING); } /** * Lets this movable look in the given direction. * * @param direction */ final void lookInDirection(EDirection direction) { this.direction = direction; } /** * Lets this movable go to the given position. * * @param targetPos * position to move to. * @return true if it was possible to calculate a path to the given position<br> * false if it wasn't possible to get a path. */ final boolean goToPos(ShortPoint2D targetPos) { assert state == EMovableState.DOING_NOTHING : "can't do goToPos() if state isn't DOING_NOTHING. curr state: " + state; Path path = grid.calculatePathTo(this, targetPos); if (path == null) { return false; } else { followPath(path); return this.path != null; } } /** * Tries to go a step in the given direction. * * @param direction * direction to go * @param mode * Use the given mode to go.<br> * @return true if the step can and will immediately be executed. <br> * false if the target position is generally blocked or a movable occupies that position. */ final boolean goInDirection(EDirection direction, EGoInDirectionMode mode) { ShortPoint2D targetPosition = direction.getNextHexPoint(position); switch (mode) { case GO_IF_ALLOWED_WAIT_TILL_FREE: { this.direction = direction; setState(EMovableState.PATHING); this.followPath(new Path(targetPosition)); return true; } case GO_IF_ALLOWED_AND_FREE: if ((grid.isValidPosition(this, targetPosition.x, targetPosition.y) && grid.hasNoMovableAt(targetPosition.x, targetPosition.y))) { initGoingSingleStep(targetPosition); setState(EMovableState.GOING_SINGLE_STEP); return true; } else { break; } case GO_IF_FREE: if (grid.isFreePosition(targetPosition)) { initGoingSingleStep(targetPosition); setState(EMovableState.GOING_SINGLE_STEP); return true; } else { break; } } return false; } final void setPosition(ShortPoint2D position) { if (visible) { grid.leavePosition(this.position, this); grid.enterPosition(position, this, true); } this.position = position; } final void setVisible(boolean visible) { if (this.visible == visible) { // nothing to change } else if (this.visible) { // is visible and gets invisible grid.leavePosition(position, this); } else { grid.enterPosition(position, this, true); } this.visible = visible; } /** * * @param dijkstra * if true, dijkstra algorithm is used<br> * if false, in area finder is used. * @param centerX * @param centerY * @param radius * @param searchType * @return true if a path has been found. */ final boolean preSearchPath(boolean dijkstra, short centerX, short centerY, short radius, ESearchType searchType) { assert state == EMovableState.DOING_NOTHING : "this method can only be invoked in state DOING_NOTHING"; if (dijkstra) { this.path = grid.searchDijkstra(this, centerX, centerY, radius, searchType); } else { this.path = grid.searchInArea(this, centerX, centerY, radius, searchType); } return path != null; } final ShortPoint2D followPresearchedPath() { assert this.path != null : "path mustn't be null to be able to followPresearchedPath()!"; followPath(this.path); return path.getTargetPos(); } final void enableNothingToDoAction(boolean enable) { this.enableNothingToDo = enable; } void abortPath() { path = null; } boolean isOnOwnGround() { return grid.getPlayerAt(position) == player; } private void followPath(Path path) { this.path = path; setState(EMovableState.PATHING); this.movableAction = EMovableAction.NO_ACTION; pathingAction(); } /** * Sets the state to the given one and resets the movable to a clean start of this state. * * @param newState */ private void setState(EMovableState newState) { this.state = newState; } /** * Used for networking to identify movables over the network. * * @param id * id to be looked for * @return returns the movable with the given ID<br> * or null if the id can not be found */ public final static ILogicMovable getMovableByID(int id) { return movablesByID.get(id); } public final static ConcurrentLinkedQueue<ILogicMovable> getAllMovables() { return allMovables; } public static void resetState() { allMovables.clear(); movablesByID.clear(); nextID = Integer.MIN_VALUE; } /** * kills this movable. */ @Override public final void kill() { if (state == EMovableState.DEAD) { return; // this movable already died. } grid.leavePosition(this.position, this); this.health = -200; this.strategy.strategyKilledEvent(path != null ? path.getTargetPos() : null); this.state = EMovableState.DEAD; this.selected = false; movablesByID.remove(this.getID()); allMovables.remove(this); grid.addSelfDeletingMapObject(position, EMapObjectType.GHOST, Constants.GHOST_PLAY_DURATION, player); } @Override public final byte getPlayerId() { return player.playerId; } /** * Gets the player object of this movable. * * @return The player object of this movable. */ public final Player getPlayer() { return player; } @Override public final boolean isSelected() { return selected; } @Override public final void setSelected(boolean selected) { this.selected = selected; } @Override public final void stopOrStartWorking(boolean stop) { strategy.stopOrStartWorking(stop); } @Override public final ESelectionType getSelectionType() { return movableType.getSelectionType(); } @Override public final void setSoundPlayed() { this.soundPlayed = true; } @Override public final boolean isSoundPlayed() { return soundPlayed; } @Override public final EMovableType getMovableType() { return movableType; } @Override public final EMovableAction getAction() { return movableAction; } @Override public final EDirection getDirection() { return direction; } @Override public final float getMoveProgress() { return ((float) (MatchConstants.clock().getTime() - animationStartTime)) / animationDuration; } @Override public final EMaterialType getMaterial() { return materialType; } @Override public final ShortPoint2D getPos() { return position; } @Override public final float getHealth() { return health; } @Override public final boolean isRightstep() { return isRightstep; } @Override public final boolean needsPlayersGround() { return movableType.needsPlayersGround(); } @Override public final short getViewDistance() { return Constants.MOVABLE_VIEW_DISTANCE; } @Override public final void debug() { System.out.println("debug: " + this); } @Override public final int getID() { return id; } /** * Converts this movable to a movable of the given {@link EMovableType}. * * @param newMovableType */ public final void convertTo(EMovableType newMovableType) { if (newMovableType == EMovableType.BEARER && !player.equals(grid.getPlayerAt(position))) { return; // can't convert to bearer if the ground does not belong to the player } if (!(movableType == EMovableType.BEARER || (movableType == EMovableType.PIONEER && newMovableType == EMovableType.BEARER) || movableType == newMovableType)) { System.err.println("Tried invalid conversion from " + movableType + " to " + newMovableType); return; // can't convert between this types } this.health = (this.health * newMovableType.getHealth()) / this.movableType.getHealth(); this.movableType = newMovableType; setVisible(true); // ensure the movable is visible setStrategy(MovableStrategy.getStrategy(this, newMovableType)); } private void setStrategy(MovableStrategy newStrategy) { this.strategy.strategyKilledEvent(path != null ? path.getTargetPos() : null); this.strategy = newStrategy; this.movableAction = EMovableAction.NO_ACTION; setState(EMovableState.DOING_NOTHING); grid.notifyAttackers(position, this, true); } public final IBuildingOccupyableMovable setOccupyableBuilding(IOccupyableBuilding building) { if (canOccupyBuilding()) { return ((SoldierStrategy) strategy).setOccupyableBuilding(building); } else { return null; } } public final boolean canOccupyBuilding() { return movableType.getSelectionType() == ESelectionType.SOLDIERS; } @Override public final boolean isAttackable() { return strategy.isAttackable(); } /** * This method may only be called if this movable shall be informed about a movable that's in it's search radius. * * @param other * The other movable. */ @Override public final void informAboutAttackable(IAttackable other) { strategy.informAboutAttackable(other); } @Override public final void receiveHit(float hitStrength, ShortPoint2D attackerPos, byte attackingPlayer) { if (strategy.receiveHit()) { this.health -= hitStrength; if (health <= 0) { this.kill(); } } player.showMessage(SimpleMessage.attacked(attackingPlayer, attackerPos)); } @Override public boolean isTower() { return false; } private void checkPlayerOfCurrentPosition() { checkPlayerOfPosition(grid.getPlayerAt(position)); } public void checkPlayerOfPosition(Player playerOfPosition) { if (playerOfPosition != player && movableType.needsPlayersGround() && strategy.getClass() != FleeStrategy.class) { setStrategy(new FleeStrategy(this)); } } @Override public String toString() { return "Movable: " + id + " position: " + position + " player: " + player.playerId + " movableType: " + movableType + " direction: " + direction + " material: " + materialType; } private enum EMovableState { PLAYING_ACTION, PATHING, DOING_NOTHING, GOING_SINGLE_STEP, WAITING, TAKE, DROP, DEAD, /** * This state may only be used for debugging reasons! */ DEBUG_STATE } }