package org.mafagafogigante.dungeon.entity.creatures;
import org.mafagafogigante.dungeon.entity.Luminosity;
import org.mafagafogigante.dungeon.entity.items.Item;
import org.mafagafogigante.dungeon.game.Direction;
import org.mafagafogigante.dungeon.game.DungeonString;
import org.mafagafogigante.dungeon.game.Game;
import org.mafagafogigante.dungeon.game.Location;
import org.mafagafogigante.dungeon.game.Point;
import org.mafagafogigante.dungeon.game.World;
import org.mafagafogigante.dungeon.io.Writer;
import org.mafagafogigante.dungeon.stats.ExplorationStatistics;
import org.mafagafogigante.dungeon.util.Percentage;
import org.mafagafogigante.dungeon.util.Utils;
import org.mafagafogigante.dungeon.world.LuminosityVisibilityCriterion;
import org.mafagafogigante.dungeon.world.VisibilityCriteria;
import org.mafagafogigante.dungeon.world.WeatherCondition;
import org.mafagafogigante.dungeon.world.WeatherConditionVisibilityCriterion;
import org.jetbrains.annotations.NotNull;
import java.awt.Color;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
/**
* An observer is used to observe from a Creature's viewpoint.
*/
public class Observer implements Serializable {
private static final VisibilityCriteria ADJACENT_LOCATIONS_VISIBILITY;
static {
LuminosityVisibilityCriterion luminosity = new LuminosityVisibilityCriterion(new Luminosity(new Percentage(0.4)));
WeatherCondition minimum = WeatherCondition.CLEAR;
WeatherCondition maximum = WeatherCondition.RAIN;
WeatherConditionVisibilityCriterion weather = new WeatherConditionVisibilityCriterion(minimum, maximum);
ADJACENT_LOCATIONS_VISIBILITY = new VisibilityCriteria(luminosity, weather);
}
private final Creature creature;
public Observer(@NotNull Creature creature) {
this.creature = creature;
}
private static List<Point> listAdjacentPoints(Point point) {
List<Point> adjacentPoints = new ArrayList<>(4);
adjacentPoints.add(new Point(point, Direction.NORTH));
adjacentPoints.add(new Point(point, Direction.EAST));
adjacentPoints.add(new Point(point, Direction.SOUTH));
adjacentPoints.add(new Point(point, Direction.WEST));
return adjacentPoints;
}
public Location getObserverLocation() {
return creature.getLocation();
}
/**
* Appends to a DungeonString the creatures that can be seen.
*/
public void writeCreatureSight(List<Creature> creatures, DungeonString dungeonString) {
if (creatures.isEmpty()) {
dungeonString.append("\nYou don't see anyone here.\n");
} else {
dungeonString.append("\nHere you can see ");
dungeonString.append(Utils.enumerateEntities(creatures));
dungeonString.append(".\n");
}
}
/**
* Appends to a DungeonString the items that can be seen.
*/
public void writeItemSight(List<Item> items, DungeonString dungeonString) {
if (!items.isEmpty()) {
dungeonString.append("\nHere you can find ");
dungeonString.append(Utils.enumerateEntities(items));
dungeonString.append(".\n");
}
}
/**
* Prints the name of the player's current location and lists all creatures and items the character sees.
*/
public void look() {
DungeonString string = new DungeonString();
Location location = creature.getLocation(); // Avoid multiple calls to the getter.
string.append("You are at ");
string.setColor(location.getDescription().getColor());
string.append(location.getName().getSingular());
string.resetColor();
string.append(". ");
string.append(location.getDescription().getInfo());
if (creature.canSeeTheSky()) {
string.append(" It is ");
World world = location.getWorld();
string.append(world.getPartOfDay().toString().toLowerCase(Locale.ENGLISH));
string.append(" and ");
string.append(world.getWeather().getCurrentCondition(world.getWorldDate()).toDescriptiveString());
string.append(".");
// Could use hearing to detect the weather based on the current condition and how underneath the character is.
String skyDescription = world.describeTheSky(this);
if (!skyDescription.isEmpty()) {
string.append(" ");
string.append("Looking upwards you see ");
string.append(skyDescription);
string.append(".");
}
}
string.append("\n");
lookLocations(string);
lookCreatures(string);
lookItems(string);
Writer.write(string);
}
/**
* Looks to the Locations adjacent to the one the Hero is in, informing if the Hero cannot see the adjacent
* Locations.
*/
private void lookLocations(DungeonString dungeonString) {
dungeonString.append("\n");
World world = creature.getLocation().getWorld();
Point point = creature.getLocation().getPoint();
lookUpwardsAndDownwards(dungeonString, world, point);
if (areThereAdjacentLocations()) {
if (canSeeAdjacentLocations()) {
lookToTheSides(dungeonString, world, point);
} else {
dungeonString.append("You can't clearly see the adjacent locations.\n");
}
}
}
private void lookToVerticalDirection(DungeonString dungeonString, World world, Point up, String adverb) {
if (world.alreadyHasLocationAt(up)) {
dungeonString.append(adverb);
dungeonString.append(" you see ");
dungeonString.append(world.getLocation(up).getName().getSingular());
dungeonString.append(".\n");
}
}
private void lookUpwardsAndDownwards(DungeonString dungeonString, World world, Point point) {
lookToVerticalDirection(dungeonString, world, new Point(point, Direction.UP), "Upwards");
lookToVerticalDirection(dungeonString, world, new Point(point, Direction.DOWN), "Downwards");
}
/**
* Evaluates if there is already any location that is adjacent to the one the creature is currently in.
*/
private boolean areThereAdjacentLocations() {
for (Point point : listAdjacentPoints(creature.getLocation().getPoint())) {
if (creature.getLocation().getWorld().alreadyHasLocationAt(point)) {
return true;
}
}
return false;
}
private boolean canSeeAdjacentLocations() {
return ADJACENT_LOCATIONS_VISIBILITY.isMetBy(this);
}
private void lookToTheSides(DungeonString dungeonString, World world, Point point) {
Map<ColoredString, ArrayList<Direction>> visibleLocations = new HashMap<>();
// Don't print the Location you just left.
Collection<Direction> directions = getHorizontalDirections();
for (Direction dir : directions) {
Point adjacentPoint = new Point(point, dir);
if (world.hasLocationAt(adjacentPoint)) {
Location adjacentLocation = world.getLocation(adjacentPoint);
ExplorationStatistics explorationStatistics = Game.getGameState().getStatistics().getExplorationStatistics();
explorationStatistics.createEntryIfNotExists(adjacentPoint, adjacentLocation.getId());
String name = adjacentLocation.getName().getSingular();
Color color = adjacentLocation.getDescription().getColor();
ColoredString locationName = new ColoredString(name, color);
if (!visibleLocations.containsKey(locationName)) {
visibleLocations.put(locationName, new ArrayList<Direction>());
}
visibleLocations.get(locationName).add(dir);
}
}
if (!visibleLocations.isEmpty()) {
for (Entry<ColoredString, ArrayList<Direction>> entry : visibleLocations.entrySet()) {
dungeonString.append(String.format("To %s you see ", Utils.enumerate(entry.getValue())));
dungeonString.setColor(entry.getKey().getColor());
dungeonString.append(String.format("%s", entry.getKey().getString()));
dungeonString.resetColor();
dungeonString.append(".\n");
}
}
}
private Collection<Direction> getHorizontalDirections() {
Collection<Direction> directions = new ArrayList<>();
directions.addAll(Arrays.asList(Direction.values()));
directions.remove(Direction.UP);
directions.remove(Direction.DOWN);
return directions;
}
/**
* Prints a human-readable description of what Creatures the Hero sees.
*/
private void lookCreatures(DungeonString builder) {
List<Creature> creatures = new ArrayList<>(creature.getLocation().getCreatures());
creatures.remove(creature);
creatures = creature.filterByVisibility(creatures);
writeCreatureSight(creatures, builder);
}
/**
* Prints a human-readable description of what the Hero sees on the ground.
*/
private void lookItems(DungeonString builder) {
List<Item> items = creature.getLocation().getItemList();
items = creature.filterByVisibility(items);
writeItemSight(items, builder);
}
}