diff --git a/jsettlers.buildingcreator/src/main/java/jsettlers/buildingcreator/job/EBuildingJobType.java b/jsettlers.buildingcreator/src/main/java/jsettlers/buildingcreator/job/EBuildingJobType.java
new file mode 100644
index 0000000000..b3bb6e948a
--- /dev/null
+++ b/jsettlers.buildingcreator/src/main/java/jsettlers/buildingcreator/job/EBuildingJobType.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2015
+ *
+ * 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.buildingcreator.job;
+
+public enum EBuildingJobType {
+ /**
+ * Waits a given time.
+ *
+ * SUCCESS: The time elapsed.
+ *
+ * Fail: impossible.
+ *
+ * @see BuildingJob#getTime();
+ */
+ WAIT,
+ /**
+ * Lets the settler walk in a given direction. The settler may wait.
+ *
+ * Parameter: direction
+ *
+ * SUCCESS: The settler is at the position
+ *
+ * Fail: Should not happen normally.
+ */
+ WALK,
+ /**
+ * Shows the settler at a given position.
+ *
+ * Parameter: dx, dy
+ *
+ * SUCCESS: The settler appeared.
+ *
+ * Fail: The settler could not appear at the given position.
+ */
+ SHOW,
+ /**
+ * Lets the settler disappear.
+ *
+ * Parameter: none
+ *
+ * SUCCESS: The settler disappeared instantly.
+ *
+ * Fail: impossible
+ */
+ HIDE,
+
+ /**
+ * Sets the material property of the settler.
+ *
+ * Parameter: material
+ *
+ * SUCCESS: good
+ *
+ * Fail: There was no given material at that position.
+ */
+ SET_MATERIAL,
+
+ /**
+ * Picks up the specified material. Does not change the material type assigned to the settler
+ *
+ * Parameter: material
+ *
+ * SUCCESS: There was a material at that position, one item was removed.
+ *
+ * Fail: There was no given material at that position.
+ */
+ TAKE,
+ /**
+ * Lets the settler drop the given material to the stack at the positon.
+ *
+ * Parameter: material
+ *
+ * SUCCESS: When the settler dropped the material.
+ *
+ * Fail: If the drop is impossible.
+ */
+ DROP,
+ /**
+ * Searches a given search type.
+ *
+ * Uses the special {@link BuildingSearchJob} class.
+ *
+ * SUCCESS: The settler found the thing he should search and went to it.
+ *
+ * Fail: If the searched thing was not found. The settler does not need to go back.
+ *
+ * @see BuildingSearchType
+ */
+ SEARCH,
+ /**
+ * Goes to the position relative to the building.
+ *
+ * SUCCESS: The settler is at the position
+ *
+ * Fail: The position is unreachable.
+ */
+ GO_TO,
+
+ /**
+ * Look at
+ *
+ * SUCCESS: The settler looks at the given new direction.
+ *
+ * Fail: impossible
+ */
+ LOOK_AT,
+ /**
+ * Plays an action animation.
+ *
+ * Parameter: time - the time the action should take.
+ *
+ * SUCCESS: The animation was played.
+ *
+ * Fail: something was wrong...
+ */
+ PLAY_ACTION1,
+ /**
+ * @see EBuildingJobType#PLAY_ACTION1
+ */
+ PLAY_ACTION2,
+ PLAY_ACTION3
+}
diff --git a/jsettlers.common/src/main/java/jsettlers/common/CommonConstants.java b/jsettlers.common/src/main/java/jsettlers/common/CommonConstants.java
index 5e375732e5..5b99551b7e 100644
--- a/jsettlers.common/src/main/java/jsettlers/common/CommonConstants.java
+++ b/jsettlers.common/src/main/java/jsettlers/common/CommonConstants.java
@@ -14,8 +14,6 @@
*******************************************************************************/
package jsettlers.common;
-import jsettlers.common.ai.EPlayerType;
-
public abstract class CommonConstants {
/**
* A byte value indicating that the given position is visible.
@@ -69,8 +67,15 @@ public abstract class CommonConstants {
* Option to disable the loading of original maps.
*/
public static boolean DISABLE_ORIGINAL_MAPS = false;
+
/**
* Disables the checksum test for original maps.
*/
public static boolean DISABLE_ORIGINAL_MAPS_CHECKSUM = false;
+
+ /**
+ * Enables debugging of behavior trees
+ */
+ public static boolean DEBUG_BEHAVIOR_TREES = false;
+
}
diff --git a/jsettlers.common/src/main/java/jsettlers/common/action/Action.java b/jsettlers.common/src/main/java/jsettlers/common/action/Action.java
index b17df2b211..16359e2a4a 100644
--- a/jsettlers.common/src/main/java/jsettlers/common/action/Action.java
+++ b/jsettlers.common/src/main/java/jsettlers/common/action/Action.java
@@ -17,7 +17,7 @@
/**
* This is a action the user has requested.
*
- * Each Action has an active status, that indicates that it is currently executed. When the execution of the action is begun, the flag should be set
+ * Each action has an active status, that indicates that it is currently executed. When the execution of the action is begun, the flag should be set
* so that the user interface enters a blocking mode, and goes back to normal mode when the action is finished. It is not guaranteed that there is no
* other action being sent during that time, e.g. an cancel-action.
*
diff --git a/jsettlers.common/src/main/java/jsettlers/common/buildings/jobs/EBuildingJobType.java b/jsettlers.common/src/main/java/jsettlers/common/buildings/jobs/EBuildingJobType.java
index e429ebd874..33c4fbe4bf 100644
--- a/jsettlers.common/src/main/java/jsettlers/common/buildings/jobs/EBuildingJobType.java
+++ b/jsettlers.common/src/main/java/jsettlers/common/buildings/jobs/EBuildingJobType.java
@@ -27,7 +27,7 @@ public enum EBuildingJobType {
*
* Parameter: time (in seconds)
*
- * Success: The time elapsed.
+ * SUCCESS: The time elapsed.
*
* Fail: impossible.
*
@@ -40,7 +40,7 @@ public enum EBuildingJobType {
*
* Parameter: direction
*
- * Success: The settler is at the position
+ * SUCCESS: The settler is at the position
*
* Fail: Should not happen normally.
*/
@@ -51,7 +51,7 @@ public enum EBuildingJobType {
*
* Parameter: dx, dy
*
- * Success: The settler appeared.
+ * SUCCESS: The settler appeared.
*
* Fail: The settler could not appear at the given position.
*/
@@ -62,7 +62,7 @@ public enum EBuildingJobType {
*
* Parameter: none
*
- * Success: The settler disappeared instantly.
+ * SUCCESS: The settler disappeared instantly.
*
* Fail: impossible
*/
@@ -73,7 +73,7 @@ public enum EBuildingJobType {
*
* Parameter: material
*
- * Success: always
+ * SUCCESS: always
*
* Fail: never
*/
@@ -84,7 +84,7 @@ public enum EBuildingJobType {
*
* Parameter: material
*
- * Success: There was a material at that position, one item was removed.
+ * SUCCESS: There was a material at that position, one item was removed.
*
* Fail: There was no given material at that position.
*/
@@ -97,7 +97,7 @@ public enum EBuildingJobType {
*
* Parameter: material
*
- * Success: When the settler dropped the material.
+ * SUCCESS: When the settler dropped the material.
*
* Fail: If the drop is impossible, e.g. because there is already material at that position.
*/
@@ -112,7 +112,7 @@ public enum EBuildingJobType {
*
* This job always fails if the working radius is 0.
*
- * Success: A path to the searched thing has been found.
+ * SUCCESS: A path to the searched thing has been found.
*
* Fail: If the searched thing was not found.
*
@@ -132,7 +132,7 @@ public enum EBuildingJobType {
*
* This job always fails if the working radius is 0.
*
- * Success: A path to the searched thing has been found.
+ * SUCCESS: A path to the searched thing has been found.
*
* Fail: If the searched thing was not found.
*
@@ -149,7 +149,7 @@ public enum EBuildingJobType {
/**
* Goes to the position relative to the building.
*
- * Success: The settler is at the position
+ * SUCCESS: The settler is at the position
*
* Fail: The position is unreachable.
*/
@@ -178,7 +178,7 @@ public enum EBuildingJobType {
*
* Parameter: direction
*
- * Success: The settler looks at the given new direction.
+ * SUCCESS: The settler looks at the given new direction.
*
* Fail: impossible
*/
@@ -189,7 +189,7 @@ public enum EBuildingJobType {
*
* Parameter: time (the time the action should take)
*
- * Success: The animation was played.
+ * SUCCESS: The animation was played.
*
* Fail: should not happen.
*/
@@ -210,7 +210,7 @@ public enum EBuildingJobType {
*
* Parameters: type ({@link jsettlers.common.material.ESearchType})
*
- * Success: the given search type has been executed
+ * SUCCESS: the given search type has been executed
*
* Fail: the given search type couldn't be executed
*/
@@ -221,7 +221,7 @@ public enum EBuildingJobType {
*
* Parameters: dx, dy, material
*
- * Success: There is material at that position.
+ * SUCCESS: There is material at that position.
*
* Fail: There is no matching material at that position
*/
@@ -232,7 +232,7 @@ public enum EBuildingJobType {
*
* Parameters: dx, dy, material
*
- * Success: The material may be placed at the given position
+ * SUCCESS: The material may be placed at the given position
*
* Fail: There is a full stack at that position, a wrong stack or it is blocked otherwise.
*/
diff --git a/jsettlers.graphics/src/main/java/jsettlers/graphics/image/reader/AdvancedDatFileReader.java b/jsettlers.graphics/src/main/java/jsettlers/graphics/image/reader/AdvancedDatFileReader.java
index 64207f43ce..f716a614e6 100644
--- a/jsettlers.graphics/src/main/java/jsettlers/graphics/image/reader/AdvancedDatFileReader.java
+++ b/jsettlers.graphics/src/main/java/jsettlers/graphics/image/reader/AdvancedDatFileReader.java
@@ -438,7 +438,7 @@ private void readSequencesAt(ByteReader reader, int sequenceIndexStart) throws I
int pointerCount = reader.read16();
if (byteCount != pointerCount * 4 + 8) {
- throw new IOException("Sequence index block length (" + pointerCount + ") and " + "bytecount (" + byteCount + ") are not consistent.");
+ throw new IOException("sequence index block length (" + pointerCount + ") and " + "bytecount (" + byteCount + ") are not consistent.");
}
int[] sequenceIndexPointers = new int[pointerCount];
diff --git a/jsettlers.graphics/src/main/java/jsettlers/graphics/image/sequence/ArraySequence.java b/jsettlers.graphics/src/main/java/jsettlers/graphics/image/sequence/ArraySequence.java
index 42dd363a43..087a0d7ec5 100644
--- a/jsettlers.graphics/src/main/java/jsettlers/graphics/image/sequence/ArraySequence.java
+++ b/jsettlers.graphics/src/main/java/jsettlers/graphics/image/sequence/ArraySequence.java
@@ -45,7 +45,7 @@ public ArraySequence(T[] images) {
/*
* (non-Javadoc)
*
- * @see Sequence#length()
+ * @see sequence#length()
*/
@Override
public int length() {
@@ -55,7 +55,7 @@ public int length() {
/*
* (non-Javadoc)
*
- * @see Sequence#getImageLink(int)
+ * @see sequence#getImageLink(int)
*/
@Override
public T getImage(int index) {
@@ -66,7 +66,7 @@ public T getImage(int index) {
/*
* (non-Javadoc)
*
- * @see Sequence#getImageSafe(int)
+ * @see sequence#getImageSafe(int)
*/
@Override
public Image getImageSafe(int index) {
diff --git a/jsettlers.logic/build.gradle b/jsettlers.logic/build.gradle
index cda994ba05..ee1cfa95ab 100644
--- a/jsettlers.logic/build.gradle
+++ b/jsettlers.logic/build.gradle
@@ -21,6 +21,7 @@ task unitTest(type: Test) {
dependencies {
implementation project(':jsettlers.common')
implementation project(':jsettlers.network')
+ implementation 'org.apache.commons:commons-text:1.3'
testImplementation project(':jsettlers.testutils')
testImplementation project(':jsettlers.main.swing')
diff --git a/jsettlers.logic/src/main/java/jsettlers/ai/highlevel/AiPositions.java b/jsettlers.logic/src/main/java/jsettlers/ai/highlevel/AiPositions.java
index 3a76eb412a..7ec1286dec 100644
--- a/jsettlers.logic/src/main/java/jsettlers/ai/highlevel/AiPositions.java
+++ b/jsettlers.logic/src/main/java/jsettlers/ai/highlevel/AiPositions.java
@@ -280,7 +280,7 @@ public String toString() {
}
public ShortPoint2D getBestRatedPoint(PositionRater rater) {
- // TODO: Parallel ?
+ // TODO: parallel ?
int currentBestRating = PositionRater.RATE_INVALID;
ShortPoint2D currentBest = null;
for (int i = 0; i < size; i++) {
diff --git a/jsettlers.logic/src/main/java/jsettlers/ai/highlevel/pioneers/PioneerGroup.java b/jsettlers.logic/src/main/java/jsettlers/ai/highlevel/pioneers/PioneerGroup.java
index 703be8ff9b..609619b78b 100644
--- a/jsettlers.logic/src/main/java/jsettlers/ai/highlevel/pioneers/PioneerGroup.java
+++ b/jsettlers.logic/src/main/java/jsettlers/ai/highlevel/pioneers/PioneerGroup.java
@@ -27,6 +27,7 @@
import jsettlers.input.tasks.ConvertGuiTask;
import jsettlers.logic.map.grid.movable.MovableGrid;
import jsettlers.logic.movable.Movable;
+import jsettlers.logic.movable.MovableDataManager;
import jsettlers.logic.movable.interfaces.ILogicMovable;
import jsettlers.network.client.interfaces.ITaskScheduler;
@@ -60,7 +61,7 @@ public void clear() {
public void removeDeadPioneers() {
Collection idsToRemove = new ArrayList<>(pioneerIds.size());
for (Integer pioneerId : pioneerIds) {
- if (Movable.getMovableByID(pioneerId) == null) {
+ if (MovableDataManager.getMovableByID(pioneerId) == null) {
idsToRemove.add(pioneerId);
}
}
@@ -90,10 +91,11 @@ public void fill(ITaskScheduler taskScheduler, AiStatistics aiStatistics, byte p
}
public PioneerGroup getPioneersWithNoAction() {
- List pioneersWithNoAction = stream(pioneerIds).filter(pioneerId -> Movable.getMovableByID(pioneerId).getAction() == EMovableAction.NO_ACTION).collect(Collectors.toList());
+ List pioneersWithNoAction = stream(pioneerIds).filter(pioneerId -> MovableDataManager.getMovableByID(pioneerId).getAction() == EMovableAction.NO_ACTION).collect(Collectors.toList());
return new PioneerGroup(pioneersWithNoAction);
}
+
public void addAll(List pioneerIds) {
this.pioneerIds.addAll(pioneerIds);
}
diff --git a/jsettlers.logic/src/main/java/jsettlers/algorithms/fogofwar/FogOfWar.java b/jsettlers.logic/src/main/java/jsettlers/algorithms/fogofwar/FogOfWar.java
index 59dcfaee2e..7b8f51d6d6 100644
--- a/jsettlers.logic/src/main/java/jsettlers/algorithms/fogofwar/FogOfWar.java
+++ b/jsettlers.logic/src/main/java/jsettlers/algorithms/fogofwar/FogOfWar.java
@@ -17,6 +17,7 @@
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
+import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
import jsettlers.algorithms.fogofwar.CachedViewCircle.CachedViewCircleIterator;
@@ -29,26 +30,26 @@
/**
* This class holds the fog of war for a given map and team.
- *
+ *
* @author Andreas Eberle
*/
public final class FogOfWar implements Serializable {
- private static final long serialVersionUID = 1877994785778678510L;
+ private static final long serialVersionUID = 1877994785778678510L;
/**
* Longest distance any unit may look
*/
private static final byte MAX_VIEW_DISTANCE = 65;
- static final int PADDING = 10;
+ static final int PADDING = 10;
private final byte team;
- private final short width;
- private final short height;
- private byte[][] sight;
+ private final short width;
+ private final short height;
+ private byte[][] sight;
- private transient boolean enabled = Constants.FOG_OF_WAR_DEFAULT_ENABLED;
+ private transient boolean enabled = Constants.FOG_OF_WAR_DEFAULT_ENABLED;
private transient IFogOfWarGrid grid;
- private transient boolean canceled;
+ private transient boolean canceled;
public FogOfWar(short width, short height, IPlayer player) {
this.width = width;
@@ -70,7 +71,7 @@ public void start(IFogOfWarGrid grid) {
/**
* Gets the visible status of a map pint
- *
+ *
* @param x
* The x coordinate of the point in 0..(mapWidth - 1)
* @param y
@@ -98,9 +99,9 @@ public void setEnabled(boolean enabled) {
}
final class NewFoWThread extends Thread {
- private static final byte DIM_DOWN_SPEED = 10;
- private final CircleDrawer drawer = new CircleDrawer();
- private byte[][] buffer = new byte[width][height];
+ private static final byte DIM_DOWN_SPEED = 10;
+ private final CircleDrawer drawer = new CircleDrawer();
+ private byte[][] buffer = new byte[width][height];
NewFoWThread() {
super("FoWThread");
@@ -143,10 +144,10 @@ private void rebuildSight() {
}
}
- ConcurrentLinkedQueue extends IViewDistancable> buildings = grid.getBuildingViewDistancables();
+ Collection extends IViewDistancable> buildings = grid.getBuildingViewDistancables();
applyViewDistances(buildings);
- ConcurrentLinkedQueue extends IViewDistancable> movables = grid.getMovableViewDistancables();
+ Collection extends IViewDistancable> movables = grid.getMovableViewDistancables();
applyViewDistances(movables);
byte[][] temp = sight;
@@ -154,14 +155,15 @@ private void rebuildSight() {
buffer = temp;
}
- private void applyViewDistances(ConcurrentLinkedQueue extends IViewDistancable> objects) {
+ private void applyViewDistances(Collection extends IViewDistancable> objects) {
for (IViewDistancable curr : objects) {
if (isPlayerOK(curr)) {
short distance = curr.getViewDistance();
if (distance > 0) {
ShortPoint2D pos = curr.getPosition();
- if (pos != null)
+ if (pos != null) {
drawer.drawCircleToBuffer(pos.x, pos.y, distance);
+ }
}
}
}
@@ -178,7 +180,7 @@ private void mySleep(long ms) {
}
final class CircleDrawer {
- private byte[][] buffer;
+ private byte[][] buffer;
private final CachedViewCircle[] cachedCircles = new CachedViewCircle[MAX_VIEW_DISTANCE];
public final void setBuffer(byte[][] buffer) {
diff --git a/jsettlers.logic/src/main/java/jsettlers/algorithms/fogofwar/IFogOfWarGrid.java b/jsettlers.logic/src/main/java/jsettlers/algorithms/fogofwar/IFogOfWarGrid.java
index e277a8af10..c6bb10ec8b 100644
--- a/jsettlers.logic/src/main/java/jsettlers/algorithms/fogofwar/IFogOfWarGrid.java
+++ b/jsettlers.logic/src/main/java/jsettlers/algorithms/fogofwar/IFogOfWarGrid.java
@@ -14,6 +14,7 @@
*******************************************************************************/
package jsettlers.algorithms.fogofwar;
+import java.util.Collection;
import java.util.concurrent.ConcurrentLinkedQueue;
import jsettlers.common.mapobject.IMapObject;
@@ -31,8 +32,8 @@ public interface IFogOfWarGrid {
IMapObject getMapObjectsAt(short x, short y);
- ConcurrentLinkedQueue extends IViewDistancable> getMovableViewDistancables();
+ Collection extends IViewDistancable> getMovableViewDistancables();
- ConcurrentLinkedQueue extends IViewDistancable> getBuildingViewDistancables();
+ Collection extends IViewDistancable> getBuildingViewDistancables();
}
diff --git a/jsettlers.logic/src/main/java/jsettlers/input/GuiInterface.java b/jsettlers.logic/src/main/java/jsettlers/input/GuiInterface.java
index 3f26b95906..915638c84f 100644
--- a/jsettlers.logic/src/main/java/jsettlers/input/GuiInterface.java
+++ b/jsettlers.logic/src/main/java/jsettlers/input/GuiInterface.java
@@ -144,7 +144,7 @@ public void run() {
@Override
public void action(IAction action) {
if (action.getActionType() != EActionType.SCREEN_CHANGE) {
- System.out.println("action(Action): " + action.getActionType() + " at game time: " + MatchConstants.clock().getTime());
+ System.out.println("action(action): " + action.getActionType() + " at game time: " + MatchConstants.clock().getTime());
}
switch (action.getActionType()) {
diff --git a/jsettlers.logic/src/main/java/jsettlers/input/GuiTaskExecutor.java b/jsettlers.logic/src/main/java/jsettlers/input/GuiTaskExecutor.java
index 116e8294eb..3723b8cf2a 100644
--- a/jsettlers.logic/src/main/java/jsettlers/input/GuiTaskExecutor.java
+++ b/jsettlers.logic/src/main/java/jsettlers/input/GuiTaskExecutor.java
@@ -25,6 +25,7 @@
import jsettlers.common.buildings.IBuilding;
import jsettlers.common.map.shapes.HexGridArea;
import jsettlers.common.movable.EMovableType;
+import jsettlers.common.movable.IMovable;
import jsettlers.common.position.ShortPoint2D;
import jsettlers.common.utils.mutables.MutableInt;
import jsettlers.input.tasks.ChangeTowerSoldiersGuiTask;
@@ -55,6 +56,7 @@
import jsettlers.logic.buildings.workers.DockyardBuilding;
import jsettlers.logic.map.grid.partition.manager.settings.MaterialProductionSettings;
import jsettlers.logic.movable.Movable;
+import jsettlers.logic.movable.MovableDataManager;
import jsettlers.logic.movable.interfaces.ILogicMovable;
import jsettlers.network.client.task.packets.TaskPacket;
import jsettlers.network.synchronic.timer.ITaskExecutor;
@@ -275,7 +277,7 @@ private void setBuildingPriority(SetBuildingPriorityGuiTask task) {
private void convertMovables(ConvertGuiTask guiTask) {
for (Integer currID : guiTask.getSelection()) {
- ILogicMovable movable = Movable.getMovableByID(currID);
+ ILogicMovable movable = MovableDataManager.getMovableByID(currID);
if (movable != null) {
movable.convertTo(guiTask.getTargetType());
}
@@ -285,7 +287,7 @@ private void convertMovables(ConvertGuiTask guiTask) {
private void stopOrStartWorking(List selectedMovables, boolean stop) {
for (Integer currID : selectedMovables) {
- ILogicMovable movable = Movable.getMovableByID(currID);
+ ILogicMovable movable = MovableDataManager.getMovableByID(currID);
if (movable != null) {
movable.stopOrStartWorking(stop);
}
@@ -294,7 +296,7 @@ private void stopOrStartWorking(List selectedMovables, boolean stop) {
private void killSelectedMovables(List selectedMovables) {
for (Integer currID : selectedMovables) {
- ILogicMovable curr = Movable.getMovableByID(currID);
+ ILogicMovable curr = MovableDataManager.getMovableByID(currID);
if (curr != null) {
curr.kill();
}
@@ -310,7 +312,18 @@ private void killSelectedMovables(List selectedMovables) {
* A list of the id's of the movables.
*/
private void moveSelectedTo(ShortPoint2D targetPosition, List movableIds) {
- List movables = stream(movableIds).map(Movable::getMovableByID).filter(Objects::nonNull).collect(Collectors.toList());
+ if (movableIds.size() == 1) {
+ ILogicMovable currMovable = MovableDataManager.getMovableByID(movableIds.get(0));
+ if (currMovable != null) {
+ currMovable.moveTo(targetPosition);
+ }
+ } else if (!movableIds.isEmpty()) {
+ sendMovablesNew(targetPosition, movableIds);
+ }
+ }
+
+ private void sendMovablesNew(ShortPoint2D targetPosition, List movableIds) {
+ List movables = stream(movableIds).map(MovableDataManager::getMovableByID).filter(Objects::nonNull).collect(Collectors.toList());
if (movables.isEmpty()) {
return;
@@ -389,8 +402,8 @@ private void unloadFerry(MovableGuiTask task) {
private void forMovables(MovableGuiTask task, Consumer movableConsumer) {
stream(task.getSelection())
- .map(Movable::getMovableByID)
- .filter(ILogicMovable::isAlive)
+ .map(MovableDataManager::getMovableByID)
+ .filter(IMovable::isAlive)
.filter(movable -> movable.getMovableType() == EMovableType.FERRY)
.forEach(movableConsumer);
}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/buildings/spawn/SpawnBuilding.java b/jsettlers.logic/src/main/java/jsettlers/logic/buildings/spawn/SpawnBuilding.java
index ba8a5e7a2d..b02c3d96c6 100644
--- a/jsettlers.logic/src/main/java/jsettlers/logic/buildings/spawn/SpawnBuilding.java
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/buildings/spawn/SpawnBuilding.java
@@ -20,7 +20,7 @@
import jsettlers.common.position.ShortPoint2D;
import jsettlers.logic.buildings.Building;
import jsettlers.logic.buildings.IBuildingsGrid;
-import jsettlers.logic.movable.Movable;
+import jsettlers.logic.movable.EntityFactory;
import jsettlers.logic.movable.interfaces.ILogicMovable;
import jsettlers.logic.player.Player;
@@ -56,7 +56,7 @@ protected int subTimerEvent() {
ILogicMovable movableAtDoor = super.grid.getMovable(super.getDoor());
if (movableAtDoor == null) {
- movableAtDoor = new Movable(super.grid.getMovableGrid(), getMovableType(), getDoor(), super.getPlayer());
+ movableAtDoor = EntityFactory.createMovable(super.grid.getMovableGrid(), getMovableType(), getDoor(), super.getPlayer());
produced++;
if (produced < getProduceLimit()) {
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/constants/Constants.java b/jsettlers.logic/src/main/java/jsettlers/logic/constants/Constants.java
index ef74fbb19f..050ebf7ec6 100644
--- a/jsettlers.logic/src/main/java/jsettlers/logic/constants/Constants.java
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/constants/Constants.java
@@ -44,6 +44,8 @@ private Constants() {
public static final short MOVABLE_BEND_DURATION = 500;
+ public static final short BRICKLAYER_ACTION_DURATION = 1000;
+
public static final short MOVABLE_VIEW_DISTANCE = 8;
public static final short MOVABLE_FLOCK_TO_DECENTRALIZE_MAX_RADIUS = 2;
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/map/grid/GameSerializer.java b/jsettlers.logic/src/main/java/jsettlers/logic/map/grid/GameSerializer.java
index f3c62f33e9..f50d900c83 100644
--- a/jsettlers.logic/src/main/java/jsettlers/logic/map/grid/GameSerializer.java
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/map/grid/GameSerializer.java
@@ -23,6 +23,7 @@
import jsettlers.logic.buildings.trading.MarketBuilding;
import jsettlers.logic.map.loading.MapLoadException;
import jsettlers.logic.movable.Movable;
+import jsettlers.logic.movable.MovableDataManager;
/**
* This class serializes and deserializes the {@link MainGrid} and therefore the complete game state.
@@ -94,7 +95,7 @@ public void run() {
Building.writeStaticState(oos);
MarketBuilding.writeStaticState(oos);
HarborBuilding.writeStaticState(oos);
- Movable.writeStaticState(oos);
+ MovableDataManager.writeStaticState(oos);
oos.writeObject(grid);
} catch (Throwable t) {
t.printStackTrace();
@@ -118,7 +119,7 @@ public void run() {
Building.readStaticState(ois);
MarketBuilding.readStaticState(ois);
HarborBuilding.readStaticState(ois);
- Movable.readStaticState(ois);
+ MovableDataManager.readStaticState(ois);
grid = (MainGrid) ois.readObject();
} catch (Throwable t) {
t.printStackTrace();
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/map/grid/MainGrid.java b/jsettlers.logic/src/main/java/jsettlers/logic/map/grid/MainGrid.java
index 4c8ec5e621..138fed1494 100644
--- a/jsettlers.logic/src/main/java/jsettlers/logic/map/grid/MainGrid.java
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/map/grid/MainGrid.java
@@ -18,6 +18,7 @@
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.BitSet;
+import java.util.Collection;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -115,7 +116,8 @@
import jsettlers.logic.map.loading.list.MapList;
import jsettlers.logic.map.loading.newmap.MapFileHeader;
import jsettlers.logic.map.loading.newmap.MapFileHeader.MapType;
-import jsettlers.logic.movable.Movable;
+import jsettlers.logic.movable.EntityFactory;
+import jsettlers.logic.movable.MovableDataManager;
import jsettlers.logic.movable.interfaces.AbstractMovableGrid;
import jsettlers.logic.movable.interfaces.IAttackable;
import jsettlers.logic.movable.interfaces.ILogicMovable;
@@ -437,7 +439,7 @@ public final boolean isInBounds(int x, int y) {
}
final ILogicMovable createNewMovableAt(ShortPoint2D pos, EMovableType type, Player player) {
- return new Movable(movablePathfinderGrid, type, pos, player);
+ return EntityFactory.createMovable(movablePathfinderGrid, type, pos, player);
}
/**
@@ -942,7 +944,7 @@ public void hitWithArrowAt(ArrowObject arrow) {
@Override
public void spawnDonkey(ShortPoint2D position, Player player) {
Player realPlayer = partitionsGrid.getPlayer(player.getPlayerId());
- ILogicMovable donkey = new Movable(movablePathfinderGrid, EMovableType.DONKEY, position, realPlayer);
+ ILogicMovable donkey = EntityFactory.createMovable(movablePathfinderGrid, EMovableType.DONKEY, position, realPlayer);
donkey.leavePosition();
}
@@ -2050,8 +2052,8 @@ public final IMapObject getMapObjectsAt(short x, short y) {
}
@Override
- public final ConcurrentLinkedQueue extends IViewDistancable> getMovableViewDistancables() {
- return Movable.getAllMovables();
+ public final Collection extends IViewDistancable> getMovableViewDistancables() {
+ return MovableDataManager.getAllMovables();
}
@Override
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/BehaviorTreeHelper.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/BehaviorTreeHelper.java
new file mode 100644
index 0000000000..6d8daa84cd
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/BehaviorTreeHelper.java
@@ -0,0 +1,303 @@
+package jsettlers.logic.movable;
+
+import jsettlers.common.material.EMaterialType;
+import jsettlers.common.movable.EMovableAction;
+import jsettlers.common.movable.EMovableType;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.constants.MatchConstants;
+import jsettlers.logic.movable.components.AnimationComponent;
+import jsettlers.logic.movable.components.GameFieldComponent;
+import jsettlers.logic.movable.components.MaterialComponent;
+import jsettlers.logic.movable.components.SteeringComponent;
+import jsettlers.logic.movable.simplebehaviortree.IBooleanConditionFunction;
+import jsettlers.logic.movable.simplebehaviortree.IEMaterialTypeSupplier;
+import jsettlers.logic.movable.simplebehaviortree.IIntegerSupplier;
+import jsettlers.logic.movable.simplebehaviortree.INodeStatusActionConsumer;
+import jsettlers.logic.movable.simplebehaviortree.INodeStatusActionFunction;
+import jsettlers.logic.movable.simplebehaviortree.IShortSupplier;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.NodeStatus;
+import jsettlers.logic.movable.simplebehaviortree.Tick;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Action;
+import jsettlers.logic.movable.simplebehaviortree.nodes.AlwaysFail;
+import jsettlers.logic.movable.simplebehaviortree.nodes.AlwaysRunning;
+import jsettlers.logic.movable.simplebehaviortree.nodes.AlwaysSucceed;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Condition;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Debug;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Guard;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Inverter;
+import jsettlers.logic.movable.simplebehaviortree.nodes.MemSelector;
+import jsettlers.logic.movable.simplebehaviortree.nodes.MemSequence;
+import jsettlers.logic.movable.simplebehaviortree.nodes.NotificationCondition;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Parallel;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Property;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Repeat;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Selector;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Sequence;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Wait;
+
+public final class BehaviorTreeHelper {
+
+ /* --- Node Factory --- */
+
+ public static Action action(INodeStatusActionConsumer action) {
+ return new Action<>(action);
+ }
+
+ public static Node action(String debugMessage, INodeStatusActionConsumer action) {
+ return debug(debugMessage, action(action));
+ }
+
+ public static Action action(INodeStatusActionFunction action) {
+ return new Action<>(action);
+ }
+
+ public static Node action(String debugMessage, INodeStatusActionFunction action) {
+ return debug(debugMessage, action(action));
+ }
+
+ public static Condition condition(IBooleanConditionFunction condition) {
+ return new Condition<>(condition);
+ }
+
+ public static Node condition(String debugMessage, IBooleanConditionFunction condition) {
+ return debug(debugMessage, condition(condition));
+ }
+
+ public static AlwaysFail alwaysFail() {
+ return new AlwaysFail<>();
+ }
+
+ public static AlwaysSucceed alwaysSucceed() {
+ return new AlwaysSucceed<>();
+ }
+
+ public static Guard guard(IBooleanConditionFunction condition, Node child) {
+ return guard(condition, true, child);
+ }
+
+ public static Node guard(String debugMessage, IBooleanConditionFunction condition, Node child) {
+ return guard(debugMessage, condition, true, child);
+ }
+
+ public static Guard guard(IBooleanConditionFunction condition, boolean shouldBe, Node child) {
+ return new Guard<>(condition, shouldBe, child);
+ }
+
+ public static Node guard(String debugMessage, IBooleanConditionFunction condition, boolean shouldBe, Node child) {
+ return debug(debugMessage, guard(condition, shouldBe, child));
+ }
+
+ public static Inverter inverter(Node child) {
+ return new Inverter<>(child);
+ }
+
+ @SafeVarargs
+ public static MemSelector memSelector(Node... children) {
+ return new MemSelector<>(children);
+ }
+
+ @SafeVarargs
+ public static Node memSelector(String debugMessage, Node... children) {
+ return debug(debugMessage, memSelector(children));
+ }
+
+ @SafeVarargs
+ public static MemSequence memSequence(Node... children) {
+ return new MemSequence<>(children);
+ }
+
+ @SafeVarargs
+ public static Node memSequence(String debugMessage, Node... children) {
+ return debug(debugMessage, memSequence(children));
+ }
+
+ @SafeVarargs
+ public static Parallel parallel(Parallel.Policy successPolicy, boolean preemptive, Node... children) {
+ return new Parallel<>(successPolicy, preemptive, children);
+ }
+
+ public static Node repeat(String debugMessage, Repeat.Policy policy, Node condition, Node child) {
+ return debug(debugMessage, new Repeat<>(policy, condition, child));
+ }
+
+ public static Repeat repeat(Repeat.Policy policy, Node condition, Node child) {
+ return new Repeat<>(policy, condition, child);
+ }
+
+ public static Repeat repeat(Node condition, Node child) {
+ return new Repeat<>(condition, child);
+ }
+
+ @SafeVarargs
+ public static Selector selector(Node... children) {
+ return new Selector<>(children);
+ }
+
+ @SafeVarargs
+ public static Node selector(String debugMessage, Node... children) {
+ return debug(debugMessage, new Selector<>(children));
+ }
+
+ @SafeVarargs
+ public static Sequence sequence(Node... children) {
+ return new Sequence<>(children);
+ }
+
+ @SafeVarargs
+ public static Node sequence(String debugMessage, Node... children) {
+ return debug(debugMessage, sequence(children));
+ }
+
+ public static Wait wait(Node condition) {
+ return new Wait<>(condition);
+ }
+
+ public static NotificationCondition notificationCondition(Class type) {
+ return new NotificationCondition(type);
+ }
+
+ public static NotificationCondition notificationCondition(Class type, boolean consume) {
+ return new NotificationCondition(type, consume);
+ }
+
+ public static NotificationCondition notificationCondition(Class type, IBooleanConditionFunction predicate, boolean consume) {
+ return new NotificationCondition(type, predicate, consume);
+ }
+
+ public static Node waitForTargetReachedAndFailIfNotReachable() {
+ return sequence("waitForTargetReachedAndFailIfNotReachable",
+ inverter(
+ notificationCondition(SteeringComponent.TargetNotReachedNotification.class, true)
+ ),
+ wait(
+ notificationCondition(SteeringComponent.TargetReachedNotification.class, true)
+ )
+ );
+ }
+
+ public static Node waitForPathFinished(Node targetReachedChild, Node targetNotReachedChild) {
+ return debug("waitForPathFinished", selector(
+ triggerGuard(SteeringComponent.TargetReachedNotification.class, debug("TargetReachedNotification", targetReachedChild)),
+ triggerGuard(SteeringComponent.TargetNotReachedNotification.class, debug("TargetNotReachedNotification", targetNotReachedChild)),
+ debug("path not finished yet", new AlwaysRunning<>())
+ ));
+ }
+
+ public static Node waitForPathFinished(Node targetReachedChild, Node targetNotReachedChild, Node whenPathFinished) {
+ return sequence(
+ waitForPathFinished(targetReachedChild, targetNotReachedChild),
+ debug("path finished", whenPathFinished)
+ );
+ }
+
+ public static Property setAttackableWhile(boolean value, Node child) {
+ return new Property<>(
+ (context, v) -> context.entity.attackableComponent().isAttackable(v),
+ (context) -> context.entity.attackableComponent().isAttackable(), value, child
+ );
+ }
+
+ public static Node defaultIdleBehavior() {
+ return debug("idle behavior",
+ setIdleBehaviorActiveWhile(true,
+ alwaysSucceed()
+ )
+ );
+ }
+
+ public static Property setIdleBehaviorActiveWhile(boolean value, Node child) {
+ return new Property<>(
+ (context, v) -> context.entity.steeringComponent().IsIdleBehaviorActive(v),
+ (context) -> context.entity.steeringComponent().IsIdleBehaviorActive(), value, child
+ );
+ }
+
+ public static Guard triggerGuard(Class extends Notification> type, Node child) {
+ return new Guard<>(entity -> entity.component.hasNotificationOfType(type), true, child);
+ }
+
+ public static Action startAnimation(EMovableAction animation, IShortSupplier durationSupplier, boolean isChained) {
+ return new Action<>(context -> {
+ context.entity.getAnimationComponent().startAnimation(animation, durationSupplier.apply(context), isChained);
+ });
+ }
+
+ public static Node startAndWaitForAnimation(EMovableAction animation, short duration) {
+ return startAndWaitForAnimation(animation, c->duration, false);
+ }
+
+ public static Node startAndWaitForAnimation(EMovableAction animation, IShortSupplier durationSupplier, boolean isChained) {
+ return memSequence("startAndWaitForAnimation with " + animation,
+ debug("start animation", startAnimation(animation, durationSupplier, isChained)),
+ debug("wait for animation to finish", waitForNotification(AnimationComponent.AnimationFinishedNotification.class, n -> n.type == animation, true))
+ );
+ }
+
+ public static void convertTo(Entity entity, EMovableType type) {
+ Entity blueprint = EntityFactory.createEntity(entity.gameFieldComponent().movableGrid, type, entity.movableComponent().getPosition(), entity.movableComponent().getPlayer());
+ entity.convertTo(blueprint);
+ }
+
+ public static Node alwaysSucceed(Node child) {
+ return new Selector(child, new AlwaysSucceed());
+ }
+
+ public static Sleep sleep(IIntegerSupplier delaySupplier) {
+ return new Sleep(delaySupplier);
+ }
+
+ public static Sleep sleep(int delay) {
+ return new Sleep(c->delay);
+ }
+
+ public static class Sleep extends Node {
+ private static final long serialVersionUID = 8774557186392581042L;
+ int endTime;
+ final IIntegerSupplier delaySupplier;
+
+ public Sleep(IIntegerSupplier delaySupplier) {
+ super();
+ this.delaySupplier = delaySupplier;
+ }
+
+ @Override
+ public NodeStatus onTick(Tick tick) {
+ int remaining = endTime - MatchConstants.clock().getTime();
+ if (remaining <= 0) { return NodeStatus.SUCCESS; }
+ tick.target.entity.setInvocationDelay(remaining);
+ return NodeStatus.RUNNING;
+ }
+
+ @Override
+ public void onOpen(Tick tick) {
+ endTime = MatchConstants.clock().getTime() + delaySupplier.apply(tick.target);
+ }
+ }
+
+ public static Node waitForNotification(Class extends Notification> type, boolean consume) {
+ return wait(notificationCondition(type, consume));
+ }
+
+ public static Node waitForNotification(Class type, IBooleanConditionFunction predicate, boolean consume) {
+ return wait(notificationCondition(type, predicate, consume));
+ }
+
+ public static Debug debug(String msg, Node child) {
+ return new Debug(msg, child);
+ }
+
+ public static Debug debug(String msg) {
+ return new Debug(msg);
+ }
+
+ public static Action dropMaterial(IEMaterialTypeSupplier materialTypeSupplier) {
+ return new Action<>(c -> {
+ EMaterialType material = materialTypeSupplier.apply(c);
+ if (material.isDroppable()) {
+ c.entity.gameFieldComponent().movableGrid.dropMaterial(c.entity.movableComponent().getPosition(), material, true, false);
+ }
+ c.entity.materialComponent().setMaterial(EMaterialType.NO_MATERIAL);
+ });
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/Context.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Context.java
new file mode 100644
index 0000000000..92b2a2b1ce
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Context.java
@@ -0,0 +1,24 @@
+package jsettlers.logic.movable;
+
+import java.io.Serializable;
+
+import jsettlers.logic.movable.components.Component;
+
+/**
+ * Created by homoroselaps
+ */
+public final class Context implements Serializable {
+ public final Entity entity;
+ public final Component component;
+
+ public int debugLevel = 0;
+
+ public Context(Entity entity, Component component) {
+ this.entity = entity;
+ this.component = component;
+ }
+
+ public Entity getEntity() { return entity; }
+
+ public Component getComponent() { return component; }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/Entity.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Entity.java
new file mode 100644
index 0000000000..64b42211ac
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Entity.java
@@ -0,0 +1,322 @@
+package jsettlers.logic.movable;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import java8.util.Optional;
+import jsettlers.logic.constants.Constants;
+import jsettlers.logic.movable.components.AnimationComponent;
+import jsettlers.logic.movable.components.AttackableComponent;
+import jsettlers.logic.movable.components.BearerComponent;
+import jsettlers.logic.movable.components.BricklayerComponent;
+import jsettlers.logic.movable.components.BuildingWorkerComponent;
+import jsettlers.logic.movable.components.Component;
+import jsettlers.logic.movable.components.DonkeyComponent;
+import jsettlers.logic.movable.components.GameFieldComponent;
+import jsettlers.logic.movable.components.MarkedPositonComponent;
+import jsettlers.logic.movable.components.MaterialComponent;
+import jsettlers.logic.movable.components.MovableComponent;
+import jsettlers.logic.movable.components.MultiMaterialComponent;
+import jsettlers.logic.movable.components.SpecialistComponent;
+import jsettlers.logic.movable.components.SteeringComponent;
+import jsettlers.logic.timer.IScheduledTimerable;
+import jsettlers.logic.timer.RescheduleTimer;
+
+/**
+ * @author homoroselaps
+ */
+
+public class Entity implements Serializable, IScheduledTimerable {
+ private static final long serialVersionUID = -5615478576016074072L;
+
+ private final int id;
+
+ private boolean debug;
+
+ private final Map, Component> components = new IdentityHashMap<>();
+ private final Map, Component> componentLookup = new IdentityHashMap<>();
+
+ private Set notificationsNext;
+ private Set notificationsCurrent;
+
+ public Set getAllNotifications() {
+ return notificationsCurrent;
+ }
+
+
+ public enum State {
+ ACTIVE,
+ INACTIVE,
+ UNINITALIZED
+ }
+
+ private State state;
+ private int invocationDelay;
+
+ public Entity() {
+ id = MovableDataManager.getNextID();
+ state = State.UNINITALIZED;
+ notificationsNext = new HashSet<>();
+ notificationsCurrent = new HashSet<>();
+ resetInvokationDelay();
+ }
+
+ public Entity(Component... cs) {
+ this();
+ for (Component c : cs) {
+ add(c);
+ }
+ }
+
+ /**
+ * Checks whether or not all Component dependencies are satisfied.
+ * @return {@code true} if all Component dependencies are satisfied, {@code false} otherwise.
+ */
+ boolean checkComponentDependencies() {
+ for (Class extends Component> cmp : this.componentLookup.keySet()) {
+ Requires ann = cmp.getAnnotation(Requires.class);
+ if (ann == null) { continue; }
+ for (Class extends Component> dependency : ann.value()) {
+ assert componentLookup.containsKey(dependency) : componentLookup.get(cmp).getClass().getName() + "[" + cmp.getName() + "]: " + dependency.getName() + " missing";
+ }
+ }
+ return true;
+ }
+
+ void toggleDebug() {
+ debug = !debug;
+ }
+
+ public boolean isInDebugMode() {
+ return debug;
+ }
+
+ private int resetInvokationDelay() {
+ int lastValue = invocationDelay;
+ invocationDelay = -1;
+ return lastValue;
+ }
+
+ public void setInvocationDelay(int delay) {
+ invocationDelay = invocationDelay > 0 ? Math.min(invocationDelay, delay) : delay;
+ }
+
+ public boolean isActive() {
+ return state == State.ACTIVE;
+ }
+
+ public void setActive(boolean active) {
+ if (active == isActive()) {
+ return;
+ }
+
+ if (state == State.UNINITALIZED) {
+ initialize();
+ }
+
+ if (active) {
+ state = State.ACTIVE;
+ for (Component c : components.values()) {
+ c.enable();
+ }
+ } else {
+ state = State.INACTIVE;
+ for (Component c : components.values()) {
+ c.disable();
+ }
+ }
+ }
+
+ private void initialize() {
+ for (Component component : components.values()) {
+ component.wakeUp();
+ }
+ RescheduleTimer.add(this, Constants.MOVABLE_INTERRUPT_PERIOD);
+ }
+
+ private void invokeUpdate() {
+ for (Component component : components.values()) {
+ component.update();
+ }
+ for (Component component : components.values()) {
+ component.lateUpdate();
+ }
+ }
+
+ private void invokeDestroy() {
+ for (Component component : components.values()) {
+ component.destroy();
+ }
+ }
+
+ public int getID() {
+ return id;
+ }
+
+ public void add(Component c) {
+ Class cls = c.getClass();
+ assert !componentLookup.containsKey(cls) : "Component already registered" + cls.getName();
+ componentLookup.put(cls, c);
+ components.put(cls, c);
+ c.entity = this;
+ // Iterate over all super classes
+ cls = cls.getSuperclass();
+ while (cls != null && cls != Component.class) {
+ assert !componentLookup.containsKey(cls) : "Component already registered" + cls.getName();
+ componentLookup.put(cls, c);
+ cls = cls.getSuperclass();
+ }
+ }
+
+ public Component remove(Class extends Component> c) {
+ Component result = componentLookup.remove(c);
+ components.remove(c);
+
+ Class cls = c.getSuperclass();
+ while (cls != null && cls != Component.class) {
+ componentLookup.remove(cls);
+ cls = cls.getSuperclass();
+ }
+
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C getComponent(Class c) {
+ return (C) componentLookup.get(c);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Optional getComponentOptional(Class c) {
+ return Optional.ofNullable((C) componentLookup.get(c));
+ }
+
+ public boolean containsComponent(Class extends Component> c) {
+ return componentLookup.containsKey(c);
+ }
+
+ public void raiseNotification(Notification note) {
+ notificationsNext.add(note);
+ }
+
+ public void convertTo(Entity blueprint) {
+ blueprint.setActive(false);
+
+ // remove all unused components
+ Iterator> local_component_it = components.keySet().iterator();
+ while (local_component_it.hasNext()) {
+ Class extends Component> cls = local_component_it.next();
+ if (!blueprint.components.containsKey(cls)) {
+ local_component_it.remove();
+ remove(cls);
+ }
+ }
+
+ // add all new components
+ List newComponents = new ArrayList<>();
+ Iterator> blueprint_component_it = blueprint.components.keySet().iterator();
+ while (blueprint_component_it.hasNext()) {
+ Class extends Component> cls = blueprint_component_it.next();
+ // ignore components we already have
+ if (components.containsKey(cls)) {
+ continue;
+ }
+ Component comp = blueprint.getComponent(cls);
+ newComponents.add(comp);
+ add(comp);
+ blueprint_component_it.remove();
+ blueprint.remove(cls);
+ }
+
+ if (state != State.UNINITALIZED) {
+ // initialize all new components
+ for (Component c : newComponents) {
+ c.wakeUp();
+ }
+ if (state == State.ACTIVE) {
+ for (Component c : newComponents) {
+ c.enable();
+ }
+ }
+ }
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder(30);
+ sb.append("Entity");
+ sb.append("(").append(id).append(")");
+ sb.append("[");
+ for (Component c : components.values()) {
+ sb.append(c).append(", ");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return id;
+ }
+
+ @Override
+ public int timerEvent() {
+ if (!isActive()) { return -1; }
+
+ invokeUpdate();
+
+ notificationsCurrent = notificationsNext;
+ notificationsNext = new HashSet<>();
+
+ return Math.max(resetInvokationDelay(), Constants.MOVABLE_INTERRUPT_PERIOD);
+ }
+
+ @Override
+ public void kill() {
+ setActive(false);
+ invokeDestroy();
+ }
+
+ public final AnimationComponent getAnimationComponent() {
+ return getComponent(AnimationComponent.class);
+ }
+
+ public final SteeringComponent steeringComponent() {
+ return getComponent(SteeringComponent.class);
+ }
+
+ public final MovableComponent movableComponent() {
+ return getComponent(MovableComponent.class);
+ }
+
+ public final BearerComponent bearerComponent() {
+ return getComponent(BearerComponent.class);
+ }
+
+ public final SpecialistComponent specialistComponent() {
+ return getComponent(SpecialistComponent.class);
+ }
+
+ public final GameFieldComponent gameFieldComponent() {
+ return getComponent(GameFieldComponent.class);
+ }
+
+ public final MaterialComponent materialComponent() { return getComponent(MaterialComponent.class); }
+
+ public final MultiMaterialComponent multiMaterialComponent() { return getComponent(MultiMaterialComponent.class); }
+
+ public final DonkeyComponent donkeyComponent() { return getComponent(DonkeyComponent.class); }
+
+ public final AttackableComponent attackableComponent() { return getComponent(AttackableComponent.class); }
+
+ public final MarkedPositonComponent markedPositonComponent() { return getComponent(MarkedPositonComponent.class); }
+
+ public final BuildingWorkerComponent buildingWorkerComponent() { return getComponent(BuildingWorkerComponent.class); }
+
+ public final BricklayerComponent bricklayerComponent() { return getComponent(BricklayerComponent.class); }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/EntityFactory.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/EntityFactory.java
new file mode 100644
index 0000000000..31e1b550ec
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/EntityFactory.java
@@ -0,0 +1,191 @@
+package jsettlers.logic.movable;
+
+import jsettlers.common.material.EMaterialType;
+import jsettlers.common.movable.EDirection;
+import jsettlers.common.movable.EMovableType;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.common.selectable.ESelectionType;
+import jsettlers.logic.constants.MatchConstants;
+import jsettlers.logic.movable.components.AnimationComponent;
+import jsettlers.logic.movable.components.AttackableComponent;
+import jsettlers.logic.movable.components.BearerBehaviorComponent;
+import jsettlers.logic.movable.components.BearerComponent;
+import jsettlers.logic.movable.components.BricklayerBehaviorComponent;
+import jsettlers.logic.movable.components.BricklayerComponent;
+import jsettlers.logic.movable.components.BuildingWorkerBehaviorComponent;
+import jsettlers.logic.movable.components.BuildingWorkerComponent;
+import jsettlers.logic.movable.components.DonkeyBehaviorComponent;
+import jsettlers.logic.movable.components.DonkeyComponent;
+import jsettlers.logic.movable.components.GameFieldComponent;
+import jsettlers.logic.movable.components.GeologistBehaviorComponent;
+import jsettlers.logic.movable.components.MarkedPositonComponent;
+import jsettlers.logic.movable.components.MaterialComponent;
+import jsettlers.logic.movable.components.MovableComponent;
+import jsettlers.logic.movable.components.MultiMaterialComponent;
+import jsettlers.logic.movable.components.PlayerComandComponent;
+import jsettlers.logic.movable.components.SelectableComponent;
+import jsettlers.logic.movable.components.SpecialistComponent;
+import jsettlers.logic.movable.components.SteeringComponent;
+import jsettlers.logic.movable.interfaces.AbstractMovableGrid;
+import jsettlers.logic.movable.interfaces.ILogicMovable;
+import jsettlers.logic.player.Player;
+
+/**
+ * @author homoroselaps
+ */
+public final class EntityFactory {
+ private EntityFactory() {}
+
+ public static ILogicMovable createMovable(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) {
+ switch (movableType) {
+ //case BEARER:
+ case BRICKLAYER:
+ case GEOLOGIST:
+ case SMITH:
+ case LUMBERJACK:
+ case STONECUTTER:
+ case SAWMILLER:
+ case FORESTER:
+ case MELTER:
+ case MINER:
+ case FISHERMAN:
+ case FARMER:
+ case MILLER:
+ case BAKER:
+ case PIG_FARMER:
+ case DONKEY_FARMER:
+ case SLAUGHTERER:
+ case CHARCOAL_BURNER:
+ case WATERWORKER:
+ case WINEGROWER:
+ case HEALER:
+ case DOCKWORKER:
+ case DONKEY:
+ return new MovableWrapper(createActiveEntity(grid, movableType, position, player));
+ default:
+ return new Movable(grid, movableType, position, player);
+ }
+ }
+
+ private static Entity createActiveEntity(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) {
+ Entity entity = createEntity(grid, movableType, position, player);
+ entity.setActive(true);
+ return entity;
+ }
+
+ public static Entity createEntity(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) {
+ Entity entity = null;
+ switch (movableType) {
+ case BEARER:
+ entity = createBearer(grid, movableType, position, player);
+ break;
+ case SMITH:
+ case LUMBERJACK:
+ case STONECUTTER:
+ case SAWMILLER:
+ case FORESTER:
+ case MELTER:
+ case MINER:
+ case FISHERMAN:
+ case FARMER:
+ case MILLER:
+ case BAKER:
+ case PIG_FARMER:
+ case DONKEY_FARMER:
+ case SLAUGHTERER:
+ case CHARCOAL_BURNER:
+ case WATERWORKER:
+ case WINEGROWER:
+ case HEALER:
+ case DOCKWORKER:
+ entity = createBuildingWorker(grid, movableType, position, player);
+ break;
+ case GEOLOGIST:
+ entity = createGeologist(grid, movableType, position, player);
+ break;
+ case DONKEY:
+ entity = createDonkey(grid, movableType, position, player);
+ break;
+ case BRICKLAYER:
+ entity = createBricklayer(grid, movableType, position, player);
+ break;
+ }
+ assert entity != null : "Type not found by EntityFactory";
+ assert entity.checkComponentDependencies() : "Not all Component dependencies are resolved.";
+ return entity;
+ }
+
+ private static Entity createBricklayer(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) {
+ Entity entity = new Entity();
+ entity.add(new BricklayerComponent());
+ entity.add(new BricklayerBehaviorComponent());
+ entity.add(new AnimationComponent());
+ EDirection dir = EDirection.VALUES[MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS)];
+ entity.add(new MovableComponent(movableType, player, position, dir));
+ entity.add(new SteeringComponent());
+ entity.add(new GameFieldComponent(grid));
+ entity.add(new SelectableComponent(ESelectionType.PEOPLE));
+ return entity;
+ }
+
+ private static Entity createBuildingWorker(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) {
+ Entity entity = new Entity();
+ entity.add(new MaterialComponent());
+ entity.add(new BuildingWorkerComponent());
+ entity.add(new BuildingWorkerBehaviorComponent());
+ entity.add(new AnimationComponent());
+ EDirection dir = EDirection.VALUES[MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS)];
+ entity.add(new MovableComponent(movableType, player, position, dir));
+ entity.add(new SteeringComponent());
+ entity.add(new GameFieldComponent(grid));
+ entity.add(new SelectableComponent(ESelectionType.PEOPLE));
+ entity.add(new MarkedPositonComponent());
+ return entity;
+ }
+
+ private static Entity createDonkey(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) {
+ Entity entity = new Entity();
+ entity.add(new MultiMaterialComponent());
+ entity.add(new DonkeyBehaviorComponent());
+ entity.add(new AnimationComponent());
+ entity.add(new AttackableComponent(movableType));
+ EDirection dir = EDirection.VALUES[MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS)];
+ entity.add(new MovableComponent(movableType, player, position, dir));
+ entity.add(new SteeringComponent());
+ entity.add(new GameFieldComponent(grid));
+ entity.add(new DonkeyComponent());
+ entity.add(new SelectableComponent(ESelectionType.PEOPLE));
+ return entity;
+ }
+
+ public static Entity createGeologist(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) {
+ Entity entity = new Entity();
+ entity.add(new GeologistBehaviorComponent());
+ entity.add(new SpecialistComponent());
+ entity.add(new AnimationComponent());
+ entity.add(new AttackableComponent(movableType));
+ entity.add(new MaterialComponent());
+ entity.add(new MarkedPositonComponent());
+ EDirection dir = EDirection.VALUES[MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS)];
+ entity.add(new MovableComponent(movableType, player, position, dir));
+ entity.add(new SteeringComponent());
+ entity.add(new GameFieldComponent(grid));
+ entity.add(new SelectableComponent(movableType.selectionType));
+ entity.add(new PlayerComandComponent());
+ return entity;
+ }
+
+ public static Entity createBearer(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D position, Player player) {
+ Entity entity = new Entity();
+ entity.add(new BearerBehaviorComponent());
+ entity.add(new BearerComponent());
+ entity.add(new AnimationComponent());
+ entity.add(new MaterialComponent());
+ EDirection dir = EDirection.VALUES[MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS)];
+ entity.add(new MovableComponent(movableType, player, position, dir));
+ entity.add(new SteeringComponent());
+ entity.add(new GameFieldComponent(grid));
+ entity.add(new SelectableComponent(movableType.selectionType));
+ return entity;
+ }
+}
\ No newline at end of file
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableBearerWrapper.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableBearerWrapper.java
new file mode 100644
index 0000000000..179b14e8c3
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableBearerWrapper.java
@@ -0,0 +1,49 @@
+package jsettlers.logic.movable;
+
+import jsettlers.common.material.EMaterialType;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.map.grid.partition.manager.manageables.IManageableBearer;
+import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IBarrack;
+import jsettlers.logic.map.grid.partition.manager.materials.interfaces.IMaterialOffer;
+import jsettlers.logic.map.grid.partition.manager.materials.interfaces.IMaterialRequest;
+import jsettlers.logic.map.grid.partition.manager.objects.WorkerCreationRequest;
+import jsettlers.logic.movable.components.BearerComponent;
+import jsettlers.logic.movable.components.MovableComponent;
+
+/**
+ * @author homoroselaps
+ */
+public final class ManageableBearerWrapper implements IManageableBearer {
+ private static final long serialVersionUID = 2252932151684965586L;
+
+ private final Entity entity;
+
+ public ManageableBearerWrapper(Entity entity) {
+ this.entity = entity;
+ }
+
+ @Override
+ public ShortPoint2D getPosition() {
+ return entity.getComponent(MovableComponent.class).getPosition();
+ }
+
+ @Override
+ public boolean becomeWorker(IWorkerRequester requester, WorkerCreationRequest request) {
+ return entity.getComponent(BearerComponent.class).becomeWorker(requester, request);
+ }
+
+ @Override
+ public boolean becomeWorker(IWorkerRequester requester, WorkerCreationRequest request, IMaterialOffer offer) {
+ return entity.getComponent(BearerComponent.class).becomeWorker(requester, request, offer);
+ }
+
+ @Override
+ public boolean becomeSoldier(IBarrack barrack) {
+ return entity.getComponent(BearerComponent.class).becomeSoldier(barrack);
+ }
+
+ @Override
+ public void deliver(EMaterialType materialType, IMaterialOffer offer, IMaterialRequest request) {
+ entity.getComponent(BearerComponent.class).deliver(materialType, offer, request);
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableBricklayerWrapper.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableBricklayerWrapper.java
new file mode 100644
index 0000000000..d4257a7678
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableBricklayerWrapper.java
@@ -0,0 +1,25 @@
+package jsettlers.logic.movable;
+
+import jsettlers.common.movable.EDirection;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.map.grid.partition.manager.manageables.IManageableBricklayer;
+import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IConstructableBuilding;
+import jsettlers.logic.movable.components.BricklayerComponent;
+
+public final class ManageableBricklayerWrapper implements IManageableBricklayer {
+ private static final long serialVersionUID = 2252932351648921543L;
+
+ private final Entity entity;
+
+ public ManageableBricklayerWrapper(Entity entity) { this.entity = entity; }
+
+ @Override
+ public ShortPoint2D getPosition() {
+ return entity.movableComponent().getPosition();
+ }
+
+ @Override
+ public boolean setBricklayerJob(IConstructableBuilding constructionSite, ShortPoint2D bricklayerTargetPos, EDirection direction) {
+ return entity.getComponent(BricklayerComponent.class).assignBricklayerJob(constructionSite, bricklayerTargetPos, direction);
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableWorkerWrapper.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableWorkerWrapper.java
new file mode 100644
index 0000000000..e5d22bdf6d
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/ManageableWorkerWrapper.java
@@ -0,0 +1,41 @@
+package jsettlers.logic.movable;
+
+import jsettlers.common.movable.EMovableType;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.map.grid.partition.manager.manageables.IManageableWorker;
+import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IWorkerRequestBuilding;
+import jsettlers.logic.movable.components.BuildingWorkerComponent;
+import jsettlers.logic.movable.components.MovableComponent;
+
+public final class ManageableWorkerWrapper implements IManageableWorker {
+ private static final long serialVersionUID = 2252932351688961586L;
+
+ private final Entity entity;
+
+ public ManageableWorkerWrapper(Entity entity) { this.entity = entity; }
+
+ @Override
+ public EMovableType getMovableType() {
+ return entity.movableComponent().getMovableType();
+ }
+
+ @Override
+ public void setWorkerJob(IWorkerRequestBuilding building) {
+ entity.getComponent(BuildingWorkerComponent.class).setWorkerJob(building);
+ }
+
+ @Override
+ public void buildingDestroyed() {
+ entity.getComponent(BuildingWorkerComponent.class).buildingDestroyed();
+ }
+
+ @Override
+ public boolean isAlive() {
+ return entity.isActive();
+ }
+
+ @Override
+ public ShortPoint2D getPosition() {
+ return entity.movableComponent().getPosition();
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/Movable.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Movable.java
index 484e242fb7..100e368805 100644
--- a/jsettlers.logic/src/main/java/jsettlers/logic/movable/Movable.java
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Movable.java
@@ -53,12 +53,9 @@
* @author Andreas Eberle
*/
public final class Movable implements ILogicMovable {
+ private static final long serialVersionUID = 2472076796407425256L;
private static final int SHIP_PUSH_DISTANCE = 10;
- private static final HashMap movablesByID = new HashMap<>();
- private static final ConcurrentLinkedQueue allMovables = new ConcurrentLinkedQueue<>();
- private static int nextID = Integer.MIN_VALUE;
-
protected final AbstractMovableGrid grid;
private final int id;
private final Player player;
@@ -108,29 +105,14 @@ public Movable(AbstractMovableGrid grid, EMovableType movableType, ShortPoint2D
RescheduleTimer.add(this, Constants.MOVABLE_INTERRUPT_PERIOD);
- this.id = nextID++;
- movablesByID.put(this.id, this);
- allMovables.offer(this);
+ this.id = MovableDataManager.getNextID();
+ MovableDataManager.add( this);
grid.enterPosition(position, this, true);
}
- @SuppressWarnings("unchecked")
- public static void readStaticState(ObjectInputStream ois) throws IOException, ClassNotFoundException {
- nextID = ois.readInt();
- allMovables.clear();
- allMovables.addAll((Collection extends ILogicMovable>) ois.readObject());
- movablesByID.putAll((Map extends Integer, ? extends ILogicMovable>) ois.readObject());
- }
-
- public static void writeStaticState(ObjectOutputStream oos) throws IOException {
- oos.writeInt(nextID);
- oos.writeObject(allMovables);
- oos.writeObject(movablesByID);
- }
-
/**
- * Tests if this movable can receive moveTo requests and if so, directs it to go to the given position.
+ * Tests if this movable can receive sendMoveToCommand requests and if so, directs it to go to the given position.
*
* @param targetPosition
* Desired position the movable should move to
@@ -785,28 +767,6 @@ 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
- * or null if the id can not be found
- */
- public static ILogicMovable getMovableByID(int id) {
- return movablesByID.get(id);
- }
-
- public static ConcurrentLinkedQueue getAllMovables() {
- return allMovables;
- }
-
- public static void resetState() {
- allMovables.clear();
- movablesByID.clear();
- nextID = Integer.MIN_VALUE;
- }
-
/**
* kills this movable.
*/
@@ -827,8 +787,7 @@ public final void kill() {
this.state = EMovableState.DEAD;
this.selected = false;
- movablesByID.remove(this.getID());
- allMovables.remove(this);
+ MovableDataManager.remove(this);
}
/**
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableDataManager.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableDataManager.java
new file mode 100644
index 0000000000..71fb7f4427
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableDataManager.java
@@ -0,0 +1,73 @@
+package jsettlers.logic.movable;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import jsettlers.logic.movable.interfaces.ILogicMovable;
+
+/**
+ * @author homoroselaps
+ */
+
+public final class MovableDataManager {
+ private static final HashMap movablesByID = new HashMap<>();
+ private static final ConcurrentLinkedQueue allMovables = new ConcurrentLinkedQueue<>();
+
+ private static int nextID = Integer.MIN_VALUE;
+
+ /**
+ * Used for networking to identify movables over the network.
+ *
+ * @param id
+ * id to be looked for
+ * @return returns the movable with the given ID
+ * or null if the id can not be found
+ */
+ public static ILogicMovable getMovableByID(int id) {
+ return movablesByID.get(id);
+ }
+
+ public static Collection getAllMovables() {
+ return allMovables;
+ }
+
+ public static void add(ILogicMovable movable) {
+ movablesByID.put(movable.getID(), movable);
+ allMovables.offer(movable);
+ }
+
+ public static void remove(ILogicMovable movable) {
+ movablesByID.remove(movable.getID());
+ allMovables.remove(movable);
+ }
+
+ public static void resetState() {
+ allMovables.clear();
+ movablesByID.clear();
+ nextID = Integer.MIN_VALUE;
+ }
+
+ static int getNextID() {
+ return nextID++;
+ }
+
+ public static void writeStaticState(ObjectOutputStream oos) throws IOException {
+ oos.writeObject(movablesByID);
+ oos.writeObject(allMovables);
+ oos.writeInt(nextID);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void readStaticState(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ movablesByID.clear();
+ movablesByID.putAll((Map extends Integer, ? extends ILogicMovable>) ois.readObject());
+ allMovables.clear();
+ allMovables.addAll((Collection extends ILogicMovable>) ois.readObject());
+ nextID = ois.readInt();
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableStrategy.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableStrategy.java
index b0afae1443..f344f3211a 100644
--- a/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableStrategy.java
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableStrategy.java
@@ -219,7 +219,7 @@ protected final void abortPath() {
* Checks preconditions before the next path step can be gone.
*
* @param pathTarget
- * Target of the current path.
+ * target of the current path.
* @param step
* The number of the current step where 1 means the first step.
* @return true if the path should be continued
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableWrapper.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableWrapper.java
new file mode 100644
index 0000000000..6ecd65f276
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/MovableWrapper.java
@@ -0,0 +1,297 @@
+package jsettlers.logic.movable;
+
+import java.io.Serializable;
+import java.util.List;
+
+import jsettlers.algorithms.path.Path;
+import jsettlers.common.buildings.EBuildingType;
+import jsettlers.common.material.EMaterialType;
+import jsettlers.common.movable.EDirection;
+import jsettlers.common.movable.EMovableAction;
+import jsettlers.common.movable.EMovableType;
+import jsettlers.common.movable.IMovable;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.common.selectable.ESelectionType;
+import jsettlers.logic.buildings.military.IBuildingOccupyableMovable;
+import jsettlers.logic.buildings.military.occupying.IOccupyableBuilding;
+import jsettlers.logic.movable.components.AnimationComponent;
+import jsettlers.logic.movable.components.AttackableComponent;
+import jsettlers.logic.movable.components.BuildingWorkerComponent;
+import jsettlers.logic.movable.components.MaterialComponent;
+import jsettlers.logic.movable.components.MovableComponent;
+import jsettlers.logic.movable.components.PlayerComandComponent;
+import jsettlers.logic.movable.components.SelectableComponent;
+import jsettlers.logic.movable.components.SteeringComponent;
+import jsettlers.logic.movable.interfaces.IAttackable;
+import jsettlers.logic.movable.interfaces.ILogicMovable;
+import jsettlers.logic.player.Player;
+
+/**
+ * @author homoroselaps
+ */
+public final class MovableWrapper implements ILogicMovable, Serializable {
+ private static final long serialVersionUID = -2853861825853788354L;
+
+ private final Entity entity;
+
+ public MovableWrapper(Entity entity) {
+ this.entity = entity;
+ }
+
+ //region Interface Implementations
+ @Override
+ public short getViewDistance() {
+ return entity.getComponentOptional(MovableComponent.class).map(MovableComponent::getViewDistance).orElse((short) 0);
+ }
+
+ @Override
+ public boolean needsPlayersGround() {
+ return entity.getComponentOptional(MovableComponent.class).map(MovableComponent::needsPlayersGround).orElse(false);
+ }
+
+ @Override
+ public boolean isShip() {
+ return false;
+ }
+
+ @Override
+ public EDirection getDirection() {
+ return entity.getComponentOptional(MovableComponent.class).map(MovableComponent::getViewDirection).orElse(EDirection.NORTH_EAST);
+ }
+
+ @Override
+ public EMovableAction getAction() {
+ return entity.getComponentOptional(AnimationComponent.class).map(AnimationComponent::getAnimation).orElse(EMovableAction.NO_ACTION);
+ }
+
+ @Override
+ public float getMoveProgress() {
+ return entity.getComponentOptional(AnimationComponent.class).map(AnimationComponent::getAnimationProgress).orElse(0f);
+ }
+
+ @Override
+ public EMaterialType getMaterial() {
+ return entity.getComponentOptional(MaterialComponent.class).map(MaterialComponent::getMaterial).orElse(EMaterialType.NO_MATERIAL);
+ }
+
+ @Override
+ public void receiveHit(float strength, ShortPoint2D attackerPos, byte attackingPlayer) {
+ entity.getComponentOptional(AttackableComponent.class).ifPresent(component -> component.receiveHit(strength, attackerPos, attackingPlayer));
+ }
+
+ @Override
+ public float getHealth() {
+ return entity.getComponentOptional(AttackableComponent.class).map(AttackableComponent::getHealth).orElse(entity.movableComponent().getMovableType().getHealth());
+ }
+
+ @Override
+ public boolean isAlive() {
+ return entity.getComponentOptional(AttackableComponent.class).map(c -> c.getHealth() > 0).orElse(true);
+ }
+
+ @Override
+ public boolean isRightstep() {
+ return entity.getComponentOptional(AnimationComponent.class).map(AnimationComponent::isRightStep).orElse(false);
+ }
+
+ @Override
+ public void stopOrStartWorking(boolean stop) {
+ entity.getComponentOptional(PlayerComandComponent.class).ifPresent(component -> {
+ if (stop) {
+ component.sendStopWorkCommand();
+ } else {
+ component.sendStartWorkCommand();
+ }
+ });
+ }
+
+ @Override
+ public List extends IMovable> getPassengers() {
+ return null;
+ }
+
+ @Override
+ public int getNumberOfCargoStacks() {
+ return 0;
+ }
+
+ @Override
+ public EMaterialType getCargoType(int stack) {
+ return null;
+ }
+
+ @Override
+ public int getCargoCount(int stack) {
+ return 0;
+ }
+
+ @Override
+ public EBuildingType getGarrisonedBuildingType() {
+ return entity.getComponentOptional(BuildingWorkerComponent.class).map(BuildingWorkerComponent::getBuildingType).orElse(null);
+ }
+
+ @Override
+ public boolean isSelected() {
+ return entity.getComponentOptional(SelectableComponent.class).map(SelectableComponent::isSelected).orElse(false);
+ }
+
+ @Override
+ public void setSelected(boolean selected) {
+ entity.getComponentOptional(SelectableComponent.class).ifPresent(component -> component.setSelected(selected));
+ }
+
+ @Override
+ public ESelectionType getSelectionType() {
+ return entity.getComponentOptional(SelectableComponent.class).map(SelectableComponent::getSelectionType).orElse(ESelectionType.PEOPLE);
+ }
+
+ @Override
+ public boolean isAttackable() {
+ return entity.getComponentOptional(AttackableComponent.class).map(AttackableComponent::isAttackable).orElse(false);
+ }
+
+ @Override
+ public void setSoundPlayed() {
+ entity.getComponent(AnimationComponent.class).setSoundPlayed();
+ }
+
+ @Override
+ public boolean isSoundPlayed() {
+ return entity.getComponentOptional(AnimationComponent.class).map(AnimationComponent::isSoundPlayed).orElse(false);
+ }
+
+ @Override
+ public EMovableType getMovableType() {
+ return entity.getComponent(MovableComponent.class).getMovableType();
+ }
+
+ @Override
+ public boolean isTower() {
+ return false;
+ }
+
+ @Override
+ public void debug() {
+ System.out.println("debug: " + entity);
+ entity.toggleDebug();
+ }
+
+ @Override
+ public void informAboutAttackable(IAttackable attackable) {
+ entity.getComponent(AttackableComponent.class).informAboutAttackable((ILogicMovable) attackable);
+ }
+
+ @Override
+ public boolean push(ILogicMovable pushingMovable) {
+ entity.raiseNotification(new SteeringComponent.LeavePositionRequest(pushingMovable));
+ return false;
+ }
+
+ @Override
+ public Path getPath() {
+ return null;
+ }
+
+ @Override
+ public void goSinglePathStep() {
+ assert false : "not implemented";
+ }
+
+ @Override
+ public ShortPoint2D getPosition() {
+ return entity.movableComponent().getPosition();
+ }
+
+ @Override
+ public ILogicMovable getPushedFrom() {
+ assert false : "not implemented";
+ return null;
+ }
+
+ @Override
+ public boolean isProbablyPushable(ILogicMovable pushingMovable) {
+ //assert false: "not implemented";
+ return false;
+ }
+
+ @Override
+ public void leavePosition() {
+ // The same as push - request to leave the place
+ //TODO: call to new implementation of push
+ }
+
+ @Override
+ public boolean canOccupyBuilding() {
+ //TODO: this method has no right to exist, refactor together with @setOccupyableBuilding
+ return false;
+ //return entity.getComponent(SelectableComponent.class).getSelectionType() == ESelectionType.SOLDIERS;
+ }
+
+ @Override
+ public IBuildingOccupyableMovable setOccupyableBuilding(IOccupyableBuilding building) {
+ //TODO: rename to occupyBuilding
+ return null;
+ }
+
+ @Override
+ public void checkPlayerOfPosition(Player playerOfPosition) {
+ //TODO: rename to: player of current position changed
+ //TODO: implement event
+ }
+
+ @Override
+ public void convertTo(EMovableType newMovableType) {
+ entity.movableComponent().convertTo(newMovableType);
+ }
+
+ @Override
+ public Player getPlayer() {
+ //TODO: switch to playerID or player everywhere
+ return entity.getComponent(MovableComponent.class).getPlayer();
+ }
+
+ @Override
+ public void moveTo(ShortPoint2D targetPosition) {
+ entity.getComponentOptional(PlayerComandComponent.class).ifPresent(component -> component.sendMoveToCommand(targetPosition));
+ }
+
+ @Override
+ public void unloadFerry() {
+ if (this.getMovableType() != EMovableType.FERRY) {
+ return;
+ }
+ //TODO call method of ferry Component
+ }
+
+ @Override
+ public boolean addPassenger(ILogicMovable movable) {
+ return false;
+ }
+
+ @Override
+ public void moveToFerry(ILogicMovable ferry, ShortPoint2D entrancePosition) {
+ assert false: "not implemented";
+ }
+
+ @Override
+ public void leaveFerryAt(ShortPoint2D position) {
+ assert false: "not implemented";
+ }
+
+ @Override
+ public int getID() {
+ return entity.getID();
+ }
+
+ @Override
+ public int timerEvent() {
+ return entity.timerEvent();
+ }
+
+ @Override
+ public void kill() {
+ entity.kill();
+ }
+
+ //endregion
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/Notification.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Notification.java
new file mode 100644
index 0000000000..ce028e5333
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Notification.java
@@ -0,0 +1,5 @@
+package jsettlers.logic.movable;
+
+import java.io.Serializable;
+
+public abstract class Notification implements Serializable {}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/Requires.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Requires.java
new file mode 100644
index 0000000000..b038de688b
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/Requires.java
@@ -0,0 +1,17 @@
+package jsettlers.logic.movable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import jsettlers.logic.movable.components.Component;
+
+/**
+ * @author homoroselaps
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Requires {
+ Class extends Component>[] value();
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/AnimationComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/AnimationComponent.java
new file mode 100644
index 0000000000..04ea850723
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/AnimationComponent.java
@@ -0,0 +1,86 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.movable.EMovableAction;
+import jsettlers.logic.constants.MatchConstants;
+import jsettlers.logic.movable.Notification;
+
+/**
+ * @author homoroselaps
+ */
+public class AnimationComponent extends Component {
+ private static final long serialVersionUID = 8064683552580286008L;
+
+ private EMovableAction animation = EMovableAction.NO_ACTION;
+ private int animationStartTime;
+ private short animationDuration;
+ private boolean isSoundPlayed = false;
+ private boolean isRightStep = false;
+ private boolean isChained = false;
+
+ public static class AnimationFinishedNotification extends Notification {
+ public final EMovableAction type;
+
+ public AnimationFinishedNotification(EMovableAction animationType) {
+ this.type = animationType;
+ }
+ }
+
+ public AnimationComponent() { }
+
+ @Override
+ protected void onUpdate() {
+ if (animation != EMovableAction.NO_ACTION && isAnimationFinished()) {
+ stopAnimation();
+ }
+ }
+
+ @Override
+ protected void onLateUpdate() {
+ if (!isAnimationFinished()) { entity.setInvocationDelay(getRemainingTime()); }
+ }
+
+ public EMovableAction getAnimation() {
+ return animation;
+ }
+
+ public float getAnimationProgress() {
+ return ((float) (MatchConstants.clock().getTime() - animationStartTime)) / animationDuration;
+ }
+
+ public boolean isAnimationFinished() {
+ return animationStartTime + animationDuration <= MatchConstants.clock().getTime();
+ }
+
+ public void startAnimation(EMovableAction animation, short duration, boolean isChained) {
+ this.animationStartTime = MatchConstants.clock().getTime();
+ this.animationDuration = duration;
+ this.animation = animation;
+ this.isSoundPlayed = false;
+ this.isChained = isChained;
+ }
+
+ private void stopAnimation() {
+ this.entity.raiseNotification(new AnimationFinishedNotification(this.animation));
+ if (!isChained) this.animation = EMovableAction.NO_ACTION;
+ }
+
+ public boolean isRightStep() {
+ return isRightStep;
+ }
+
+ public void switchStep() {
+ isRightStep = !isRightStep;
+ }
+
+ public void setSoundPlayed() {
+ isSoundPlayed = true;
+ }
+
+ public boolean isSoundPlayed() {
+ return isSoundPlayed;
+ }
+
+ public int getRemainingTime() {
+ return animationStartTime + animationDuration - MatchConstants.clock().getTime();
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/AttackableComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/AttackableComponent.java
new file mode 100644
index 0000000000..9b7aa23f0b
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/AttackableComponent.java
@@ -0,0 +1,47 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.movable.EMovableType;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.movable.Notification;
+import jsettlers.logic.movable.interfaces.ILogicMovable;
+
+/**
+ * @author homoroselaps
+ */
+
+public class AttackableComponent extends Component {
+ public static class ReceivedHit extends Notification {}
+
+ private static final long serialVersionUID = -5453513130369184993L;
+
+ private float health;
+ private boolean isAttackable = false;
+
+ public AttackableComponent(EMovableType movableType) {
+ this.health = movableType.getHealth();
+ }
+
+ public boolean isAttackable() { return isAttackable; }
+
+ public void isAttackable(boolean isAttackable) { this.isAttackable = isAttackable; }
+
+ public void receiveHit(float strength, ShortPoint2D attackerPos, byte attackingPlayer) {
+ health -= strength;
+ entity.raiseNotification(new ReceivedHit());
+ }
+
+ public float getHealth() {
+ return health;
+ }
+
+ public void setHealth(float health) { this.health = health; }
+
+ public void informAboutAttackable(ILogicMovable other) {
+ assert false : "Not implemented";
+ }
+
+ @Override
+ protected void onDestroy() {
+ health = -200;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BearerBehaviorComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BearerBehaviorComponent.java
new file mode 100644
index 0000000000..08b75b986a
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BearerBehaviorComponent.java
@@ -0,0 +1,249 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.material.EMaterialType;
+import jsettlers.common.movable.EMovableAction;
+import jsettlers.common.movable.EMovableType;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.constants.Constants;
+import jsettlers.logic.movable.Context;
+import jsettlers.logic.movable.ManageableBearerWrapper;
+import jsettlers.logic.movable.Requires;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.NodeStatus;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Action;
+
+import static jsettlers.logic.movable.BehaviorTreeHelper.action;
+import static jsettlers.logic.movable.BehaviorTreeHelper.alwaysFail;
+import static jsettlers.logic.movable.BehaviorTreeHelper.alwaysSucceed;
+import static jsettlers.logic.movable.BehaviorTreeHelper.condition;
+import static jsettlers.logic.movable.BehaviorTreeHelper.convertTo;
+import static jsettlers.logic.movable.BehaviorTreeHelper.debug;
+import static jsettlers.logic.movable.BehaviorTreeHelper.dropMaterial;
+import static jsettlers.logic.movable.BehaviorTreeHelper.guard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.memSequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.selector;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.setIdleBehaviorActiveWhile;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sleep;
+import static jsettlers.logic.movable.BehaviorTreeHelper.startAndWaitForAnimation;
+import static jsettlers.logic.movable.BehaviorTreeHelper.triggerGuard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.waitForTargetReachedAndFailIfNotReachable;
+
+/**
+ * @author homoroselaps
+ */
+
+@Requires({
+ MaterialComponent.class,
+ BearerComponent.class,
+ SteeringComponent.class,
+ GameFieldComponent.class,
+ AnimationComponent.class,
+ MovableComponent.class
+})
+public final class BearerBehaviorComponent extends BehaviorComponent {
+ private static final long serialVersionUID = -4581600901753172458L;
+
+ @Override
+ protected Node createBehaviorTree() {
+ return setIdleBehaviorActiveWhile(false,
+ selector(
+ triggerGuard(BearerComponent.DeliveryJob.class,
+ guard("DeliveryJob", c -> c.entity.bearerComponent().hasJob(), false,
+ debug("accepting delivery job", acceptDeliveryJob())
+ )
+ ),
+ triggerGuard(BearerComponent.BecomeSoldierJob.class,
+ guard("BecomeSoldierJob", c -> c.entity.bearerComponent().hasJob(), false,
+ debug("accepting become soldier job", acceptBecomeSoldierJob())
+ )
+ ),
+ triggerGuard(BearerComponent.BecomeWorkerJob.class,
+ guard("BecomeWorkerJob", c -> c.entity.bearerComponent().hasJob(), false,
+ debug("accepting become worker job", acceptBecomeWorkerJob())
+ )
+ ),
+ guard(c -> c.entity.bearerComponent().hasBecomeWorkerJob(), true,
+ selector("hasBecomeWorkerJob",
+ memSequence("try to fulfil the job",
+ alwaysSucceed(guard("grab a tool if needed", c -> c.entity.bearerComponent().materialOffer != null,
+ selector(
+ memSequence(
+ action("go to the tool", c -> {
+ c.entity.steeringComponent().setTarget(c.entity.bearerComponent().materialOffer.getPosition());
+ }),
+ waitForTargetReachedAndFailIfNotReachable(),
+ condition("can we pick it up?", BearerBehaviorComponent::canTakeMaterial),
+ startAndWaitForAnimation(EMovableAction.BEND_DOWN, Constants.MOVABLE_BEND_DURATION),
+ tryTakeMaterialFromMap(),
+ startAndWaitForAnimation(EMovableAction.RAISE_UP, Constants.MOVABLE_BEND_DURATION)
+ ),
+ sequence("handle failure",
+ action(BearerBehaviorComponent::distributionAborted),
+ alwaysFail()
+ )
+ )
+ )),
+ action("convert Entity to a worker", c -> {
+ convertTo(c.entity, c.entity.bearerComponent().workerCreationRequest.requestedMovableType());
+ })
+ ),
+ sequence("handle failure",
+ action(BearerBehaviorComponent::workerCreationRequestFailed),
+ action(BearerBehaviorComponent::resetJob),
+ alwaysFail()
+ )
+ )
+ ),
+ guard(c -> c.entity.bearerComponent().hasDeliveryJob(), true,
+ memSequence(
+ selector(
+ memSequence("go to materialOffer and take material",
+ action(c->{c.entity.steeringComponent().setTarget(c.entity.bearerComponent().materialOffer.getPosition());}),
+ waitForTargetReachedAndFailIfNotReachable(),
+ debug("can take material", condition(BearerBehaviorComponent::canTakeMaterial)),
+ startAndWaitForAnimation(EMovableAction.BEND_DOWN, c->Constants.MOVABLE_BEND_DURATION, true),
+ tryTakeMaterialFromMap(),
+ startAndWaitForAnimation(EMovableAction.RAISE_UP, Constants.MOVABLE_BEND_DURATION)
+ ),
+ sequence("handle failure",
+ action(BearerBehaviorComponent::distributionAborted),
+ action(BearerBehaviorComponent::deliveryAborted),
+ action(BearerBehaviorComponent::resetJob),
+ alwaysFail()
+ )
+ ),
+ selector(
+ memSequence("go to request & drop material",
+ action(c -> {
+ c.entity.steeringComponent().setTarget(c.entity.bearerComponent().deliveryRequest.getPosition());
+ }),
+ waitForTargetReachedAndFailIfNotReachable(),
+ condition(c -> c.entity.bearerComponent().materialType.isDroppable()),
+ startAndWaitForAnimation(EMovableAction.BEND_DOWN, c->Constants.MOVABLE_BEND_DURATION, true),
+ guard(BearerBehaviorComponent::canFulfillRequest, sequence(
+ dropMaterial(c->c.entity.materialComponent().getMaterial()),
+ action(BearerBehaviorComponent::deliveryFulfilled)
+ )),
+ startAndWaitForAnimation(EMovableAction.RAISE_UP, Constants.MOVABLE_BEND_DURATION),
+ action(BearerBehaviorComponent::resetJob)
+ ),
+ sequence("handle failure",
+ debug("reoffer the material", dropMaterial(c->c.entity.materialComponent().getMaterial())),
+ action(BearerBehaviorComponent::deliveryAborted),
+ action(BearerBehaviorComponent::resetJob),
+ alwaysFail()
+ )
+ )
+ )
+ ),
+ guard(c -> c.entity.bearerComponent().hasBecomeSoldierJob(), true,
+ selector(
+ memSequence("become a soldier",
+ action(c -> {
+ c.entity.steeringComponent().setTarget(c.entity.bearerComponent().barrack.getDoor());
+ }),
+ waitForTargetReachedAndFailIfNotReachable(),
+ tryTakeWeapon_ConvertToSoldier()
+ ),
+ sequence("handle failure",
+ action(BearerBehaviorComponent::bearerRequestFailed),
+ action(BearerBehaviorComponent::resetJob),
+ alwaysFail()
+ )
+ )
+ ),
+ debug("idle behavior",
+ setIdleBehaviorActiveWhile(true,
+ sleep(1000)
+ )
+ )
+ )
+ );
+ }
+
+ private static Action acceptDeliveryJob() {
+ return new Action<>(context -> {
+ context.component.forFirstNotificationOfTypeC(BearerComponent.DeliveryJob.class, job -> {
+ job.offer.distributionAccepted();
+ job.request.deliveryAccepted();
+ context.entity.bearerComponent().setDeliveryJob(job);
+ }, true);
+ });
+ }
+
+ private static Action acceptBecomeSoldierJob() {
+ return new Action<>(context -> {
+ context.component.forFirstNotificationOfTypeC(BearerComponent.BecomeSoldierJob.class, context.entity.bearerComponent()::setBecomeSoldierJob, true);
+ });
+ }
+
+ private static Action acceptBecomeWorkerJob() {
+ return new Action<>(context -> {
+ context.component.forFirstNotificationOfTypeC(BearerComponent.BecomeWorkerJob.class, job -> {
+ if (job.offer != null) job.offer.distributionAccepted();
+ context.entity.bearerComponent().setBecomeWorkerJob(job);
+ }, true);
+ });
+ }
+
+ private static Node tryTakeMaterialFromMap() {
+ return debug("try take material", new Action<>(c -> {
+ EMaterialType materialToTake = c.entity.bearerComponent().materialType;
+ if (c.entity.gameFieldComponent().movableGrid.takeMaterial(c.entity.movableComponent().getPosition(), materialToTake)) {
+ c.entity.materialComponent().setMaterial(materialToTake);
+ c.entity.bearerComponent().materialOffer.offerTaken();
+ return NodeStatus.SUCCESS;
+ }
+ return NodeStatus.FAILURE;
+ }));
+ }
+
+ private static Action tryTakeWeapon_ConvertToSoldier() {
+ return new Action<>(c -> {
+ ShortPoint2D targetPosition = c.entity.bearerComponent().barrack.getSoldierTargetPosition();
+ EMovableType type = c.entity.bearerComponent().barrack.popWeaponForBearer();
+ if (type != null) {
+ convertTo(c.entity, type);
+ c.entity.steeringComponent().setTarget(targetPosition);
+ c.entity.movableComponent().getPlayer().getEndgameStatistic().incrementAmountOfProducedSoldiers();
+ return NodeStatus.SUCCESS;
+ }
+ return NodeStatus.FAILURE;
+ });
+ }
+
+ private static boolean canFulfillRequest(Context c) {
+ return c.entity.bearerComponent().deliveryRequest.isActive() && c.entity.bearerComponent().deliveryRequest.getPosition().equals(c.entity.movableComponent().getPosition());
+ }
+
+ private static boolean canTakeMaterial(Context c) {
+ EMaterialType materialToTake = c.entity.bearerComponent().materialType;
+ return c.entity.gameFieldComponent().movableGrid.canTakeMaterial(c.entity.movableComponent().getPosition(), materialToTake);
+ }
+
+ private static void bearerRequestFailed(Context c) {
+ c.entity.bearerComponent().barrack.bearerRequestFailed();
+ }
+
+ private static void resetJob(Context c) {
+ c.entity.bearerComponent().resetJob();
+ c.entity.gameFieldComponent().movableGrid.addJobless(new ManageableBearerWrapper(c.entity));
+ }
+
+ private static void distributionAborted(Context c) {
+ c.entity.bearerComponent().materialOffer.distributionAborted();
+ }
+
+ private static void deliveryFulfilled(Context c) {
+ c.entity.bearerComponent().deliveryRequest.deliveryFulfilled();
+ }
+
+ private static void deliveryAborted(Context c) {
+ c.entity.bearerComponent().deliveryRequest.deliveryAborted();
+ }
+
+ private static void workerCreationRequestFailed(Context c) {
+ c.entity.bearerComponent().workerRequester.workerCreationRequestFailed(c.entity.bearerComponent().workerCreationRequest);
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BearerComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BearerComponent.java
new file mode 100644
index 0000000000..c7b6f0f491
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BearerComponent.java
@@ -0,0 +1,150 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.material.EMaterialType;
+import jsettlers.logic.map.grid.partition.manager.manageables.IManageableBearer;
+import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IBarrack;
+import jsettlers.logic.map.grid.partition.manager.materials.interfaces.IMaterialOffer;
+import jsettlers.logic.map.grid.partition.manager.materials.interfaces.IMaterialRequest;
+import jsettlers.logic.map.grid.partition.manager.objects.WorkerCreationRequest;
+import jsettlers.logic.movable.ManageableBearerWrapper;
+import jsettlers.logic.movable.Notification;
+
+/**
+ * @author homoroselaps
+ */
+
+public class BearerComponent extends Component {
+ private static final long serialVersionUID = -3315837668805312398L;
+
+ public static class DeliveryJob extends Notification {
+ public final EMaterialType materialType;
+ public final IMaterialOffer offer;
+ public final IMaterialRequest request;
+
+ public DeliveryJob(EMaterialType materialType, IMaterialOffer offer, IMaterialRequest request) {
+ this.offer = offer;
+ this.request = request;
+ this.materialType = materialType;
+ }
+ }
+
+ public static class BecomeWorkerJob extends Notification {
+ public final IManageableBearer.IWorkerRequester requester;
+ public final WorkerCreationRequest workerCreationRequest;
+ public final IMaterialOffer offer;
+
+ public BecomeWorkerJob(IManageableBearer.IWorkerRequester requester, WorkerCreationRequest workerCreationRequest, IMaterialOffer offer) {
+ this.requester = requester;
+ this.workerCreationRequest = workerCreationRequest;
+ this.offer = offer;
+ }
+ }
+
+ public static class BecomeSoldierJob extends Notification {
+ public final IBarrack barrack;
+
+ public BecomeSoldierJob(IBarrack barrack) {
+ this.barrack = barrack;
+ }
+ }
+
+ public EMaterialType materialType;
+ IMaterialOffer materialOffer;
+ IMaterialRequest deliveryRequest;
+
+ IManageableBearer.IWorkerRequester workerRequester;
+ WorkerCreationRequest workerCreationRequest;
+
+ public IBarrack barrack;
+
+ private boolean hasDeliveryJob = false;
+ private boolean hasBecomeWorkerJob = false;
+ private boolean hasBecomeSoldierJob = false;
+
+ public boolean hasJob() {
+ return hasBecomeSoldierJob || hasDeliveryJob || hasBecomeWorkerJob;
+ }
+
+ public void setBecomeSoldierJob(BecomeSoldierJob job) {
+ assert job != null : "No Null";
+ resetJob();
+ barrack = job.barrack;
+ hasBecomeSoldierJob = true;
+ }
+
+ public boolean hasBecomeSoldierJob() {
+ return hasBecomeSoldierJob;
+ }
+
+ public void setDeliveryJob(DeliveryJob job) {
+ assert job != null : "No Null";
+ resetJob();
+ materialType = job.materialType;
+ materialOffer = job.offer;
+ deliveryRequest = job.request;
+ hasDeliveryJob = true;
+ }
+
+ public boolean hasDeliveryJob() {
+ return hasDeliveryJob;
+ }
+
+ public void setBecomeWorkerJob(BecomeWorkerJob job) {
+ assert job != null : "No Null";
+ resetJob();
+ materialOffer = job.offer;
+ workerRequester = job.requester;
+ workerCreationRequest = job.workerCreationRequest;
+ hasBecomeWorkerJob = true;
+ }
+
+ @Override
+ protected void onWakeUp() {
+ this.entity.gameFieldComponent().movableGrid.addJobless(new ManageableBearerWrapper(this.entity));
+ }
+
+ public boolean hasBecomeWorkerJob() {
+ return hasBecomeWorkerJob;
+ }
+
+ public void resetJob() {
+ hasDeliveryJob = false;
+ hasBecomeSoldierJob = false;
+ hasBecomeWorkerJob = false;
+
+ materialOffer = null;
+ workerRequester = null;
+ workerCreationRequest = null;
+ barrack = null;
+ deliveryRequest = null;
+ materialType = EMaterialType.NO_MATERIAL;
+ }
+
+ public void deliver(EMaterialType materialType, IMaterialOffer offer, IMaterialRequest request) {
+ entity.raiseNotification(new DeliveryJob(materialType, offer, request));
+ }
+
+ public boolean becomeWorker(IManageableBearer.IWorkerRequester requester, WorkerCreationRequest workerCreationRequest) {
+ if (!hasJob()) {
+ entity.raiseNotification(new BecomeWorkerJob(requester, workerCreationRequest, null));
+ return true;
+ }
+ return false;
+ }
+
+ public boolean becomeWorker(IManageableBearer.IWorkerRequester requester, WorkerCreationRequest workerCreationRequest, IMaterialOffer offer) {
+ if (!hasJob()) {
+ entity.raiseNotification(new BecomeWorkerJob(requester, workerCreationRequest, offer));
+ return true;
+ }
+ return false;
+ }
+
+ public boolean becomeSoldier(IBarrack barrack) {
+ if (!hasJob()) {
+ entity.raiseNotification(new BecomeSoldierJob(barrack));
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BehaviorComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BehaviorComponent.java
new file mode 100644
index 0000000000..6a6f6d7a06
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BehaviorComponent.java
@@ -0,0 +1,30 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.logic.movable.Context;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.Root;
+import jsettlers.logic.movable.simplebehaviortree.Tick;
+
+import static jsettlers.logic.movable.BehaviorTreeHelper.debug;
+
+/**
+ * @author homoroselaps
+ */
+public abstract class BehaviorComponent extends Component {
+ private static final long serialVersionUID = -7388888039559869043L;
+
+ private Tick tick;
+
+ @Override
+ protected void onWakeUp() {
+ tick = new Tick<>(new Context(entity, this), new Root<>(debug("==== of " + entity.getID(), createBehaviorTree())));
+ }
+
+ @Override
+ protected void onUpdate() {
+ tick.target.debugLevel = 0;
+ tick.tick();
+ }
+
+ protected abstract Node createBehaviorTree();
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BricklayerBehaviorComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BricklayerBehaviorComponent.java
new file mode 100644
index 0000000000..112c71fa1e
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BricklayerBehaviorComponent.java
@@ -0,0 +1,90 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.movable.EMovableAction;
+import jsettlers.logic.constants.Constants;
+import jsettlers.logic.movable.Context;
+import jsettlers.logic.movable.Requires;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.NodeStatus;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Repeat;
+
+import static jsettlers.logic.movable.BehaviorTreeHelper.action;
+import static jsettlers.logic.movable.BehaviorTreeHelper.alwaysFail;
+import static jsettlers.logic.movable.BehaviorTreeHelper.condition;
+import static jsettlers.logic.movable.BehaviorTreeHelper.debug;
+import static jsettlers.logic.movable.BehaviorTreeHelper.guard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.memSequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.repeat;
+import static jsettlers.logic.movable.BehaviorTreeHelper.selector;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.setIdleBehaviorActiveWhile;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sleep;
+import static jsettlers.logic.movable.BehaviorTreeHelper.startAndWaitForAnimation;
+import static jsettlers.logic.movable.BehaviorTreeHelper.triggerGuard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.waitForTargetReachedAndFailIfNotReachable;
+
+/**
+ * @author homoroselaps
+ */
+
+@Requires({
+ MaterialComponent.class,
+ SteeringComponent.class,
+ GameFieldComponent.class,
+ AnimationComponent.class,
+ MovableComponent.class
+})
+public final class BricklayerBehaviorComponent extends BehaviorComponent {
+ private static final long serialVersionUID = -4581601951753172458L;
+
+ @Override
+ protected Node createBehaviorTree() {
+ return setIdleBehaviorActiveWhile(false,
+ selector(
+ triggerGuard(BricklayerComponent.BricklayerJob.class,
+ action("accepting Bricklayer job", context -> {
+ context.component.forFirstNotificationOfTypeC(BricklayerComponent.BricklayerJob.class, job -> {
+ context.entity.bricklayerComponent().setBricklayerJob(job);
+ }, true);
+ })
+ ),
+ guard("has active bricklayer job",c -> c.entity.bricklayerComponent().hasJob() && c.entity.bricklayerComponent().isBricklayerRequestActive(),
+ selector(
+ memSequence(
+ action(c->{c.entity.steeringComponent().setTarget(c.entity.bricklayerComponent().getBricklayerTargetPos());}),
+ selector(
+ waitForTargetReachedAndFailIfNotReachable(),
+ sequence(
+ abortJob(),
+ alwaysFail()
+ )
+ ),
+ action("look in direction", c -> { c.entity.movableComponent().setViewDirection(c.entity.bricklayerComponent().getLookDirection());}),
+ repeat("try to build", Repeat.Policy.NONPREEMPTIVE,
+ condition(c -> c.entity.bricklayerComponent().isBricklayerRequestActive()),
+ memSequence(
+ action("try take material", c -> { return NodeStatus.of(c.entity.bricklayerComponent().tryTakeMaterialFromConstructionSite()); } ),
+ startAndWaitForAnimation(EMovableAction.ACTION1, Constants.BRICKLAYER_ACTION_DURATION)
+ )
+ )
+ ),
+ jobFinished()
+ )
+ ),
+ debug("idle behavior",
+ setIdleBehaviorActiveWhile(true,
+ sleep(1000)
+ )
+ )
+ )
+ );
+ }
+
+ private static Node jobFinished() {
+ return debug("job finished",action(c->{c.entity.bricklayerComponent().jobFinished();}));
+ }
+
+ private static Node abortJob() {
+ return debug("abort job",action(c->{c.entity.bricklayerComponent().abortJob();}));
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BricklayerComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BricklayerComponent.java
new file mode 100644
index 0000000000..cd1bc04743
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BricklayerComponent.java
@@ -0,0 +1,79 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.movable.EDirection;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IConstructableBuilding;
+import jsettlers.logic.movable.ManageableWorkerWrapper;
+import jsettlers.logic.movable.Notification;
+
+/**
+ * @author homoroselaps
+ */
+
+public class BricklayerComponent extends Component {
+ private static final long serialVersionUID = -3315837368825352398L;
+
+ private IConstructableBuilding constructionSite = null;
+ private ShortPoint2D bricklayerTargetPos = null;
+ private EDirection lookDirection = null;
+
+ public static class BricklayerJob extends Notification {
+ public final IConstructableBuilding constructionSite;
+ public final ShortPoint2D bricklayerTargetPos;
+ public final EDirection lookDirection;
+
+ public BricklayerJob(IConstructableBuilding constructionSite, ShortPoint2D bricklayerTargetPos, EDirection lookDirection) {
+ this.constructionSite = constructionSite;
+ this.bricklayerTargetPos = bricklayerTargetPos;
+ this.lookDirection = lookDirection;
+ }
+ }
+
+ public ShortPoint2D getBricklayerTargetPos() { return bricklayerTargetPos; }
+
+ public EDirection getLookDirection() { return lookDirection; }
+
+ public boolean hasJob() {
+ return constructionSite != null;
+ }
+
+ public void resetJob() {
+ constructionSite = null;
+ bricklayerTargetPos = null;
+ lookDirection = null;
+ }
+
+ public void jobFinished() {
+ resetJob();
+ entity.gameFieldComponent().movableGrid.addJobless(new ManageableWorkerWrapper(entity));
+ }
+
+ public void abortJob() {
+ if (constructionSite != null) {
+ constructionSite.bricklayerRequestFailed(bricklayerTargetPos, lookDirection);
+ }
+ jobFinished();
+ }
+
+ public void setBricklayerJob(BricklayerJob job) {
+ constructionSite = job.constructionSite;
+ bricklayerTargetPos = job.bricklayerTargetPos;
+ lookDirection = job.lookDirection;
+ }
+
+ public boolean assignBricklayerJob(IConstructableBuilding constructionSite, ShortPoint2D bricklayerTargetPos, EDirection direction) {
+ if (!hasJob()) {
+ entity.raiseNotification(new BricklayerComponent.BricklayerJob(constructionSite, bricklayerTargetPos, direction));
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isBricklayerRequestActive() {
+ return constructionSite != null && constructionSite.isBricklayerRequestActive();
+ }
+
+ public boolean tryTakeMaterialFromConstructionSite() {
+ return constructionSite != null && constructionSite.tryToTakeMaterial();
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BuildingWorkerBehaviorComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BuildingWorkerBehaviorComponent.java
new file mode 100644
index 0000000000..3c67e0642c
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BuildingWorkerBehaviorComponent.java
@@ -0,0 +1,527 @@
+package jsettlers.logic.movable.components;
+
+import com.sun.net.httpserver.Authenticator;
+
+import jsettlers.algorithms.path.Path;
+import jsettlers.common.buildings.EBuildingType;
+import jsettlers.common.buildings.jobs.EBuildingJobType;
+import jsettlers.common.buildings.jobs.IBuildingJob;
+import jsettlers.common.mapobject.EMapObjectType;
+import jsettlers.common.material.EMaterialType;
+import jsettlers.common.material.EPriority;
+import jsettlers.common.movable.EDirection;
+import jsettlers.common.movable.EMovableAction;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.buildings.workers.DockyardBuilding;
+import jsettlers.logic.buildings.workers.MillBuilding;
+import jsettlers.logic.buildings.workers.SlaughterhouseBuilding;
+import jsettlers.logic.constants.Constants;
+import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IWorkerRequestBuilding;
+import jsettlers.logic.movable.Context;
+import jsettlers.logic.movable.EGoInDirectionMode;
+import jsettlers.logic.movable.MovableWrapper;
+import jsettlers.logic.movable.Requires;
+import jsettlers.logic.movable.simplebehaviortree.IBooleanConditionFunction;
+import jsettlers.logic.movable.simplebehaviortree.INodeStatusActionConsumer;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.NodeStatus;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Action;
+import jsettlers.logic.movable.simplebehaviortree.nodes.AlwaysSucceed;
+
+import static jsettlers.logic.movable.BehaviorTreeHelper.action;
+import static jsettlers.logic.movable.BehaviorTreeHelper.alwaysFail;
+import static jsettlers.logic.movable.BehaviorTreeHelper.alwaysSucceed;
+import static jsettlers.logic.movable.BehaviorTreeHelper.condition;
+import static jsettlers.logic.movable.BehaviorTreeHelper.debug;
+import static jsettlers.logic.movable.BehaviorTreeHelper.defaultIdleBehavior;
+import static jsettlers.logic.movable.BehaviorTreeHelper.dropMaterial;
+import static jsettlers.logic.movable.BehaviorTreeHelper.guard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.memSelector;
+import static jsettlers.logic.movable.BehaviorTreeHelper.memSequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.selector;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.setIdleBehaviorActiveWhile;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sleep;
+import static jsettlers.logic.movable.BehaviorTreeHelper.startAndWaitForAnimation;
+import static jsettlers.logic.movable.BehaviorTreeHelper.triggerGuard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.waitForPathFinished;
+import static jsettlers.logic.movable.BehaviorTreeHelper.waitForTargetReachedAndFailIfNotReachable;
+
+@Requires({
+ GameFieldComponent.class,
+ MovableComponent.class,
+ SteeringComponent.class,
+ BuildingWorkerComponent.class,
+ MaterialComponent.class,
+ MarkedPositonComponent.class
+})
+public class BuildingWorkerBehaviorComponent extends BehaviorComponent {
+ private static final long serialVersionUID = -5394764830129769392L;
+
+ @Override
+ protected Node createBehaviorTree() {
+ return setIdleBehaviorActiveWhile(false,
+ selector(
+ triggerGuard(BuildingWorkerComponent.BuildingDestroyed.class, sequence(
+ action("handle building destroyed", c-> {
+ c.entity.movableComponent().setVisible(true);
+ c.entity.steeringComponent().resetTarget();
+ }),
+ action(c->{ c.entity.buildingWorkerComponent().reportAsJobless(); }),
+ dropMaterial(c->c.entity.materialComponent().getMaterial()),
+ action(c->{ c.entity.markedPositonComponent().clearMark();})
+ )),
+ guard("has a job", c->c.entity.buildingWorkerComponent().hasJob(),
+ memSelector("try execute job",
+ guard(isCurrentJobType(EBuildingJobType.GO_TO),
+ selector(
+ memSequence("go to job pos",
+ action(c->{c.entity.steeringComponent().setTarget(c.entity.buildingWorkerComponent().getCurrentJobPos());}),
+ waitForTargetReachedAndFailIfNotReachable(),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.TRY_TAKING_RESOURCE),
+ sequence("try taking resource",
+ action(c->{ c.entity.markedPositonComponent().clearMark();}),
+ selector(
+ sequence(
+ tryTakingResource(),
+ jobFinished()),
+ jobFailed()
+ )
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.TRY_TAKING_FOOD),
+ selector(
+ sequence(
+ tryTakingFood(),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.WAIT),
+ memSequence("wait",
+ sleep(c -> (short)c.entity.buildingWorkerComponent().getCurrentJob().getTime() * 1000),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.WALK),
+ memSequence("walk in direction of job",
+ action(c->{c.entity.steeringComponent().goInDirection(c.entity.buildingWorkerComponent().getCurrentJob().getDirection(), EGoInDirectionMode.GO_IF_ALLOWED_WAIT_TILL_FREE);}),
+ waitForTargetReachedAndFailIfNotReachable(),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.SHOW),
+ sequence(
+ condition("Is building not stopped",c->c.entity.buildingWorkerComponent().getBuildingPriority() != EPriority.STOPPED),
+ debug("show", show()),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.HIDE),
+ sequence(
+ action("hide", c->{c.entity.movableComponent().setVisible(false);}),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.SET_MATERIAL),
+ sequence(
+ action("set material", c->{
+ entity.materialComponent().setMaterial(entity.buildingWorkerComponent().getCurrentJob().getMaterial());}
+ ),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.TAKE),
+ selector(
+ memSequence("try take material",
+ debug("can take material", condition(BuildingWorkerBehaviorComponent::canTakeMaterial)),
+ startAndWaitForAnimation(EMovableAction.BEND_DOWN, c->Constants.MOVABLE_BEND_DURATION, true),
+ tryTakeMaterial(),
+ startAndWaitForAnimation(EMovableAction.RAISE_UP, Constants.MOVABLE_BEND_DURATION),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.DROP),
+ memSequence("drop Material",
+ startAndWaitForAnimation(EMovableAction.BEND_DOWN, c->Constants.MOVABLE_BEND_DURATION, true),
+ dropMaterial(c->c.entity.buildingWorkerComponent().getCurrentJob().getMaterial()),
+ alwaysSucceed(guard("increment gold count if goldmelt",c->c.entity.buildingWorkerComponent().getBuildingType() == EBuildingType.GOLDMELT,
+ action(c->{c.entity.movableComponent().getPlayer().getEndgameStatistic().incrementAmountOfProducedGold();})
+ )),
+ startAndWaitForAnimation(EMovableAction.RAISE_UP, Constants.MOVABLE_BEND_DURATION),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.DROP_POPPED),
+ memSequence("drop Material",
+ startAndWaitForAnimation(EMovableAction.BEND_DOWN, c->Constants.MOVABLE_BEND_DURATION, true),
+ dropMaterial(c->c.entity.buildingWorkerComponent().getPoppedMaterial()),
+ startAndWaitForAnimation(EMovableAction.RAISE_UP, Constants.MOVABLE_BEND_DURATION),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PRE_SEARCH),
+ selector(
+ sequence("search for work",
+ condition("find path to work",c->c.entity.buildingWorkerComponent().preSearchPath(false)),
+ action("building can work", c->{c.entity.buildingWorkerComponent().workSearchSucceeded();}),
+ jobFinished()
+ ),
+ sequence("no work found",
+ action("building cannot work", c->{c.entity.buildingWorkerComponent().workSearchFailed();}),
+ jobFailed()
+ )
+ )
+
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PRE_SEARCH_IN_AREA),
+ selector(
+ sequence("search for work",
+ condition("found path to work",c->c.entity.buildingWorkerComponent().preSearchPath(true)),
+ action("building can work", c->{c.entity.buildingWorkerComponent().workSearchSucceeded();}),
+ jobFinished()
+ ),
+ sequence("no work found",
+ action("building cannot work", c->{c.entity.buildingWorkerComponent().workSearchFailed();}),
+ jobFailed()
+ )
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.FOLLOW_SEARCHED),
+ selector(
+ memSequence("follow presearched path",
+ action(c->{
+ Path path = c.entity.buildingWorkerComponent().getPreSearchedPath();
+ c.entity.buildingWorkerComponent().mark(path.getTargetPosition());
+ c.entity.steeringComponent().setPath(path);
+ }),
+ waitForTargetReachedAndFailIfNotReachable(),
+ jobFinished()
+ ),
+ sequence("path aborted",
+ guard("has current Job", c->c.entity.buildingWorkerComponent().getCurrentJob() != null,
+ jobFailed()
+ ),
+ action(c->{c.entity.buildingWorkerComponent().clearMark();})
+ )
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.LOOK_AT_SEARCHED),
+ selector(
+ sequence(
+ action("Look in direction",c->{
+ EDirection direction = c.entity.gameFieldComponent().movableGrid.getDirectionOfSearched(c.entity.movableComponent().getPosition(), c.entity.buildingWorkerComponent().getCurrentJob().getSearchType());
+ if (direction != null) {
+ c.entity.movableComponent().setViewDirection(direction);
+ return NodeStatus.SUCCESS;
+ }
+ return NodeStatus.FAILURE;
+ }),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.GO_TO_DOCK),
+ selector(
+ memSequence("go to docks",
+ condition("set target position", c-> {
+ DockyardBuilding dockyard = (DockyardBuilding)c.entity.buildingWorkerComponent().getBuilding();
+ ShortPoint2D dockEndPosition = dockyard.getDock().getEndPosition();
+ return c.entity.steeringComponent().setTarget(dockEndPosition);
+ }),
+ waitForTargetReachedAndFailIfNotReachable(),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.BUILD_SHIP),
+ sequence(
+ action("build ship action", c->{
+ DockyardBuilding dockyard = (DockyardBuilding)c.entity.buildingWorkerComponent().getBuilding();
+ dockyard.buildShipAction();
+ }),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.LOOK_AT),
+ sequence(
+ action("look in job direction", c->{c.entity.movableComponent().setViewDirection(c.entity.buildingWorkerComponent().getCurrentJob().getDirection());}),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.EXECUTE),
+ sequence(
+ action("clear Mark", c->{c.entity.buildingWorkerComponent().clearMark();}),
+ selector(
+ sequence(
+ action("execute Search", c-> {
+ ShortPoint2D pos = c.entity.movableComponent().getPosition();
+ MovableWrapper movable = c.entity.movableComponent().getMovableWrapper();
+ c.entity.gameFieldComponent().movableGrid.executeSearchType(movable, pos, c.entity.buildingWorkerComponent().getCurrentJob().getSearchType());
+ }),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PLAY_ACTION1),
+ sequence("play action 1",
+ startAndWaitForAnimation(EMovableAction.ACTION1, c->(short) (1000 * c.entity.buildingWorkerComponent().getCurrentJob().getTime()), false),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PLAY_ACTION2),
+ sequence("play action 2",
+ startAndWaitForAnimation(EMovableAction.ACTION2, c->(short) (1000 * c.entity.buildingWorkerComponent().getCurrentJob().getTime()), false),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PLAY_ACTION3),
+ sequence("play action 3",
+ startAndWaitForAnimation(EMovableAction.ACTION3, c->(short) (1000 * c.entity.buildingWorkerComponent().getCurrentJob().getTime()), false),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.AVAILABLE),
+ selector(
+ sequence(
+ condition("is material available", c->
+ c.entity.gameFieldComponent().movableGrid.canTakeMaterial(c.entity.buildingWorkerComponent().getCurrentJobPos(), c.entity.buildingWorkerComponent().getCurrentJob().getMaterial())
+ ),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.NOT_FULL),
+ selector(
+ sequence(
+ condition("is material available", c->
+ c.entity.gameFieldComponent().movableGrid.canPushMaterial(c.entity.buildingWorkerComponent().getCurrentJobPos())
+ ),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.SMOKE_ON),
+ sequence(
+ placeSmoke(true),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.SMOKE_OFF),
+ sequence(
+ placeSmoke(false),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.START_WORKING),
+ sequence(
+ selector(
+ guard("is slaughterhouse", c->c.entity.buildingWorkerComponent().getBuilding() instanceof SlaughterhouseBuilding,
+ action("play slaughter sound", c->{((SlaughterhouseBuilding) c.entity.buildingWorkerComponent().getBuilding()).requestSound();})
+ ),
+ guard("is MillBuilding", c->c.entity.buildingWorkerComponent().getBuilding() instanceof MillBuilding,
+ action("start rotate the mill", c->{((MillBuilding) c.entity.buildingWorkerComponent().getBuilding()).setRotating(true);})
+ )
+ ),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.STOP_WORKING),
+ sequence(
+ selector(
+ guard("is slaughterhouse", c->c.entity.buildingWorkerComponent().getBuilding() instanceof SlaughterhouseBuilding,
+ action("play slaughter sound", c->{((SlaughterhouseBuilding) c.entity.buildingWorkerComponent().getBuilding()).requestSound();})
+ ),
+ guard("is MillBuilding", c->c.entity.buildingWorkerComponent().getBuilding() instanceof MillBuilding,
+ action("stop rotate the mill", c->{((MillBuilding) c.entity.buildingWorkerComponent().getBuilding()).setRotating(false);})
+ )
+ ),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PIG_IS_ADULT),
+ selector(
+ sequence(
+ condition("is pig adult", c->c.entity.gameFieldComponent().movableGrid.isPigAdult(c.entity.buildingWorkerComponent().getCurrentJobPos())),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PIG_IS_THERE),
+ selector(
+ sequence(
+ condition("is pig there", c->c.entity.gameFieldComponent().movableGrid.hasPigAt(c.entity.buildingWorkerComponent().getCurrentJobPos())),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PIG_PLACE),
+ sequence(
+ placeOrRemovePigAction(true),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.PIG_REMOVE),
+ sequence(
+ placeOrRemovePigAction(false),
+ jobFinished()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.POP_TOOL),
+ selector(
+ sequence(
+ popToolRequestAction(),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.POP_WEAPON),
+ selector(
+ sequence(
+ popWeaponRequestAction(),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ ),
+ guard(isCurrentJobType(EBuildingJobType.GROW_DONKEY),
+ selector(
+ sequence(
+ growDonkeyAction(),
+ jobFinished()
+ ),
+ jobFailed()
+ )
+ )
+ )
+ ),
+ defaultIdleBehavior()
+ )
+ );
+ }
+
+ private static Node placeSmoke(boolean onOrOff) {
+ return action("place smoke",c->{
+ c.entity.gameFieldComponent().movableGrid.placeSmoke(c.entity.buildingWorkerComponent().getCurrentJobPos(), onOrOff);
+ c.entity.buildingWorkerComponent().addMapObjectCleanupPosition(c.entity.buildingWorkerComponent().getCurrentJobPos(), EMapObjectType.SMOKE);
+ });
+ }
+
+ private static IBooleanConditionFunction isCurrentJobType(EBuildingJobType type) {
+ return c->c.entity.buildingWorkerComponent().getCurrentJob().getType() == type;
+ }
+
+ private static Node jobFinished() {
+ return debug("job finished",action(c->{c.entity.buildingWorkerComponent().jobFinished();}));
+ }
+
+ private static Node jobFailed() {
+ return debug("job failed",action(c->{c.entity.buildingWorkerComponent().jobFailed();}));
+ }
+
+ private static Node tryTakingResource() {
+ return new Action<>(c->{return NodeStatus.of(c.entity.buildingWorkerComponent().tryTakingResource());});
+ }
+
+ private static Node tryTakingFood() {
+ return new Action<>(c->{return NodeStatus.of(c.entity.buildingWorkerComponent().tryTakingFood());});
+ }
+
+ private static Node show() {
+ return new Action<>(c->{
+ BuildingWorkerComponent bwc = c.entity.buildingWorkerComponent();
+ MovableComponent mc = c.entity.movableComponent();
+
+ ShortPoint2D pos = bwc.getCurrentJobPos();
+ if (bwc.getCurrentJob().getDirection() != null) {
+ mc.setViewDirection(bwc.getCurrentJob().getDirection());
+ }
+ mc.setPos(pos);
+ mc.setVisible(true);
+ });
+ }
+
+ private static boolean canTakeMaterial(Context c) {
+ EMaterialType materialToTake = c.entity.buildingWorkerComponent().getCurrentJob().getMaterial();
+ boolean takeFromMap = c.entity.buildingWorkerComponent().getCurrentJob().isTakeMaterialFromMap();
+ return !takeFromMap || c.entity.gameFieldComponent().movableGrid.canTakeMaterial(c.entity.movableComponent().getPosition(), materialToTake);
+ }
+
+ private static Node tryTakeMaterial() {
+ return debug("try take material", new Action<>(c -> {
+ final BuildingWorkerComponent bwc = c.entity.buildingWorkerComponent();
+ final GameFieldComponent gfc = c.entity.gameFieldComponent();
+
+ final EMaterialType materialToTake = bwc.getCurrentJob().getMaterial();
+ final boolean takeFromMap = bwc.getCurrentJob().isTakeMaterialFromMap();
+ if (gfc.movableGrid.takeMaterial(c.entity.movableComponent().getPosition(), materialToTake) || !takeFromMap) {
+ c.entity.materialComponent().setMaterial(materialToTake);
+ return NodeStatus.SUCCESS;
+ }
+ return NodeStatus.FAILURE;
+ }));
+ }
+
+ private static Node placeOrRemovePigAction(boolean placePig) {
+ return action(placePig ? "place " : "remove " + "pig",c-> {
+ ShortPoint2D pos = c.entity.buildingWorkerComponent().getCurrentJobPos();
+ c.entity.gameFieldComponent().movableGrid.placePigAt(pos, placePig);
+ c.entity.buildingWorkerComponent().getBuilding().addMapObjectCleanupPosition(pos, EMapObjectType.PIG);
+ });
+ }
+
+ private static Node growDonkeyAction() {
+ return action("grow Donkey if at position", c->{
+ ShortPoint2D pos = c.entity.buildingWorkerComponent().getCurrentJobPos();
+ if (c.entity.gameFieldComponent().movableGrid.feedDonkeyAt(pos)) {
+ c.entity.buildingWorkerComponent().getBuilding().addMapObjectCleanupPosition(pos, EMapObjectType.DONKEY);
+ return NodeStatus.SUCCESS;
+ } else {
+ return NodeStatus.FAILURE;
+ }
+ });
+ }
+
+ private static Node popWeaponRequestAction() {
+ return action("pop requested action if available",c->{
+ EMaterialType poppedMaterial = c.entity.buildingWorkerComponent().getBuilding().getMaterialProduction().getWeaponToProduce();
+ c.entity.buildingWorkerComponent().setPoppedMaterial(poppedMaterial);
+ return NodeStatus.of(poppedMaterial != null);
+ });
+ }
+
+ private static Node popToolRequestAction() {
+ return action("pop requested tool if available", c->{
+ IWorkerRequestBuilding building = c.entity.buildingWorkerComponent().getBuilding();
+ ShortPoint2D pos = building.getDoor();
+
+ EMaterialType poppedMaterial = building.getMaterialProduction().drawRandomAbsolutelyRequestedTool(); // first priority: Absolutely set tool production requests of user
+ if (poppedMaterial == null) {
+ poppedMaterial = c.entity.gameFieldComponent().movableGrid.popToolProductionRequest(pos); // second priority: Tools needed by settlers (automated production)
+ }
+ if (poppedMaterial == null) {
+ poppedMaterial = building.getMaterialProduction().drawRandomRelativelyRequestedTool(); // third priority: Relatively set tool production requests of user
+ }
+ c.entity.buildingWorkerComponent().setPoppedMaterial(poppedMaterial);
+
+ return NodeStatus.of(poppedMaterial != null);
+ });
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BuildingWorkerComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BuildingWorkerComponent.java
new file mode 100644
index 0000000000..7d15715ecf
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/BuildingWorkerComponent.java
@@ -0,0 +1,190 @@
+package jsettlers.logic.movable.components;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import jsettlers.algorithms.path.Path;
+import jsettlers.common.buildings.EBuildingType;
+import jsettlers.common.buildings.jobs.IBuildingJob;
+import jsettlers.common.landscape.EResourceType;
+import jsettlers.common.mapobject.EMapObjectType;
+import jsettlers.common.material.EMaterialType;
+import jsettlers.common.material.EPriority;
+import jsettlers.common.menu.messages.SimpleMessage;
+import jsettlers.common.movable.EDirection;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.DockPosition;
+import jsettlers.logic.buildings.workers.DockyardBuilding;
+import jsettlers.logic.map.grid.partition.manager.manageables.interfaces.IWorkerRequestBuilding;
+import jsettlers.logic.movable.Context;
+import jsettlers.logic.movable.ManageableWorkerWrapper;
+import jsettlers.logic.movable.Notification;
+import jsettlers.logic.movable.Requires;
+
+/**
+ * @author homoroselaps
+ */
+@Requires({
+ GameFieldComponent.class,
+ MovableComponent.class,
+ SteeringComponent.class,
+})
+public class BuildingWorkerComponent extends Component {
+ private static final long serialVersionUID = -5007619305126786807L;
+ private ShortPoint2D markedPosition;
+
+ public static class BuildingDestroyed extends Notification { }
+
+ private transient IBuildingJob currentJob = null;
+ protected IWorkerRequestBuilding building;
+ private EMaterialType poppedMaterial = EMaterialType.NO_MATERIAL;
+ private Path preSearchedPath = null;
+ private int searchFailedCount = 0;
+
+ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ ois.defaultReadObject();
+ String currentJobName = ois.readUTF();
+ if (currentJobName.equals("null")) {
+ currentJob = null;
+ } else {
+ currentJob = building.getBuildingType().getJobByName(currentJobName);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream oos) throws IOException {
+ oos.defaultWriteObject();
+ if (currentJob != null) {
+ oos.writeUTF(currentJob.getName());
+ } else {
+ oos.writeUTF("null");
+ }
+ }
+
+ @Override
+ protected void onWakeUp() {
+ reportAsJobless();
+ }
+
+ public boolean hasJob() { return currentJob != null; }
+
+ public IBuildingJob getCurrentJob() { return currentJob; }
+
+ public ShortPoint2D getCurrentJobPos() {
+ return currentJob.calculatePoint(building);
+ }
+
+ public void jobFinished() {
+ this.currentJob = this.currentJob.getNextSucessJob();
+ }
+
+ public void jobFailed() {
+ this.currentJob = this.currentJob.getNextFailJob();
+ }
+
+ public EMaterialType getPoppedMaterial() {
+ return poppedMaterial;
+ }
+
+ public void setPoppedMaterial(EMaterialType material) {
+ poppedMaterial = material;
+ }
+
+ public Path getPreSearchedPath() {
+ return preSearchedPath;
+ }
+
+ public boolean preSearchPath(boolean searchInArea) {
+ assert entity.movableComponent().getPosition() == getCurrentJob();
+
+ entity.movableComponent().setPos(getCurrentJobPos());
+ ShortPoint2D workAreaCenter = building.getWorkAreaCenter();
+ preSearchedPath = entity.steeringComponent().preSearchPath(!searchInArea, workAreaCenter.x, workAreaCenter.y, building.getBuildingType().getWorkRadius(), currentJob.getSearchType());
+ return preSearchedPath != null;
+ }
+
+ public void workSearchSucceeded() {
+ searchFailedCount = 0;
+ this.building.setCannotWork(false);
+ }
+
+ public void workSearchFailed() {
+ searchFailedCount++;
+ if (searchFailedCount > 10) {
+ this.building.setCannotWork(true);
+ entity.movableComponent().getPlayer().showMessage(SimpleMessage.cannotFindWork(building));
+ }
+ }
+
+ public void mark(ShortPoint2D position) {
+ clearMark();
+ markedPosition = position;
+ entity.gameFieldComponent().movableGrid.setMarked(position, true);
+ }
+
+ public void clearMark() {
+ if (markedPosition != null) {
+ entity.gameFieldComponent().movableGrid.setMarked(markedPosition, false);
+ markedPosition = null;
+ }
+ }
+
+ public EPriority getBuildingPriority() {
+ if (building != null) {
+ return building.getPriority();
+ } else {
+ return null;
+ }
+ }
+
+ public void addMapObjectCleanupPosition(ShortPoint2D pos, EMapObjectType objectType){
+ building.addMapObjectCleanupPosition(pos, objectType);
+ }
+
+ public EBuildingType getBuildingType() {
+ if (building != null) {
+ return building.getBuildingType();
+ } else {
+ return null;
+ }
+ }
+
+ public IWorkerRequestBuilding getBuilding() {
+ return building;
+ }
+
+ public void setWorkerJob(IWorkerRequestBuilding building) {
+ this.building = building;
+ this.currentJob = building.getBuildingType().getStartJob();
+ this.building.occupyBuilding(new ManageableWorkerWrapper(entity));
+ }
+
+ public void buildingDestroyed() {
+ entity.raiseNotification(new BuildingDestroyed());
+ }
+
+ public void reportAsJobless() {
+ entity.gameFieldComponent().movableGrid.addJobless(new ManageableWorkerWrapper(entity));
+ currentJob = null;
+ building = null;
+ }
+
+ public boolean tryTakingResource() {
+ switch (building.getBuildingType()) {
+ case FISHER:
+ MovableComponent movableComponent = this.entity.movableComponent();
+ EDirection fishDirection = movableComponent.getViewDirection();
+ return this.entity.gameFieldComponent().movableGrid.tryTakingResource(fishDirection.getNextHexPoint(movableComponent.getPosition()), EResourceType.FISH);
+ case COALMINE:
+ case IRONMINE:
+ case GOLDMINE:
+ return building.tryTakingResource();
+ default:
+ return false;
+ }
+ }
+
+ public boolean tryTakingFood() {
+ return building.tryTakingFood(currentJob.getFoodOrder());
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/Component.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/Component.java
new file mode 100644
index 0000000000..69767fd2f6
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/Component.java
@@ -0,0 +1,112 @@
+package jsettlers.logic.movable.components;
+
+import java.io.Serializable;
+import java.util.HashSet;
+
+import java8.util.Optional;
+import java8.util.function.Consumer;
+import java8.util.function.Predicate;
+import java8.util.stream.Stream;
+import jsettlers.common.movable.EMovableType;
+import jsettlers.logic.movable.Entity;
+import jsettlers.logic.movable.Notification;
+
+import static java8.util.stream.StreamSupport.stream;
+
+public abstract class Component implements Serializable {
+ private static final long serialVersionUID = -3071296154652495126L;
+
+ public Entity entity;
+ private HashSet consumedNotifications = new HashSet<>();
+
+ /**
+ * Called once when the entity gets enabled for the first time
+ * If you want to save references as shorthand to other components do so in onEnable
+ */
+ public final void wakeUp() {
+ onWakeUp();
+ }
+
+ protected void onWakeUp() {}
+
+ public final void update() {
+ consumedNotifications.clear();
+ onUpdate();
+ }
+
+ protected void onUpdate() {}
+
+ public final void lateUpdate() { onLateUpdate(); }
+
+ protected void onLateUpdate() {}
+
+ /**
+ * Called when the entity is set to active
+ * If you want to save references as shorthand to other components do so in onEnable
+ */
+ public final void enable() { onEnable(); }
+
+ protected void onEnable() {}
+
+ /**
+ * Called when the entity is set to inactive
+ */
+ public final void disable() { onDisable(); }
+
+ protected void onDisable() {}
+
+ /**
+ * Called before the entity gets destroyed/killed
+ */
+ public final void destroy() { onDestroy(); }
+
+ protected void onDestroy() {}
+
+ Optional getNextNotification(Class type, boolean consume) {
+ return getNextNotification(type, n -> true, consume);
+ }
+
+ Optional getNextNotification(Class type, Predicate predicate, boolean consume) {
+ Optional result = getNotificationsOfType(type).filter(predicate).findFirst();
+ if (consume) {
+ result.ifPresent(this::consumeNotification);
+ }
+ return result;
+ }
+
+ public boolean hasNotificationOfType(Class type) {
+ return getNotificationsOfType(type).findAny().isPresent();
+ }
+
+ public boolean hasNotificationOfType(Class type, boolean consume) { // this implementation uses findFirst to guarantee determinism
+ return getNextNotification(type, consume).isPresent();
+ }
+
+ public boolean hasNotificationOfType(Class type, Predicate predicate, boolean consume) { // this implementation uses findFirst to guarantee determinism
+ return getNextNotification(type, predicate, consume).isPresent();
+ }
+
+ private Stream getNotificationsOfType(Class type) {
+ //noinspection unchecked
+ return stream(entity.getAllNotifications())
+ .parallel()
+ .filter(notification -> !consumedNotifications.contains(notification))
+ .filter(type::isInstance).map(notification -> (T) notification);
+ }
+
+ public void forFirstNotificationOfTypeC(Class type, Consumer consumer, boolean consume) {
+ getNextNotification(type, consume).ifPresent(consumer);
+ }
+
+ public boolean forFirstNotificationOfTypeP(Class type, Predicate predicate, boolean consume) {
+ return getNextNotification(type, predicate, consume).isPresent();
+ }
+
+ boolean consumeNotification(Notification notification) {
+ if (entity.getAllNotifications().contains(notification)) {
+ consumedNotifications.add(notification);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/DonkeyBehaviorComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/DonkeyBehaviorComponent.java
new file mode 100644
index 0000000000..5d5d386c35
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/DonkeyBehaviorComponent.java
@@ -0,0 +1,151 @@
+package jsettlers.logic.movable.components;
+
+import java8.util.Optional;
+import jsettlers.common.material.EMaterialType;
+import jsettlers.logic.movable.Context;
+import jsettlers.logic.movable.Requires;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.NodeStatus;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Action;
+import jsettlers.logic.movable.simplebehaviortree.nodes.MemSelector;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Repeat;
+import jsettlers.logic.movable.strategies.trading.ITradeBuilding;
+
+import static jsettlers.logic.movable.BehaviorTreeHelper.action;
+import static jsettlers.logic.movable.BehaviorTreeHelper.alwaysSucceed;
+import static jsettlers.logic.movable.BehaviorTreeHelper.condition;
+import static jsettlers.logic.movable.BehaviorTreeHelper.debug;
+import static jsettlers.logic.movable.BehaviorTreeHelper.guard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.memSequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.repeat;
+import static jsettlers.logic.movable.BehaviorTreeHelper.selector;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.setAttackableWhile;
+import static jsettlers.logic.movable.BehaviorTreeHelper.setIdleBehaviorActiveWhile;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sleep;
+import static jsettlers.logic.movable.BehaviorTreeHelper.triggerGuard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.waitForTargetReachedAndFailIfNotReachable;
+
+/**
+ * @author homoroselaps
+ */
+
+@Requires({
+ MultiMaterialComponent.class,
+ DonkeyComponent.class,
+ SteeringComponent.class,
+ AttackableComponent.class,
+ GameFieldComponent.class,
+ AnimationComponent.class,
+ MovableComponent.class
+})
+public final class DonkeyBehaviorComponent extends BehaviorComponent {
+ private static final long serialVersionUID = -9105595769767841134L;
+
+ @Override
+ protected Node createBehaviorTree() {
+ return setAttackableWhile(false,
+ setIdleBehaviorActiveWhile(false,
+ selector(
+ triggerGuard(AttackableComponent.ReceivedHit.class,
+ debug("received hit", sequence(
+ debug("unassign market", action(c -> {
+ c.entity.donkeyComponent().resetMarket();
+ })),
+ debug("stop going to market", action(c -> {
+ c.entity.steeringComponent().resetTarget();
+ })),
+ debug("drop all materials", repeat(condition(c -> !c.entity.multiMaterialComponent().isEmpty()), alwaysSucceed(tryDropMaterial())))
+ ))
+ ),
+ guard(DonkeyBehaviorComponent::hasValidMarket, true,
+ selector(
+ debug("fulfill request", memSequence(
+ debug("go to market", action(c -> {
+ c.entity.steeringComponent().setTarget(c.entity.donkeyComponent().getMarket().getPickUpPosition());
+ })),
+ debug("wait for target reached", waitForTargetReachedAndFailIfNotReachable()),
+ debug("check for pending transport jobs", condition(c -> c.entity.donkeyComponent().getMarket().needsTrader())),
+ debug("take material", tryTakeMaterialFromMarket()),
+ debug("optionally take a second material", alwaysSucceed(tryTakeMaterialFromMarket())),
+ setAttackableWhile(true,
+ debug("follow waypoints", repeat(Repeat.Policy.NONPREEMPTIVE,
+ condition(c -> c.entity.donkeyComponent().hasNextWaypoint()),
+ memSequence(
+ debug("go to next waypoint", action(c -> {
+ c.entity.steeringComponent().setTarget(c.entity.donkeyComponent().peekNextWaypoint());
+ })),
+ debug("wait", waitForTargetReachedAndFailIfNotReachable()),
+ action(c -> {
+ c.entity.donkeyComponent().getNextWaypoint();
+ })
+ )
+ ))
+ ),
+ debug("drop all materials", repeat(condition(c -> !c.entity.multiMaterialComponent().isEmpty()), alwaysSucceed(tryDropMaterial()))),
+ selector(
+ debug("try find new market", tryFindNewMarket()),
+ debug("go back to market", memSequence(
+ action(c -> {
+ c.entity.steeringComponent().setTarget(c.entity.donkeyComponent().getMarket().getPickUpPosition());
+ }),
+ alwaysSucceed(debug("wait", waitForTargetReachedAndFailIfNotReachable())),
+ action(c -> {
+ c.entity.donkeyComponent().resetMarket();
+ })
+ ))
+ )
+ )),
+ debug("resolve failures", sequence(
+ debug("invalidate market", action(c -> {
+ c.entity.donkeyComponent().resetMarket();
+ })),
+ debug("drop materials", repeat(condition(c -> !c.entity.multiMaterialComponent().isEmpty()), alwaysSucceed(tryDropMaterial())))
+ ))
+ )
+ ),
+ // if no market in need then wait for second
+ guard(DonkeyBehaviorComponent::hasValidMarket, false,
+ setIdleBehaviorActiveWhile(true,
+ debug("search for valid market", new MemSelector<>(
+ tryFindNewMarket(),
+ sleep(1000)
+ ))
+ )
+ )
+ )
+ )
+ );
+ }
+
+ private static Action tryDropMaterial() {
+ return new Action<>(c -> {
+ EMaterialType material = c.entity.multiMaterialComponent().removeMaterial();
+ if (material == EMaterialType.NO_MATERIAL) { return NodeStatus.FAILURE; }
+ c.entity.gameFieldComponent().movableGrid.dropMaterial(c.entity.movableComponent().getPosition(), material, true, true);
+ return NodeStatus.SUCCESS;
+ });
+ }
+
+ private static Action tryTakeMaterialFromMarket() {
+ return new Action<>(c -> {
+ Optional material = c.entity.donkeyComponent().getMarket().tryToTakeMaterial(1);
+ if (!material.isPresent()) { return NodeStatus.FAILURE; }
+ c.entity.multiMaterialComponent().addMaterial(material.get().materialType);
+ return NodeStatus.SUCCESS;
+ });
+ }
+
+ private static Action tryFindNewMarket() {
+ return new Action<>(c -> {
+ ITradeBuilding market = c.entity.donkeyComponent().findTradeBuildingWithWork();
+ if (market == null) { return NodeStatus.FAILURE; }
+ c.entity.donkeyComponent().setMarket(market);
+ return NodeStatus.SUCCESS;
+ });
+ }
+
+ private static boolean hasValidMarket(Context c) {
+ return c.entity.donkeyComponent().getMarket() != null;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/DonkeyComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/DonkeyComponent.java
new file mode 100644
index 0000000000..ddf585beb1
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/DonkeyComponent.java
@@ -0,0 +1,70 @@
+package jsettlers.logic.movable.components;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import java8.util.stream.Collectors;
+import java8.util.stream.Stream;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.buildings.trading.MarketBuilding;
+import jsettlers.logic.constants.MatchConstants;
+import jsettlers.logic.movable.Requires;
+import jsettlers.logic.movable.strategies.trading.ITradeBuilding;
+
+/**
+ * @author homoroselaps
+ */
+
+@Requires({MovableComponent.class})
+public class DonkeyComponent extends Component {
+ private static final long serialVersionUID = -4747039405397703303L;
+
+ private ITradeBuilding market;
+ private Iterator waypoints;
+ private ShortPoint2D nextWaypoint;
+
+ public ITradeBuilding getMarket() {
+ return market;
+ }
+
+ public void setMarket(ITradeBuilding market) {
+ assert market != null : "market should not be null, use reset() instead";
+ this.market = market;
+ this.waypoints = market.getWaypointsIterator();
+ this.nextWaypoint = waypoints != null ? waypoints.next() : null;
+ }
+
+ public void resetMarket() {
+ this.market = null;
+ this.waypoints = null;
+ }
+
+ public ShortPoint2D getNextWaypoint() {
+ ShortPoint2D last = nextWaypoint;
+ this.nextWaypoint = waypoints != null ? waypoints.next() : null;
+ return last;
+ }
+
+ public boolean hasNextWaypoint() {
+ return nextWaypoint != null;
+ }
+
+ public ShortPoint2D peekNextWaypoint() {
+ return nextWaypoint;
+ }
+
+ public ITradeBuilding findTradeBuildingWithWork() {
+ List extends ITradeBuilding> tradeBuilding = getTradersWithWork().filter(ITradeBuilding::needsTrader).collect(Collectors.toList());
+
+ if (!tradeBuilding.isEmpty()) { // randomly distribute the donkeys onto the markets needing them
+ return tradeBuilding.get(MatchConstants.random().nextInt(tradeBuilding.size()));
+ } else {
+ return null;
+ }
+ }
+
+ protected Stream getTradersWithWork() {
+ return MarketBuilding.getAllMarkets(entity.movableComponent().getPlayer());
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/GameFieldComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/GameFieldComponent.java
new file mode 100644
index 0000000000..1828c53b8b
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/GameFieldComponent.java
@@ -0,0 +1,26 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.logic.movable.MovableDataManager;
+import jsettlers.logic.movable.interfaces.AbstractMovableGrid;
+import jsettlers.logic.movable.interfaces.ILogicMovable;
+
+/**
+ * @author homoroselaps
+ */
+public class GameFieldComponent extends Component {
+ private static final long serialVersionUID = 476680901281177567L;
+
+ public final AbstractMovableGrid movableGrid;
+
+ public GameFieldComponent(AbstractMovableGrid grid) {
+ this.movableGrid = grid;
+ }
+
+ void addNewMovable(ILogicMovable movable) {
+ MovableDataManager.add(movable);
+ }
+
+ void removeMovable(ILogicMovable movable) {
+ MovableDataManager.remove(movable);
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/GeologistBehaviorComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/GeologistBehaviorComponent.java
new file mode 100644
index 0000000000..ed63309bda
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/GeologistBehaviorComponent.java
@@ -0,0 +1,198 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.algorithms.path.Path;
+import jsettlers.common.map.shapes.HexGridArea;
+import jsettlers.common.material.ESearchType;
+import jsettlers.common.movable.EMovableAction;
+import jsettlers.common.position.MutablePoint2D;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.common.utils.mutables.MutableDouble;
+import jsettlers.logic.movable.Context;
+import jsettlers.logic.movable.Requires;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.NodeStatus;
+import jsettlers.logic.movable.simplebehaviortree.nodes.Action;
+
+import static jsettlers.logic.movable.BehaviorTreeHelper.action;
+import static jsettlers.logic.movable.BehaviorTreeHelper.alwaysSucceed;
+import static jsettlers.logic.movable.BehaviorTreeHelper.debug;
+import static jsettlers.logic.movable.BehaviorTreeHelper.defaultIdleBehavior;
+import static jsettlers.logic.movable.BehaviorTreeHelper.guard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.memSequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.selector;
+import static jsettlers.logic.movable.BehaviorTreeHelper.setIdleBehaviorActiveWhile;
+import static jsettlers.logic.movable.BehaviorTreeHelper.startAndWaitForAnimation;
+import static jsettlers.logic.movable.BehaviorTreeHelper.triggerGuard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.waitForPathFinished;
+import static jsettlers.logic.movable.BehaviorTreeHelper.waitForTargetReachedAndFailIfNotReachable;
+
+/**
+ * @author homoroselaps
+ */
+
+@Requires({
+ SpecialistComponent.class,
+ SteeringComponent.class,
+ AttackableComponent.class,
+ GameFieldComponent.class,
+ AnimationComponent.class,
+ MovableComponent.class,
+ PlayerComandComponent.class,
+ MarkedPositonComponent.class,
+})
+public final class GeologistBehaviorComponent extends BehaviorComponent {
+ private static final long serialVersionUID = -4157235942699928852L;
+
+ private static final short ACTION1_DURATION = 1400;
+ private static final short ACTION2_DURATION = 1500;
+
+ private boolean goingToPlayerCommandLocation = false;
+
+ @Override
+ protected Node createBehaviorTree() {
+ return setIdleBehaviorActiveWhile(false,
+ selector(
+ triggerGuard(PlayerComandComponent.MoveToCommand.class,
+ action("MoveToCommand", c -> {
+ c.component.forFirstNotificationOfTypeC(PlayerComandComponent.MoveToCommand.class, command -> c.entity.steeringComponent().setTarget(command.pos), true);
+ goingToPlayerCommandLocation = true;
+ c.entity.specialistComponent().setIsWorking(true);
+ })
+ ),
+ triggerGuard(PlayerComandComponent.StartWorkCommand.class,
+ debug("StartWorkCommand",
+ setIsWorkingAction(true)
+ )
+ ),
+ triggerGuard(PlayerComandComponent.StopWorkCommand.class,
+ debug("StopWorkCommand",
+ setIsWorkingAction(false)
+ )
+ ),
+ guard(c -> goingToPlayerCommandLocation, true,
+ waitForPathFinished(null, setIsWorkingAction(false),
+ action(c -> { goingToPlayerCommandLocation = false; }))
+ ),
+ guard(c -> c.entity.specialistComponent().isWorking(), true,
+ selector("isWorking",
+ memSequence("find a place and work there",
+ debug("FindGoToWorkablePosition", new FindGoToWorkablePosition()),
+ waitForTargetReachedAndFailIfNotReachable(),
+ debug("markOnCurrentPositionIfWorkingIsPossible", markOnCurrentPositionIfWorkingIsPossible()),
+ startAndWaitForAnimation(EMovableAction.ACTION1, ACTION1_DURATION),
+ startAndWaitForAnimation(EMovableAction.ACTION2, ACTION2_DURATION),
+ debug("placeSign", placeSign())
+ ),
+ debug("on failure: stop working", setIsWorkingAction(false))
+ )
+ ),
+ defaultIdleBehavior()
+ )
+ );
+ }
+
+ @Override
+ protected void onLateUpdate() {
+ if (!entity.specialistComponent().isWorking()) {
+ entity.setInvocationDelay(500);
+ }
+ }
+
+ private Action setIsWorkingAction(boolean isWorking) {
+ return action(c -> {
+ c.entity.specialistComponent().setIsWorking(isWorking);
+ });
+ }
+
+ private Node placeSign() {
+ return new Action<>(c -> {
+ ShortPoint2D position = c.entity.movableComponent().getPosition();
+
+ c.entity.markedPositonComponent().clearMark();
+ c.entity.gameFieldComponent().movableGrid.executeSearchType(c.entity.movableComponent(), position, ESearchType.RESOURCE_SIGNABLE);
+ });
+ }
+
+ private static Action markOnCurrentPositionIfWorkingIsPossible() {
+ return new Action<>(c -> {
+ ShortPoint2D position = c.entity.movableComponent().getPosition();
+
+ if (c.entity.specialistComponent().getCenterOfWork() == null) {
+ c.entity.specialistComponent().setCenterOfWork(position);
+ }
+
+ c.entity.markedPositonComponent().clearMark();
+ boolean canWorkOnPos = c.entity.gameFieldComponent().movableGrid.fitsSearchType(c.entity.movableComponent(), position.x, position.y, ESearchType.RESOURCE_SIGNABLE);
+
+ if (canWorkOnPos) {
+ c.entity.markedPositonComponent().setMark(position);
+ return NodeStatus.SUCCESS;
+ }
+ return NodeStatus.FAILURE;
+ }
+ );
+ }
+
+ private static class FindGoToWorkablePosition extends Action {
+ private static final long serialVersionUID = -5393050237159114345L;
+
+ FindGoToWorkablePosition() {
+ super(FindGoToWorkablePosition::run);
+ }
+
+ public static NodeStatus run(Context c) {
+ MovableComponent movableComponent = c.entity.movableComponent();
+ GameFieldComponent gameFieldComponent = c.entity.gameFieldComponent();
+ SpecialistComponent specialistComponent = c.entity.specialistComponent();
+ SteeringComponent steeringComponent = c.entity.steeringComponent();
+
+ if (specialistComponent.getCenterOfWork() == null) {
+ specialistComponent.setCenterOfWork(movableComponent.getPosition());
+ }
+
+ ShortPoint2D closeWorkablePos = getCloseWorkablePos(c);
+
+ if (closeWorkablePos != null && steeringComponent.setTarget(closeWorkablePos)) {
+ c.entity.markedPositonComponent().setMark(closeWorkablePos);
+ return NodeStatus.SUCCESS;
+ }
+ specialistComponent.setCenterOfWork(null);
+
+ ShortPoint2D pos = movableComponent.getPosition();
+ Path path = steeringComponent.preSearchPath(true, pos.x, pos.y, (short) 30, ESearchType.RESOURCE_SIGNABLE);
+ if (path != null) {
+ steeringComponent.setPath(path);
+ return NodeStatus.SUCCESS;
+ }
+
+ return NodeStatus.FAILURE;
+ }
+
+ private static ShortPoint2D getCloseWorkablePos(Context c) {
+ MovableComponent movableComponent = c.entity.movableComponent();
+ GameFieldComponent gameFieldComponent = c.entity.gameFieldComponent();
+ SpecialistComponent specialistComponent = c.entity.specialistComponent();
+
+ MutablePoint2D bestNeighbourPos = new MutablePoint2D(-1, -1);
+ MutableDouble bestNeighbourDistance = new MutableDouble(Double.MAX_VALUE); // distance from start point
+
+ HexGridArea.streamBorder(movableComponent.getPosition(), 2).filter((x, y) ->
+ gameFieldComponent.movableGrid.isValidPosition(movableComponent, x, y)
+ && gameFieldComponent.movableGrid.fitsSearchType(movableComponent, x, y, ESearchType.RESOURCE_SIGNABLE)
+ ).forEach((x, y) -> {
+ double distance = ShortPoint2D.getOnGridDist(x - specialistComponent.getCenterOfWork().x, y - specialistComponent.getCenterOfWork().y);
+ if (distance < bestNeighbourDistance.value) {
+ bestNeighbourDistance.value = distance;
+ bestNeighbourPos.x = x;
+ bestNeighbourPos.y = y;
+ }
+ });
+
+ if (bestNeighbourDistance.value != Double.MAX_VALUE) {
+ return bestNeighbourPos.createShortPoint2D();
+ } else {
+ return null;
+ }
+ }
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MarkedPositonComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MarkedPositonComponent.java
new file mode 100644
index 0000000000..9f8958a7b5
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MarkedPositonComponent.java
@@ -0,0 +1,30 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.position.ShortPoint2D;
+
+/**
+ * @author homoroselaps
+ */
+public class MarkedPositonComponent extends Component {
+ private static final long serialVersionUID = -3582535279041109009L;
+
+ private ShortPoint2D markedPosition;
+
+ @Override
+ protected void onDestroy() {
+ clearMark();
+ }
+
+ public void setMark(ShortPoint2D position) {
+ clearMark();
+ markedPosition = position;
+ entity.gameFieldComponent().movableGrid.setMarked(position, true);
+ }
+
+ public void clearMark() {
+ if (markedPosition != null) {
+ entity.gameFieldComponent().movableGrid.setMarked(markedPosition, false);
+ markedPosition = null;
+ }
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MaterialComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MaterialComponent.java
new file mode 100644
index 0000000000..0f423d7e68
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MaterialComponent.java
@@ -0,0 +1,21 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.material.EMaterialType;
+
+/**
+ * @author homoroselaps
+ */
+
+public class MaterialComponent extends Component {
+ private static final long serialVersionUID = -3337241844215162194L;
+
+ private EMaterialType material = EMaterialType.NO_MATERIAL;
+
+ public EMaterialType getMaterial() {
+ return material;
+ }
+
+ public void setMaterial(EMaterialType material) {
+ this.material = material;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MovableComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MovableComponent.java
new file mode 100644
index 0000000000..90ffbed140
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MovableComponent.java
@@ -0,0 +1,145 @@
+package jsettlers.logic.movable.components;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+import jsettlers.algorithms.path.IPathCalculatable;
+import jsettlers.common.mapobject.EMapObjectType;
+import jsettlers.common.movable.EDirection;
+import jsettlers.common.movable.EMovableType;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.constants.Constants;
+import jsettlers.logic.movable.EntityFactory;
+import jsettlers.logic.movable.MovableWrapper;
+import jsettlers.logic.movable.Requires;
+import jsettlers.logic.player.Player;
+
+/**
+ * @author homoroselaps
+ */
+@Requires({GameFieldComponent.class})
+public class MovableComponent extends Component implements IPathCalculatable {
+ private static final long serialVersionUID = -7615132582559956988L;
+
+ private EMovableType movableType;
+ private Player player;
+ private ShortPoint2D position;
+ private EDirection viewDirection;
+ private boolean visible = true;
+ //TODO: make @movableWrapper not necessary
+ private MovableWrapper movableWrapper;
+
+ private GameFieldComponent gameComponent;
+
+ public MovableComponent(EMovableType movableType, Player player, ShortPoint2D position, EDirection viewDirection) {
+ this.movableType = movableType;
+ this.player = player;
+ this.position = position;
+ this.viewDirection = viewDirection;
+ }
+
+ @Override
+ protected void onWakeUp() {
+ movableWrapper = new MovableWrapper(entity);
+ }
+
+ @Override
+ protected void onEnable() {
+ gameComponent = entity.getComponent(GameFieldComponent.class);
+ gameComponent.addNewMovable(movableWrapper);
+ gameComponent.movableGrid.enterPosition(position, movableWrapper, true);
+ }
+
+ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ ois.defaultReadObject();
+ }
+
+ @Override
+ protected void onDisable() {
+ // TODO: refactor leavePosition to not use the instance
+ gameComponent.movableGrid.leavePosition(position, gameComponent.movableGrid.getMovableAt(position.x, position.y));
+ gameComponent.removeMovable(getMovableWrapper());
+ }
+
+ @Override
+ protected void onDestroy() {
+ gameComponent.movableGrid.addSelfDeletingMapObject(position, EMapObjectType.GHOST, Constants.GHOST_PLAY_DURATION, player);
+ }
+
+ public MovableWrapper getMovableWrapper() {
+ return movableWrapper;
+ }
+
+ public void setViewDirection(EDirection viewDirection) {
+ this.viewDirection = viewDirection;
+ }
+
+ public EDirection getViewDirection() {
+ return viewDirection;
+ }
+
+ public short getViewDistance() {
+ return Constants.MOVABLE_VIEW_DISTANCE;
+ }
+
+ @Override
+ public boolean needsPlayersGround() {
+ return movableType.needsPlayersGround();
+ }
+
+ @Override
+ public boolean isShip() {
+ return this.getMovableType() == EMovableType.FERRY;
+ }
+
+ @Override
+ public ShortPoint2D getPosition() {
+ return position;
+ }
+
+ public void setPos(ShortPoint2D position) {
+ if (visible) {
+ gameComponent.movableGrid.leavePosition(this.position, movableWrapper);
+ gameComponent.movableGrid.enterPosition(position, movableWrapper, false);
+ }
+ this.position = position;
+ }
+
+ public void setPlayer(Player player) {
+ this.player = player;
+ }
+
+ public Player getPlayer() {return this.player;}
+
+ public EMovableType getMovableType() {
+ return movableType;
+ }
+
+ final void setVisible(boolean visible) {
+ if (this.visible == visible) return; // nothing to change
+
+ if (this.visible) { // is visible and gets invisible
+ gameComponent.movableGrid.leavePosition(position, movableWrapper);
+ } else {
+ gameComponent.movableGrid.enterPosition(position, movableWrapper, true);
+ }
+
+ this.visible = visible;
+ }
+
+ public void convertTo(EMovableType newMovableType) {
+ if (newMovableType == EMovableType.BEARER && !player.equals(gameComponent.movableGrid.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
+ }
+ entity.getComponentOptional(AttackableComponent.class).ifPresent(c ->
+ c.setHealth(c.getHealth() / movableType.getHealth() * newMovableType.getHealth())
+ );
+ movableType = newMovableType;
+ setVisible(true); // ensure the movable is visible
+ entity.convertTo(EntityFactory.createEntity(gameComponent.movableGrid, newMovableType, position, player));
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MultiMaterialComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MultiMaterialComponent.java
new file mode 100644
index 0000000000..2a40153b10
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/MultiMaterialComponent.java
@@ -0,0 +1,62 @@
+package jsettlers.logic.movable.components;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import java8.util.Maps;
+import jsettlers.common.material.EMaterialType;
+
+/**
+ * @author homoroselaps
+ */
+
+public class MultiMaterialComponent extends MaterialComponent {
+ private static final long serialVersionUID = -2141241181575955088L;
+
+ private final Map materials = new HashMap<>();
+
+ private int sum = 0;
+
+ public void addMaterial(EMaterialType material) {
+ if (material == null || material == EMaterialType.NO_MATERIAL) { return; }
+ materials.put(material, Maps.getOrDefault(materials, material, 0) + 1);
+ sum++;
+ super.setMaterial(EMaterialType.BASKET);
+ }
+
+ @Override
+ public void setMaterial(EMaterialType material) {
+ addMaterial(material);
+ }
+
+ public EMaterialType removeMaterial(EMaterialType material) {
+ int amount = Maps.getOrDefault(materials, material, 0);
+ EMaterialType result = EMaterialType.NO_MATERIAL;
+ if (amount > 0) {
+ materials.put(material, amount - 1);
+ sum--;
+ result = material;
+ }
+ if (isEmpty()) { super.setMaterial(EMaterialType.NO_MATERIAL); }
+ return result;
+ }
+
+ public EMaterialType removeMaterial() {
+ EMaterialType result = EMaterialType.NO_MATERIAL;
+ for (EMaterialType material : materials.keySet()) {
+ int amount = Maps.getOrDefault(materials, material, 0);
+ if (amount > 0) {
+ materials.put(material, amount - 1);
+ sum--;
+ result = material;
+ break;
+ }
+ }
+ if (isEmpty()) { super.setMaterial(EMaterialType.NO_MATERIAL); }
+ return result;
+ }
+
+ public boolean isEmpty() {
+ return sum <= 0;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/PlayerComandComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/PlayerComandComponent.java
new file mode 100644
index 0000000000..c4bda47475
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/PlayerComandComponent.java
@@ -0,0 +1,48 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.movable.Notification;
+
+/**
+ * @author homoroselaps
+ */
+
+public class PlayerComandComponent extends Component {
+ private static final long serialVersionUID = -3188445864619388414L;
+
+ public static class LeftClickCommand extends Notification {
+ public final ShortPoint2D pos;
+
+ LeftClickCommand(ShortPoint2D pos) {
+ this.pos = pos;
+ }
+ }
+
+ public static class MoveToCommand extends Notification {
+ public final ShortPoint2D pos;
+
+ MoveToCommand(ShortPoint2D pos) {
+ this.pos = pos;
+ }
+ }
+
+ public static class StartWorkCommand extends Notification {}
+
+ public static class StopWorkCommand extends Notification {}
+
+ public void sendLeftClick(ShortPoint2D pos) {
+ entity.raiseNotification(new LeftClickCommand(pos));
+ }
+
+ public void sendMoveToCommand(ShortPoint2D pos) {
+ entity.raiseNotification(new MoveToCommand(pos));
+ }
+
+ public void sendStartWorkCommand() {
+ entity.raiseNotification(new StartWorkCommand());
+ }
+
+ public void sendStopWorkCommand() {
+ entity.raiseNotification(new StopWorkCommand());
+ }
+}
\ No newline at end of file
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SelectableComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SelectableComponent.java
new file mode 100644
index 0000000000..e55c3e8385
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SelectableComponent.java
@@ -0,0 +1,34 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.selectable.ESelectionType;
+
+/**
+ * @author homoroselaps
+ */
+public class SelectableComponent extends Component {
+ private static final long serialVersionUID = 665477836143096339L;
+
+ private final ESelectionType selectionType;
+ private boolean selected;
+
+ public SelectableComponent(ESelectionType selectionType) {
+ this.selectionType = selectionType;
+ }
+
+ public ESelectionType getSelectionType() {
+ return selectionType;
+ }
+
+ public boolean isSelected() {
+ return selected;
+ }
+
+ public void setSelected(boolean selected) {
+ this.selected = selected;
+ }
+
+ @Override
+ protected void onDisable() {
+ setSelected(false);
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SpecialistComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SpecialistComponent.java
new file mode 100644
index 0000000000..94754467b8
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SpecialistComponent.java
@@ -0,0 +1,26 @@
+package jsettlers.logic.movable.components;
+
+import jsettlers.common.position.ShortPoint2D;
+
+/**
+ * @author homoroselaps
+ */
+public class SpecialistComponent extends Component {
+ private static final long serialVersionUID = -5944104465410121876L;
+
+ private boolean isWorking = false;
+ private ShortPoint2D centerOfWork;
+
+
+ public boolean isWorking() {
+ return isWorking;
+ }
+
+ public void setIsWorking(boolean isWorking) {
+ this.isWorking = isWorking;
+ }
+
+ public ShortPoint2D getCenterOfWork() { return centerOfWork; }
+
+ public void setCenterOfWork(ShortPoint2D centerOfWork) { this.centerOfWork = centerOfWork; }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SteeringComponent.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SteeringComponent.java
new file mode 100644
index 0000000000..d71cc1897d
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/components/SteeringComponent.java
@@ -0,0 +1,274 @@
+package jsettlers.logic.movable.components;
+
+import java8.util.Optional;
+import jsettlers.algorithms.path.Path;
+import jsettlers.common.material.ESearchType;
+import jsettlers.common.movable.EDirection;
+import jsettlers.common.movable.EMovableAction;
+import jsettlers.common.position.ILocatable;
+import jsettlers.common.position.ShortPoint2D;
+import jsettlers.logic.constants.MatchConstants;
+import jsettlers.logic.movable.BehaviorTreeHelper;
+import jsettlers.logic.movable.Context;
+import jsettlers.logic.movable.EGoInDirectionMode;
+import jsettlers.logic.movable.Notification;
+import jsettlers.logic.movable.Requires;
+import jsettlers.logic.movable.interfaces.ILogicMovable;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.NodeStatus;
+import jsettlers.logic.movable.simplebehaviortree.Root;
+import jsettlers.logic.movable.simplebehaviortree.Tick;
+
+import static jsettlers.logic.movable.BehaviorTreeHelper.action;
+import static jsettlers.logic.movable.BehaviorTreeHelper.condition;
+import static jsettlers.logic.movable.BehaviorTreeHelper.debug;
+import static jsettlers.logic.movable.BehaviorTreeHelper.guard;
+import static jsettlers.logic.movable.BehaviorTreeHelper.memSequence;
+import static jsettlers.logic.movable.BehaviorTreeHelper.selector;
+import static jsettlers.logic.movable.BehaviorTreeHelper.sleep;
+import static jsettlers.logic.movable.BehaviorTreeHelper.alwaysSucceed;
+import static jsettlers.logic.movable.BehaviorTreeHelper.triggerGuard;
+
+/**
+ * @author homoroselaps
+ */
+@Requires({
+ GameFieldComponent.class,
+ MovableComponent.class,
+ AnimationComponent.class
+})
+public class SteeringComponent extends Component {
+ private static final long serialVersionUID = 8281773945922792414L;
+
+ private Path path;
+ private GameFieldComponent gameFieldComponent;
+ private MovableComponent movableComponent;
+ private AnimationComponent animationComponent;
+ private Tick tick;
+ private boolean isIdleBehaviorActive = false;
+
+ public boolean IsIdleBehaviorActive() { return isIdleBehaviorActive; }
+
+ public void IsIdleBehaviorActive(boolean value) { isIdleBehaviorActive = value; }
+
+ public static class TargetReachedNotification extends Notification {}
+
+ public static class TargetNotReachedNotification extends Notification {}
+
+ public static class LeavePositionRequest extends Notification {
+ public final ILocatable sender;
+
+ public LeavePositionRequest(ILocatable sender) {
+ this.sender = sender;
+ }
+ }
+
+ @Override
+ protected void onWakeUp() {
+ tick = new Tick<>(new Context(entity, this), new Root<>(debug("==== of " + entity.getID(), createBehaviorTree())));
+ }
+
+ @Override
+ protected void onEnable() {
+ gameFieldComponent = entity.getComponent(GameFieldComponent.class);
+ movableComponent = entity.getComponent(MovableComponent.class);
+ animationComponent = entity.getComponent(AnimationComponent.class);
+ }
+
+ public boolean setTarget(ShortPoint2D targetPos) {
+ if (movableComponent.getPosition().equals(targetPos)) {
+ entity.raiseNotification(new TargetReachedNotification());
+ return true;
+ }
+ path = gameFieldComponent.movableGrid.calculatePathTo(movableComponent, targetPos);
+ return path != null;
+ }
+
+ public void resetTarget() {
+ path = null;
+ }
+
+ public void setPath(Path path) {
+ assert path != null : "path must not be null";
+ this.path = path;
+ }
+
+ public Path preSearchPath(boolean dijkstra, short centerX, short centerY, short radius, ESearchType searchType) {
+ if (dijkstra) {
+ return gameFieldComponent.movableGrid.searchDijkstra(movableComponent, centerX, centerY, radius, searchType);
+ } else {
+ return gameFieldComponent.movableGrid.searchInArea(movableComponent, centerX, centerY, radius, searchType);
+ }
+ }
+
+ protected Node createBehaviorTree() {
+ return selector(
+ guard(c -> path != null, true,
+ debug("follow path", action(c -> { followPath(); }))
+ ),
+ guard(c -> ((SteeringComponent)c.component).isIdleBehaviorActive,
+ selector(
+ guard(c -> gameFieldComponent.movableGrid.isBlockedOrProtected(movableComponent.getPosition().x, movableComponent.getPosition().y), true,
+ action(c -> {
+ goToNonBlockedOrProtectedPosition();
+ })
+ ),
+ debug("if LeavePositionRequest", triggerGuard(LeavePositionRequest.class,
+ debug("try go in random direction", action(context -> {
+ Optional note = context.component.getNextNotification(LeavePositionRequest.class, false);
+ if (note.isPresent() && goToRandomDirection(note.get().sender)) {
+ context.component.consumeNotification(note.get());
+ return NodeStatus.SUCCESS;
+ }
+ return NodeStatus.FAILURE;
+ }))
+ )),
+ debug("move away from other movables", memSequence(
+ condition(c -> this.flockToDecentralize()),
+ sleep(500)
+ )),
+ debug("turn in a random direction", memSequence(
+ action(c -> {
+ turnInRandomDirection();
+ }),
+ sleep(1000)
+ ))
+ )
+ ),
+ debug("nothing to do", alwaysSucceed())
+ );
+ }
+
+ @Override
+ protected void onUpdate() {
+ tick.tick();
+ }
+
+ private boolean goToRandomDirection(ILocatable pushingMovable) {
+ int offset = MatchConstants.random().nextInt(EDirection.NUMBER_OF_DIRECTIONS);
+ EDirection pushedFromDir = EDirection.getDirection(movableComponent.getPosition(), pushingMovable.getPosition());
+
+ 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;
+ }
+
+ private void followPath() {
+ // if path is finished
+ if (!path.hasNextStep()) {
+ path = null;
+ entity.raiseNotification(new TargetReachedNotification());
+ return;
+ }
+
+ ILogicMovable blockingMovable = gameFieldComponent.movableGrid.getMovableAt(path.nextX(), path.nextY());
+ if (blockingMovable == null) { // if we can go on to the next step
+ if (gameFieldComponent.movableGrid.isValidNextPathPosition(movableComponent, path.getNextPos(), path.getTargetPosition())) { // next position is valid
+ goSingleStep(path.getNextPos());
+ path.goToNextStep();
+ } else { // next position is invalid
+
+ Path newPath = gameFieldComponent.movableGrid.calculatePathTo(movableComponent, path.getTargetPosition()); // try to find a new path
+
+ if (newPath == null) { // no path found
+ path = null;
+ entity.raiseNotification(new TargetNotReachedNotification());
+ } else {
+ this.path = newPath; // continue with new path
+ if (gameFieldComponent.movableGrid.hasNoMovableAt(path.nextX(), path.nextY())) { // path is valid, but maybe blocked (leaving blocked area)
+ goSingleStep(path.getNextPos());
+ path.goToNextStep();
+ }
+ }
+ }
+ } else { // step not possible, so try it next time (push not supported)
+ blockingMovable.push(movableComponent.getMovableWrapper());
+ }
+ }
+
+ private void goToNonBlockedOrProtectedPosition() {
+ Path newPath = gameFieldComponent.movableGrid.searchDijkstra(movableComponent, movableComponent.getPosition().x, movableComponent.getPosition().y, (short) 50, ESearchType
+ .NON_BLOCKED_OR_PROTECTED);
+ if (newPath == null) {
+ entity.kill();
+ } else {
+ setPath(newPath);
+ }
+ }
+
+ private void turnInRandomDirection() {
+ int turnDirection = MatchConstants.random().nextInt(-8, 8);
+ if (Math.abs(turnDirection) <= 1) {
+ movableComponent.setViewDirection(movableComponent.getViewDirection().getNeighbor(turnDirection));
+ }
+ }
+
+ private void goSingleStep(ShortPoint2D targetPosition) {
+ movableComponent.setViewDirection(EDirection.getDirection(movableComponent.getPosition(), targetPosition));
+ movableComponent.setPos(targetPosition);
+ animationComponent.startAnimation(EMovableAction.WALKING, movableComponent.getMovableType().getStepDurationMs(), false);
+ animationComponent.switchStep();
+ }
+
+ /**
+ * 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 = gameFieldComponent.movableGrid.calcDecentralizeVector(movableComponent.getPosition().x, movableComponent.getPosition().y);
+
+ EDirection randomDirection = movableComponent.getViewDirection().getNeighbor(MatchConstants.random().nextInt(-1, 1));
+ int dx = randomDirection.gridDeltaX + decentVector.x;
+ int dy = randomDirection.gridDeltaY + decentVector.y;
+
+ if (ShortPoint2D.getOnGridDist(dx, dy) >= 2) {
+ return goInDirection(EDirection.getApproxDirection(0, 0, dx, dy), EGoInDirectionMode.GO_IF_ALLOWED_AND_FREE);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Tries to go a step in the given direction.
+ *
+ * @param direction
+ * direction to go
+ * @param mode
+ * Use the given mode to go.
+ * @return true if the step can and will immediately be executed.
+ * 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(movableComponent.getPosition());
+
+ switch (mode) {
+ case GO_IF_ALLOWED_WAIT_TILL_FREE: {
+ movableComponent.setViewDirection(direction);
+ this.setPath(new Path(targetPosition));
+ return true;
+ }
+ case GO_IF_ALLOWED_AND_FREE:
+ if ((gameFieldComponent.movableGrid.isValidPosition(movableComponent, targetPosition.x, targetPosition.y)
+ && gameFieldComponent.movableGrid.hasNoMovableAt(targetPosition.x, targetPosition.y))) {
+ goSingleStep(targetPosition);
+ return true;
+ } else {
+ break;
+ }
+ case GO_IF_FREE:
+ if (gameFieldComponent.movableGrid.isFreePosition(targetPosition.x, targetPosition.y)) {
+ goSingleStep(targetPosition);
+ return true;
+ } else {
+ break;
+ }
+ }
+ return false;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Composite.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Composite.java
new file mode 100644
index 0000000000..9d6143fd46
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Composite.java
@@ -0,0 +1,10 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+public class Composite extends Node {
+ private static final long serialVersionUID = 8795400757387672902L;
+
+ @SafeVarargs
+ protected Composite(Node... children) {
+ super(children);
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Decorator.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Decorator.java
new file mode 100644
index 0000000000..7f864cc010
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Decorator.java
@@ -0,0 +1,12 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+public class Decorator extends Node {
+ private static final long serialVersionUID = 2453864576230160564L;
+
+ public final Node child;
+
+ public Decorator(Node child) {
+ super(child);
+ this.child = child;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IBooleanConditionFunction.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IBooleanConditionFunction.java
new file mode 100644
index 0000000000..5b13f27fa8
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IBooleanConditionFunction.java
@@ -0,0 +1,9 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+import java.io.Serializable;
+
+import java8.util.function.Function;
+import java8.util.function.Predicate;
+
+@FunctionalInterface
+public interface IBooleanConditionFunction extends Predicate, Serializable {}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IEMaterialTypeSupplier.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IEMaterialTypeSupplier.java
new file mode 100644
index 0000000000..33e917ac50
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IEMaterialTypeSupplier.java
@@ -0,0 +1,9 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+import java.io.Serializable;
+
+import java8.util.function.Function;
+import jsettlers.common.material.EMaterialType;
+
+@FunctionalInterface
+public interface IEMaterialTypeSupplier extends Function, Serializable {}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IIntegerSupplier.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IIntegerSupplier.java
new file mode 100644
index 0000000000..5e7a72452b
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IIntegerSupplier.java
@@ -0,0 +1,8 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+import java.io.Serializable;
+
+import java8.util.function.Function;
+
+@FunctionalInterface
+public interface IIntegerSupplier extends Function, Serializable {}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/INodeStatusActionConsumer.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/INodeStatusActionConsumer.java
new file mode 100644
index 0000000000..2b3647bf7d
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/INodeStatusActionConsumer.java
@@ -0,0 +1,8 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+import java.io.Serializable;
+
+import java8.util.function.Consumer;
+
+@FunctionalInterface
+public interface INodeStatusActionConsumer extends Consumer, Serializable {}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/INodeStatusActionFunction.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/INodeStatusActionFunction.java
new file mode 100644
index 0000000000..e5a134fea9
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/INodeStatusActionFunction.java
@@ -0,0 +1,8 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+import java.io.Serializable;
+
+import java8.util.function.Function;
+
+@FunctionalInterface
+public interface INodeStatusActionFunction extends Function, Serializable {}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IShortSupplier.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IShortSupplier.java
new file mode 100644
index 0000000000..5ef687ab98
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/IShortSupplier.java
@@ -0,0 +1,8 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+import java.io.Serializable;
+
+import java8.util.function.Function;
+
+@FunctionalInterface
+public interface IShortSupplier extends Function, Serializable {}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Node.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Node.java
new file mode 100644
index 0000000000..8b11733e55
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Node.java
@@ -0,0 +1,83 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class Node implements Serializable {
+ private static final long serialVersionUID = -4544227752720944971L;
+
+ private int id;
+ private boolean isOpen = false;
+
+ public int getId() { return id; }
+
+ protected final ArrayList> children;
+
+ @SafeVarargs
+ public Node(Node... children) {
+ this.children = new ArrayList<>(children.length);
+ this.children.addAll(Arrays.asList(children));
+ }
+
+ public NodeStatus execute(Tick tick) {
+ if (!isOpen) {
+ open(tick);
+ }
+ enter(tick);
+ NodeStatus status = this.tick(tick);
+ exit(tick);
+ if (!status.equals(NodeStatus.RUNNING)) {
+ close(tick);
+ }
+ return status;
+ }
+
+ private void enter(Tick tick) {
+ tick.visitNode(this);
+ onEnter(tick);
+ }
+
+ private void open(Tick tick) {
+ isOpen = true;
+ onOpen(tick);
+ }
+
+ private NodeStatus tick(Tick tick) {
+ tick.tickNode(this);
+ return onTick(tick);
+ }
+
+ public void close(Tick tick) {
+ if (isOpen) {
+ tick.leaveNode(this);
+ isOpen = false;
+ onClose(tick);
+ }
+ }
+
+ private void exit(Tick tick) {
+ onExit(tick);
+ }
+
+ protected void onEnter(Tick tick) { }
+
+ protected void onOpen(Tick tick) { }
+
+ protected NodeStatus onTick(Tick tick) {
+ return NodeStatus.SUCCESS;
+ }
+
+ protected void onClose(Tick tick) { }
+
+ protected void onExit(Tick tick) { }
+
+ protected int initiate(int maxId) {
+ maxId++;
+ this.id = maxId;
+ for (Node child : children) {
+ maxId = child.initiate(maxId);
+ }
+ return maxId;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/NodeStatus.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/NodeStatus.java
new file mode 100644
index 0000000000..b738ebe3bf
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/NodeStatus.java
@@ -0,0 +1,11 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+public enum NodeStatus {
+ SUCCESS,
+ FAILURE,
+ RUNNING;
+
+ public static NodeStatus of(boolean value) {
+ return value ? SUCCESS : FAILURE;
+ }
+}
\ No newline at end of file
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Root.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Root.java
new file mode 100644
index 0000000000..9e7a398206
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Root.java
@@ -0,0 +1,27 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+public class Root extends Node {
+ private static final long serialVersionUID = 4857616270171506110L;
+
+ protected final Node child;
+ private int maxID = -1;
+
+ public int getChildrenCount() {
+ return maxID + 1;
+ }
+
+ public Root(Node child) {
+ super(child);
+ this.child = child;
+ }
+
+ @Override
+ protected NodeStatus onTick(Tick tick) {
+ return child.execute(tick);
+ }
+
+ public Root init() {
+ maxID = initiate(-1);
+ return this;
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Tick.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Tick.java
new file mode 100644
index 0000000000..4d628a7ea3
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/Tick.java
@@ -0,0 +1,53 @@
+package jsettlers.logic.movable.simplebehaviortree;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.Stack;
+
+public class Tick implements Serializable {
+ private static final long serialVersionUID = 3673558738736795584L;
+
+ public final Root root;
+ public final T target;
+
+ private final Stack> openNodes = new Stack<>();
+ private boolean blockOpenNodes = false;
+
+ public Tick(T target, Root root) {
+ this.root = root;
+ this.target = target;
+ }
+
+ public NodeStatus tick() {
+ LinkedList> lastOpenNodes = new LinkedList<>(openNodes);
+ openNodes.clear();
+ NodeStatus state = root.execute(this);
+ for (Node node : openNodes) {
+ if (lastOpenNodes.size() <= 0) {
+ break;
+ }
+ if (node == lastOpenNodes.peek()) {
+ lastOpenNodes.removeFirst();
+ } else {
+ break;
+ }
+ }
+ blockOpenNodes = true;
+ for (Node node : lastOpenNodes) {
+ node.close(this);
+ }
+ blockOpenNodes = false;
+ return state;
+ }
+
+ public void visitNode(Node node) {
+ if (!blockOpenNodes) { openNodes.push(node); }
+ }
+
+ public void tickNode(Node node) {
+ }
+
+ public void leaveNode(Node node) {
+ if (!blockOpenNodes) { openNodes.pop(); }
+ }
+}
diff --git a/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/nodes/Action.java b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/nodes/Action.java
new file mode 100644
index 0000000000..d50b316e37
--- /dev/null
+++ b/jsettlers.logic/src/main/java/jsettlers/logic/movable/simplebehaviortree/nodes/Action.java
@@ -0,0 +1,30 @@
+package jsettlers.logic.movable.simplebehaviortree.nodes;
+
+import jsettlers.logic.movable.simplebehaviortree.INodeStatusActionConsumer;
+import jsettlers.logic.movable.simplebehaviortree.INodeStatusActionFunction;
+import jsettlers.logic.movable.simplebehaviortree.Node;
+import jsettlers.logic.movable.simplebehaviortree.NodeStatus;
+import jsettlers.logic.movable.simplebehaviortree.Tick;
+
+public class Action extends Node {
+ private static final long serialVersionUID = -4535362950446826714L;
+
+ private final INodeStatusActionFunction