package org.multiverseking.field.position; import org.multiverseking.field.position.component.HexPositionComponent; import org.multiverseking.field.position.component.MoveToComponent; import com.jme3.cinematic.MotionPath; import com.jme3.cinematic.MotionPathListener; import com.jme3.cinematic.events.MotionEvent; import com.jme3.math.Vector3f; import com.jme3.scene.Spatial; import com.simsilica.es.Entity; import com.simsilica.es.EntityId; import com.simsilica.es.EntitySet; import java.util.HashMap; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.hexgridapi.core.AbstractHexGridAppState; import org.hexgridapi.core.coordinate.HexCoordinate; import org.hexgridapi.core.data.MapData; import org.hexgridapi.pathfinding.Astar; import org.hexgridapi.pathfinding.Pathfinder; import org.hexgridapi.utility.Rotation; import org.multiverseking.loader.EntityLoader; import org.multiverseking.render.RenderComponent; import org.multiverseking.render.RenderSystem; import org.multiverseking.render.animation.Animation; import org.multiverseking.render.animation.AnimationComponent; import org.multiverseking.core.utility.EntitySystemAppState; import org.multiverseking.core.utility.SubSystem; /** * @todo : behavior when unit got an obstacle appearing when moving (stop it and * remove the component). * @todo : behavior when unit is moving and a tile change. * @TODO replace the current system by a lighter one * @author Eike Foede, roah */ public class HexMovementSystem extends EntitySystemAppState implements SubSystem { private MapData mapData; private RenderSystem renderSystem; private HexPositionSystem hexPositionsystem; private final Pathfinder pathfinder = new Astar(); private HashMap<EntityId, HexCoordinate> movementUpdateGoal = new HashMap<>(); private HashMap<EntityId, HexCoordinate> movementGoal = new HashMap<>(); @Override protected EntitySet initialiseSystem() { mapData = app.getStateManager().getState(AbstractHexGridAppState.class).getMapData(); pathfinder.setMapData(mapData); renderSystem = app.getStateManager().getState(RenderSystem.class); renderSystem.registerSubSystem(this, true); hexPositionsystem = app.getStateManager().getState(HexPositionSystem.class); return entityData.getEntities(HexPositionComponent.class, MoveToComponent.class, RenderComponent.class); } @Override protected void updateSystem(float tpf) { } @Override protected void addEntity(Entity e) { if (!movementGoal.containsKey(e.getId())) { if (!initialiseControl(e, getPathfinderPath(e.get(HexPositionComponent.class).getPosition(), e.get(MoveToComponent.class).getPosition()))) { entityData.removeComponent(e.getId(), MoveToComponent.class); } else { movementGoal.put(e.getId(), e.get(MoveToComponent.class).getPosition()); hexPositionsystem.registerEntityForCulling(e.getId()); } } } private List<HexCoordinate> getPathfinderPath(HexCoordinate from, HexCoordinate to) { List<HexCoordinate> path = pathfinder.getPath(from, to); if (path == null) { Logger.getGlobal().log(Level.WARNING, "{0} : No path found by the Pathfinder !", getClass().getName()); } return path; } /** * @return true if the control is initialised correctly. */ private boolean initialiseControl(Entity e, List<HexCoordinate> path) { if (path != null) { float ms = new EntityLoader(app).loadTitanStats( e.get(RenderComponent.class).getName()) .getInitialStatsComponent().getMoveSpeed(); MotionPath motionPath = addPathListener(e.getId(), buildPath(null, path)); MotionEvent motionControl = new MotionEvent(renderSystem.getSpatial(e.getId()), motionPath, ms * path.size()); motionControl.setDirectionType(MotionEvent.Direction.Path); motionControl.play(); entityData.setComponent(e.getId(), new AnimationComponent(Animation.WALK)); return true; } return false; } @Override protected void updateEntity(Entity e) { MoveToComponent moveTo = e.get(MoveToComponent.class); /** * We check if the goal of the control is the same than the one of the * component. */ if (!moveTo.getPosition().equals(movementGoal.get(e.getId()))) { movementUpdateGoal.put(e.getId(), moveTo.getPosition()); // movementGoal.put(e.getId(), moveTo.getPosition()); // /** // * We build a new path starting from the next waypoint. // */ // List<HexCoordinate> newPath = getPathfinderPath( // new HexCoordinate(motionControl.getPath().getWayPoint( // motionControl.getCurrentWayPoint()+1)), moveTo.getPosition()); // if(newPath == null){ // return; // } // movementUpdateQueue.put(e.getId(), newPath); // Movement movement = movements.get(e.getId()); // movement.path = pathfinder.getPath(movement.path.get(movement.actualPosition-1), moveTo.getPosition()); // movement.goal = moveTo.getPosition(); // movement.actualPosition = 0; // Curve curve = // movements.get(e.getId()).curve = new Curve(CurveType.LINEAR, GLOBAL_SPEED / e.get(MovementComponent.class).getMoveSpeed(), movement.path); // e.set(e.get(HexPositionComponent.class).movement(curve)); } } @Override protected void removeEntity(Entity e) { // System.err.println(renderSystem.getSpatial(e.getId()).getName() + " is removed."); movementGoal.remove(e.getId()); Spatial s = renderSystem.getSpatial(e.getId()); if (s != null) { MotionEvent control = s.getControl(MotionEvent.class); if (control != null) { control.getPath().disableDebugShape(); s.removeControl(control); } } // if (movementUpdateGoal.containsKey(e.getId()) // && buildNewPath(e.getId(), control, null)) { // entityData.setComponent(e.getId(), new MoveToComponent(movementUpdateGoal.get(e.getId()))); // return; // } hexPositionsystem.removeEntityFromCulling(e.getId()); entityData.setComponent(e.getId(), new AnimationComponent(Animation.IDLE)); } @Override protected void cleanupSystem() { renderSystem.removeSubSystem(this, true); } @Override public void rootSystemIsRemoved() { app.getStateManager().detach(this); //todo disable all currently running movement } private MotionPath buildPath(Vector3f initPosition, List<HexCoordinate> wayPoint) { MotionPath path = new MotionPath(); if (initPosition != null) { path.addWayPoint(initPosition); } for (int i = 0; i < wayPoint.size(); i++) { HexCoordinate pos = wayPoint.get(i); path.addWayPoint(pos.toWorldPosition(mapData.getTile(pos).getHeight())); } path.enableDebugShape(app.getAssetManager(), renderSystem.getSubSystemNode(this)); return path; } private MotionPath addPathListener(final EntityId id, MotionPath path) { path.addListener(new MotionPathListener() { @Override public void onWayPointReach(MotionEvent control, int wayPointIndex) { if (control.getPath().getNbWayPoints() == wayPointIndex + 1) { // System.err.println(control.getSpatial().getName() + " has finished moving. "); entityData.removeComponent(id, MoveToComponent.class); } else { // System.err.println(control.getSpatial().getName() + " has reached way point " + wayPointIndex); } updatePosition(id, control); if (movementUpdateGoal.containsKey(id)) { // System.err.println("Start a new movement path."); buildNewPath(id, control, this); } else if (control.getPath().getNbWayPoints() == wayPointIndex + 1) { // System.err.println("All movement has been done."); entityData.removeComponent(id, MoveToComponent.class); } } }); return path; } /** * @return true if a new path have been build. */ private boolean buildNewPath(EntityId id, MotionEvent motionControl, MotionPathListener listeners) { boolean result = false; /** * We build a new path starting from the current waypoint. */ List<HexCoordinate> newPath = getPathfinderPath( new HexCoordinate(motionControl.getPath().getWayPoint(motionControl.getCurrentWayPoint() + 1)), movementUpdateGoal.get(id)); if (newPath != null) { motionControl.stop(); motionControl.getPath().disableDebugShape(); MotionPath newMotion = buildPath(motionControl.getSpatial().getLocalTranslation(), newPath); motionControl.setPath(newMotion); float ms = new EntityLoader(app).loadTitanStats( entityData.getComponent(id, RenderComponent.class).getName()) .getInitialStatsComponent().getMoveSpeed(); float addedDist = motionControl.getSpatial().getLocalTranslation().distance( newPath.get(0).toWorldPosition(mapData.getTile(newPath.get(0)).getHeight())); float baseDist = newPath.get(0).toWorldPosition(mapData.getTile(newPath.get(0)).getHeight()) .distance(newPath.get(1).toWorldPosition(mapData.getTile(newPath.get(1)).getHeight())); motionControl.setInitialDuration(ms * (newPath.size() + 1));// + (ms / baseDist * addedDist)); if (listeners != null) { newMotion.addListener(listeners); } else { addPathListener(id, newMotion); } movementGoal.put(id, movementUpdateGoal.get(id)); motionControl.play(); result = true; } else if (movementGoal.containsKey(id)) { entityData.setComponent(id, new MoveToComponent(movementGoal.get(id))); } movementUpdateGoal.remove(id); return result; } private void updatePosition(EntityId id, MotionEvent control) { HexCoordinate newPos = new HexCoordinate(control.getPath() .getWayPoint(control.getPath().getNbWayPoints() - 1)); Rotation rot = new HexCoordinate(control.getPath() .getWayPoint(control.getPath().getNbWayPoints() - 2)).getDirection(newPos); entityData.setComponent(id, new HexPositionComponent(newPos, rot)); } }