diff --git a/src/toniarts/openkeeper/game/MapSelector.java b/src/toniarts/openkeeper/game/MapSelector.java index 25867a239..df3de47b6 100644 --- a/src/toniarts/openkeeper/game/MapSelector.java +++ b/src/toniarts/openkeeper/game/MapSelector.java @@ -39,7 +39,7 @@ * @author ArchDemon */ public final class MapSelector { - + private static final Logger logger = System.getLogger(MapSelector.class.getName()); private final List skirmishMaps = new ArrayList<>(); diff --git a/src/toniarts/openkeeper/game/controller/GameController.java b/src/toniarts/openkeeper/game/controller/GameController.java index 9e07ea491..8e29c2f54 100644 --- a/src/toniarts/openkeeper/game/controller/GameController.java +++ b/src/toniarts/openkeeper/game/controller/GameController.java @@ -23,9 +23,7 @@ import java.io.IOException; import java.lang.System.Logger; import java.lang.System.Logger.Level; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,33 +34,9 @@ import toniarts.openkeeper.game.data.ActionPoint; import toniarts.openkeeper.game.data.GameResult; import toniarts.openkeeper.game.data.GameTimer; -import toniarts.openkeeper.game.data.GeneralLevel; import toniarts.openkeeper.game.data.Keeper; import toniarts.openkeeper.game.data.Settings; -import toniarts.openkeeper.game.logic.ChickenAiSystem; -import toniarts.openkeeper.game.logic.ChickenSpawnSystem; -import toniarts.openkeeper.game.logic.CreatureAiSystem; -import toniarts.openkeeper.game.logic.CreatureExperienceSystem; -import toniarts.openkeeper.game.logic.CreatureFallSystem; -import toniarts.openkeeper.game.logic.CreatureSpawnSystem; -import toniarts.openkeeper.game.logic.CreatureTorturingSystem; -import toniarts.openkeeper.game.logic.CreatureViewSystem; -import toniarts.openkeeper.game.logic.DeathSystem; -import toniarts.openkeeper.game.logic.DecaySystem; -import toniarts.openkeeper.game.logic.DoorViewSystem; -import toniarts.openkeeper.game.logic.DungeonHeartConstruction; -import toniarts.openkeeper.game.logic.GameLogicManager; -import toniarts.openkeeper.game.logic.HaulingSystem; -import toniarts.openkeeper.game.logic.HealthSystem; -import toniarts.openkeeper.game.logic.IEntityPositionLookup; -import toniarts.openkeeper.game.logic.IGameLogicUpdatable; -import toniarts.openkeeper.game.logic.LooseObjectSystem; -import toniarts.openkeeper.game.logic.ManaCalculatorLogic; -import toniarts.openkeeper.game.logic.MovementSystem; -import toniarts.openkeeper.game.logic.PlayerCreatureSystem; -import toniarts.openkeeper.game.logic.PlayerSpellbookSystem; -import toniarts.openkeeper.game.logic.PositionSystem; -import toniarts.openkeeper.game.logic.SlapSystem; +import toniarts.openkeeper.game.logic.*; import toniarts.openkeeper.game.navigation.INavigationService; import toniarts.openkeeper.game.navigation.NavigationService; import toniarts.openkeeper.game.state.session.PlayerService; @@ -80,73 +54,34 @@ import toniarts.openkeeper.tools.convert.map.Player; import toniarts.openkeeper.tools.convert.map.Thing; import toniarts.openkeeper.tools.convert.map.Variable; -import toniarts.openkeeper.utils.GameLoop; -import toniarts.openkeeper.utils.PathUtils; /** * The game controller, runs the game simulation itself * * @author Toni Helenius */ -public final class GameController implements IGameLogicUpdatable, AutoCloseable, IGameTimer, ILevelInfo, IGameController { +public final class GameController implements IGameLogicUpdatable, IGameController { private static final Logger logger = System.getLogger(GameController.class.getName()); - - public static final int LEVEL_TIMER_MAX_COUNT = 16; - private static final int LEVEL_FLAG_MAX_COUNT = 128; - private final String level; - private KwdFile kwdFile; + private final LevelInfo levelInfo; private final toniarts.openkeeper.game.data.Level levelObject; - private final SortedMap players = new TreeMap<>(); private final Map playerControllers = HashMap.newHashMap(6); private final Map gameSettings; private final EntityData entityData; private final PlayerService playerService; - private GameLoop gameLogicLoop; - private GameLoop steeringCalculatorLoop; - private GameLoop gameAnimationLoop; - private GameLogicManager gameAnimationThread; - private GameLogicManager gameLogicThread; private TriggerControl triggerControl = null; - private CreatureTriggerLogicController creatureTriggerState; - private ObjectTriggerLogicController objectTriggerState; - private DoorTriggerLogicController doorTriggerState; - private PartyTriggerLogicController partyTriggerState; - private ActionPointTriggerLogicController actionPointController; - private PlayerTriggerLogicController playerTriggerLogicController; - private final List flags = new ArrayList<>(LEVEL_FLAG_MAX_COUNT); - private final SafeArrayList timers = new SafeArrayList<>(GameTimer.class, LEVEL_TIMER_MAX_COUNT); - private final Map actionPointsById = new HashMap<>(); - private final List actionPoints = new ArrayList<>(); - private int levelScore = 0; - private boolean campaign; + private final List controllers = new ArrayList<>(); + private GameWorldController gameWorldController; private INavigationService navigationService; private PositionSystem positionSystem; private GameResult gameResult = null; - private Float timeLimit = null; private TaskManager taskManager; - /** - * Single use game states - * - * @param level the level to load - * @param entityData - * @param gameSettings - * @param playerService - */ - public GameController(String level, EntityData entityData, Map gameSettings, PlayerService playerService) { - this.level = level; - this.levelObject = null; - this.entityData = entityData; - this.gameSettings = gameSettings; - this.playerService = playerService; - } - /** * Single use game states * @@ -156,161 +91,95 @@ public GameController(String level, EntityData entityData, Map players, EntityData entityData, Map gameSettings, PlayerService playerService) { - this.level = null; - this.kwdFile = level; - this.levelObject = null; + public GameController(KwdFile level, List players, EntityData entityData, + Map gameSettings, + PlayerService playerService) { + + levelInfo = new LevelInfo(); + levelInfo.kwdFile = level; + levelObject = null; this.entityData = entityData; this.gameSettings = gameSettings; this.playerService = playerService; if (players != null) { for (Keeper keeper : players) { - this.players.put(keeper.getId(), keeper); + levelInfo.players.put(keeper.getId(), keeper); } } - } - - /** - * Single use game states - * - * @param selectedLevel the level to load - * @param entityData - * @param gameSettings - * @param playerService - */ - public GameController(GeneralLevel selectedLevel, EntityData entityData, Map gameSettings, PlayerService playerService) { - this.level = null; - this.kwdFile = selectedLevel.getKwdFile(); - this.entityData = entityData; - this.gameSettings = gameSettings; - this.playerService = playerService; - if (selectedLevel instanceof toniarts.openkeeper.game.data.Level level1) { - this.levelObject = level1; - } else { - this.levelObject = null; - } + createNewGame(); } public void createNewGame() { - // Load the level data - try { - if (level != null) { - - kwdFile = new KwdFile(Main.getDkIIFolder(), - Paths.get(PathUtils.getRealFileName(Main.getDkIIFolder(), PathUtils.DKII_MAPS_FOLDER + level + ".kwd"))); - - } else { - kwdFile.load(); - } - } catch (IOException ex) { - logger.log(Level.ERROR, "Failed to load the map file!", ex); - throw new RuntimeException(level, ex); - } - + levelInfo.load(); // The players setupPlayers(); - // Action points - loadActionPoints(); - + final GameTimeController gameTimer = new GameTimeController(); // The world - gameWorldController = new GameWorldController(kwdFile, entityData, gameSettings, players, playerControllers, this); - gameWorldController.createNewGame(this, this); + gameWorldController = new GameWorldController(this, levelInfo, entityData, gameSettings, playerControllers, gameTimer); - positionSystem = new PositionSystem(gameWorldController.getMapController(), entityData, gameWorldController.getCreaturesController(), gameWorldController.getDoorsController(), gameWorldController.getObjectsController()); + positionSystem = new PositionSystem(gameWorldController.getMapController(), entityData, + gameWorldController.getCreaturesController(), gameWorldController.getDoorsController(), + gameWorldController.getObjectsController()); gameWorldController.setEntityPositionLookup(positionSystem); // Navigation navigationService = new NavigationService(gameWorldController.getMapController(), positionSystem); // Initialize tasks - taskManager = new TaskManager(entityData, gameWorldController, gameWorldController.getMapController(), gameWorldController.getObjectsController(), gameWorldController.getCreaturesController(), navigationService, playerControllers.values(), this, positionSystem, gameSettings); + taskManager = new TaskManager(entityData, gameWorldController, gameWorldController.getMapController(), + gameWorldController.getObjectsController(), gameWorldController.getCreaturesController(), + navigationService, playerControllers.values(), levelInfo, positionSystem, gameSettings); // The triggers - partyTriggerState = new PartyTriggerLogicController(this, this, this, gameWorldController.getMapController(), gameWorldController.getCreaturesController()); - creatureTriggerState = new CreatureTriggerLogicController(this, this, this, gameWorldController.getMapController(), gameWorldController.getCreaturesController(), playerService, entityData); - objectTriggerState = new ObjectTriggerLogicController(this, this, this, gameWorldController.getMapController(), gameWorldController.getCreaturesController(), playerService, entityData, gameWorldController.getObjectsController()); - doorTriggerState = new DoorTriggerLogicController(this, this, this, gameWorldController.getMapController(), gameWorldController.getCreaturesController(), playerService, entityData, gameWorldController.getDoorsController()); - actionPointController = new ActionPointTriggerLogicController(this, this, this, gameWorldController.getMapController(), gameWorldController.getCreaturesController(), positionSystem); - playerTriggerLogicController = new PlayerTriggerLogicController(this, this, this, gameWorldController.getMapController(), gameWorldController.getCreaturesController(), playerService); - - // Trigger data - for (short i = 0; i < LEVEL_FLAG_MAX_COUNT; i++) { - flags.add(i, 0); - } - - for (byte i = 0; i < LEVEL_TIMER_MAX_COUNT; i++) { - timers.add(i, new GameTimer()); - } - - int triggerId = kwdFile.getGameLevel().getTriggerId(); + controllers.add(new PartyTriggerLogicController(this, levelInfo, gameTimer, + gameWorldController.getMapController(), gameWorldController.getCreaturesController())); + controllers.add(new CreatureTriggerLogicController(this, levelInfo, gameTimer, + gameWorldController.getMapController(), gameWorldController.getCreaturesController(), + playerService, entityData)); + controllers.add(new ObjectTriggerLogicController(this, levelInfo, gameTimer, + gameWorldController.getMapController(), gameWorldController.getCreaturesController(), + playerService, entityData, gameWorldController.getObjectsController())); + controllers.add(new DoorTriggerLogicController(this, levelInfo, gameTimer, + gameWorldController.getMapController(), gameWorldController.getCreaturesController(), + playerService, entityData, gameWorldController.getDoorsController())); + controllers.add(new ActionPointTriggerLogicController(this, levelInfo, gameTimer, + gameWorldController.getMapController(), gameWorldController.getCreaturesController(), + positionSystem)); + controllers.add(new PlayerTriggerLogicController(this, levelInfo, gameTimer, + gameWorldController.getMapController(), gameWorldController.getCreaturesController(), + playerService)); + controllers.add(gameTimer); + controllers.add(positionSystem); + + int triggerId = levelInfo.kwdFile.getGameLevel().getTriggerId(); if (triggerId != 0) { - triggerControl = new TriggerControl(this, this, this, gameWorldController.getMapController(), gameWorldController.getCreaturesController(), triggerId); + triggerControl = new TriggerControl(this, levelInfo, gameTimer, gameWorldController.getMapController(), gameWorldController.getCreaturesController(), triggerId); } - // Create the game loops ready to start - // Game logic - gameLogicThread = new GameLogicManager(positionSystem, - gameWorldController.getMapController(), - new DecaySystem(entityData), - new CreatureExperienceSystem(entityData, kwdFile, gameSettings, gameWorldController.getCreaturesController()), - new SlapSystem(entityData, kwdFile, playerControllers.values(), gameSettings), - new HealthSystem(entityData, kwdFile, positionSystem, gameSettings, gameWorldController.getCreaturesController(), this, playerControllers.values(), gameWorldController.getMapController()), - new CreatureTorturingSystem(entityData, gameWorldController.getCreaturesController(), gameWorldController.getMapController()), - new DeathSystem(entityData, gameSettings, positionSystem), - new PlayerCreatureSystem(entityData, kwdFile, playerControllers.values()), - new PlayerSpellbookSystem(entityData, kwdFile, playerControllers.values()), - this, - new CreatureSpawnSystem(gameWorldController.getCreaturesController(), playerControllers.values(), gameSettings, this, gameWorldController.getMapController()), - new ChickenSpawnSystem(entityData, gameWorldController.getObjectsController(), playerControllers.values(), gameSettings, this, gameWorldController.getMapController()), - new ManaCalculatorLogic(playerControllers.values(), entityData), - new CreatureAiSystem(entityData, gameWorldController.getCreaturesController(), taskManager), - new ChickenAiSystem(entityData, gameWorldController.getObjectsController()), - new CreatureViewSystem(entityData), - new DoorViewSystem(entityData, positionSystem), - new LooseObjectSystem(entityData, gameWorldController.getMapController(), playerControllers, positionSystem), - new HaulingSystem(entityData), - taskManager); - gameLogicLoop = new GameLoop(gameLogicThread, 1000000000 / kwdFile.getGameLevel().getTicksPerSec(), "GameLogic"); - - // Animation systems - gameAnimationThread = new GameLogicManager(new DungeonHeartConstruction(entityData, getLevelVariable(Variable.MiscVariable.MiscType.TIME_BEFORE_DUNGEON_HEART_CONSTRUCTION_BEGINS)), new CreatureFallSystem(entityData)); - gameAnimationLoop = new GameLoop(gameAnimationThread, GameLoop.INTERVAL_FPS_60, "GameAnimation"); - - // Steering - steeringCalculatorLoop = new GameLoop(new GameLogicManager(new MovementSystem(entityData)), GameLoop.INTERVAL_FPS_60, "SteeringCalculator"); - } - - public void startGame() { - - // Game logic thread & movement - gameLogicLoop.start(); - gameAnimationLoop.start(); - steeringCalculatorLoop.start(); } private void setupPlayers() { - // Setup players - boolean addMissingPlayers = players.isEmpty(); // Add all if none is given (campaign...) - for (Map.Entry entry : kwdFile.getPlayers().entrySet()) { + boolean addMissingPlayers = levelInfo.players.isEmpty(); // Add all if none is given (campaign...) + for (Map.Entry entry : levelInfo.kwdFile.getPlayers().entrySet()) { Keeper keeper = null; - if (players.containsKey(entry.getKey())) { - keeper = players.get(entry.getKey()); + if (levelInfo.players.containsKey(entry.getKey())) { + keeper = levelInfo.players.get(entry.getKey()); keeper.setPlayer(entry.getValue()); } else if (addMissingPlayers || entry.getKey() < Player.KEEPER1_ID) { keeper = new Keeper(entry.getValue()); - players.put(entry.getKey(), keeper); + levelInfo.players.put(entry.getKey(), keeper); } // Init if (keeper != null) { - PlayerController playerController = new PlayerController(kwdFile, keeper, kwdFile.getImp(), entityData, gameSettings); + PlayerController playerController = new PlayerController(levelInfo.kwdFile, keeper, levelInfo.kwdFile.getImp(), entityData, gameSettings); playerControllers.put(entry.getKey(), playerController); // Spells are all available for research unless otherwise stated - for (KeeperSpell spell : kwdFile.getKeeperSpells()) { + for (KeeperSpell spell : levelInfo.kwdFile.getKeeperSpells()) { if (spell.getBonusRTime() != 0) { playerController.getSpellControl().setTypeAvailable(spell, true, false); } @@ -319,7 +188,7 @@ private void setupPlayers() { } // Set the alliances - for (Variable.PlayerAlliance playerAlliance : kwdFile.getPlayerAlliances()) { + for (Variable.PlayerAlliance playerAlliance : levelInfo.kwdFile.getPlayerAlliances()) { short player1 = (short) playerAlliance.getPlayerIdOne(); short player2 = (short) playerAlliance.getPlayerIdTwo(); createAlliance(player1, player2); @@ -327,15 +196,15 @@ private void setupPlayers() { // Set player availabilities // TODO: the player customized game settings - for (Variable.Availability availability : kwdFile.getAvailabilities()) { + for (Variable.Availability availability : levelInfo.kwdFile.getAvailabilities()) { if (availability.getPlayerId() == 0) { // All players - for (Keeper player : players.values()) { + for (Keeper player : levelInfo.players.values()) { setAvailability(player, availability); } } else { - Keeper player = players.get((short) availability.getPlayerId()); + Keeper player = levelInfo.players.get((short) availability.getPlayerId()); // Not all the players are participating... if (player != null) { @@ -359,92 +228,56 @@ private void setAvailability(Keeper player, Variable.Availability availability) boolean discovered = availability.getValue() == Variable.Availability.AvailabilityValue.AVAILABLE; switch (availability.getType()) { case CREATURE: { - playerController.getCreatureControl().setTypeAvailable(kwdFile.getCreature((short) availability.getTypeId()), available); + playerController.getCreatureControl().setTypeAvailable(levelInfo.kwdFile.getCreature((short) availability.getTypeId()), available); break; } case ROOM: { - playerController.getRoomControl().setTypeAvailable(kwdFile.getRoomById((short) availability.getTypeId()), available, discovered); + playerController.getRoomControl().setTypeAvailable(levelInfo.kwdFile.getRoomById((short) availability.getTypeId()), available, discovered); break; } case SPELL: { - playerController.getSpellControl().setTypeAvailable(kwdFile.getKeeperSpellById(availability.getTypeId()), available, discovered); + playerController.getSpellControl().setTypeAvailable(levelInfo.kwdFile.getKeeperSpellById(availability.getTypeId()), available, discovered); break; } case DOOR: { - playerController.getDoorControl().setTypeAvailable(kwdFile.getDoorById((short) availability.getTypeId()), available, discovered); + playerController.getDoorControl().setTypeAvailable(levelInfo.kwdFile.getDoorById((short) availability.getTypeId()), available, discovered); break; } case TRAP: { - playerController.getTrapControl().setTypeAvailable(kwdFile.getTrapById((short) availability.getTypeId()), available, discovered); + playerController.getTrapControl().setTypeAvailable(levelInfo.kwdFile.getTrapById((short) availability.getTypeId()), available, discovered); break; } } } - private void loadActionPoints() { - for (Thing.ActionPoint thing : getLevelData().getThings(Thing.ActionPoint.class)) { - ActionPoint ap = new ActionPoint(thing); - actionPointsById.put(ap.getId(), ap); - actionPoints.add(ap); - } - } - @Override public IPlayerController getPlayerController(short playerId) { return playerControllers.get(playerId); } @Override - public Collection getPlayerControllers() { - return playerControllers.values(); + public Map getPlayerControllers() { + return playerControllers; } @Override - public void pauseGame() { - if (steeringCalculatorLoop != null) { - steeringCalculatorLoop.pause(); - } - if (gameAnimationLoop != null) { - gameAnimationLoop.pause(); - } - if (gameLogicLoop != null) { - gameLogicLoop.pause(); - } - playerService.setGamePaused(true); - } - - @Override - public void resumeGame() { - if (gameLogicLoop != null) { - gameLogicLoop.resume(); - } - if (gameAnimationLoop != null) { - gameAnimationLoop.resume(); - } - if (steeringCalculatorLoop != null) { - steeringCalculatorLoop.resume(); - } - playerService.setGamePaused(false); - } - - @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { // Update time for AI GdxAI.getTimepiece().update(tpf); // Time limit is a special timer, it just ticks towards 0 if it is set - if (timeLimit != null) { - timeLimit -= tpf; + if (levelInfo.timeLimit != null) { + levelInfo.timeLimit -= tpf; // We rest on zero - if (timeLimit < 0) { - timeLimit = 0f; + if (levelInfo.timeLimit < 0) { + levelInfo.timeLimit = 0f; } } // Advance game timers - for (GameTimer timer : timers.getArray()) { + for (GameTimer timer : levelInfo.timers.getArray()) { timer.update(tpf); } @@ -452,86 +285,17 @@ public void processTick(float tpf, double gameTime) { triggerControl.update(tpf); } - if (partyTriggerState != null) { - partyTriggerState.processTick(tpf, gameTime); - } - - if (creatureTriggerState != null) { - creatureTriggerState.processTick(tpf, gameTime); - } - - if (objectTriggerState != null) { - objectTriggerState.processTick(tpf, gameTime); - } - - if (doorTriggerState != null) { - doorTriggerState.processTick(tpf, gameTime); - } - - if (actionPointController != null) { - actionPointController.processTick(tpf, gameTime); - } + controllers.stream().forEach(controller -> controller.processTick(tpf)); - if (playerTriggerLogicController != null) { - playerTriggerLogicController.processTick(tpf, gameTime); - } - - if (timeLimit != null && timeLimit <= 0) { + if (levelInfo.timeLimit != null && levelInfo.timeLimit <= 0) { //TODO: throw new RuntimeException("Level time limit exceeded!"); } } - /** - * Get the level raw data file - * - * @return the KWD - */ - @Override - public KwdFile getLevelData() { - return kwdFile; - } - - @Override - public int getFlag(int id) { - return flags.get(id); - } - - @Override - public void setFlag(int id, int value) { - flags.set(id, value); - } - - @Override - public GameTimer getTimer(int id) { - return timers.get(id); - } - - /** - * @see GameLogicManager#getGameTime() - * @return the game time - */ - @Override - public double getGameTime() { - if (gameLogicThread != null) { - return gameLogicThread.getGameTime(); - } - return 0; - } - - @Override - public Float getTimeLimit() { - return timeLimit; - } - - @Override - public void setTimeLimit(float timeLimit) { - this.timeLimit = timeLimit; - } - @Override public void endGame(short playerId, boolean win) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } public void setEnd(boolean win) { @@ -539,7 +303,7 @@ public void setEnd(boolean win) { // TODO: this is client stuff, we should only determine here what happens to the game & players gameResult = new GameResult(); gameResult.setData(GameResult.ResultType.LEVEL_WON, win); - gameResult.setData(GameResult.ResultType.TIME_TAKEN, gameLogicThread.getGameTime()); + gameResult.setData(GameResult.ResultType.TIME_TAKEN, getContoller(IGameTimer.class).getGameTime()); // Enable the end game state // stateManager.getState(PlayerState.class).endGame(win); @@ -562,56 +326,23 @@ public ITaskManager getTaskManager() { return taskManager; } - @Override - public Keeper getPlayer(short playerId) { - return players.get(playerId); - } - - @Override - public Collection getPlayers() { - return players.values(); - } - /** * Get level variable value * * @param variable the variable type * @return variable value */ + @Override public float getLevelVariable(Variable.MiscVariable.MiscType variable) { // TODO: player is able to change these, so need a wrapper and store these to GameState - return kwdFile.getVariables().get(variable).getValue(); - } - - /** - * Get level score, not really a player score... kinda - * - * @return the level score - */ - @Override - public int getLevelScore() { - return levelScore; - } - - @Override - public void setLevelScore(int levelScore) { - this.levelScore = levelScore; + return levelInfo.kwdFile.getVariables().get(variable).getValue(); } - public CreatureTriggerLogicController getCreatureTriggerState() { - return creatureTriggerState; - } - - public ObjectTriggerLogicController getObjectTriggerState() { - return objectTriggerState; - } - - public DoorTriggerLogicController getDoorTriggerState() { - return doorTriggerState; - } - - public PartyTriggerLogicController getPartyTriggerState() { - return partyTriggerState; + public T getContoller(Class clazz) { + return (T) controllers.stream() + .filter(controller -> controller.getClass().isInstance(clazz)) + .findFirst().orElseThrow(() -> new IndexOutOfBoundsException( + "Game contoller have not child of class " + clazz)); } /** @@ -622,11 +353,11 @@ public PartyTriggerLogicController getPartyTriggerState() { */ @Override public void createAlliance(short playerOneId, short playerTwoId) { - if (players.containsKey(playerOneId)) { - players.get(playerOneId).createAlliance(playerTwoId); + if (levelInfo.players.containsKey(playerOneId)) { + levelInfo.players.get(playerOneId).createAlliance(playerTwoId); } - if (players.containsKey(playerTwoId)) { - players.get(playerTwoId).createAlliance(playerOneId); + if (levelInfo.players.containsKey(playerTwoId)) { + levelInfo.players.get(playerTwoId).createAlliance(playerOneId); } } @@ -638,11 +369,11 @@ public void createAlliance(short playerOneId, short playerTwoId) { */ @Override public void breakAlliance(short playerOneId, short playerTwoId) { - if (players.containsKey(playerOneId)) { - players.get(playerOneId).breakAlliance(playerTwoId); + if (levelInfo.players.containsKey(playerOneId)) { + levelInfo.players.get(playerOneId).breakAlliance(playerTwoId); } - if (players.containsKey(playerTwoId)) { - players.get(playerTwoId).breakAlliance(playerOneId); + if (levelInfo.players.containsKey(playerTwoId)) { + levelInfo.players.get(playerTwoId).breakAlliance(playerOneId); } } @@ -653,26 +384,10 @@ public GameResult getGameResult() { @Override public void setPossession(EntityId target, short playerId) { - players.get(playerId).setPossession(target != null); + levelInfo.players.get(playerId).setPossession(target != null); playerService.setPossession(target, playerId); } - @Override - public void close() { - if (steeringCalculatorLoop != null) { - steeringCalculatorLoop.stop(); - steeringCalculatorLoop = null; - } - if (gameAnimationLoop != null) { - gameAnimationLoop.stop(); - gameAnimationLoop = null; - } - if (gameLogicLoop != null) { - gameLogicLoop.stop(); - gameLogicLoop = null; - } - } - @Override public IGameWorldController getGameWorldController() { return gameWorldController; @@ -680,32 +395,131 @@ public IGameWorldController getGameWorldController() { @Override public void start() { - + controllers.stream().forEach(IGameLogicUpdatable::start); } @Override public void stop() { - + controllers.stream().forEach(IGameLogicUpdatable::stop); + controllers.clear(); } @Override - public ActionPoint getActionPoint(int id) { - return actionPointsById.get(id); + public INavigationService getNavigationService() { + return navigationService; } @Override - public List getActionPoints() { - return actionPoints; + public IEntityPositionLookup getEntityLookupService() { + return positionSystem; } @Override - public INavigationService getNavigationService() { - return navigationService; + public ILevelInfo getLevelInfo() { + return levelInfo; } - @Override - public IEntityPositionLookup getEntityLookupService() { - return positionSystem; + private static class LevelInfo implements ILevelInfo { + + private static final int LEVEL_TIMER_MAX_COUNT = 16; + private static final int LEVEL_FLAG_MAX_COUNT = 128; + + private KwdFile kwdFile; + private int levelScore = 0; + private Float timeLimit = null; + + private final SortedMap players = new TreeMap<>(); + private final List flags = new ArrayList<>(LEVEL_FLAG_MAX_COUNT); + private final SafeArrayList timers = new SafeArrayList<>(GameTimer.class, LEVEL_TIMER_MAX_COUNT); + private final Map actionPointsById = new HashMap<>(); + private final List actionPoints = new ArrayList<>(); + + public void load() { + kwdFile.load(); + // Action points + loadActionPoints(); + // Triggers data + for (short i = 0; i < LEVEL_FLAG_MAX_COUNT; i++) { + flags.add(i, 0); + } + // Timers data + for (byte i = 0; i < LEVEL_TIMER_MAX_COUNT; i++) { + timers.add(i, new GameTimer()); + } + } + + private void loadActionPoints() { + for (Thing.ActionPoint thing : kwdFile.getThings(Thing.ActionPoint.class)) { + ActionPoint ap = new ActionPoint(thing); + actionPointsById.put(ap.getId(), ap); + actionPoints.add(ap); + } + } + + @Override + public KwdFile getLevelData() { + return kwdFile; + } + + @Override + public int getFlag(int id) { + return flags.get(id); + } + + @Override + public void setFlag(int id, int value) { + flags.set(id, value); + } + + @Override + public GameTimer getTimer(int id) { + return timers.get(id); + } + + @Override + public Float getTimeLimit() { + return timeLimit; + } + + @Override + public void setTimeLimit(float time) { + timeLimit = time; + } + + /** + * Get level score, not really a player score... kinda + * + * @return the level score + */ + @Override + public int getLevelScore() { + return levelScore; + } + + @Override + public void setLevelScore(int score) { + levelScore = score; + } + + @Override + public ActionPoint getActionPoint(int id) { + return actionPointsById.get(id); + } + + @Override + public List getActionPoints() { + return actionPoints; + } + + @Override + public Keeper getPlayer(short playerId) { + return players.get(playerId); + } + + @Override + public Map getPlayers() { + return players; + } } } diff --git a/src/toniarts/openkeeper/game/controller/GameTimeController.java b/src/toniarts/openkeeper/game/controller/GameTimeController.java new file mode 100644 index 000000000..b90d38681 --- /dev/null +++ b/src/toniarts/openkeeper/game/controller/GameTimeController.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014-2025 OpenKeeper + * + * OpenKeeper is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * OpenKeeper is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenKeeper. If not, see . + */ +package toniarts.openkeeper.game.controller; + +import toniarts.openkeeper.utils.GameTimeCounter; + +/** + * + * @author ArchDemon + */ +public final class GameTimeController extends GameTimeCounter implements IGameTimer { + + private long ticks; + + @Override + public void start() { + ticks = 0; + timeElapsed = 0.0; + } + + @Override + public void stop() { + // nope + } + + @Override + public void processTick(float tpf) { + super.processTick(tpf); + ticks++; + } + + @Override + public double getGameTime() { + return timeElapsed; + } +} diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index 441f40a64..d39a46ca6 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -81,6 +81,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.SequencedMap; import java.util.Set; import java.util.SortedMap; import java.util.stream.Collectors; @@ -91,13 +92,12 @@ * @author Toni Helenius */ public final class GameWorldController implements IGameWorldController, IPlayerActions { - + private static final System.Logger logger = System.getLogger(GameWorldController.class.getName()); /** - * When dealing with gold... We currently better lock it. Logic stuff - * happens in single thread. But player actions are prosessed in real time - * by possibly other threads. We must not lose any gold from the world. + * When dealing with gold... We currently better lock it. Logic stuff happens in single thread. But player + * actions are prosessed in real time by possibly other threads. We must not lose any gold from the world. */ public static final Object GOLD_LOCK = new Object(); @@ -110,23 +110,28 @@ public final class GameWorldController implements IGameWorldController, IPlayerA private IShotsController shotsController; private IEntityPositionLookup entityPositionLookup; private final Map playerControllers; - private final SortedMap players; + private final Map players; private final IGameTimer gameTimer; private IMapController mapController; private final Map gameSettings; private final SafeArrayList listeners = new SafeArrayList<>(PlayerActionListener.class); - public GameWorldController(KwdFile kwdFile, EntityData entityData, Map gameSettings, SortedMap players, Map playerControllers, IGameTimer gameTimer) { - this.kwdFile = kwdFile; + public GameWorldController(IGameController gameController, ILevelInfo levelInfo, EntityData entityData, + Map gameSettings, + Map playerControllers, IGameTimer gameTimer) { + + this.kwdFile = levelInfo.getLevelData(); this.entityData = entityData; this.gameSettings = gameSettings; this.gameTimer = gameTimer; this.playerControllers = playerControllers; - this.players = players; + this.players = levelInfo.getPlayers(); + + this.createNewGame(gameController, levelInfo); } - public void createNewGame(IGameController gameController, ILevelInfo levelInfo) { + private void createNewGame(IGameController gameController, ILevelInfo levelInfo) { // Load objects objectsController = new ObjectsController(kwdFile, entityData, gameSettings, gameTimer, gameController, levelInfo); @@ -189,8 +194,7 @@ private void initPlayerRooms() { } /** - * Add a lump sum of gold to a player, distributes the gold to the available - * rooms + * Add a lump sum of gold to a player, distributes the gold to the available rooms * * @param playerId for the player * @param sum the gold sum @@ -204,8 +208,7 @@ public int addGold(short playerId, int sum) { } /** - * Add a lump sum of gold to a player, distributes the gold to the available - * rooms + * Add a lump sum of gold to a player, distributes the gold to the available rooms * * @param playerId for the player * @param p a point where to drop the gold, can be {@code null} @@ -598,11 +601,9 @@ private void putToKeeperHand(PlayerHandControl playerHandControl, EntityId entit playerHandControl.push(entity); /** - * TODO: Basically here we can have a concurrency problem, especially - * visible with things that have AI. The AI or other systems may be - * still processing and manipulating stuff. One way to fix would be to - * add this to game loop queue to be executed. That would introduce a - * delay though... + * TODO: Basically here we can have a concurrency problem, especially visible with things that have + * AI. The AI or other systems may be still processing and manipulating stuff. One way to fix would be + * to add this to game loop queue to be executed. That would introduce a delay though... */ // Lose the position component on the entity, do it here since we have the knowledge on locations etc. keep the "hand" simple // And also no need to create a system for this which saves resources diff --git a/src/toniarts/openkeeper/game/controller/IGameController.java b/src/toniarts/openkeeper/game/controller/IGameController.java index 52dbb0d71..81345b2a1 100644 --- a/src/toniarts/openkeeper/game/controller/IGameController.java +++ b/src/toniarts/openkeeper/game/controller/IGameController.java @@ -18,10 +18,12 @@ import com.simsilica.es.EntityId; import java.util.Collection; +import java.util.Map; import toniarts.openkeeper.game.data.GameResult; import toniarts.openkeeper.game.logic.IEntityPositionLookup; import toniarts.openkeeper.game.navigation.INavigationService; import toniarts.openkeeper.game.task.ITaskManager; +import toniarts.openkeeper.tools.convert.map.Variable; /** * Game controller. Controls the game @@ -50,13 +52,13 @@ public interface IGameController { IPlayerController getPlayerController(short playerId); - Collection getPlayerControllers(); + Map getPlayerControllers(); ITaskManager getTaskManager(); - void pauseGame(); + ILevelInfo getLevelInfo(); - void resumeGame(); + float getLevelVariable(Variable.MiscVariable.MiscType variable); /** * End the game (for one player) diff --git a/src/toniarts/openkeeper/game/controller/ILevelInfo.java b/src/toniarts/openkeeper/game/controller/ILevelInfo.java index 22779bb4c..e871627dc 100644 --- a/src/toniarts/openkeeper/game/controller/ILevelInfo.java +++ b/src/toniarts/openkeeper/game/controller/ILevelInfo.java @@ -18,6 +18,7 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import toniarts.openkeeper.game.data.ActionPoint; import toniarts.openkeeper.game.data.GameTimer; import toniarts.openkeeper.game.data.Keeper; @@ -39,7 +40,7 @@ public interface ILevelInfo { Keeper getPlayer(short playerId); - Collection getPlayers(); + Map getPlayers(); int getFlag(int id); diff --git a/src/toniarts/openkeeper/game/controller/MapController.java b/src/toniarts/openkeeper/game/controller/MapController.java index 41d1648f2..4f3021fbd 100644 --- a/src/toniarts/openkeeper/game/controller/MapController.java +++ b/src/toniarts/openkeeper/game/controller/MapController.java @@ -74,22 +74,24 @@ public final class MapController extends Container implements IMapController { /** * Load map data from a KWD file straight (new game) * - * @param kwdFile the KWD file + * @param kwdFile the KWD file * @param objectsController objects controller - * @param gameSettings the game settings + * @param gameSettings the game settings * @param gameTimer * @param entityData * @param levelInfo */ - public MapController(KwdFile kwdFile, IObjectsController objectsController, Map gameSettings, + public MapController(KwdFile kwdFile, IObjectsController objectsController, + Map gameSettings, IGameTimer gameTimer, EntityData entityData, ILevelInfo levelInfo) { + this.kwdFile = kwdFile; this.objectsController = objectsController; - this.mapData = new MapData(kwdFile, entityData, levelInfo.getPlayers()); + this.mapData = new MapData(kwdFile, entityData, levelInfo.getPlayers().values()); this.gameSettings = gameSettings; this.gameTimer = gameTimer; this.entityData = entityData; - this.mapInformation = new MapInformation(mapData, kwdFile, levelInfo.getPlayers()); + this.mapInformation = new MapInformation(mapData, kwdFile, levelInfo.getPlayers().values()); this.levelInfo = levelInfo; // Load rooms @@ -145,19 +147,16 @@ private void loadRoom(Point p) { // TODO: A bit of a design problem here /** - * Unclear responsibilities between the world and map controller. - * Also a result of how we handle the building and selling by - * destroying rooms. - * But at least keep anyone who is listening intact + * Unclear responsibilities between the world and map controller. Also a result of how we handle the + * building and selling by destroying rooms. But at least keep anyone who is listening intact */ notifyOnBuild(roomController.getRoomInstance().getOwnerId(), roomController); } /** - * Find the room starting from a certain point, rooms are never diagonally - * attached + * Find the room starting from a certain point, rooms are never diagonally attached * - * @param p starting point + * @param p starting point * @param roomInstance the room instance */ private void findRoom(Point p, RoomInstance roomInstance) { @@ -342,7 +341,7 @@ public Collection getRoomControllers() { @Override public IRoomController getRoomControllerByCoordinates(Point p) { IMapTileInformation tile = getMapData().getTile(p); - if(tile == null) { + if (tile == null) { return null; } @@ -373,10 +372,8 @@ public void removeRoomInstances(EntityId... instances) { // TODO: A bit of a design problem here /** - * Unclear responsibilities between the world and map controller. - * Also a result of how we handle the building and selling by - * destroying rooms. - * But at least keep anyone who is listening intact + * Unclear responsibilities between the world and map controller. Also a result of how we handle + * the building and selling by destroying rooms. But at least keep anyone who is listening intact */ notifyOnSold(roomController.getRoomInstance().getOwnerId(), roomController); } @@ -386,7 +383,7 @@ public void removeRoomInstances(EntityId... instances) { * Get rooms by function.
FIXME: Should the player have ready lists? * * @param objectType the function - * @param playerId the player id, can be null + * @param playerId the player id, can be null * @return list of rooms that match the criteria */ @Override @@ -532,7 +529,7 @@ public int damageTile(Point point, short playerId, ICreatureController creature) /** * Heal a tile * - * @param point the point + * @param point the point * @param playerId the player applying the healing */ @Override @@ -593,7 +590,7 @@ public void healTile(Point point, short playerId) { /** * Damage a room * - * @param point tile coordinate + * @param point tile coordinate * @param playerId for the player */ private void damageRoom(Point point, short playerId) { @@ -760,7 +757,7 @@ public void stop() { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { this.update(tpf); } diff --git a/src/toniarts/openkeeper/game/controller/chicken/ChickenController.java b/src/toniarts/openkeeper/game/controller/chicken/ChickenController.java index ebda45f98..cc7da3d52 100644 --- a/src/toniarts/openkeeper/game/controller/chicken/ChickenController.java +++ b/src/toniarts/openkeeper/game/controller/chicken/ChickenController.java @@ -48,7 +48,7 @@ * @author Toni Helenius */ public final class ChickenController extends EntityController implements IChickenController { - + private static final Logger logger = System.getLogger(ChickenController.class.getName()); private final INavigationService navigationService; @@ -158,7 +158,7 @@ public void start() { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { /** * Hmm, I'm not sure how to do this, this is not ideal either, how to diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index 7eea5ea4e..155a14cca 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -915,7 +915,7 @@ public void start() { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { /** * Hmm, I'm not sure how to do this, this is not ideal either, how to diff --git a/src/toniarts/openkeeper/game/logic/ChickenAiSystem.java b/src/toniarts/openkeeper/game/logic/ChickenAiSystem.java index da456d656..8e87155a2 100644 --- a/src/toniarts/openkeeper/game/logic/ChickenAiSystem.java +++ b/src/toniarts/openkeeper/game/logic/ChickenAiSystem.java @@ -60,18 +60,16 @@ public ChickenAiSystem(EntityData entityData, IObjectsController objectsControll } @Override - public void processTick(float tpf, double gameTime) { - + public void processTick(float tpf) { // Add new & remove old if (chickenEntities.applyChanges()) { processDeletedEntities(chickenEntities.getRemovedEntities()); - processAddedEntities(chickenEntities.getAddedEntities()); } // Process ticks for (IChickenController creatureController : chickenControllers.getArray()) { - creatureController.processTick(tpf, gameTime); + creatureController.processTick(tpf); } // This is shorthand for managing also the view state.... Not sure if smart or not diff --git a/src/toniarts/openkeeper/game/logic/ChickenSpawnSystem.java b/src/toniarts/openkeeper/game/logic/ChickenSpawnSystem.java index 52d3835ba..3fe097025 100644 --- a/src/toniarts/openkeeper/game/logic/ChickenSpawnSystem.java +++ b/src/toniarts/openkeeper/game/logic/ChickenSpawnSystem.java @@ -43,6 +43,7 @@ import toniarts.openkeeper.game.listener.RoomListener; import toniarts.openkeeper.tools.convert.map.Room; import toniarts.openkeeper.tools.convert.map.Variable; +import toniarts.openkeeper.utils.GameTimeCounter; import toniarts.openkeeper.utils.Utils; import toniarts.openkeeper.utils.WorldUtils; @@ -51,7 +52,7 @@ * * @author Toni Helenius */ -public final class ChickenSpawnSystem implements IGameLogicUpdatable { +public final class ChickenSpawnSystem extends GameTimeCounter { private final EntityData entityData; private final IObjectsController objectsController; @@ -70,6 +71,7 @@ public final class ChickenSpawnSystem implements IGameLogicUpdatable { public ChickenSpawnSystem(EntityData entityData, IObjectsController objectsController, Collection playerControllers, Map gameSettings, ILevelInfo levelInfo, IMapController mapController) { + this.entityData = entityData; this.objectsController = objectsController; this.mapController = mapController; @@ -118,37 +120,33 @@ public ChickenSpawnSystem(EntityData entityData, IObjectsController objectsContr } @Override - public void processTick(float tpf, double gameTime) { - + public void processTick(float tpf) { + super.processTick(tpf); // Add new & remove old // Basically they don't move, so lets not worry about that now, also the owner goes with the room if (freerangeChickenGenerators.applyChanges()) { processDeletedEntities(freerangeChickenGenerators.getRemovedEntities()); - processAddedEntities(freerangeChickenGenerators.getAddedEntities()); } if (freerangeChickens.applyChanges()) { processDeletedChickenEntities(freerangeChickens.getRemovedEntities()); - processAddedChickenEntities(freerangeChickens.getAddedEntities()); - processChangedChickenEntities(freerangeChickens.getChangedEntities()); } for (IChickenGenerator entrance : entrances.getArray()) { - evaluateAndSpawnCreature(entrance, gameTime); + evaluateAndSpawnCreature(entrance); } } - private void evaluateAndSpawnCreature(IChickenGenerator entrance, double gameTime) { - double timeSinceLastSpawn = gameTime - entrance.getLastSpawnTime(); + private void evaluateAndSpawnCreature(IChickenGenerator entrance) { + double timeSinceLastSpawn = timeElapsed - entrance.getLastSpawnTime(); boolean spawned = false; EntityId entityId = null; if (timeSinceLastSpawn >= entranceCooldownTime) { if (!entrance.isFullCapacity()) { - // Spawn chicken Point entranceCoordinate = entrance.getEntranceCoordinate(); entityId = objectsController.spawnChicken(entrance.getOwnerId(), WorldUtils.pointToVector3f(entranceCoordinate)); @@ -156,22 +154,21 @@ private void evaluateAndSpawnCreature(IChickenGenerator entrance, double gameTim } else if (freeRangeChickensByPlayer.get(entrance.getOwnerId()).size() < maximumFreerangeChickenCount && freerangeChickenGeneratorsByRoom.get(entrance) != null) { Set generators = freerangeChickenGeneratorsByRoom.get(entrance); Optional generator = Utils.getRandomItem(generators); - if (generator.isPresent()) { + if (generator.isPresent()) { // Spawn a free range chicken // Don't give the entity ID, it is not added to room inventory // TODO: Need to have the generator IN USE component etc. This goes for all the objects, how we use them Position position = entityData.getComponent(generator.get(), Position.class); - objectsController.spawnFreerangeChicken(entrance.getOwnerId(), position.position.clone(), gameTime); + objectsController.spawnFreerangeChicken(entrance.getOwnerId(), position.position.clone(), timeElapsed); spawned = true; } } } if (spawned) { - // Reset spawn time - entrance.onSpawn(gameTime, entityId); + entrance.onSpawn(timeElapsed, entityId); } } diff --git a/src/toniarts/openkeeper/game/logic/CreatureAiSystem.java b/src/toniarts/openkeeper/game/logic/CreatureAiSystem.java index 73e170695..d1582b88d 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureAiSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureAiSystem.java @@ -31,9 +31,8 @@ import toniarts.openkeeper.game.task.ITaskManager; /** - * Handles creature logic updates, the creature AI updates that is. The AI is - * implemented elsewhere for clarity. This class just attaches the AI to the - * entity having this component and updates it periodically. + * Handles creature logic updates, the creature AI updates that is. The AI is implemented elsewhere for + * clarity. This class just attaches the AI to the entity having this component and updates it periodically. * * @author Toni Helenius */ @@ -57,18 +56,16 @@ public CreatureAiSystem(EntityData entityData, ICreaturesController creaturesCon } @Override - public void processTick(float tpf, double gameTime) { - + public void processTick(float tpf) { // Add new & remove old if (creatureEntities.applyChanges()) { processDeletedEntities(creatureEntities.getRemovedEntities()); - processAddedEntities(creatureEntities.getAddedEntities()); } // Process ticks for (ICreatureController creatureController : creatureControllers.getArray()) { - creatureController.processTick(tpf, gameTime); + creatureController.processTick(tpf); } // We have a specialty here, process creature worker queue diff --git a/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java b/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java index 8d130a15a..e483aea3e 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java @@ -84,7 +84,7 @@ public CreatureExperienceSystem(EntityData entityData, KwdFile kwdFile, } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { if (experienceEntities.applyChanges()) { processAddedEntities(experienceEntities.getAddedEntities(), experienceEntityIds); diff --git a/src/toniarts/openkeeper/game/logic/CreatureFallSystem.java b/src/toniarts/openkeeper/game/logic/CreatureFallSystem.java index abc1fe45d..035ace02c 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureFallSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureFallSystem.java @@ -24,20 +24,21 @@ import toniarts.openkeeper.game.component.CreatureFall; import toniarts.openkeeper.game.component.Position; import toniarts.openkeeper.game.controller.creature.CreatureState; +import toniarts.openkeeper.utils.GameTimeCounter; import toniarts.openkeeper.utils.WorldUtils; -import toniarts.openkeeper.view.map.MapViewController; /** - * Handles creature falling (dropped from hand). In the future maybe all these - * would be handled by a physics thingie? + * Handles creature falling (dropped from hand). In the future maybe all these would be handled by a physics + * thingie? * * @author Toni Helenius */ -public final class CreatureFallSystem implements IGameLogicUpdatable { +public final class CreatureFallSystem extends GameTimeCounter { + + private static final float GRAVITY = 2.0f; private final EntityData entityData; private final EntitySet fallEntities; - private static final float GRAVITY = 2.0f; public CreatureFallSystem(EntityData entityData) { this.entityData = entityData; @@ -46,8 +47,8 @@ public CreatureFallSystem(EntityData entityData) { } @Override - public void processTick(float tpf, double gameTime) { - + public void processTick(float tpf) { + super.processTick(tpf); // Add new & remove old fallEntities.applyChanges(); @@ -64,7 +65,7 @@ public void processTick(float tpf, double gameTime) { // The state, depending on the landing CreatureComponent creatureComponent = entity.get(CreatureComponent.class); - entityData.setComponent(entity.getId(), new CreatureAi(gameTime, creatureComponent.stunDuration > 0f ? CreatureState.FALLEN : CreatureState.IDLE, creatureComponent.creatureId)); + entityData.setComponent(entity.getId(), new CreatureAi(timeElapsed, creatureComponent.stunDuration > 0f ? CreatureState.FALLEN : CreatureState.IDLE, creatureComponent.creatureId)); } } } diff --git a/src/toniarts/openkeeper/game/logic/CreatureMoodSystem.java b/src/toniarts/openkeeper/game/logic/CreatureMoodSystem.java index 0f0f356c2..1a8a5e58c 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureMoodSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureMoodSystem.java @@ -43,7 +43,7 @@ public CreatureMoodSystem(EntityData entityData, Map - * It is confirmed behavior in the original game that multiple portals that have - * the same counter value will spawn creatures at the same time. Even one could - * think that since the creature count goes up, we wouldn't be able to spawn - * more than one creature at the time as the cooldown requirement increases. We - * replicate this behavior 100% since we actually count the creatures once per - * tick. + * It is confirmed behavior in the original game that multiple portals that have the same counter value will + * spawn creatures at the same time. Even one could think that since the creature count goes up, we wouldn't + * be able to spawn more than one creature at the time as the cooldown requirement increases. We replicate + * this behavior 100% since we actually count the creatures once per tick. * * @author Toni Helenius */ -public final class CreatureSpawnSystem implements IGameLogicUpdatable { +public final class CreatureSpawnSystem extends GameTimeCounter { private final ICreaturesController creaturesController; private final int minimumImpCount; @@ -105,20 +104,22 @@ public CreatureSpawnSystem(ICreaturesController creaturesController, Collection< } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { + super.processTick(tpf); + for (ICreatureEntrance entrance : entrances.getArray()) { - evaluateAndSpawnCreature(entrance, gameTime); + evaluateAndSpawnCreature(entrance); } } - private void evaluateAndSpawnCreature(ICreatureEntrance entrance, double gameTime) { + private void evaluateAndSpawnCreature(ICreatureEntrance entrance) { // TODO: we should have a listener for destroy that we can remove the room if (entrance.isDestroyed()) { return; } - double timeSinceLastSpawn = gameTime - entrance.getLastSpawnTime(); + double timeSinceLastSpawn = timeElapsed - entrance.getLastSpawnTime(); IPlayerController player = playerControllersById.get(entrance.getOwnerId()); boolean spawned = false; EntityId entityId = null; @@ -160,7 +161,7 @@ private void evaluateAndSpawnCreature(ICreatureEntrance entrance, double gameTim if (spawned) { // Reset spawn time - entrance.onSpawn(gameTime, entityId); + entrance.onSpawn(timeElapsed, entityId); } } @@ -220,8 +221,7 @@ public void stop() { } /** - * In DK 2 it is not possible to place spawn points in game, but but we - * don't know that + * In DK 2 it is not possible to place spawn points in game, but but we don't know that */ private final class EntranceListener implements RoomListener { diff --git a/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java b/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java index 794fb96a2..9db9084aa 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java @@ -25,15 +25,15 @@ import toniarts.openkeeper.game.component.Position; import toniarts.openkeeper.game.controller.ICreaturesController; import toniarts.openkeeper.game.map.IMapInformation; +import toniarts.openkeeper.utils.GameTimeCounter; import toniarts.openkeeper.utils.WorldUtils; /** - * Handles creatures torturing. When enough... persuasion has been received... - * join the player army + * Handles creatures torturing. When enough... persuasion has been received... join the player army * * @author Toni Helenius */ -public final class CreatureTorturingSystem implements IGameLogicUpdatable { +public final class CreatureTorturingSystem extends GameTimeCounter { private final EntityData entityData; private final ICreaturesController creaturesController; @@ -50,15 +50,15 @@ public CreatureTorturingSystem(EntityData entityData, ICreaturesController creat } @Override - public void processTick(float tpf, double gameTime) { - + public void processTick(float tpf) { + super.processTick(tpf); // Add new & remove old torturedEntities.applyChanges(); // Process ticks for (Entity entity : torturedEntities) { CreatureTortured creatureTortured = entity.get(CreatureTortured.class); - if (creatureTortured.tortureCheckTime >= gameTime) { + if (creatureTortured.tortureCheckTime >= timeElapsed) { continue; } @@ -76,7 +76,7 @@ public void processTick(float tpf, double gameTime) { } } - entity.set(new CreatureTortured(creatureTortured.timeTortured + tpf, gameTime, creatureTortured.healthCheckTime)); + entity.set(new CreatureTortured(creatureTortured.timeTortured + tpf, timeElapsed, creatureTortured.healthCheckTime)); } } diff --git a/src/toniarts/openkeeper/game/logic/CreatureViewSystem.java b/src/toniarts/openkeeper/game/logic/CreatureViewSystem.java index f88a8244d..6f8c6e63d 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureViewSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureViewSystem.java @@ -35,14 +35,15 @@ import toniarts.openkeeper.game.task.TaskType; import static toniarts.openkeeper.game.task.TaskType.TRAIN; import toniarts.openkeeper.tools.convert.map.Creature; +import toniarts.openkeeper.utils.GameTimeCounter; /** - * Handles creature animation states. These are based on the creature states and - * such. I'm not entirely sure is this the way this would work but hey... + * Handles creature animation states. These are based on the creature states and such. I'm not entirely sure + * is this the way this would work but hey... * * @author Toni Helenius */ -public final class CreatureViewSystem implements IGameLogicUpdatable { +public final class CreatureViewSystem extends GameTimeCounter { private final EntityData entityData; private final EntitySet creatureViewEntities; @@ -58,13 +59,11 @@ public CreatureViewSystem(EntityData entityData) { } @Override - public void processTick(float tpf, double gameTime) { - + public void processTick(float tpf) { + super.processTick(tpf); // Add new & remove old if (creatureViewEntities.applyChanges()) { - processAddedEntities(creatureViewEntities.getAddedEntities()); - processDeletedEntities(creatureViewEntities.getRemovedEntities()); } @@ -95,7 +94,7 @@ public void processTick(float tpf, double gameTime) { // Change! if (currentState != targetState) { - entityData.setComponent(entityId, new CreatureViewState(state.creatureId, gameTime, targetState)); + entityData.setComponent(entityId, new CreatureViewState(state.creatureId, timeElapsed, targetState)); } } } diff --git a/src/toniarts/openkeeper/game/logic/DeathSystem.java b/src/toniarts/openkeeper/game/logic/DeathSystem.java index 34b551612..750792ff7 100644 --- a/src/toniarts/openkeeper/game/logic/DeathSystem.java +++ b/src/toniarts/openkeeper/game/logic/DeathSystem.java @@ -26,16 +26,16 @@ import java.util.Set; import toniarts.openkeeper.game.component.Death; import toniarts.openkeeper.tools.convert.map.Variable; +import toniarts.openkeeper.utils.GameTimeCounter; /** - * The waste disposal class, removes entities after reasonable amount of time - * has passed (i.e. death animations or corpse decays have passed). Also handles - * passing other posessions related to the entity, maybe some other universal - * remove listener would be better in the long run... + * The waste disposal class, removes entities after reasonable amount of time has passed (i.e. death + * animations or corpse decays have passed). Also handles passing other posessions related to the entity, + * maybe some other universal remove listener would be better in the long run... * * @author Toni Helenius */ -public final class DeathSystem implements IGameLogicUpdatable { +public final class DeathSystem extends GameTimeCounter { private final IEntityPositionLookup entityPositionLookup; private final EntitySet deathEntities; @@ -56,24 +56,23 @@ public DeathSystem(EntityData entityData, Map= timeToDecay) { + if (timeElapsed - death.startTime >= timeToDecay) { entityData.removeEntity(entityId); } } } - private void processAddedEntities(Set entities) { for (Entity entity : entities) { int index = Collections.binarySearch(entityIds, entity.getId()); diff --git a/src/toniarts/openkeeper/game/logic/DecaySystem.java b/src/toniarts/openkeeper/game/logic/DecaySystem.java index 831fe89fb..5329d50af 100644 --- a/src/toniarts/openkeeper/game/logic/DecaySystem.java +++ b/src/toniarts/openkeeper/game/logic/DecaySystem.java @@ -27,13 +27,14 @@ import toniarts.openkeeper.game.component.Health; import toniarts.openkeeper.game.component.Position; import toniarts.openkeeper.game.controller.entity.EntityController; +import toniarts.openkeeper.utils.GameTimeCounter; /** * Handles entity decaying * * @author Toni Helenius */ -public final class DecaySystem implements IGameLogicUpdatable { +public final class DecaySystem extends GameTimeCounter { private final EntitySet decayEntities; private final EntityData entityData; @@ -48,7 +49,9 @@ public DecaySystem(EntityData entityData) { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { + super.processTick(tpf); + if (decayEntities.applyChanges()) { processDeletedEntities(decayEntities.getRemovedEntities()); @@ -58,8 +61,7 @@ public void processTick(float tpf, double gameTime) { // Decay stuff for (Entity entity : decayEntities) { Decay decay = entity.get(Decay.class); - if (gameTime - decay.startTime >= decay.duration) { - + if (timeElapsed - decay.startTime >= decay.duration) { // Decay entityData.removeComponent(entity.getId(), Decay.class); decay(entity.getId()); diff --git a/src/toniarts/openkeeper/game/logic/DoorViewSystem.java b/src/toniarts/openkeeper/game/logic/DoorViewSystem.java index 2efa7ff8c..a56dc21ef 100644 --- a/src/toniarts/openkeeper/game/logic/DoorViewSystem.java +++ b/src/toniarts/openkeeper/game/logic/DoorViewSystem.java @@ -60,7 +60,7 @@ public DoorViewSystem(EntityData entityData, IEntityPositionLookup entityPositio } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { // Add new & remove old if (doorEntities.applyChanges()) { diff --git a/src/toniarts/openkeeper/game/logic/DungeonHeartConstruction.java b/src/toniarts/openkeeper/game/logic/DungeonHeartConstruction.java index eaad9d397..b2f53e588 100644 --- a/src/toniarts/openkeeper/game/logic/DungeonHeartConstruction.java +++ b/src/toniarts/openkeeper/game/logic/DungeonHeartConstruction.java @@ -28,13 +28,14 @@ import toniarts.openkeeper.game.component.Owner; import toniarts.openkeeper.game.component.Position; import toniarts.openkeeper.game.controller.room.FiveByFiveRotatedController; +import toniarts.openkeeper.utils.GameTimeCounter; /** * Constructs dungeon hearts (deals with the animation) * * @author Toni Helenius */ -public final class DungeonHeartConstruction implements IGameLogicUpdatable { +public final class DungeonHeartConstruction extends GameTimeCounter { private static final float GRAVITY = 9.81f; private final float velocity = 7f; @@ -44,7 +45,6 @@ public final class DungeonHeartConstruction implements IGameLogicUpdatable { private final Map> dungeonHeartStairs; private final Map> dungeonHeartArches; private final float delay; - private float duration = 0.0f; private boolean done = false; public DungeonHeartConstruction(EntityData entityData, float delay) { @@ -98,19 +98,23 @@ public DungeonHeartConstruction(EntityData entityData, float delay) { } @Override - public void processTick(float tpf, double gameTime) { - if (!done && gameTime >= delay) { - - // Process ticks - for (EntityId entityId : dungeonHeartPlugs) { - if (duration > 11) { - //plugDecay.removeFromParent(); - //spatial.removeControl(this); - showStepsAndArches(entityId); - done = true; - } else if (duration > 9) { - - // This I think is effect rather than real objects, so purely on client then? + public void processTick(float tpf) { + super.processTick(tpf); + + if (done || timeElapsed < delay) { + return; + } + + // Process ticks + for (EntityId entityId : dungeonHeartPlugs) { + if (timeElapsed > 11) { + //plugDecay.removeFromParent(); + //spatial.removeControl(this); + showStepsAndArches(entityId); + done = true; + } else if (timeElapsed > 9) { + + // This I think is effect rather than real objects, so purely on client then? // velocity -= GRAVITY * tpf; // for (Spatial piece : plugDecay.getChildren()) { // float rotate = (float) piece.getUserData("rotate") * 10 * tpf; @@ -121,25 +125,22 @@ public void processTick(float tpf, double gameTime) { // //piece.move(tpf * FastMath.cos(step), velocity * tpf, tpf * FastMath.sin(step)); // piece.rotate(rotate, rotate, rotate); // } - } else if (duration > 6) { + } else if (timeElapsed > 6) { - // Remove the plug - entityData.removeEntity(entityId); + // Remove the plug + entityData.removeEntity(entityId); - // Show the pieces - //plugDecay.setCullHint(Spatial.CullHint.Inherit); - } + // Show the pieces + //plugDecay.setCullHint(Spatial.CullHint.Inherit); } + } - duration += tpf; - - // If done, cleanup - if (done) { - dungeonHeartPlugs.clear(); - dungeonHeartPlugPieces.clear(); - dungeonHeartStairs.clear(); - dungeonHeartArches.clear(); - } + // If done, cleanup + if (done) { + dungeonHeartPlugs.clear(); + dungeonHeartPlugPieces.clear(); + dungeonHeartStairs.clear(); + dungeonHeartArches.clear(); } } diff --git a/src/toniarts/openkeeper/game/logic/GameLogicManager.java b/src/toniarts/openkeeper/game/logic/GameLogicManager.java index c783c93f7..708a8da6a 100644 --- a/src/toniarts/openkeeper/game/logic/GameLogicManager.java +++ b/src/toniarts/openkeeper/game/logic/GameLogicManager.java @@ -18,20 +18,16 @@ import java.lang.System.Logger; import java.lang.System.Logger.Level; -import java.util.concurrent.TimeUnit; -import toniarts.openkeeper.utils.IGameLoopManager; /** * Runs the game logic tasks, well, doesn't literally run them but wraps them up * * @author Toni Helenius */ -public final class GameLogicManager implements IGameLoopManager { - +public final class GameLogicManager implements IGameLogicUpdatable { + private static final Logger logger = System.getLogger(GameLogicManager.class.getName()); - private long ticks = 0; - private double timeElapsed = 0.0; protected final IGameLogicUpdatable[] updatables; public GameLogicManager(IGameLogicUpdatable... updatables) { @@ -46,28 +42,15 @@ public void start() { } @Override - public void processTick(long delta) { - - // Update game time - long start = System.nanoTime(); - float tpf = delta / 1000000000f; - + public void processTick(float tpf) { // Update updatables for (IGameLogicUpdatable updatable : updatables) { try { - updatable.processTick(tpf, timeElapsed); + updatable.processTick(tpf); } catch (Exception e) { logger.log(Level.ERROR, "Error in game logic tick on " + updatable.getClass() + "!", e); } } - - // Logging - long tickTime = System.nanoTime() - start; - logger.log(tickTime < delta ? Level.TRACE : Level.ERROR, "Tick took {0} ms!", TimeUnit.MILLISECONDS.convert(tickTime, TimeUnit.NANOSECONDS)); - - // Increase ticks & time - timeElapsed += tpf; - ticks++; } @Override @@ -77,24 +60,6 @@ public void stop() { } } - /** - * Get the elapsed game time, in seconds - * - * @return the game time - */ - public double getGameTime() { - return timeElapsed; - } - - /** - * Get the amount of game ticks ticked over - * - * @param ticks the ticks - */ - public void setTicks(long ticks) { - this.ticks = ticks; - } - // private void drawCreatureVisibilities() { // Node node = new Node("Visibilities"); // float elevation = 0.1f; @@ -139,7 +104,4 @@ public void setTicks(long ticks) { // worldState.getWorld().attachChild(node); // }); // } - public long getTicks() { - return ticks; - } } diff --git a/src/toniarts/openkeeper/game/logic/HaulingSystem.java b/src/toniarts/openkeeper/game/logic/HaulingSystem.java index e0d04025f..58ddefc15 100644 --- a/src/toniarts/openkeeper/game/logic/HaulingSystem.java +++ b/src/toniarts/openkeeper/game/logic/HaulingSystem.java @@ -40,7 +40,7 @@ public HaulingSystem(EntityData entityData) { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { // Add new & remove old hauledEntities.applyChanges(); diff --git a/src/toniarts/openkeeper/game/logic/HealthSystem.java b/src/toniarts/openkeeper/game/logic/HealthSystem.java index 11778018a..43135e35a 100644 --- a/src/toniarts/openkeeper/game/logic/HealthSystem.java +++ b/src/toniarts/openkeeper/game/logic/HealthSystem.java @@ -55,16 +55,15 @@ import toniarts.openkeeper.tools.convert.map.Creature; import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Variable; +import toniarts.openkeeper.utils.GameTimeCounter; /** - * Manages and monitors thing healthiness. Beeb... beeb... beeeeeeeeeeeeeeeeeeeb - * :) + * Manages and monitors thing healthiness. Beeb... beeb... beeeeeeeeeeeeeeeeeeeb :) * * @author Toni Helenius */ -public final class HealthSystem implements IGameLogicUpdatable { +public final class HealthSystem extends GameTimeCounter { - private final KwdFile kwdFile; private final EntityData entityData; private final SafeArrayList entityIds; private final IEntityPositionLookup entityPositionLookup; @@ -83,11 +82,11 @@ public final class HealthSystem implements IGameLogicUpdatable { private final EntitySet regeneratedEntities; private final EntitySet recuperatingEntities; - public HealthSystem(EntityData entityData, KwdFile kwdFile, IEntityPositionLookup entityPositionLookup, + public HealthSystem(EntityData entityData, IEntityPositionLookup entityPositionLookup, Map gameSettings, ICreaturesController creaturesController, ILevelInfo levelInfo, Collection playerControllers, IMapController mapController) { - this.kwdFile = kwdFile; + this.entityData = entityData; this.entityPositionLookup = entityPositionLookup; this.creaturesController = creaturesController; @@ -120,16 +119,13 @@ public HealthSystem(EntityData entityData, KwdFile kwdFile, IEntityPositionLooku } @Override - public void processTick(float tpf, double gameTime) { - + public void processTick(float tpf) { + super.processTick(tpf); // Update the registry of all entities with health if (healthEntities.applyChanges()) { - processAddedEntities(healthEntities.getAddedEntities()); - processDeletedEntities(healthEntities.getRemovedEntities()); - - processChangedEntities(healthEntities.getChangedEntities(), gameTime); + processChangedEntities(healthEntities.getChangedEntities()); } // Update other monitorable sets @@ -150,8 +146,8 @@ public void processTick(float tpf, double gameTime) { // From unconsciousness we start the countdown to death if (unconscious != null) { - if (gameTime - unconscious.startTime >= timeToDeath) { - processDeath(entityId, gameTime); + if (timeElapsed - unconscious.startTime >= timeToDeath) { + processDeath(entityId); } continue; } @@ -162,21 +158,21 @@ public void processTick(float tpf, double gameTime) { continue; } - int healthChange = calculateHealthChange(entityId, health, gameTime); + int healthChange = calculateHealthChange(entityId, health, timeElapsed); if (healthChange == 0) { continue; } // Set new health or death if (health.health + healthChange <= 0) { - processHealthDepleted(entityId, gameTime, health); + processHealthDepleted(entityId, health); } else { entityData.setComponent(entityId, new Health(Math.min(health.health + healthChange, health.maxHealth), health.maxHealth)); } } } - private void processHealthDepleted(EntityId entityId, double gameTime, Health health) { + private void processHealthDepleted(EntityId entityId, Health health) { // Death or destruction!!!! // No body, just vanish from the world @@ -187,7 +183,7 @@ private void processHealthDepleted(EntityId entityId, double gameTime, Health he // Tortured entities just die outright if (torturedEntities.containsId(entityId)) { - processDeath(entityId, gameTime); + processDeath(entityId); return; } @@ -199,12 +195,12 @@ private void processHealthDepleted(EntityId entityId, double gameTime, Health he creaturesController.turnCreatureIntoAnother(entityId, owner.controlId, creatureId); return; } - processDeath(entityId, gameTime); + processDeath(entityId); return; } // Leave the entity incapacitaded and waiting for death... or rescue - processUnconscious(entityId, health, gameTime); + processUnconscious(entityId, health); } private Short getRoomCreatureId(EntityId entityId) { @@ -238,13 +234,14 @@ private boolean canRiseAsSkeleton(Owner owner, short creatureId) { int capacity = mapController.getPlayerSkeletonCapacity(owner.controlId); - return capacity > playerControllersById.get(owner.controlId).getCreatureControl().getTypeCount(kwdFile.getCreature(creatureId)); + return capacity > playerControllersById.get(owner.controlId).getCreatureControl() + .getTypeCount(levelInfo.getLevelData().getCreature(creatureId)); } - private void processUnconscious(EntityId entityId, Health health, double gameTime) { + private void processUnconscious(EntityId entityId, Health health) { entityData.removeComponent(entityId, AttackTarget.class); entityData.setComponent(entityId, new Health(0, health.maxHealth)); - entityData.setComponent(entityId, new Unconscious(gameTime)); + entityData.setComponent(entityId, new Unconscious(timeElapsed)); //entityData.setComponent(entityId, new CreatureAi(gameTime, CreatureState.UNCONSCIOUS, creatureComponent.creatureId)); // Hmm creaturesController.createController(entityId).getStateMachine().changeState(CreatureState.UNCONSCIOUS); entityData.removeComponent(entityId, Navigation.class); @@ -252,7 +249,9 @@ private void processUnconscious(EntityId entityId, Health health, double gameTim private boolean isLeaveDeadBody(EntityId entityId) { CreatureComponent creatureComponent = entityData.getComponent(entityId, CreatureComponent.class); - return creatureComponent != null && kwdFile.getCreature(creatureComponent.creatureId).getFlags().contains(Creature.CreatureFlag.GENERATE_DEAD_BODY); + return creatureComponent != null && levelInfo.getLevelData() + .getCreature(creatureComponent.creatureId) + .getFlags().contains(Creature.CreatureFlag.GENERATE_DEAD_BODY); } private int calculateHealthChange(EntityId entityId, Health health, double gameTime) { @@ -330,7 +329,7 @@ private int calculateHealthChange(EntityId entityId, Health health, double gameT return delta; } - private void processDeath(EntityId entityId, double gameTime) { + private void processDeath(EntityId entityId) { entityData.removeComponent(entityId, Health.class); entityData.removeComponent(entityId, CreatureAi.class); entityData.removeComponent(entityId, ChickenAi.class); @@ -342,7 +341,7 @@ private void processDeath(EntityId entityId, double gameTime) { entityData.removeComponent(entityId, CreatureImprisoned.class); entityData.removeComponent(entityId, CreatureTortured.class); entityData.removeComponent(entityId, CreatureMood.class); - entityData.setComponent(entityId, new Death(gameTime)); + entityData.setComponent(entityId, new Death(timeElapsed)); } private void processAddedEntities(Set entities) { @@ -359,9 +358,8 @@ private void processDeletedEntities(Set entities) { } } - private void processChangedEntities(Set entities, double gameTime) { + private void processChangedEntities(Set entities) { for (Entity entity : entities) { - // If the health is changed (either by us or damage)... // Reset the health regen counter Regeneration regeneration = entityData.getComponent(entity.getId(), Regeneration.class); diff --git a/src/toniarts/openkeeper/game/logic/IGameLogicUpdatable.java b/src/toniarts/openkeeper/game/logic/IGameLogicUpdatable.java index 53148e94d..f051d8c50 100644 --- a/src/toniarts/openkeeper/game/logic/IGameLogicUpdatable.java +++ b/src/toniarts/openkeeper/game/logic/IGameLogicUpdatable.java @@ -39,8 +39,7 @@ public interface IGameLogicUpdatable { * * @param tpf time since the last call to update(), in seconds. Our tick * rate. - * @param gameTime elapsed game time */ - public void processTick(float tpf, double gameTime); + public void processTick(float tpf); } diff --git a/src/toniarts/openkeeper/game/logic/LooseObjectSystem.java b/src/toniarts/openkeeper/game/logic/LooseObjectSystem.java index 865335541..f3f59c59f 100644 --- a/src/toniarts/openkeeper/game/logic/LooseObjectSystem.java +++ b/src/toniarts/openkeeper/game/logic/LooseObjectSystem.java @@ -68,7 +68,7 @@ public LooseObjectSystem(EntityData entityData, IMapController mapController, Ma } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { // Add new & remove old if (looseObjectEntities.applyChanges()) { diff --git a/src/toniarts/openkeeper/game/logic/ManaCalculatorLogic.java b/src/toniarts/openkeeper/game/logic/ManaCalculatorLogic.java index b3a6e027a..af004bf8e 100644 --- a/src/toniarts/openkeeper/game/logic/ManaCalculatorLogic.java +++ b/src/toniarts/openkeeper/game/logic/ManaCalculatorLogic.java @@ -61,7 +61,7 @@ public ManaCalculatorLogic(Collection playerControllers, Enti } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { tick += tpf; if (tick >= 1) { updateManaSources(); diff --git a/src/toniarts/openkeeper/game/logic/MovementSystem.java b/src/toniarts/openkeeper/game/logic/MovementSystem.java index e7a4f6306..e495a5136 100644 --- a/src/toniarts/openkeeper/game/logic/MovementSystem.java +++ b/src/toniarts/openkeeper/game/logic/MovementSystem.java @@ -70,7 +70,7 @@ public MovementSystem(EntityData entityData) { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { if (movableEntities.applyChanges()) { processDeletedEntities(movableEntities.getRemovedEntities()); diff --git a/src/toniarts/openkeeper/game/logic/PlayerCreatureSystem.java b/src/toniarts/openkeeper/game/logic/PlayerCreatureSystem.java index 9821abc0f..e5cec3a3a 100644 --- a/src/toniarts/openkeeper/game/logic/PlayerCreatureSystem.java +++ b/src/toniarts/openkeeper/game/logic/PlayerCreatureSystem.java @@ -56,7 +56,7 @@ public PlayerCreatureSystem(EntityData entityData, KwdFile kwdFile, Collection[][] initializeMatrix(int width, int height) { } @Override - public void processTick(float tpf, double gameTime) { - + public void processTick(float tpf) { // This is just a cache for a tick sensedEntitiesByEntity.clear(); if (positionedEntities.applyChanges()) { - processAddedEntities(positionedEntities.getAddedEntities()); - processDeletedEntities(positionedEntities.getRemovedEntities()); - processChangedEntities(positionedEntities.getChangedEntities()); } } private void processChangedEntities(Set entities) { - // Update for (Entity entity : entities) { Point p = WorldUtils.vectorToPoint(entity.get(Position.class).position); diff --git a/src/toniarts/openkeeper/game/logic/PossessedSystem.java b/src/toniarts/openkeeper/game/logic/PossessedSystem.java index 41c64beb4..45b4a4e6c 100644 --- a/src/toniarts/openkeeper/game/logic/PossessedSystem.java +++ b/src/toniarts/openkeeper/game/logic/PossessedSystem.java @@ -28,13 +28,14 @@ import toniarts.openkeeper.game.controller.IGameController; import toniarts.openkeeper.game.controller.IPlayerController; import toniarts.openkeeper.game.controller.player.PlayerManaControl; +import toniarts.openkeeper.utils.GameTimeCounter; /** * Maintains possessed state, sees when we need to stop possessing * * @author Toni Helenius */ -public final class PossessedSystem implements IGameLogicUpdatable { +public final class PossessedSystem extends GameTimeCounter { private final EntitySet possessedEntities; private final Map manaControls; @@ -58,11 +59,11 @@ public PossessedSystem(Collection playerControllers, EntityDa } @Override - public void processTick(float tpf, double gameTime) { - if (possessedEntities.applyChanges()) { + public void processTick(float tpf) { + super.processTick(tpf); + if (possessedEntities.applyChanges()) { processAddedEntities(possessedEntities.getAddedEntities()); - processDeletedEntities(possessedEntities.getRemovedEntities()); } @@ -71,14 +72,14 @@ public void processTick(float tpf, double gameTime) { // See if the player is running out of mana Possessed possessed = entity.get(Possessed.class); Owner owner = entity.get(Owner.class); - if (possessed.manaCheckTime + 1 < gameTime) { + if (possessed.manaCheckTime + 1 < timeElapsed) { continue; } if (!manaControls.get(owner.ownerId).hasEnoughMana(possessed.manaDrain)) { entityData.removeComponent(entity.getId(), Possessed.class); } else { - entityData.setComponent(entity.getId(), new Possessed(possessed.manaDrain, gameTime)); + entityData.setComponent(entity.getId(), new Possessed(possessed.manaDrain, timeElapsed)); } } } diff --git a/src/toniarts/openkeeper/game/logic/SlapSystem.java b/src/toniarts/openkeeper/game/logic/SlapSystem.java index f76a65e18..ba7b449a2 100644 --- a/src/toniarts/openkeeper/game/logic/SlapSystem.java +++ b/src/toniarts/openkeeper/game/logic/SlapSystem.java @@ -37,13 +37,16 @@ import toniarts.openkeeper.tools.convert.map.Creature; import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Variable; +import toniarts.openkeeper.utils.GameTimeCounter; /** * Manages slapping of entities, the added effects etc * * @author Toni Helenius */ -public final class SlapSystem implements IGameLogicUpdatable { +public final class SlapSystem extends GameTimeCounter { + + private final static int EFFICIENCY_BONUS = 10; private final KwdFile kwdFile; private final EntitySet creatureEntities; @@ -53,8 +56,6 @@ public final class SlapSystem implements IGameLogicUpdatable { private final Map statControls; private final Map slapStartTimesByEntityId = new HashMap<>(); - private final static int EFFICIENCY_BONUS = 10; - public SlapSystem(EntityData entityData, KwdFile kwdFile, Collection playerControllers, Map gameSettings) { this.kwdFile = kwdFile; @@ -72,29 +73,25 @@ public SlapSystem(EntityData entityData, KwdFile kwdFile, Collection entry : slapStartTimesByEntityId.entrySet()) { - if (gameTime - entry.getValue() >= maxSlapDuration) { + if (timeElapsed - entry.getValue() >= maxSlapDuration) { entityData.removeComponent(entry.getKey(), Slapped.class); } } diff --git a/src/toniarts/openkeeper/game/state/GameServerState.java b/src/toniarts/openkeeper/game/state/GameServerState.java index 98a74c0a8..75243a7ca 100644 --- a/src/toniarts/openkeeper/game/state/GameServerState.java +++ b/src/toniarts/openkeeper/game/state/GameServerState.java @@ -26,7 +26,6 @@ import java.lang.System.Logger.Level; import java.util.List; import toniarts.openkeeper.Main; -import toniarts.openkeeper.game.controller.GameController; import toniarts.openkeeper.game.controller.IGameWorldController; import toniarts.openkeeper.game.controller.IMapController; import toniarts.openkeeper.game.controller.IPlayerController; @@ -37,6 +36,7 @@ import toniarts.openkeeper.game.data.Keeper; import toniarts.openkeeper.game.listener.MapListener; import toniarts.openkeeper.game.listener.PlayerActionListener; +import toniarts.openkeeper.game.state.loop.GameLoopManager; import toniarts.openkeeper.game.state.session.GameSessionServerService; import toniarts.openkeeper.game.state.session.GameSessionServiceListener; import toniarts.openkeeper.tools.convert.map.Door; @@ -52,7 +52,7 @@ * @author Toni Helenius */ public final class GameServerState extends AbstractAppState { - + private static final Logger logger = System.getLogger(GameServerState.class.getName()); private Main app; @@ -63,7 +63,6 @@ public final class GameServerState extends AbstractAppState { private final Object loadingObject = new Object(); private volatile boolean gameLoaded = false; - private final String level; private final KwdFile kwdFile; private final toniarts.openkeeper.game.data.Level levelObject; @@ -74,7 +73,7 @@ public final class GameServerState extends AbstractAppState { private final MapListener mapListener = new MapListenerImpl(); private final GameSessionServiceListener gameSessionListener = new GameSessionServiceListenerImpl(); private final PlayerActionListener playerActionListener = new PlayerActionListenerImpl(); - private GameController gameController; + private GameLoopManager game; private IGameWorldController gameWorldController; /** @@ -86,7 +85,6 @@ public final class GameServerState extends AbstractAppState { * @param gameService the game service */ public GameServerState(KwdFile level, List players, boolean campaign, GameSessionServerService gameService) { - this.level = null; this.kwdFile = level; this.levelObject = null; this.campaign = campaign; @@ -131,9 +129,8 @@ public void initialize(final AppStateManager stateManager, final Application app } /** - * If you are getting rid of the game state, use this so that all the - * related states are detached on the same render loop. Otherwise the app - * might crash. + * If you are getting rid of the game state, use this so that all the related states are detached on the + * same render loop. Otherwise the app might crash. */ public void detach() { if (loader != null && loader.isAlive()) { @@ -141,9 +138,9 @@ public void detach() { } stateManager.detach(this); - if (gameController != null) { + if (game != null) { try { - gameController.close(); + game.stop(); } catch (Exception ex) { logger.log(Level.ERROR, "Failed to close the game!", ex); } @@ -176,37 +173,36 @@ public GameLoader(List players) { public void run() { // Make sure the KWD file is fully loaded - kwdFile.load(); + kwdFile.load(); - // Create the central game controller - gameController = new GameController(kwdFile, players, gameService.getEntityData(), kwdFile.getVariables(), gameService); - gameController.createNewGame(); + // Create the central game controller + game = new GameLoopManager(kwdFile, gameService, players); - gameWorldController = gameController.getGameWorldController(); - mapController = gameWorldController.getMapController(); - gameWorldController.addListener(playerActionListener); + gameWorldController = game.getGameController().getGameWorldController(); + mapController = gameWorldController.getMapController(); + gameWorldController.addListener(playerActionListener); - // Send the the initial game data - gameService.sendGameData(gameController.getPlayers()); + // Send the the initial game data + gameService.sendGameData(game.getGameController().getLevelInfo().getPlayers().values()); - // Set up a listener for the map - mapController.addListener(mapListener); + // Set up a listener for the map + mapController.addListener(mapListener); - // Set up a listener for the player changes, they are per player - for (IPlayerController playerController : gameController.getPlayerControllers()) { - playerController.addListener(gameService); - } + // Set up a listener for the player changes, they are per player + for (IPlayerController playerController : game.getGameController().getPlayerControllers().values()) { + playerController.addListener(gameService); + } - // Nullify the thread object - loader = null; + // Nullify the thread object + loader = null; - // Mark the server end ready - synchronized (loadingObject) { - gameLoaded = true; - loadingObject.notifyAll(); - } + // Mark the server end ready + synchronized (loadingObject) { + gameLoaded = true; + loadingObject.notifyAll(); } } + } /** * Listen for basically clients' requests @@ -230,7 +226,7 @@ public void onStartGame() { } // Start the actual game - gameController.startGame(); + game.start(); } @Override @@ -286,14 +282,14 @@ public void onTransitionEnd(short playerId) { @Override public void onPauseRequest(short playerId) { // TODO: We should only allow the server owner etc. to pause, otherwise, send a system message that player x wants to pause? - gameController.pauseGame(); + game.pause(); } @Override public void onResumeRequest(short playerId) { // TODO: We should only allow the server owner etc. to pause, otherwise, send a system message that player x wants to pause? - gameController.resumeGame(); + game.resume(); } @Override @@ -320,7 +316,7 @@ public void onCheatTriggered(CheatState.CheatType cheat, short playerId) { break; } case MANA: { - gameController.getPlayerController(playerId).getManaControl().addMana(100000); + game.getGameController().getPlayerController(playerId).getManaControl().addMana(100000); break; } case MONEY: { @@ -332,26 +328,30 @@ public void onCheatTriggered(CheatState.CheatType cheat, short playerId) { break; } case UNLOCK_ROOMS: { - PlayerRoomControl playerRoomControl = gameController.getPlayerController(playerId).getRoomControl(); + PlayerRoomControl playerRoomControl = game.getGameController() + .getPlayerController(playerId).getRoomControl(); for (Room room : kwdFile.getRooms()) { playerRoomControl.setTypeAvailable(room, true); } break; } case UNLOCK_DOORS_TRAPS: { - PlayerDoorControl playerDoorControl = gameController.getPlayerController(playerId).getDoorControl(); + PlayerDoorControl playerDoorControl = game.getGameController() + .getPlayerController(playerId).getDoorControl(); for (Door door : kwdFile.getDoors()) { playerDoorControl.setTypeAvailable(door, true); } - PlayerTrapControl playerTrapControl = gameController.getPlayerController(playerId).getTrapControl(); + PlayerTrapControl playerTrapControl = game.getGameController() + .getPlayerController(playerId).getTrapControl(); for (Trap trap : kwdFile.getTraps()) { playerTrapControl.setTypeAvailable(trap, true); } break; } case UNLOCK_SPELLS: { - PlayerSpellControl playerSpellControl = gameController.getPlayerController(playerId).getSpellControl(); + PlayerSpellControl playerSpellControl = game.getGameController() + .getPlayerController(playerId).getSpellControl(); for (KeeperSpell keeperSpell : kwdFile.getKeeperSpells()) { playerSpellControl.setTypeAvailable(keeperSpell, true); playerSpellControl.setSpellDiscovered(keeperSpell, true); @@ -359,7 +359,7 @@ public void onCheatTriggered(CheatState.CheatType cheat, short playerId) { break; } case WIN_LEVEL: { - gameController.endGame(playerId, true); + game.getGameController().endGame(playerId, true); break; } default: diff --git a/src/toniarts/openkeeper/game/state/MainMenuState.java b/src/toniarts/openkeeper/game/state/MainMenuState.java index 436190f97..f15716ae3 100644 --- a/src/toniarts/openkeeper/game/state/MainMenuState.java +++ b/src/toniarts/openkeeper/game/state/MainMenuState.java @@ -79,7 +79,7 @@ * @author Toni Helenius */ public final class MainMenuState extends AbstractAppState { - + private static final Logger logger = System.getLogger(MainMenuState.class.getName()); protected Main app; @@ -102,16 +102,17 @@ public final class MainMenuState extends AbstractAppState { private final MainMenuConnectionErrorListener connectionErrorListener = new MainMenuConnectionErrorListener(); /** - * (c) Construct a MainMenuState, you should only have one of these. Disable - * when not in use. + * (c) Construct a MainMenuState, you should only have one of these. Disable when not in use. * - * @param enabled whether to load the menu scene now, or later when needed - * (has its own loading screen here) + * @param enabled whether to load the menu scene now, or later when needed (has its own loading screen + * here) * @param assetManager asset manager for loading the screen * @param app the main application * @throws java.io.IOException may fail to load the main menu map scene */ - public MainMenuState(final boolean enabled, final AssetManager assetManager, final Main app) throws IOException { + public MainMenuState(final boolean enabled, final AssetManager assetManager, final Main app) + throws IOException { + listener = new MainMenuInteraction(this); super.setEnabled(enabled); @@ -132,7 +133,8 @@ public MainMenuState(final boolean enabled, final AssetManager assetManager, fin private void loadMenuScene(final SingleBarLoadingState loadingScreen, final AssetManager assetManager, final Main app) throws IOException { // Load the 3D Front end - kwdFile = new KwdFile(Main.getDkIIFolder(), Paths.get(PathUtils.getRealFileName(Main.getDkIIFolder() + PathUtils.DKII_MAPS_FOLDER, "FrontEnd3DLevel.kwd"))); + kwdFile = new KwdFile(Main.getDkIIFolder(), Paths.get(PathUtils.getRealFileName( + Main.getDkIIFolder() + PathUtils.DKII_MAPS_FOLDER, "FrontEnd3DLevel.kwd"))); if (loadingScreen != null) { loadingScreen.setProgress(0.25f); } @@ -222,7 +224,7 @@ public void cleanup() { shutdownMultiplayer(); if (gameController != null) { - gameController.close(); + gameController.stop(); gameController = null; } @@ -230,8 +232,7 @@ public void cleanup() { } /** - * Initialize the start menu, sets the menu scene in place & sets the - * controls and start screen + * Initialize the start menu, sets the menu scene in place & sets the controls and start screen */ private void initializeMainMenu() { @@ -426,8 +427,7 @@ public LobbyState getLobbyState() { /** * Called by the GUI, start the selected level * - * @param type where level selected. @TODO change campaign like others or - * otherwise + * @param type where level selected. @TODO change campaign like others or otherwise */ public void startLevel(String type) { if ("campaign".equals(type.toLowerCase())) { @@ -468,8 +468,7 @@ protected void onPlayingEnd() { * * @param transition name of the transition (without file extension) * @param screen the screen name - * @param transitionStatic set start static location of camera after - * transition + * @param transitionStatic set start static location of camera after transition */ protected void doTransitionAndGoToScreen(final String transition, final String screen, final String transitionStatic) { @@ -539,8 +538,7 @@ public void doDebriefing(GameResult result) { } /** - * See if the map thumbnail exist, otherwise create one TODO maybe move to - * KwdFile class ??? + * See if the map thumbnail exist, otherwise create one TODO maybe move to KwdFile class ??? * * @param map * @return path to map thumbnail file diff --git a/src/toniarts/openkeeper/game/state/loop/GameLoopManager.java b/src/toniarts/openkeeper/game/state/loop/GameLoopManager.java new file mode 100644 index 000000000..b9b251f55 --- /dev/null +++ b/src/toniarts/openkeeper/game/state/loop/GameLoopManager.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2014-2025 OpenKeeper + * + * OpenKeeper is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * OpenKeeper is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenKeeper. If not, see . + */ +package toniarts.openkeeper.game.state.loop; + +import com.simsilica.es.EntityData; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import toniarts.openkeeper.game.controller.GameController; +import toniarts.openkeeper.game.controller.IGameController; +import toniarts.openkeeper.game.controller.IGameWorldController; +import toniarts.openkeeper.game.controller.ILevelInfo; +import toniarts.openkeeper.game.controller.IPlayerController; +import toniarts.openkeeper.game.data.Keeper; +import toniarts.openkeeper.game.logic.*; +import toniarts.openkeeper.game.state.session.GameSessionServerService; +import toniarts.openkeeper.game.task.ITaskManager; +import toniarts.openkeeper.tools.convert.map.KwdFile; +import toniarts.openkeeper.tools.convert.map.Variable; +import toniarts.openkeeper.utils.GameLoop; + +/** + * + * @author ArchDemon + */ +public final class GameLoopManager { + + private final List loops = new ArrayList<>(); + + private final GameSessionServerService gameService; + + private final IGameController gameController; + + /** + * Single use game states + * + * @param level the level to load + * @param gameService + * @param players player participating in this game, can be {@code null} + */ + public GameLoopManager(KwdFile level, GameSessionServerService gameService, List players) { + this.gameService = gameService; + final EntityData entityData = gameService.getEntityData(); + final Map gameSettings = level.getVariables(); + + gameController = new GameController(level, players, gameService.getEntityData(), gameSettings, gameService); + final ILevelInfo levelInfo = gameController.getLevelInfo(); + final IGameWorldController gameWorldController = gameController.getGameWorldController(); + final IEntityPositionLookup positionSystem = gameController.getEntityLookupService(); + final Map playerControllers = gameController.getPlayerControllers(); + final ITaskManager taskManager = gameController.getTaskManager(); + + // Game logic + final GameLogicManager gameLogicThread = new GameLogicManager( + gameWorldController.getMapController(), + new DecaySystem(entityData), + new CreatureExperienceSystem(entityData, levelInfo.getLevelData(), gameSettings, + gameWorldController.getCreaturesController()), + new SlapSystem(entityData, levelInfo.getLevelData(), playerControllers.values(), gameSettings), + new HealthSystem(entityData, positionSystem, gameSettings, gameWorldController.getCreaturesController(), + levelInfo, playerControllers.values(), gameWorldController.getMapController()), + new CreatureTorturingSystem(entityData, gameWorldController.getCreaturesController(), + gameWorldController.getMapController()), + new DeathSystem(entityData, gameSettings, positionSystem), + new PlayerCreatureSystem(entityData, levelInfo.getLevelData(), playerControllers.values()), + new PlayerSpellbookSystem(entityData, levelInfo.getLevelData(), playerControllers.values()), + (IGameLogicUpdatable) gameController, + new CreatureSpawnSystem(gameWorldController.getCreaturesController(), playerControllers.values(), + gameSettings, levelInfo, gameWorldController.getMapController()), + new ChickenSpawnSystem(entityData, gameWorldController.getObjectsController(), + playerControllers.values(), gameSettings, levelInfo, gameWorldController.getMapController()), + new ManaCalculatorLogic(playerControllers.values(), entityData), + new CreatureAiSystem(entityData, gameWorldController.getCreaturesController(), taskManager), + new ChickenAiSystem(entityData, gameWorldController.getObjectsController()), + new CreatureViewSystem(entityData), + new DoorViewSystem(entityData, positionSystem), + new LooseObjectSystem(entityData, gameWorldController.getMapController(), playerControllers, positionSystem), + new HaulingSystem(entityData), + (IGameLogicUpdatable) taskManager); + + loops.add(new GameLoop(gameLogicThread, 1_000_000_000 / levelInfo.getLevelData().getGameLevel().getTicksPerSec(), "Logic")); + + // Animation systems + final GameLogicManager gameAnimationThread = new GameLogicManager( + new DungeonHeartConstruction( + entityData, + gameController.getLevelVariable(Variable.MiscVariable.MiscType.TIME_BEFORE_DUNGEON_HEART_CONSTRUCTION_BEGINS)), + new CreatureFallSystem(entityData)); + loops.add(new GameLoop(gameAnimationThread, GameLoop.INTERVAL_FPS_60, "Animation")); + + // Steering + loops.add(new GameLoop(new GameLogicManager(new MovementSystem(entityData)), GameLoop.INTERVAL_FPS_60, "Steering")); + } + + public void pause() { + loops.stream().forEach(GameLoop::pause); + gameService.setGamePaused(true); + } + + public void resume() { + loops.stream().forEach(GameLoop::resume); + gameService.setGamePaused(false); + } + + public void start() { + loops.stream().forEach(GameLoop::start); + } + + public void stop() { + loops.forEach(GameLoop::stop); + loops.clear(); + } + + public IGameController getGameController() { + return gameController; + } + +} diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index 7ccfad18f..dfa196658 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -106,7 +106,7 @@ * @author Toni Helenius */ public final class TaskManager implements ITaskManager, IGameLogicUpdatable { - + private static final Logger logger = System.getLogger(TaskManager.class.getName()); private final IMapController mapController; @@ -194,7 +194,7 @@ public void stop() { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { if (taskEntities.applyChanges()) { processDeletedTasks(taskEntities.getRemovedEntities()); processAddedTasks(taskEntities.getAddedEntities()); diff --git a/src/toniarts/openkeeper/game/trigger/AbstractThingTriggerLogicController.java b/src/toniarts/openkeeper/game/trigger/AbstractThingTriggerLogicController.java index 048837a61..d3e53f085 100644 --- a/src/toniarts/openkeeper/game/trigger/AbstractThingTriggerLogicController.java +++ b/src/toniarts/openkeeper/game/trigger/AbstractThingTriggerLogicController.java @@ -64,7 +64,7 @@ public AbstractThingTriggerLogicController(final Map logger.log(Level.WARNING, "Trigger is null!"); + case null -> + logger.log(Level.WARNING, "Trigger is null!"); case TriggerGenericData triggerGenericData -> { if (next == null && isActive(triggerGenericData)) { diff --git a/src/toniarts/openkeeper/game/trigger/actionpoint/ActionPointTriggerLogicController.java b/src/toniarts/openkeeper/game/trigger/actionpoint/ActionPointTriggerLogicController.java index 923cd8fa5..afaf4651e 100644 --- a/src/toniarts/openkeeper/game/trigger/actionpoint/ActionPointTriggerLogicController.java +++ b/src/toniarts/openkeeper/game/trigger/actionpoint/ActionPointTriggerLogicController.java @@ -56,7 +56,7 @@ public void stop() { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { for (ActionPointTriggerControl triggerControl : triggers.getArray()) { triggerControl.update(tpf); } diff --git a/src/toniarts/openkeeper/game/trigger/party/PartyTriggerLogicController.java b/src/toniarts/openkeeper/game/trigger/party/PartyTriggerLogicController.java index 096d95170..0f99b9f95 100644 --- a/src/toniarts/openkeeper/game/trigger/party/PartyTriggerLogicController.java +++ b/src/toniarts/openkeeper/game/trigger/party/PartyTriggerLogicController.java @@ -55,7 +55,7 @@ public void stop() { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { for (PartyTriggerControl partyTriggerControl : partyControllers.getArray()) { partyTriggerControl.update(tpf); } diff --git a/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerLogicController.java b/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerLogicController.java index 2b1ebc10d..d1081fc05 100644 --- a/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerLogicController.java +++ b/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerLogicController.java @@ -40,7 +40,7 @@ public final class PlayerTriggerLogicController implements IGameLogicUpdatable { public PlayerTriggerLogicController(final IGameController gameController, final ILevelInfo levelInfo, final IGameTimer gameTimer, final IMapController mapController, final ICreaturesController creaturesController, final PlayerService playerService) { Map players = levelInfo.getLevelData().getPlayers(); - for (Keeper keeper : levelInfo.getPlayers()) { + for (Keeper keeper : levelInfo.getPlayers().values()) { int triggerId = players.get(keeper.getId()).getTriggerId(); if (triggerId != 0) { playerTriggerControls.add(new PlayerTriggerControl(gameController, levelInfo, gameTimer, mapController, creaturesController, triggerId, keeper.getId(), playerService)); @@ -61,7 +61,7 @@ public void stop() { } @Override - public void processTick(float tpf, double gameTime) { + public void processTick(float tpf) { for (PlayerTriggerControl playerTriggerControl : playerTriggerControls.getArray()) { playerTriggerControl.update(tpf); } diff --git a/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java b/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java index 2d58570f7..c8efe4463 100644 --- a/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java +++ b/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java @@ -93,7 +93,7 @@ private void unloadMap() { mainMenuEntityViewState = null; } if (gameController != null) { - gameController.close(); + gameController.stop(); gameController = null; } if (mapEntityData != null) { diff --git a/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java b/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java index 112ee9318..e2275e96e 100644 --- a/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java +++ b/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java @@ -123,9 +123,9 @@ public String toString() { return name; } } - + private static final Logger logger = System.getLogger(ModelViewer.class.getName()); - + //private final static float SCALE = 2; private static String dkIIFolder; private final Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); @@ -200,22 +200,21 @@ public ModelViewer() { super(); } - @Override public void simpleInitApp() { - ((GLRenderer)renderer).setDebugEnabled(true); // get debug names for GL objects + ((GLRenderer) renderer).setDebugEnabled(true); // get debug names for GL objects // Distribution locator assetManager.registerLocator(AssetsConverter.getAssetsFolder(), FileLocator.class); assetManager.registerLoader(MP2Loader.class, "mp2"); - //Effects manager + // Effects manager this.effectManagerState = new EffectManagerState(getKwdFile(), assetManager); stateManager.attach(effectManagerState); + // init sound loader //soundLoader = new SoundsLoader(assetManager); - // Map loader mapLoaderAppState = new MapLoaderAppState(); stateManager.attach(mapLoaderAppState); @@ -268,7 +267,7 @@ public void simpleInitApp() { Node node = (Node) loader.load(asset); setupModel(node, false); } catch (Exception e) { - logger.log(Level.ERROR, "Failed to handle: " + kmfModel, e); + logger.log(Level.ERROR, "Failed to handle: " + kmfModel, e); } } } @@ -285,9 +284,9 @@ public void simpleRender(RenderManager rm) { private NiftyJmeDisplay getNiftyDisplay() { if (niftyDisplay == null) { niftyDisplay = new NiftyJmeDisplay(assetManager, - inputManager, - audioRenderer, - guiViewPort); + inputManager, + audioRenderer, + guiViewPort); guiViewPort.addProcessor(niftyDisplay); } @@ -449,8 +448,7 @@ public void onSelectionChanged(Object selection) { } case MAPS: { // Load the selected map - String file = (String) selection + ".kwd"; - KwdFile kwd = new KwdFile(dkIIFolder, Paths.get(dkIIFolder, PathUtils.DKII_MAPS_FOLDER, file)); + KwdFile kwd = getKwdFile((String) selection); Node spat = mapLoaderAppState.loadMap(kwd); GameLevel gameLevel = kwd.getGameLevel(); @@ -556,11 +554,9 @@ private void setupModel(final Node spat, boolean isMap) { // subSpat.setLocalScale(1); // subSpat.setLocalTranslation(0, 0, 0); // } - // Make it bigger and move // spat.scale(10); // spat.setLocalTranslation(10, 25, 30); - // Make it rotate RotatorControl rotator = new RotatorControl(); rotator.setEnabled(rotate); @@ -716,16 +712,19 @@ protected void fillList(Types type) { } private KwdFile getKwdFile() { + // Read Alcatraz.kwd by default if (kwdFile == null) { - - // Read Alcatraz.kwd by default - kwdFile = new KwdFile(dkIIFolder, - Paths.get(dkIIFolder, PathUtils.DKII_MAPS_FOLDER, "Alcatraz.kwd")); + kwdFile = getKwdFile("Alcatraz.kwd"); } return kwdFile; } + private KwdFile getKwdFile(String name) { + return new KwdFile(dkIIFolder, + Paths.get(dkIIFolder, PathUtils.DKII_MAPS_FOLDER, name)); + } + public void onSoundChanged(SoundFile soundFile) { AudioNode node = SoundsLoader.getAudioNode(assetManager, soundFile); node.setLooping(false); diff --git a/src/toniarts/openkeeper/utils/GameLoop.java b/src/toniarts/openkeeper/utils/GameLoop.java index 34b072b93..9fd991b58 100644 --- a/src/toniarts/openkeeper/utils/GameLoop.java +++ b/src/toniarts/openkeeper/utils/GameLoop.java @@ -16,7 +16,11 @@ */ package toniarts.openkeeper.utils; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import toniarts.openkeeper.game.logic.IGameLogicUpdatable; /** * A game loop. This is a fork of Paul Speeds class of a same name. @@ -25,88 +29,49 @@ */ public final class GameLoop { + private static final Logger logger = System.getLogger(GameLoop.class.getName()); + public static final long INTERVAL_FPS_60 = 16666667L; - private static final String DEFAULT_NAME = "GameLoopThread"; + private static final String NAME_PREFIX = "Game"; - private final IGameLoopManager gameLoopManager; + private final IGameLogicUpdatable gameLoopManager; private final Runner loop; private final long updateRate; - private long idleSleepTime = 0; private final AtomicBoolean pauseFlag = new AtomicBoolean(false); - public GameLoop(IGameLoopManager gameLoopManager) { + public GameLoop(IGameLogicUpdatable gameLoopManager) { this(gameLoopManager, INTERVAL_FPS_60); // 60 FPS } - public GameLoop(IGameLoopManager gameLoopManager, long updateRateNanos) { - this(gameLoopManager, updateRateNanos, DEFAULT_NAME); - } - - public GameLoop(IGameLoopManager gameLoopManager, long updateRateNanos, String name) { - this(gameLoopManager, updateRateNanos, name, null); + public GameLoop(IGameLogicUpdatable gameLoopManager, long updateRateNanos) { + this(gameLoopManager, updateRateNanos, ""); } - public GameLoop(IGameLoopManager gameLoopManager, long updateRateNanos, String name, Long idleSleepTime) { + public GameLoop(IGameLogicUpdatable gameLoopManager, long updateRateNanos, String name) { this.gameLoopManager = gameLoopManager; this.updateRate = updateRateNanos; - this.loop = new Runner(name); - setIdleSleepTime(idleSleepTime); + this.loop = new Runner(NAME_PREFIX + " " + name); } /** - * Starts the background game loop thread and initializes and starts the - * game system manager (if it hasn't been initialized or started already). - * The systems will be initialized and started on the game loop background - * thread. + * Starts the background game loop thread and initializes and starts the game system manager (if it hasn't + * been initialized or started already). The systems will be initialized and started on the game loop + * background thread. */ public void start() { loop.start(); } /** - * Stops the background game loop thread, stopping and terminating the game - * systems. This method will wait until the thread has been fully shut down - * before returning. The systems will be stopped and terminated on the game - * loop background thread. + * Stops the background game loop thread, stopping and terminating the game systems. This method will wait + * until the thread has been fully shut down before returning. The systems will be stopped and terminated + * on the game loop background thread. */ public void stop() { loop.close(); } - /** - * Sets the period of time in milliseconds that the update loop will wait - * between time interval checks. This defaults to 0 because on Windows the - * default 60 FPS update rate will cause frame drops for a idle sleep time - * of 1 or more. However, 0 causes the loop to consume a noticable amount of - * CPU. For lower framerates, 1 is recommended and will be set automically - * based on the current frame rate interval if null is specified. - * - * @param millis - */ - public final void setIdleSleepTime(Long millis) { - if (millis == null) { - - // Configure a reasonable default - if (updateRate > INTERVAL_FPS_60) { - - // Can probably get away with more sleep - this.idleSleepTime = 1; - } else { - - // Else on Windows, sleep > 0 will take almost as long as - // a frame - this.idleSleepTime = 0; - } - } else { - this.idleSleepTime = millis; - } - } - - public Long getIdleSleepTime() { - return idleSleepTime; - } - public void pause() { pauseFlag.set(true); } @@ -119,10 +84,9 @@ public void resume() { } /** - * Use our own thread instead of a java executor because we need more - * control over the update loop. ScheduledThreadPoolExecutor will try to - * call makeup frames if it gets behind and we'd rather just drop them. - * Furthermore, this allows us to 'busy wait' for the next 'frame'. + * Use our own thread instead of a java executor because we need more control over the update loop. + * ScheduledThreadPoolExecutor will try to call makeup frames if it gets behind and we'd rather just drop + * them. Furthermore, this allows us to 'busy wait' for the next 'frame'. */ protected final class Runner extends Thread { @@ -146,13 +110,12 @@ public void close() { public void run() { gameLoopManager.start(); + long frameRate = 0; long lastTime = System.nanoTime(); while (go.get()) { - // Check pause if (pauseFlag.get()) { synchronized (pauseFlag) { - // We are in a while loop here to protect against spurious interrupts while (pauseFlag.get()) { try { @@ -169,12 +132,14 @@ public void run() { long time = System.nanoTime(); long delta = time - lastTime; - if (delta >= updateRate) { - - // Time to update + if (delta + frameRate >= updateRate) { lastTime = time; - gameLoopManager.processTick(delta); - continue; + frameRate += delta - updateRate; + gameLoopManager.processTick(delta / 1_000_000_000f); + long tickTime = System.nanoTime() - time; + // Logging + logger.log(tickTime < updateRate ? Level.TRACE : Level.ERROR, "Loop \"{0}\" took {1} ms!", + getName(), TimeUnit.NANOSECONDS.toMillis(delta)); } // Wait just a little. This is an important enough thread @@ -192,20 +157,19 @@ public void run() { // Which probably means that on Windows, we'll be responsive // every other loop iteration. It's an imperfect world and // the only alternative is true CPU heating busy-waiting. - if (delta * 2 > updateRate) { - continue; - } - try { - - // More then 0 here and we underflow constantly. - // 0 and we gobble up noticable CPU. Slower update - // rates could probably get away with the longer sleep - // but 60 FPS can't keep up on Windows. So we'll let - // the sleep time be externally defined for cases - // where the caller knows better. - Thread.sleep(idleSleepTime); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted sleeping", e); + long sleep = TimeUnit.NANOSECONDS.toMillis(updateRate - frameRate); + if (sleep > 0) { + try { + // More then 0 here and we underflow constantly. + // 0 and we gobble up noticable CPU. Slower update + // rates could probably get away with the longer sleep + // but 60 FPS can't keep up on Windows. So we'll let + // the sleep time be externally defined for cases + // where the caller knows better. + Thread.sleep(sleep); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted sleeping", e); + } } } diff --git a/src/toniarts/openkeeper/utils/IGameLoopManager.java b/src/toniarts/openkeeper/utils/GameTimeCounter.java similarity index 62% rename from src/toniarts/openkeeper/utils/IGameLoopManager.java rename to src/toniarts/openkeeper/utils/GameTimeCounter.java index 03b799da1..95dc66cb2 100644 --- a/src/toniarts/openkeeper/utils/IGameLoopManager.java +++ b/src/toniarts/openkeeper/utils/GameTimeCounter.java @@ -16,29 +16,20 @@ */ package toniarts.openkeeper.utils; +import toniarts.openkeeper.game.logic.IGameLogicUpdatable; + /** * Simple interface for enabling game logic update * * @author Toni Helenius */ -public interface IGameLoopManager { - - /** - * Signals start for the manager - */ - public void start(); +abstract public class GameTimeCounter implements IGameLogicUpdatable { - /** - * Signals stop to the manager - */ - public void stop(); + protected double timeElapsed = 0.0; - /** - * Process one game tick. Note that this is not likely run from a render - * loop. So you can't modify the scene from here. - * - * @param delta time since the last call to update(), in nanoseconds - */ - public void processTick(long delta); + @Override + public void processTick(float tpf) { + timeElapsed += tpf; + } }