package org.mafagafogigante.dungeon.game; import org.mafagafogigante.dungeon.achievements.AchievementStoreFactory; import org.mafagafogigante.dungeon.date.Date; import org.mafagafogigante.dungeon.entity.creatures.Creature; import org.mafagafogigante.dungeon.entity.creatures.Hero; import org.mafagafogigante.dungeon.io.Writer; import org.mafagafogigante.dungeon.util.Utils; import java.awt.Color; /** * Engine class that contains most static methods that need to be called to alter the loaded GameState. */ public final class Engine { private static final int BATTLE_TURN_DURATION = 30; private Engine() { // Ensure that this class cannot be instantiated. throw new AssertionError(); } /** * Refreshes the game. This method should be called whenever the state of the game is changed and the engine should be * updated. If time passed, use {@link Engine#rollDateAndRefresh(int)}. */ public static void refresh() { effectivelyUpdate(0); } /** * Rolls the world date forward and refreshes the game. This method should be called whenever the state of the game is * changed and the engine should be updated. If no time passed, use {@link Engine#refresh()}. * * @param seconds how many seconds to roll the date forward, a positive integer */ public static void rollDateAndRefresh(int seconds) { if (seconds <= 0) { throw new IllegalArgumentException("seconds should be positive."); } effectivelyUpdate(seconds); } /** * Effectively updates the game. Rolls time forward before silently refreshing the game. * * @param seconds how many seconds to roll the date forward, nonnegative */ private static void effectivelyUpdate(int seconds) { if (seconds < 0) { throw new IllegalArgumentException("seconds should be nonnegative."); } if (seconds > 0) { Game.getGameState().getWorld().rollDate(seconds); } silentRefresh(); notifyGameStateModification(); } /** * Sets the status of the GameState to unsaved. */ private static void notifyGameStateModification() { Game.getGameState().setSaved(false); } /** * Silently refreshes the game. Does not produce any visible textual output. */ private static void silentRefresh() { refreshSpawners(); refreshItems(); } /** * Ends the turn, refreshing the game state and checking if any achievements were unlocked. */ public static void endTurn() { silentRefresh(); refreshAchievements(); } /** * Refreshes all relevant Spawners in the world, currently, that is the spawners of the location the Hero is at. */ private static void refreshSpawners() { Game.getGameState().getHero().getLocation().refreshSpawners(); } /** * Refreshes all the items in the location the Hero is at. */ private static void refreshItems() { Game.getGameState().getHero().getLocation().refreshItems(); } /** * Iterates over all achievements, trying to unlock yet to be unlocked achievements. */ private static void refreshAchievements() { Date worldDate = Game.getGameState().getWorld().getWorldDate(); Game.getGameState().getHero().getAchievementTracker().update(AchievementStoreFactory.getDefaultStore(), worldDate); } /** * Simulates a battle between the hero and a creature. * * @param hero the attacker * @param foe the defender */ public static void battle(Hero hero, Creature foe) { if (hero == foe) { Writer.write(new DungeonString("You cannot attempt suicide.")); return; } while (hero.getHealth().isAlive() && foe.getHealth().isAlive()) { hero.hit(foe); Engine.rollDateAndRefresh(BATTLE_TURN_DURATION); // No contract specifies that calling hit on the Hero will not kill it, so check both creatures again. // Additionally, rolling the date forward may kill the hero in the future. if (hero.getHealth().isAlive() && foe.getHealth().isAlive()) { foe.hit(hero); Engine.rollDateAndRefresh(BATTLE_TURN_DURATION); } } Creature survivor = hero.getHealth().isAlive() ? hero : foe; Creature defeated = (survivor == hero) ? foe : hero; // Imagine if a third factor (such as hunger) could kill one of the creatures. // I think it still makes sense to say that the survivor managed to kill the defeated, but that's just me. DungeonString dungeonString = new DungeonString(); dungeonString.setColor(Color.CYAN); dungeonString.append(survivor.getName().getSingular()); dungeonString.append(" managed to kill "); dungeonString.append(defeated.getName().getSingular()); dungeonString.append(".\n"); Writer.write(dungeonString); writeDrops(defeated); if (hero == survivor) { PartOfDay partOfDay = PartOfDay.getCorrespondingConstant(Game.getGameState().getWorld().getWorldDate()); Game.getGameState().getStatistics().getBattleStatistics().addBattle(foe, defeated.getCauseOfDeath(), partOfDay); Game.getGameState().getStatistics().getExplorationStatistics().addKill(hero.getLocation().getPoint()); } } /** * Writes a message with what the creature dropped if it dropped something. * * @param source a dead Creature */ private static void writeDrops(Creature source) { if (!source.getDroppedItemsList().isEmpty()) { DungeonString string = new DungeonString(); string.append(source.getName().getSingular() + " dropped "); string.append(Utils.enumerateEntities(source.getDroppedItemsList())); string.append(".\n"); Writer.write(string); } } }