package com.galvarez.ttw.model; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.artemis.Aspect; import com.artemis.ComponentMapper; import com.artemis.Entity; import com.artemis.EntitySystem; import com.artemis.annotations.Wire; import com.artemis.utils.ImmutableBag; import com.galvarez.ttw.model.DiplomaticSystem.State; import com.galvarez.ttw.model.components.AIControlled; import com.galvarez.ttw.model.components.Army; import com.galvarez.ttw.model.components.Destination; import com.galvarez.ttw.model.components.Diplomacy; import com.galvarez.ttw.model.components.InfluenceSource; import com.galvarez.ttw.model.map.GameMap; import com.galvarez.ttw.model.map.Influence; import com.galvarez.ttw.model.map.MapPosition; import com.galvarez.ttw.model.map.PathFinding; import com.galvarez.ttw.model.map.Terrain; import com.galvarez.ttw.rendering.IconsSystem.Type; import com.galvarez.ttw.rendering.NotificationsSystem; import com.galvarez.ttw.rendering.components.Name; import com.galvarez.ttw.screens.overworld.OverworldScreen; /** * Moves the entities having a {@link Destination} across the map. * * @author Guillaume Alvarez */ @Wire public final class DestinationSystem extends EntitySystem { private static final Logger log = LoggerFactory.getLogger(DestinationSystem.class); private ComponentMapper<Destination> destinations; private ComponentMapper<MapPosition> positions; private ComponentMapper<Diplomacy> relations; private ComponentMapper<InfluenceSource> sources; private ComponentMapper<Army> armies; private ComponentMapper<Name> names; private ComponentMapper<AIControlled> ai; private NotificationsSystem notifications; private final GameMap map; private final PathFinding astar; private final OverworldScreen screen; @SuppressWarnings("unchecked") public DestinationSystem(GameMap map, OverworldScreen screen) { super(Aspect.getAspectForAll(Destination.class, MapPosition.class)); this.map = map; this.screen = screen; this.astar = new PathFinding(map); } @Override protected void inserted(Entity e) { super.inserted(e); if (!ai.has(e)) // make sure player moves the entity at least one time notifications.addNotification(() -> screen.select(e, true), () -> !needDestination(e), Type.FLAG, "Select destination for %s...", names.get(e)); } private boolean needDestination(Entity e) { Destination d = destinations.getSafe(e); if (d == null) return false; else return d.target == null || d.path == null || d.path.isEmpty(); } @Override protected void processEntities(ImmutableBag<Entity> entities) { for (Entity e : entities) { Destination destination = destinations.get(e); // Destination component might have been removed during this turn if (destination != null && destination.path != null && !destination.path.isEmpty()) moveToNext(e, destination); // else no current destination } } private void moveToNext(Entity e, Destination dest) { MapPosition current = positions.get(e); MapPosition next = dest.path.get(0); if (canMoveTo(e, dest, next)) { if (++dest.progress >= dest.turnsToMove) { dest.progress = 0; dest.path.remove(0); e.edit().remove(current).add(next); map.moveEntity(e, current, next); if (sources.has(e)) { Influence inf = map.getInfluenceAt(next); // just make sure to have enough influence inf.setInfluence(e, inf.getMaxInfluence()); if (inf.hasMainInfluence() && !inf.isMainInfluencer(e)) inf.setInfluence(e, inf.getMaxInfluence() + 1); } if (dest.path.isEmpty()) { dest.target = null; dest.path = null; if (!ai.has(e)) { // do not force player to move its units if he does not want to notifications.addNotification(() -> screen.select(e, true), null, Type.FLAG, "Finished moving %s!", names.get(e)); } log.debug("Moved {} to {}", names.get(e), next); } } } } public void moveTo(Entity e, MapPosition target) { MapPosition current = positions.get(e); e.edit().remove(current).add(target); map.moveEntity(e, current, target); if (destinations.has(e)) { Destination dest = destinations.get(e); dest.path = null; dest.target = null; dest.progress = 0; } } private boolean canMoveTo(Entity e, Destination dest, MapPosition next) { Terrain terrain = map.getTerrainAt(next); if (terrain.moveBlock() || dest.forbiddenTiles.contains(terrain) || map.hasEntity(next)) return false; if (armies.has(e)) return map.getInfluenceAt(next).isMainInfluencer(armies.get(e).source); else return map.getInfluenceAt(next).isMainInfluencer(e); } /** Return the possible move target tiles for the source. */ public Collection<MapPosition> getTargetTiles(Entity e) { Destination dest = destinations.get(e); Entity empire = armies.has(e) ? armies.get(e).source : e; Set<MapPosition> set = new HashSet<>(); InfluenceSource source = sources.get(empire); Diplomacy treaties = relations.get(empire); for (MapPosition pos : source.influencedTiles) { for (MapPosition neighbor : map.getNeighbors(pos)) { if (!dest.forbiddenTiles.contains(map.getTerrainAt(neighbor))) { Influence inf = map.getInfluenceAt(neighbor); if (!inf.hasMainInfluence()) set.add(neighbor); else if (treaties.getRelationWith(inf.getMainInfluenceSource(world)) != State.TREATY) set.add(neighbor); } } } return set; } /** * Compute the path for the entity to the target. Returns <code>null</code> if * no path can be found. */ public List<MapPosition> computePath(Entity e, MapPosition target) { Destination dest = destinations.get(e); MapPosition start = positions.get(e); List<MapPosition> path = astar.aStarSearch(start, target,// p -> (!dest.forbiddenTiles.contains(map.getTerrainAt(p)) || map.getTerrainAt(start) == map.getTerrainAt(p)) && map.getEntityAt(p) == null); if (path == null) return null; dest.target = target; dest.path = path; dest.progress = 0; return path; } }