package com.galvarez.ttw.model;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.Comparator.comparingInt;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
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.EntityFactory;
import com.galvarez.ttw.model.EventsSystem.EventHandler;
import com.galvarez.ttw.model.components.AIControlled;
import com.galvarez.ttw.model.components.ArmyCommand;
import com.galvarez.ttw.model.components.InfluenceSource;
import com.galvarez.ttw.model.components.Policies;
import com.galvarez.ttw.model.data.Culture;
import com.galvarez.ttw.model.data.Empire;
import com.galvarez.ttw.model.data.SessionSettings;
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.MapTools.Border;
import com.galvarez.ttw.rendering.FadingMessageRenderSystem;
import com.galvarez.ttw.rendering.IconsSystem.Type;
import com.galvarez.ttw.rendering.NotificationsSystem;
import com.galvarez.ttw.rendering.components.Description;
import com.galvarez.ttw.rendering.components.Name;
import com.galvarez.ttw.screens.overworld.OverworldScreen;
/**
* This classes check if new empires should revolt from existing ones with
* negative stability.
*
* @author Guillaume Alvarez
*/
@Wire
public final class RevoltSystem extends EntitySystem implements EventHandler {
private static final Logger log = LoggerFactory.getLogger(RevoltSystem.class);
private ComponentMapper<Empire> empires;
private ComponentMapper<ArmyCommand> army;
private ComponentMapper<Policies> policies;
private ComponentMapper<InfluenceSource> sources;
private ComponentMapper<MapPosition> positions;
private ComponentMapper<AIControlled> ai;
private DiscoverySystem discoveries;
private NotificationsSystem notifications;
private FadingMessageRenderSystem fadingSystem;
private final GameMap map;
private final SessionSettings settings;
private final OverworldScreen screen;
@SuppressWarnings("unchecked")
public RevoltSystem(GameMap gameMap, SessionSettings settings, OverworldScreen screen) {
super(Aspect.getAspectForAll(InfluenceSource.class));
this.map = gameMap;
this.settings = settings;
this.screen = screen;
}
@Override
public String getType() {
return "Revolt";
}
@Override
protected void initialize() {
super.initialize();
world.getSystem(EventsSystem.class).addEventType(this);
}
@Override
protected boolean checkProcessing() {
return false;
}
@Override
protected void processEntities(ImmutableBag<Entity> empires) {
// progress toward next revolt is triggered by EventsSystem
}
@Override
public int getProgress(Entity empire) {
return max(0, getInstability(empire) / 2);
}
public int getInstability(Entity empire) {
InfluenceSource source = sources.get(empire);
Policies empirePolicies = policies.get(empire);
int totalStability = empirePolicies.stability + max(0, army.get(empire).militaryPower);
return source.influencedTiles.size() - totalStability;
}
/**
* Cities revolt when the source power is above its stability. The higher it
* is the higher chance it will revolt.
*/
@Override
public boolean execute(Entity empire) {
int instability = getInstability(empire);
if (instability <= 0)
return false;
InfluenceSource source = sources.get(empire);
// revolt happens, select the tile!
Optional<Influence> tile = source.influencedTiles.stream() //
.filter(p -> isValidRevoltTile(empire, p, instability)) //
.map(p -> map.getInfluenceAt(p)) //
.min(comparingInt(Influence::getSecondInfluenceDiff));
if (tile.isPresent()) {
if (ai.has(empire)) {
revolt(empire, instability, source, tile);
return true;
} else {
Map<String, Runnable> choices = new HashMap<String, Runnable>();
choices.put("Let them revolt!", () -> revolt(empire, instability, source, tile));
choices.put("Try to ease the tension...", () -> reduceInstability(empire, instability));
notifications.addNotification(() -> screen.askEvent("Our people are revolting, what should we do?", choices),
() -> getInstability(empire) <= 0, Type.REVOLT, "Revolt is sprawling!");
// TODO display choice screen asking what to do
// - lose power but keep tiles
// - create revoltee
// - change policies?
return true;
}
} else if (instability > 50) {
updateEmpireAfterRevolt(empire, instability, source);
log.warn("Found no tile to revolt for {} with instability at {}%, decrease power to {}",
empire.getComponent(Name.class), instability, source.power());
if (!ai.has(empire))
notifications.addNotification(() -> screen.select(empire, false), null, Type.REVOLT,
"Instability decrease %s power to %s!", empire.getComponent(Description.class), source.power());
return true;
}
return false;
}
private void revolt(Entity empire, int instability, InfluenceSource source, Optional<Influence> tile) {
updateEmpireAfterRevolt(empire, instability, source);
createRevoltingEmpire(empire, instability, tile.get());
}
private void reduceInstability(Entity empire, int instability) {
// stop the revolt
policies.get(empire).stability += instability;
// decrease power by instability to avoid popping revolts in loop
InfluenceSource source = sources.get(empire);
source.setPower(max(max(1, source.power() / 2), source.power() - instability));
}
private void updateEmpireAfterRevolt(Entity empire, int instability, InfluenceSource source) {
// decrease power by instability to avoid popping revolts in loop
policies.get(empire).stability += instability;
source.setPower(max(max(1, source.power() / 2), source.power() - instability / 5));
}
private boolean isValidRevoltTile(Entity empire, MapPosition pos, int instability) {
if (!map.getTerrainAt(pos).canStart() || map.hasEntity(pos))
return false;
if (instability < map.getInfluenceAt(pos).getMaxInfluence())
return false;
boolean atInfluenceBorder = false;
for (Border b : Border.values()) {
MapPosition neighbor = b.getNeighbor(pos);
// don't accept tiles on map border
if (!map.isOnMap(neighbor))
return false;
// don't accept near other entities
if (map.hasEntity(neighbor))
return false;
// accept if different overlord
atInfluenceBorder |= !map.getInfluenceAt(neighbor).isMainInfluencer(empire);
}
return atInfluenceBorder;
}
private void createRevoltingEmpire(Entity empire, int instability, Influence inf) {
Culture culture = empires.get(empire).culture;
Empire data = new Empire(settings.guessColor(), culture, true);
settings.empires.add(data);
Entity revoltee = EntityFactory.createEmpire(world, inf.position.x, inf.position.y, culture.newCityName(), data);
map.setEntity(revoltee, inf.position);
// get starting power from generating instability
// ensure it has enough power to control its own tile and resist a bit
int sourcePower = sources.get(empire).power();
sources.get(revoltee).setPower(max(min(sourcePower, instability + sourcePower / 2), inf.getMaxInfluence() + 1));
inf.setInfluence(revoltee, sources.get(revoltee).power());
// do not forget neighboring tiles
for (MapPosition n : map.getNeighbors(inf.position))
map.getInfluenceAt(n).setInfluence(revoltee, min(sourcePower, instability + sourcePower / 2));
// add all discoveries from original empire
discoveries.copyDiscoveries(empire, revoltee);
// notify player
log.info("{} revolted from {} (instability={})", revoltee.getComponent(Description.class),
empire.getComponent(Description.class), instability);
fadingSystem.createFadingIcon(Type.REVOLT, empires.get(revoltee).color, positions.get(revoltee), 3f);
if (!ai.has(empire))
notifications.addNotification(() -> screen.select(revoltee, false), null, Type.REVOLT, "%s revolted from %s!",
revoltee.getComponent(Description.class), empire.getComponent(Description.class));
}
}