From a75e7f48c4f7fac99b89830b83a7598acc73cf0e Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Wed, 26 Jul 2023 14:09:50 +0300 Subject: [PATCH 01/70] Simple training task --- .../game/controller/GameController.java | 2 +- .../controller/RoomControllerFactory.java | 6 +- .../room/AbstractRoomController.java | 2 +- .../room/TrainingRoomController.java | 45 +++++++++ .../room/storage/RoomTraineeControl.java | 91 ++++++++++++++++++ .../game/logic/CreatureExperienceSystem.java | 1 + .../openkeeper/game/task/TaskManager.java | 18 +++- .../openkeeper/game/task/TaskType.java | 3 +- .../openkeeper/game/task/creature/Train.java | 94 +++++++++++++++++++ .../view/control/CreatureFlowerControl.java | 2 + .../view/text/CreatureTextParser.java | 3 + 11 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 src/toniarts/openkeeper/game/controller/room/TrainingRoomController.java create mode 100644 src/toniarts/openkeeper/game/controller/room/storage/RoomTraineeControl.java create mode 100644 src/toniarts/openkeeper/game/task/creature/Train.java diff --git a/src/toniarts/openkeeper/game/controller/GameController.java b/src/toniarts/openkeeper/game/controller/GameController.java index 7652d8d67..653e50266 100644 --- a/src/toniarts/openkeeper/game/controller/GameController.java +++ b/src/toniarts/openkeeper/game/controller/GameController.java @@ -223,7 +223,7 @@ public void createNewGame() { navigationService = new NavigationService(gameWorldController.getMapController(), positionSystem); // Initialize tasks - taskManager = new TaskManager(entityData, gameWorldController, gameWorldController.getMapController(), gameWorldController.getObjectsController(), gameWorldController.getCreaturesController(), navigationService, playerControllers.values(), this, positionSystem); + taskManager = new TaskManager(entityData, gameWorldController, gameWorldController.getMapController(), gameWorldController.getObjectsController(), gameWorldController.getCreaturesController(), navigationService, playerControllers.values(), this, positionSystem, gameSettings); // The triggers partyTriggerState = new PartyTriggerLogicController(this, this, this, gameWorldController.getMapController(), gameWorldController.getCreaturesController()); diff --git a/src/toniarts/openkeeper/game/controller/RoomControllerFactory.java b/src/toniarts/openkeeper/game/controller/RoomControllerFactory.java index 03caf90e5..0806c40cb 100644 --- a/src/toniarts/openkeeper/game/controller/RoomControllerFactory.java +++ b/src/toniarts/openkeeper/game/controller/RoomControllerFactory.java @@ -34,6 +34,7 @@ import toniarts.openkeeper.game.controller.room.TempleController; import toniarts.openkeeper.game.controller.room.ThreeByThreeController; import toniarts.openkeeper.game.controller.room.TortureChamberController; +import toniarts.openkeeper.game.controller.room.TrainingRoomController; import toniarts.openkeeper.game.controller.room.TreasuryController; import toniarts.openkeeper.game.controller.room.WorkshopController; import toniarts.openkeeper.tools.convert.map.KwdFile; @@ -78,9 +79,8 @@ public static IRoomController constructRoom(KwdFile kwdFile, RoomInstance roomIn return new LairController(kwdFile, roomInstance, objectsController, gameTimer); } else if (roomName.equalsIgnoreCase("Library")) { return new LibraryController(kwdFile, roomInstance, objectsController, gameTimer); -// } //else if (roomName.equalsIgnoreCase("Training Room")) { -// return new TrainingRoom(assetManager, roomInstance, objectLoader, worldState, effectManager); -// } else + } else if (roomName.equalsIgnoreCase("Training Room")) { + return new TrainingRoomController(kwdFile, roomInstance, objectsController, gameTimer); } else if (roomName.equalsIgnoreCase("Work Shop")) { return new WorkshopController(kwdFile, roomInstance, objectsController); // } else if (roomName.equalsIgnoreCase("Guard Room")) { diff --git a/src/toniarts/openkeeper/game/controller/room/AbstractRoomController.java b/src/toniarts/openkeeper/game/controller/room/AbstractRoomController.java index 80dd468e9..79285a813 100644 --- a/src/toniarts/openkeeper/game/controller/room/AbstractRoomController.java +++ b/src/toniarts/openkeeper/game/controller/room/AbstractRoomController.java @@ -42,7 +42,7 @@ public abstract class AbstractRoomController implements IRoomController { */ public enum ObjectType { - GOLD, LAIR, SPELL_BOOK, SPECIAL, RESEARCHER, PRISONER, TORTUREE, FOOD; + GOLD, LAIR, SPELL_BOOK, SPECIAL, RESEARCHER, PRISONER, TORTUREE, FOOD, TRAINEE; }; diff --git a/src/toniarts/openkeeper/game/controller/room/TrainingRoomController.java b/src/toniarts/openkeeper/game/controller/room/TrainingRoomController.java new file mode 100644 index 000000000..78691e5cd --- /dev/null +++ b/src/toniarts/openkeeper/game/controller/room/TrainingRoomController.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014-2023 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.room; + +import toniarts.openkeeper.common.RoomInstance; +import toniarts.openkeeper.game.controller.IGameTimer; +import toniarts.openkeeper.game.controller.IObjectsController; +import toniarts.openkeeper.game.controller.room.storage.RoomTraineeControl; +import toniarts.openkeeper.tools.convert.map.KwdFile; + +/** + * The training room + * + * @author Toni Helenius + */ +public class TrainingRoomController extends NormalRoomController { + + public TrainingRoomController(KwdFile kwdFile, RoomInstance roomInstance, IObjectsController objectsController, + IGameTimer gameTimer) { + super(kwdFile, roomInstance, objectsController); + + addObjectControl(new RoomTraineeControl(kwdFile, this, objectsController, gameTimer) { + + @Override + protected int getNumberOfAccessibleTiles() { + return getFurnitureCount(); + } + }); + } + +} diff --git a/src/toniarts/openkeeper/game/controller/room/storage/RoomTraineeControl.java b/src/toniarts/openkeeper/game/controller/room/storage/RoomTraineeControl.java new file mode 100644 index 000000000..a0f23c398 --- /dev/null +++ b/src/toniarts/openkeeper/game/controller/room/storage/RoomTraineeControl.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014-2023 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.room.storage; + +import com.simsilica.es.EntityId; +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import toniarts.openkeeper.game.component.Position; +import toniarts.openkeeper.game.controller.IGameTimer; +import toniarts.openkeeper.game.controller.IObjectsController; +import toniarts.openkeeper.game.controller.room.AbstractRoomController.ObjectType; +import toniarts.openkeeper.game.controller.room.IRoomController; +import toniarts.openkeeper.tools.convert.map.KwdFile; +import toniarts.openkeeper.utils.WorldUtils; + +/** + * Holds out the trainees populating a room + * + * @author Toni Helenius + */ +public abstract class RoomTraineeControl extends AbstractRoomObjectControl { + + public RoomTraineeControl(KwdFile kwdFile, IRoomController parent, IObjectsController objectsController, IGameTimer gameTimer) { + super(kwdFile, parent, objectsController, gameTimer); + } + + @Override + public int getCurrentCapacity() { + return objectsByCoordinate.size(); + } + + @Override + protected int getObjectsPerTile() { + return 1; + } + + @Override + public ObjectType getObjectType() { + return ObjectType.TRAINEE; + } + + @Override + public EntityId addItem(EntityId trainee, Point p) { + setRoomStorageToItem(trainee, false); + + return trainee; + } + + @Override + public void destroy() { + + // TODO: The trainee can't do his/her job + } + + @Override + public void captured(short playerId) { + + } + + @Override + protected Collection getCoordinates() { + + // Only furniture + List coordinates = new ArrayList<>(parent.getFloorFurnitureCount() + parent.getWallFurnitureCount()); + for (EntityId oc : parent.getFloorFurniture()) { + coordinates.add(WorldUtils.vectorToPoint(objectsController.getEntityData().getComponent(oc, Position.class).position)); + } + for (EntityId oc : parent.getWallFurniture()) { + coordinates.add(WorldUtils.vectorToPoint(objectsController.getEntityData().getComponent(oc, Position.class).position)); + } + + return coordinates; + } + +} diff --git a/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java b/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java index d332b1d51..807d3babb 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java @@ -151,6 +151,7 @@ public void start() { public void stop() { experienceEntities.release(); timeWorkingByEntityId.clear(); + entityIds.clear(); } } diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index c84f6090f..bc5fdc126 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -72,6 +72,7 @@ import toniarts.openkeeper.game.task.creature.GoToEat; import toniarts.openkeeper.game.task.creature.GoToSleep; import toniarts.openkeeper.game.task.creature.Research; +import toniarts.openkeeper.game.task.creature.Train; import toniarts.openkeeper.game.task.objective.AbstractObjectiveTask; import toniarts.openkeeper.game.task.objective.KillPlayer; import toniarts.openkeeper.game.task.objective.SendToActionPoint; @@ -91,6 +92,7 @@ import toniarts.openkeeper.tools.convert.map.Room; import toniarts.openkeeper.tools.convert.map.Terrain; import toniarts.openkeeper.tools.convert.map.Thing; +import toniarts.openkeeper.tools.convert.map.Variable; import toniarts.openkeeper.utils.Utils; import toniarts.openkeeper.utils.WorldUtils; @@ -110,6 +112,7 @@ public class TaskManager implements ITaskManager, IGameLogicUpdatable { private final INavigationService navigationService; private final ILevelInfo levelInfo; private final IEntityPositionLookup entityPositionLookup; + private final Map gameSettings; private final EntityData entityData; private final EntitySet taskEntities; private final EntitySet unconsciousEntities; @@ -123,7 +126,8 @@ public class TaskManager implements ITaskManager, IGameLogicUpdatable { public TaskManager(EntityData entityData, IGameWorldController gameWorldController, IMapController mapController, IObjectsController objectsController, ICreaturesController creaturesController, INavigationService navigationService, - Collection players, ILevelInfo levelInfo, IEntityPositionLookup entityPositionLookup) { + Collection players, ILevelInfo levelInfo, IEntityPositionLookup entityPositionLookup, + Map gameSettings) { this.entityData = entityData; this.mapController = mapController; this.gameWorldController = gameWorldController; @@ -132,6 +136,7 @@ public TaskManager(EntityData entityData, IGameWorldController gameWorldControll this.navigationService = navigationService; this.levelInfo = levelInfo; this.entityPositionLookup = entityPositionLookup; + this.gameSettings = gameSettings; // Set the players // Create a queue for each managed player (everybody except Good & Neutral) @@ -616,15 +621,16 @@ private boolean assignClosestRoomTask(ICreatureController creature, ObjectType o return task.isValid(creature); } - if (task instanceof AbstractCapacityCriticalRoomTask) { + if (task instanceof AbstractCapacityCriticalRoomTask abstractCapacityCriticalRoomTask) { if (taskPoints == null) { taskPoints = new HashMap<>(); } - taskPoints.put(target, (AbstractCapacityCriticalRoomTask) task); + taskPoints.put(target, abstractCapacityCriticalRoomTask); roomTasks.put(room, taskPoints); } task.assign(creature, true); tasksByIds.put(task.getId(), task); + return true; } } @@ -663,6 +669,9 @@ private AbstractTask getRoomTask(ObjectType objectType, Point target, EntityId t case SPELL_BOOK: { return new CarryObjectToStorageTask(navigationService, mapController, target, creature.getOwnerId(), room, this, objectsController.createController(targetEntity)); } + case TRAINEE: { + return new Train(navigationService, mapController, target, creature.getOwnerId(), room, this, gameWorldController, gameSettings, playerControllers.get(creature.getOwnerId())); + } } return null; } @@ -722,6 +731,9 @@ private boolean assignTask(ICreatureController creature, Creature.JobType jobTyp case RESEARCH: { return assignClosestRoomTask(creature, ObjectType.RESEARCHER, null, assign); } + case TRAIN: { + return assignClosestRoomTask(creature, ObjectType.TRAINEE, null, assign); + } default: return false; } diff --git a/src/toniarts/openkeeper/game/task/TaskType.java b/src/toniarts/openkeeper/game/task/TaskType.java index a2f10b49e..262f11a4c 100644 --- a/src/toniarts/openkeeper/game/task/TaskType.java +++ b/src/toniarts/openkeeper/game/task/TaskType.java @@ -40,5 +40,6 @@ public enum TaskType { FETCH_OBJECT, REPAIR_WALL, RESCUE_CREATURE, - GO_TO_EAT + GO_TO_EAT, + TRAIN } diff --git a/src/toniarts/openkeeper/game/task/creature/Train.java b/src/toniarts/openkeeper/game/task/creature/Train.java new file mode 100644 index 000000000..9ea327f71 --- /dev/null +++ b/src/toniarts/openkeeper/game/task/creature/Train.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014-2023 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.task.creature; + +import com.jme3.math.Vector2f; +import java.awt.Point; +import java.util.Map; +import toniarts.openkeeper.game.controller.IGameWorldController; +import toniarts.openkeeper.game.controller.IMapController; +import toniarts.openkeeper.game.controller.IPlayerController; +import toniarts.openkeeper.game.controller.creature.ICreatureController; +import toniarts.openkeeper.game.controller.room.AbstractRoomController.ObjectType; +import toniarts.openkeeper.game.controller.room.IRoomController; +import toniarts.openkeeper.game.navigation.INavigationService; +import toniarts.openkeeper.game.task.AbstractCapacityCriticalRoomTask; +import toniarts.openkeeper.game.task.TaskManager; +import toniarts.openkeeper.game.task.TaskType; +import toniarts.openkeeper.tools.convert.map.Variable; +import toniarts.openkeeper.tools.convert.map.Variable.MiscVariable; +import toniarts.openkeeper.tools.convert.map.Variable.MiscVariable.MiscType; +import toniarts.openkeeper.utils.WorldUtils; + +/** + * Trains creature + * + * @author Toni Helenius + */ +public class Train extends AbstractCapacityCriticalRoomTask { + + private final IGameWorldController gameWorldController; + private final IPlayerController playerController; + private final int trainingRoomMaxExperienceLevel; + private final int trainingCost; + + public Train(final INavigationService navigationService, final IMapController mapController, + Point p, short playerId, IRoomController room, TaskManager taskManager, + IGameWorldController gameWorldController, Map gameSettings, + IPlayerController playerController) { + super(navigationService, mapController, p, playerId, room, taskManager); + + this.gameWorldController = gameWorldController; + this.playerController = playerController; + + trainingRoomMaxExperienceLevel = (int) gameSettings.get(Variable.MiscVariable.MiscType.TRAINING_ROOM_MAX_EXPERIENCE_LEVEL).getValue(); + trainingCost = Math.abs((int) gameSettings.get(Variable.MiscVariable.MiscType.MODIFY_PLAYER_GOLD_WHILE_TRAINING_PER_SECOND).getValue()); + } + + @Override + public boolean isValid(ICreatureController creature) { + return playerController.getKeeper().getGold() >= trainingCost && creature.getLevel() < trainingRoomMaxExperienceLevel && super.isValid(creature); + } + + @Override + public Vector2f getTarget(ICreatureController creature) { + return WorldUtils.pointToVector2f(getTaskLocation()); // FIXME 0.5f not needed? + } + + @Override + protected ObjectType getRoomObjectType() { + return ObjectType.TRAINEE; + } + + @Override + public void executeTask(ICreatureController creature, float executionDuration) { + + // TODO: is this a general case or even smart to do this like this...? + if (executionDuration - getExecutionDuration(creature) < 1.0f) { + return; + } + + setExecutionDuration(creature, executionDuration - getExecutionDuration(creature)); + gameWorldController.substractGold(trainingCost, playerId); + } + + @Override + public TaskType getTaskType() { + return TaskType.TRAIN; + } + +} diff --git a/src/toniarts/openkeeper/view/control/CreatureFlowerControl.java b/src/toniarts/openkeeper/view/control/CreatureFlowerControl.java index b4f67deb0..bb9713129 100644 --- a/src/toniarts/openkeeper/view/control/CreatureFlowerControl.java +++ b/src/toniarts/openkeeper/view/control/CreatureFlowerControl.java @@ -202,6 +202,8 @@ private static String getTaskIcon(TaskType taskType) { return "Textures/GUI/moods/SJ-Library.png"; case GO_TO_EAT: return "Textures/GUI/moods/SJ-Hungry.png"; + case TRAIN: + return "Textures/GUI/moods/SJ-Training.png"; default: return null; } diff --git a/src/toniarts/openkeeper/view/text/CreatureTextParser.java b/src/toniarts/openkeeper/view/text/CreatureTextParser.java index db145b671..c81d9f5f6 100644 --- a/src/toniarts/openkeeper/view/text/CreatureTextParser.java +++ b/src/toniarts/openkeeper/view/text/CreatureTextParser.java @@ -175,6 +175,9 @@ private static String getTaskTooltip(TaskComponent taskComponent, IMapInformatio case GO_TO_EAT: { return Utils.getMainTextResourceBundle().getString("2668"); } + case TRAIN: { + return Utils.getMainTextResourceBundle().getString("2633"); + } } return ""; From 584bca62b8781ea22b42fa74d8fd69777adf37f4 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 3 Sep 2023 23:55:03 +0300 Subject: [PATCH 02/70] Reduce nesting + double sided locking --- src/toniarts/openkeeper/utils/PathUtils.java | 72 +++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/toniarts/openkeeper/utils/PathUtils.java b/src/toniarts/openkeeper/utils/PathUtils.java index 8054d1ef1..4056005b3 100644 --- a/src/toniarts/openkeeper/utils/PathUtils.java +++ b/src/toniarts/openkeeper/utils/PathUtils.java @@ -202,45 +202,53 @@ public static String getRealFileName(final String realPath, String uncertainPath // See cache String cachedName = FILENAME_CACHE.get(fileKey); - if (cachedName == null) { - synchronized (FILENAME_LOCK) { + if (cachedName != null) { + return cachedName; + } + + synchronized (FILENAME_LOCK) { + + cachedName = FILENAME_CACHE.get(fileKey); + if (cachedName != null) { + return cachedName; + } - // If it exists as such, that is super! - Path testFile = Paths.get(fileName); - if (Files.exists(testFile)) { - cachedName = testFile.toRealPath().toString(); - FILENAME_CACHE.put(fileKey, cachedName); + // If it exists as such, that is super! + Path testFile = Paths.get(fileName); + if (Files.exists(testFile)) { + cachedName = testFile.toRealPath().toString(); + FILENAME_CACHE.put(fileKey, cachedName); - return cachedName; - } + return cachedName; + } - // Otherwise we need to do a recursive search - String certainPath = PATH_CACHE.getCertainPath(fileName, realPath); - final String[] path = fileName.substring(certainPath.length()).split(QUOTED_FILE_SEPARATOR); - - // If the path length is 1, lets try, maybe it was just the file name - if (path.length == 1 && !certainPath.equalsIgnoreCase(realPath)) { - Path p = Paths.get(certainPath, path[0]); - if (Files.exists(p)) { - cachedName = p.toRealPath().toString(); - FILENAME_CACHE.put(fileKey, cachedName); - return cachedName; - } - } + // Otherwise we need to do a recursive search + String certainPath = PATH_CACHE.getCertainPath(fileName, realPath); + final String[] path = fileName.substring(certainPath.length()).split(QUOTED_FILE_SEPARATOR); - // Find the file - final Path realPathAsPath = Paths.get(certainPath); - FileFinder fileFinder = new FileFinder(realPathAsPath, path); - Files.walkFileTree(realPathAsPath, fileFinder); - FILENAME_CACHE.put(fileKey, fileFinder.file); - cachedName = fileFinder.file; - if (fileFinder.file == null) { - throw new IOException("File not found " + testFile + "!"); + // If the path length is 1, lets try, maybe it was just the file name + if (path.length == 1 && !certainPath.equalsIgnoreCase(realPath)) { + Path p = Paths.get(certainPath, path[0]); + if (Files.exists(p)) { + cachedName = p.toRealPath().toString(); + FILENAME_CACHE.put(fileKey, cachedName); + + return cachedName; } + } - // Cache the known path - PATH_CACHE.setPathToCache(fileFinder.file); + // Find the file + final Path realPathAsPath = Paths.get(certainPath); + FileFinder fileFinder = new FileFinder(realPathAsPath, path); + Files.walkFileTree(realPathAsPath, fileFinder); + FILENAME_CACHE.put(fileKey, fileFinder.file); + cachedName = fileFinder.file; + if (fileFinder.file == null) { + throw new IOException("File not found " + testFile + "!"); } + + // Cache the known path + PATH_CACHE.setPathToCache(fileFinder.file); } return cachedName; From f785bf3f524a2db432a1e785b554e08cd55b0b79 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 3 Sep 2023 23:57:19 +0300 Subject: [PATCH 03/70] Basics of keeper spell casting and trap/door adding --- .../game/controller/GameWorldController.java | 142 ++++++++++++++++++ .../game/controller/IGameWorldController.java | 29 ++++ .../game/network/game/GameClientService.java | 15 ++ .../game/network/game/GameHostedService.java | 21 +++ .../game/state/GameServerState.java | 15 ++ .../game/state/session/GameSession.java | 29 ++++ .../session/GameSessionServiceListener.java | 32 ++++ .../game/state/session/LocalGameSession.java | 21 +++ .../view/PlayerInteractionState.java | 28 +++- 9 files changed, 330 insertions(+), 2 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index be18d9631..47db2b39c 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -32,6 +32,8 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; import toniarts.openkeeper.common.RoomInstance; import toniarts.openkeeper.game.component.AttackTarget; @@ -69,12 +71,16 @@ import toniarts.openkeeper.game.listener.PlayerActionListener; import toniarts.openkeeper.game.map.IMapTileController; import toniarts.openkeeper.game.map.IMapTileInformation; +import toniarts.openkeeper.tools.convert.map.Creature; +import toniarts.openkeeper.tools.convert.map.Door; import toniarts.openkeeper.tools.convert.map.GameObject; +import toniarts.openkeeper.tools.convert.map.KeeperSpell; import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Player; import toniarts.openkeeper.tools.convert.map.Room; import toniarts.openkeeper.tools.convert.map.Terrain; import toniarts.openkeeper.tools.convert.map.Tile; +import toniarts.openkeeper.tools.convert.map.Trap; import toniarts.openkeeper.tools.convert.map.Variable; import toniarts.openkeeper.utils.WorldUtils; @@ -84,6 +90,8 @@ * @author Toni Helenius */ public class GameWorldController implements IGameWorldController, IPlayerActions { + + private static final Logger LOGGER = Logger.getLogger(GameWorldController.class.getName()); /** * When dealing with gold... We currently better lock it. Logic stuff @@ -874,6 +882,140 @@ private static boolean isEntityIncapacitated(EntityId entityId, EntityData entit return false; } + @Override + public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Vector2f position, short playerId) { + KeeperSpell keeperSpell = kwdFile.getKeeperSpellById(keeperSpellId); + if (keeperSpell == null) { + LOGGER.log(Level.WARNING, "Invalid spell ID for spell casting received, was: {0}", keeperSpellId); + return; + } + + IMapTileInformation mapTile = mapController.getMapData().getTile(tile); + if (mapTile == null) { + LOGGER.log(Level.WARNING, "Invalid map location for spell casting received, was: {0}", tile); + return; + } + + Keeper player = players.get(playerId); + if (player == null) { + LOGGER.log(Level.WARNING, "Invalid player for spell casting received, was: {0}", playerId); + return; + } + + // Validate + // Mana... + if (keeperSpell.getManaCost() > player.getMana()) { + return; + } + + // Where allowed to cast + boolean isSolid = kwdFile.getTerrain(mapTile.getTerrainId()).getFlags().contains(Terrain.TerrainFlag.SOLID); + switch (keeperSpell.getCastRule()) { + case OWN_LAND: { + if (isSolid || mapTile.getOwnerId() != playerId) { + return; + } + break; + } + case OWN_AND_NEUTRAL_LAND: { + if (isSolid || mapTile.getOwnerId() != playerId || mapTile.getOwnerId() != Player.NEUTRAL_PLAYER_ID) { + return; + } + break; + } + case ENEMY_LAND: { + if (isSolid || !players.get(playerId).isEnemy(mapTile.getOwnerId())) { + return; + } + } + case ANY_LAND: + if (isSolid) { + return; + } + case ANYWHERE: + break; + case NONE: + return; + } + + // The target cast upon + Creature creature = null; + Short owner = null; + if (target != null) { + CreatureComponent creatureComponent = entityData.getComponent(target, CreatureComponent.class); + if (creatureComponent != null) { + creature = kwdFile.getCreature(creatureComponent.creatureId); + } + Owner ownerComponent = entityData.getComponent(target, Owner.class); + if (ownerComponent != null) { + owner = ownerComponent.ownerId; + } + } + switch (keeperSpell.getTargetRule()) { + case ALL: + case LAND: + break; + case NONE: + return; + case ALL_CREATURES: { + + } + case ENEMY_CREATURES: { + + } + case OWN_CREATURES: + case POSESSION: { + + } + } + } + + @Override + public void placeDoor(short doorId, Point tile, short playerId) { + Door door = kwdFile.getDoorById(doorId); + if (door == null) { + LOGGER.log(Level.WARNING, "Invalid door ID for door placement received, was: {0}", door); + return; + } + + IMapTileInformation mapTile = mapController.getMapData().getTile(tile); + if (mapTile == null) { + LOGGER.log(Level.WARNING, "Invalid map location for door placement received, was: {0}", tile); + return; + } + + Keeper player = players.get(playerId); + if (player == null) { + LOGGER.log(Level.WARNING, "Invalid player for door placement received, was: {0}", playerId); + return; + } + + // Validate + } + + @Override + public void placeTrap(short trapId, Point tile, short playerId) { + Trap trap = kwdFile.getTrapById(trapId); + if (trap == null) { + LOGGER.log(Level.WARNING, "Invalid trap ID for trap placement received, was: {0}", trap); + return; + } + + IMapTileInformation mapTile = mapController.getMapData().getTile(tile); + if (mapTile == null) { + LOGGER.log(Level.WARNING, "Invalid map location for trap placement received, was: {0}", tile); + return; + } + + Keeper player = players.get(playerId); + if (player == null) { + LOGGER.log(Level.WARNING, "Invalid player for trap placement received, was: {0}", playerId); + return; + } + + // Validate + } + @Override public ICreaturesController getCreaturesController() { return creaturesController; diff --git a/src/toniarts/openkeeper/game/controller/IGameWorldController.java b/src/toniarts/openkeeper/game/controller/IGameWorldController.java index f4ea7f995..7f448b534 100644 --- a/src/toniarts/openkeeper/game/controller/IGameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/IGameWorldController.java @@ -153,4 +153,33 @@ public interface IGameWorldController { * @return objects controller */ public IObjectsController getObjectsController(); + + /** + * Cast a keeper spell on target / location + * + * @param keeperSpellId the spell casted + * @param target the target casted on (can be null) + * @param tile the tile casted on + * @param position the actual coordinates of the cast + * @param playerId the player who is casting + */ + public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Vector2f position, short playerId); + + /** + * Place a door (blueprint.. manufacturing order) + * + * @param doorId the door to place + * @param tile the tile to place the door to + * @param playerId the player placing the door + */ + public void placeDoor(short doorId, Point tile, short playerId); + + /** + * Place a trap (blueprint.. manufacturing order) + * + * @param trapId the door to place + * @param tile the tile to place the trap to + * @param playerId the player placing the trap + */ + public void placeTrap(short trapId, Point tile, short playerId); } diff --git a/src/toniarts/openkeeper/game/network/game/GameClientService.java b/src/toniarts/openkeeper/game/network/game/GameClientService.java index daaa3cdef..43aea4498 100644 --- a/src/toniarts/openkeeper/game/network/game/GameClientService.java +++ b/src/toniarts/openkeeper/game/network/game/GameClientService.java @@ -207,6 +207,21 @@ public void triggerCheat(CheatState.CheatType cheat) { getDelegate().triggerCheat(cheat); } + @Override + public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Vector2f position) { + getDelegate().castKeeperSpell(keeperSpellId, target, tile, position); + } + + @Override + public void placeDoor(short doorId, Point tile) { + getDelegate().placeDoor(doorId, tile); + } + + @Override + public void placeTrap(short trapId, Point tile) { + getDelegate().placeTrap(trapId, tile); + } + private class ClientMessageListener implements MessageListener { public ClientMessageListener() { diff --git a/src/toniarts/openkeeper/game/network/game/GameHostedService.java b/src/toniarts/openkeeper/game/network/game/GameHostedService.java index 01cf1ae3d..cffd54f12 100644 --- a/src/toniarts/openkeeper/game/network/game/GameHostedService.java +++ b/src/toniarts/openkeeper/game/network/game/GameHostedService.java @@ -577,6 +577,27 @@ public void triggerCheat(CheatState.CheatType cheat) { } } + @Override + public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Vector2f position) { + for (GameSessionServiceListener listener : serverListeners.getArray()) { + listener.onCastKeeperSpell(keeperSpellId, target, tile, position, clientInfo.getKeeper().getId()); + } + } + + @Override + public void placeDoor(short doorId, Point tile) { + for (GameSessionServiceListener listener : serverListeners.getArray()) { + listener.onPlaceDoor(doorId, tile, clientInfo.getKeeper().getId()); + } + } + + @Override + public void placeTrap(short trapId, Point tile) { + for (GameSessionServiceListener listener : serverListeners.getArray()) { + listener.onPlaceTrap(trapId, tile, clientInfo.getKeeper().getId()); + } + } + @Override public EntityData getEntityData() { return null; // Cached on client... diff --git a/src/toniarts/openkeeper/game/state/GameServerState.java b/src/toniarts/openkeeper/game/state/GameServerState.java index 8409e90bf..9ee788161 100644 --- a/src/toniarts/openkeeper/game/state/GameServerState.java +++ b/src/toniarts/openkeeper/game/state/GameServerState.java @@ -263,6 +263,21 @@ public void onDrop(EntityId entity, Point tile, Vector2f coordinates, EntityId d gameWorldController.drop(entity, tile, coordinates, dropOnEntity, playerId); } + @Override + public void onCastKeeperSpell(short keeperSpellId, EntityId target, Point tile, Vector2f position, short playerId) { + gameWorldController.castKeeperSpell(keeperSpellId, target, tile, position, playerId); + } + + @Override + public void onPlaceDoor(short doorId, Point tile, short playerId) { + gameWorldController.placeDoor(doorId, tile, playerId); + } + + @Override + public void onPlaceTrap(short trapId, Point tile, short playerId) { + gameWorldController.placeTrap(trapId, tile, playerId); + } + @Override public void onTransitionEnd(short playerId) { // We are not really interested in this, the status is also tracked in the local clients diff --git a/src/toniarts/openkeeper/game/state/session/GameSession.java b/src/toniarts/openkeeper/game/state/session/GameSession.java index 8ff5aa844..f24233877 100644 --- a/src/toniarts/openkeeper/game/state/session/GameSession.java +++ b/src/toniarts/openkeeper/game/state/session/GameSession.java @@ -153,4 +153,33 @@ public interface GameSession { @Asynchronous public void triggerCheat(CheatState.CheatType cheat); + /** + * Cast a keeper spell + * + * @param keeperSpellId the spell to cast + * @param target entity to cast the spell on (can be null) + * @param tile the tile to cast on + * @param position the actual position of the cast + */ + @Asynchronous + public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Vector2f position); + + /** + * Place a door + * + * @param doorId the door ID to place + * @param tile the tile to place door on + */ + @Asynchronous + public void placeDoor(short doorId, Point tile); + + /** + * Place a trap + * + * @param trapId the trap ID to place + * @param tile the tile to place trap on + */ + @Asynchronous + public void placeTrap(short trapId, Point tile); + } diff --git a/src/toniarts/openkeeper/game/state/session/GameSessionServiceListener.java b/src/toniarts/openkeeper/game/state/session/GameSessionServiceListener.java index f5e44b3ea..87c2eda3c 100644 --- a/src/toniarts/openkeeper/game/state/session/GameSessionServiceListener.java +++ b/src/toniarts/openkeeper/game/state/session/GameSessionServiceListener.java @@ -147,4 +147,36 @@ public interface GameSessionServiceListener { @Asynchronous public void onCheatTriggered(CheatState.CheatType cheat, short playerId); + /** + * Player wants to cast a spell + * + * @param keeperSpellId the spell to cast + * @param target the target to cast on + * @param tile the tile to cast + * @param position the actual cast position + * @param playerId the player who wants to cast the spell + */ + @Asynchronous + public void onCastKeeperSpell(short keeperSpellId, EntityId target, Point tile, Vector2f position, short playerId); + + /** + * Player wants to place a door + * + * @param doorId the door ID + * @param tile the tile the door should be placed on + * @param playerId the player who wants to place a door + */ + @Asynchronous + public void onPlaceDoor(short doorId, Point tile, short playerId); + + /** + * Player wants to place a trap + * + * @param trapId the trap ID + * @param tile the tile the trap should be placed on + * @param playerId the player who wants to place a trap + */ + @Asynchronous + public void onPlaceTrap(short trapId, Point tile, short playerId); + } diff --git a/src/toniarts/openkeeper/game/state/session/LocalGameSession.java b/src/toniarts/openkeeper/game/state/session/LocalGameSession.java index c378dafd9..782da72f5 100644 --- a/src/toniarts/openkeeper/game/state/session/LocalGameSession.java +++ b/src/toniarts/openkeeper/game/state/session/LocalGameSession.java @@ -374,6 +374,27 @@ public void triggerCheat(CheatState.CheatType cheat) { } } + @Override + public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Vector2f position) { + for (GameSessionServiceListener listener : serverListeners.getArray()) { + listener.onCastKeeperSpell(keeperSpellId, target, tile, position, PLAYER_ID); + } + } + + @Override + public void placeDoor(short doorId, Point tile) { + for (GameSessionServiceListener listener : serverListeners.getArray()) { + listener.onPlaceDoor(doorId, tile, PLAYER_ID); + } + } + + @Override + public void placeTrap(short trapId, Point tile) { + for (GameSessionServiceListener listener : serverListeners.getArray()) { + listener.onPlaceTrap(trapId, tile, PLAYER_ID); + } + } + @Override public void updateTiles(List updatedTiles) { for (GameSessionListener listener : listeners.getArray()) { diff --git a/src/toniarts/openkeeper/view/PlayerInteractionState.java b/src/toniarts/openkeeper/view/PlayerInteractionState.java index e1dd1b729..0a11fabbf 100644 --- a/src/toniarts/openkeeper/view/PlayerInteractionState.java +++ b/src/toniarts/openkeeper/view/PlayerInteractionState.java @@ -53,6 +53,7 @@ import toniarts.openkeeper.game.state.PlayerScreenController; import toniarts.openkeeper.game.state.PlayerState; import toniarts.openkeeper.gui.CursorFactory; +import toniarts.openkeeper.tools.convert.map.KeeperSpell; import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Player; import toniarts.openkeeper.tools.convert.map.Room; @@ -557,8 +558,9 @@ public void onMouseButtonEvent(MouseButtonEvent evt) { if (evt.isPressed()) { if (interactionState.getType() == Type.SPELL) { + castSpell(kwdFile.getKeeperSpellById(interactionState.getItemId()), interactiveControl, selectionHandler.getPointedTileIndex(), selectionHandler.getActualPointedPosition()); //TODO correct interactiveControl.isPickable - if (interactiveControl != null && interactionState.getItemId() == SPELL_POSSESSION_ID + /*if (interactiveControl != null && interactionState.getItemId() == SPELL_POSSESSION_ID && interactiveControl.isPickable(player.getPlayerId())) { CreatureControl cc = interactiveControl.getSpatial().getControl(CreatureControl.class); if (cc != null) { @@ -567,7 +569,7 @@ public void onMouseButtonEvent(MouseButtonEvent evt) { // TODO disable selection box setInteractionState(Type.NONE, 0); } - } + }*/ } else if (interactionState.getType() == Type.TRAP) { //TODO put trap } else if (interactionState.getType() == Type.DOOR) { @@ -721,6 +723,28 @@ public void onTouchEvent(TouchEvent evt) { }; } + /** + * Cast a keeper spell + * + * @param keeperSpell the spell + * @param object pointed object, can be null + * @param tile the tile index + * @param position the actual cursor position + * @return true if spell can be cast + */ + private boolean castSpell(KeeperSpell keeperSpell, IEntityViewControl object, Point tile, Vector2f position) { + if (!canCastSpell(keeperSpell, object, tile, position)) { + return false; + } + gameClientState.getGameClientService().castKeeperSpell(keeperSpell.getId(), object != null ? object.getEntityId() : null, tile, position); + + return true; + } + + private boolean canCastSpell(KeeperSpell keeperSpell, IEntityViewControl object, Point tile, Vector2f position) { + return true; + } + /** * Picks up an object, places it in Keeper's hand * From 1d7072421827a50d30c9e2df72dc4184cdafc5eb Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 20 Oct 2023 18:59:24 +0300 Subject: [PATCH 04/70] EntityLookUp to GameWorldController --- .../game/controller/GameWorldController.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index 47db2b39c..dd1fbd3bb 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -69,6 +69,7 @@ import toniarts.openkeeper.game.controller.room.storage.RoomGoldControl; import toniarts.openkeeper.game.data.Keeper; import toniarts.openkeeper.game.listener.PlayerActionListener; +import toniarts.openkeeper.game.logic.IEntityPositionLookup; import toniarts.openkeeper.game.map.IMapTileController; import toniarts.openkeeper.game.map.IMapTileInformation; import toniarts.openkeeper.tools.convert.map.Creature; @@ -106,6 +107,7 @@ public class GameWorldController implements IGameWorldController, IPlayerActions private ICreaturesController creaturesController; private IDoorsController doorsController; private ITrapsController trapsController; + private IEntityPositionLookup entityPositionLookup; private final Map playerControllers; private final SortedMap players; private final IGameTimer gameTimer; @@ -124,6 +126,7 @@ public GameWorldController(KwdFile kwdFile, EntityData entityData, Map Date: Wed, 29 Nov 2023 20:20:29 +0200 Subject: [PATCH 05/70] Misc fixes --- src/toniarts/openkeeper/tools/convert/map/Trigger.java | 3 ++- .../openkeeper/tools/modelviewer/ModelViewer.java | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/toniarts/openkeeper/tools/convert/map/Trigger.java b/src/toniarts/openkeeper/tools/convert/map/Trigger.java index e3708a64e..1cb4708b8 100644 --- a/src/toniarts/openkeeper/tools/convert/map/Trigger.java +++ b/src/toniarts/openkeeper/tools/convert/map/Trigger.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Map; /** * Container class for *Triggers.kld @@ -32,7 +33,7 @@ public abstract class Trigger { private int idNext; // SiblingID private int idChild; // ChildID private short repeatTimes; // Repeat x times, 255 = always - protected HashMap data = null; + protected Map data = null; public Trigger(KwdFile kwdFile) { this.kwdFile = kwdFile; diff --git a/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java b/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java index 2b13e5f3b..42da83928 100644 --- a/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java +++ b/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java @@ -66,7 +66,6 @@ import java.util.logging.Logger; import toniarts.openkeeper.Main; import toniarts.openkeeper.audio.plugins.MP2Loader; -import toniarts.openkeeper.game.MapSelector; import toniarts.openkeeper.game.data.ISoundable; import toniarts.openkeeper.game.sound.SoundCategory; import toniarts.openkeeper.game.sound.SoundFile; @@ -408,9 +407,11 @@ public void visit(Spatial spatial) { mat.setColor("Color", ColorRGBA.Red); normalGeometry.setMaterial(mat); nodeNormals.attachChild(normalGeometry); - - g.setMaterial(new Material(assetManager, - "Common/MatDefs/Misc/ShowNormals.j3md")); + + if (!g.isGrouped()) { + g.setMaterial(new Material(assetManager, + "Common/MatDefs/Misc/ShowNormals.j3md")); + } } } }); From 29033ed1a31d27e2c2a79ad0af57f5823ba5e19a Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 7 Jan 2024 18:29:22 +0200 Subject: [PATCH 06/70] Unnecessary variable --- src/toniarts/openkeeper/game/state/MainMenuScreenController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/state/MainMenuScreenController.java b/src/toniarts/openkeeper/game/state/MainMenuScreenController.java index 8c9b0383c..7d4385913 100644 --- a/src/toniarts/openkeeper/game/state/MainMenuScreenController.java +++ b/src/toniarts/openkeeper/game/state/MainMenuScreenController.java @@ -801,7 +801,6 @@ private void setControlSettingsToGUI() { ListBox listBox = screen.findNiftyControl("keyboardSetup", ListBox.class); int i = 0; int selected = 0; - KeyNames kNames = new KeyNames(); listBox.clear(); List settings = Settings.Setting.getSettings(Settings.SettingCategory.CONTROLS); From 7a70986a76eab61387a8acf33931e6758b8212c5 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sat, 9 Mar 2024 11:46:37 +0200 Subject: [PATCH 07/70] Gradle 8.5 -> 8.6 and automatic JDK resolution --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 20 ++++++++++---------- settings.gradle | 6 +++++- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index 03032f434..6f310f76d 100644 --- a/build.gradle +++ b/build.gradle @@ -124,5 +124,5 @@ compileJava { } wrapper { - gradleVersion = '8.5' + gradleVersion = '8.6' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e0930..a80b22ce5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..7101f8e46 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/settings.gradle b/settings.gradle index 5e1c03ba1..6700c1211 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,5 @@ -rootProject.name = 'OpenKeeper' +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} + +rootProject.name = 'OpenKeeper' \ No newline at end of file From e6ade8779b1af1a69fe31174a1090a31ba265b31 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 16 Jun 2024 17:09:57 +0300 Subject: [PATCH 08/70] Fix too bright videos and graphics --- src/toniarts/openkeeper/game/data/Settings.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/toniarts/openkeeper/game/data/Settings.java b/src/toniarts/openkeeper/game/data/Settings.java index ffa39a05b..5e47bdef8 100644 --- a/src/toniarts/openkeeper/game/data/Settings.java +++ b/src/toniarts/openkeeper/game/data/Settings.java @@ -284,6 +284,9 @@ private Settings(final AppSettings settings) { // Assing some app level settings settings.setTitle(TITLE); settings.setIcons(getApplicationIcons()); + + // We don't allow this to be changed, assets were not meant to use this + settings.setGammaCorrection(false); } /** From c90f5d6c356c4aa1354f12d40382d37b998ec691 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 16 Jun 2024 17:10:20 +0300 Subject: [PATCH 09/70] Default resolution has no effect --- src/toniarts/openkeeper/game/data/Settings.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/toniarts/openkeeper/game/data/Settings.java b/src/toniarts/openkeeper/game/data/Settings.java index 5e47bdef8..edd2caf72 100644 --- a/src/toniarts/openkeeper/game/data/Settings.java +++ b/src/toniarts/openkeeper/game/data/Settings.java @@ -267,10 +267,6 @@ private Settings(final AppSettings settings) { // Init the settings this.settings = settings; - //Default resolution - if (!this.settings.containsKey("Width") || !this.settings.containsKey("Height")) { - this.settings.setResolution(800, 600); // Default resolution - } if (Files.exists(USER_SETTINGS_FILE)) { try (InputStream in = Files.newInputStream(USER_SETTINGS_FILE); BufferedInputStream bin = new BufferedInputStream(in)) { From a11a70664d5ff336c9a593efb624ab70a4241ded Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 16 Jun 2024 17:14:34 +0300 Subject: [PATCH 10/70] Get display modes via LWJGL rather than AWT. Abstracts video modes --- .../game/state/MainMenuScreenController.java | 15 ++- .../openkeeper/game/state/MainMenuState.java | 36 ------ .../utils/AwtDisplayModeProvider.java | 75 ++++++++++++ .../utils/DefaultDisplayModeProvider.java | 44 +++++++ .../DisplayMode.java} | 42 +++---- .../openkeeper/utils/DisplayModeProvider.java | 36 ++++++ .../openkeeper/utils/DisplayModeUtils.java | 79 ++++++++++++ .../utils/Lwjgl2DisplayModeProvider.java | 87 +++++++++++++ .../utils/Lwjgl3DisplayModeProvider.java | 114 ++++++++++++++++++ 9 files changed, 459 insertions(+), 69 deletions(-) create mode 100644 src/toniarts/openkeeper/utils/AwtDisplayModeProvider.java create mode 100644 src/toniarts/openkeeper/utils/DefaultDisplayModeProvider.java rename src/toniarts/openkeeper/{game/state/MyDisplayMode.java => utils/DisplayMode.java} (65%) create mode 100644 src/toniarts/openkeeper/utils/DisplayModeProvider.java create mode 100644 src/toniarts/openkeeper/utils/DisplayModeUtils.java create mode 100644 src/toniarts/openkeeper/utils/Lwjgl2DisplayModeProvider.java create mode 100644 src/toniarts/openkeeper/utils/Lwjgl3DisplayModeProvider.java diff --git a/src/toniarts/openkeeper/game/state/MainMenuScreenController.java b/src/toniarts/openkeeper/game/state/MainMenuScreenController.java index 8c9b0383c..64ffb765a 100644 --- a/src/toniarts/openkeeper/game/state/MainMenuScreenController.java +++ b/src/toniarts/openkeeper/game/state/MainMenuScreenController.java @@ -42,8 +42,6 @@ import de.lessvoid.nifty.screen.Screen; import de.lessvoid.nifty.tools.Color; import de.lessvoid.nifty.tools.SizeValue; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; import java.io.File; import java.io.IOException; import java.lang.System.Logger; @@ -84,6 +82,8 @@ import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.modelviewer.SoundsLoader; import toniarts.openkeeper.utils.AssetUtils; +import toniarts.openkeeper.utils.DisplayMode; +import toniarts.openkeeper.utils.DisplayModeUtils; import toniarts.openkeeper.utils.PathUtils; import toniarts.openkeeper.utils.Utils; @@ -271,7 +271,7 @@ public void applyGraphicsSettings() { DropDown aa = screen.findNiftyControl("antialiasing", DropDown.class); DropDown af = screen.findNiftyControl("anisotropicFiltering", DropDown.class); CheckBox ssao = screen.findNiftyControl("ssao", CheckBox.class); - MyDisplayMode mdm = (MyDisplayMode) res.getSelection(); + DisplayMode mdm = (DisplayMode) res.getSelection(); // TODO: See if we need a restart, but keep in mind that the settings are saved in the restart // Set the settings @@ -568,7 +568,7 @@ private void setScreen(Screen screen) { } @NiftyEventSubscriber(id = "resolution") - public void onResolutionChanged(final String id, final DropDownSelectionChangedEvent event) { + public void onResolutionChanged(final String id, final DropDownSelectionChangedEvent event) { // Set the bit depths DropDown bitDepth = screen.findNiftyControl("bitDepth", DropDown.class); @@ -729,9 +729,8 @@ private void setGraphicsSettingsToGUI() { // Application settings AppSettings settings = Main.getUserSettings().getAppSettings(); - GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - MyDisplayMode mdm = new MyDisplayMode(settings); - List resolutions = state.getResolutions(device); + DisplayMode mdm = new DisplayMode(settings); + List resolutions = DisplayModeUtils.getInstance().getDisplayModes(); int resolutionSelectedIndex = Collections.binarySearch(resolutions, mdm); // Get values to the settings screen @@ -762,7 +761,7 @@ private void setGraphicsSettingsToGUI() { // Fullscreen CheckBox fullscreen = screen.findNiftyControl("fullscreen", CheckBox.class); fullscreen.setChecked(settings.isFullscreen()); - fullscreen.setEnabled(device.isFullScreenSupported()); + fullscreen.setEnabled(DisplayModeUtils.getInstance().isFullScreenSupported()); // VSync CheckBox vsync = screen.findNiftyControl("verticalSync", CheckBox.class); diff --git a/src/toniarts/openkeeper/game/state/MainMenuState.java b/src/toniarts/openkeeper/game/state/MainMenuState.java index 2d9f3515f..d7a615571 100644 --- a/src/toniarts/openkeeper/game/state/MainMenuState.java +++ b/src/toniarts/openkeeper/game/state/MainMenuState.java @@ -31,16 +31,12 @@ import com.simsilica.es.EntityData; import com.simsilica.es.EntityId; import com.simsilica.es.base.DefaultEntityData; -import java.awt.DisplayMode; -import java.awt.GraphicsDevice; import java.io.File; 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.Collections; -import java.util.List; import toniarts.openkeeper.Main; import static toniarts.openkeeper.Main.getDkIIFolder; import toniarts.openkeeper.cinematics.CameraSweepData; @@ -532,38 +528,6 @@ protected void clearLevelBriefingNarration() { levelBriefing = null; } - /** - * Gets the resolutions supported by the given device. The resolutions are - * sorted by their native order - * - * @param device the graphics device to query resolutions from - * @return sorted list of available resolutions - */ - protected List getResolutions(GraphicsDevice device) { - - // Get from the system - DisplayMode[] modes = device.getDisplayModes(); - - List displayModes = new ArrayList<>(modes.length); - - // Loop them through - for (DisplayMode dm : modes) { - - // They may already exist, then just add the possible resfresh rate - MyDisplayMode mdm = new MyDisplayMode(dm); - int index = Collections.binarySearch(displayModes, mdm); - if (index > -1) { - mdm = displayModes.get(index); - mdm.addRefreshRate(dm); - mdm.addBitDepth(dm); - } else { - displayModes.add(~index, mdm); - } - } - - return displayModes; - } - public void doDebriefing(GameResult result) { setEnabled(true); if (selectedLevel != null && result != null) { diff --git a/src/toniarts/openkeeper/utils/AwtDisplayModeProvider.java b/src/toniarts/openkeeper/utils/AwtDisplayModeProvider.java new file mode 100644 index 000000000..fc5cdfbba --- /dev/null +++ b/src/toniarts/openkeeper/utils/AwtDisplayModeProvider.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2014-2024 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.utils; + +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.util.ArrayList; +import java.util.List; + +/** + * Uses Java's own way of getting available display modes. Often errorenious and + * locks up on MacOS with LWJGL 3 + * + * @author Toni Helenius + */ +class AwtDisplayModeProvider extends DefaultDisplayModeProvider { + + public AwtDisplayModeProvider() { + } + + @Override + public List getDisplayModes() { + GraphicsDevice device = getGraphicsDevice(); + + java.awt.DisplayMode[] modes = device.getDisplayModes(); + + List displayModes = new ArrayList<>(modes.length); + + // Loop them through + for (java.awt.DisplayMode dm : modes) { + DisplayMode mdm = getDisplayMode(dm); + addDisplayMode(displayModes, mdm); + } + + return displayModes; + } + + private GraphicsDevice getGraphicsDevice() { + return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + } + + private DisplayMode getDisplayMode(java.awt.DisplayMode dm) { + DisplayMode displayMode = new DisplayMode(dm.getWidth(), dm.getHeight()); + if (dm.getRefreshRate() != java.awt.DisplayMode.REFRESH_RATE_UNKNOWN) { + displayMode.addRefreshRate(dm.getRefreshRate()); + } + if (dm.getBitDepth() != java.awt.DisplayMode.BIT_DEPTH_MULTI) { + displayMode.addBitDepth(dm.getBitDepth()); + } + + return displayMode; + } + + @Override + public boolean isFullScreenSupported() { + GraphicsDevice device = getGraphicsDevice(); + + return device.isFullScreenSupported(); + } + +} diff --git a/src/toniarts/openkeeper/utils/DefaultDisplayModeProvider.java b/src/toniarts/openkeeper/utils/DefaultDisplayModeProvider.java new file mode 100644 index 000000000..f2d1cac4a --- /dev/null +++ b/src/toniarts/openkeeper/utils/DefaultDisplayModeProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014-2024 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.utils; + +import java.util.Collections; +import java.util.List; + +/** + * Simple groups some common methods for display mode providers + * + * @author Toni Helenius + */ +abstract class DefaultDisplayModeProvider implements DisplayModeProvider { + + protected void addDisplayMode(List displayModes, DisplayMode mdm) { + int index = Collections.binarySearch(displayModes, mdm); + if (index > -1) { + DisplayMode existingDm = displayModes.get(index); + for (Integer refreshRate : mdm.getRefreshRates()) { + existingDm.addRefreshRate(refreshRate); + } + for (Integer bitDepth : mdm.getBitDepths()) { + existingDm.addBitDepth(bitDepth); + } + } else { + displayModes.add(~index, mdm); + } + } + +} diff --git a/src/toniarts/openkeeper/game/state/MyDisplayMode.java b/src/toniarts/openkeeper/utils/DisplayMode.java similarity index 65% rename from src/toniarts/openkeeper/game/state/MyDisplayMode.java rename to src/toniarts/openkeeper/utils/DisplayMode.java index 499505634..7f047cb4a 100644 --- a/src/toniarts/openkeeper/game/state/MyDisplayMode.java +++ b/src/toniarts/openkeeper/utils/DisplayMode.java @@ -14,48 +14,43 @@ * You should have received a copy of the GNU General Public License * along with OpenKeeper. If not, see . */ -package toniarts.openkeeper.game.state; +package toniarts.openkeeper.utils; import com.jme3.system.AppSettings; -import java.awt.DisplayMode; import java.util.Collection; +import java.util.Set; import java.util.TreeSet; /** + * Our own presentation of display mode. Groups everything under resolution. * * @author ArchDemon */ -public final class MyDisplayMode implements Comparable { +public final class DisplayMode implements Comparable { - private final int height; private final int width; - private final Collection refreshRates = new TreeSet<>(); - private final Collection bitDepths = new TreeSet<>(); + private final int height; + private final Set refreshRates = new TreeSet<>(); + private final Set bitDepths = new TreeSet<>(); - public MyDisplayMode(DisplayMode dm) { - height = dm.getHeight(); - width = dm.getWidth(); - addBitDepth(dm); - addRefreshRate(dm); + public DisplayMode(int width, int height) { + this.width = width; + this.height = height; } - MyDisplayMode(AppSettings settings) { + public DisplayMode(AppSettings settings) { height = settings.getHeight(); width = settings.getWidth(); bitDepths.add(settings.getBitsPerPixel()); refreshRates.add(settings.getFrequency()); } - public void addRefreshRate(DisplayMode dm) { - if (dm.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN && !refreshRates.contains(dm.getRefreshRate())) { - refreshRates.add(dm.getRefreshRate()); - } + protected void addRefreshRate(Integer refreshRate) { + refreshRates.add(refreshRate); } - public void addBitDepth(DisplayMode dm) { - if (dm.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI && !bitDepths.contains(dm.getBitDepth())) { - bitDepths.add(dm.getBitDepth()); - } + protected void addBitDepth(Integer bitDepth) { + bitDepths.add(bitDepth); } public int getWidth() { @@ -67,9 +62,6 @@ public int getHeight() { } public Collection getBitDepths() { - if (bitDepths.isEmpty()) { - bitDepths.add(24); // Add default - } return bitDepths; } @@ -85,7 +77,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) { return false; } - final MyDisplayMode other = (MyDisplayMode) obj; + final DisplayMode other = (DisplayMode) obj; if (this.height != other.height) { return false; } @@ -106,7 +98,7 @@ public String toString() { } @Override - public int compareTo(MyDisplayMode o) { + public int compareTo(DisplayMode o) { int result = Integer.compare(width, o.width); if (result == 0) { result = Integer.compare(height, o.height); diff --git a/src/toniarts/openkeeper/utils/DisplayModeProvider.java b/src/toniarts/openkeeper/utils/DisplayModeProvider.java new file mode 100644 index 000000000..6049f3962 --- /dev/null +++ b/src/toniarts/openkeeper/utils/DisplayModeProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2014-2024 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.utils; + +import java.util.List; + +/** + * + * @author Toni Helenius + */ +public interface DisplayModeProvider { + + /** + * Gets the diplay modes supported by the current device. The resolutions + * are sorted by their native order + * + * @return sorted list of available display modes + */ + List getDisplayModes(); + + boolean isFullScreenSupported(); +} diff --git a/src/toniarts/openkeeper/utils/DisplayModeUtils.java b/src/toniarts/openkeeper/utils/DisplayModeUtils.java new file mode 100644 index 000000000..e9f9f36b5 --- /dev/null +++ b/src/toniarts/openkeeper/utils/DisplayModeUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2014-2024 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.utils; + +import java.util.List; + +/** + * Java's own graphics settings are more often wrong than right. Also they block + * MacOS from working (AWT & LWJGL 3 issue). This class tries to probe either + * LWJGL 2 or 3 depending which is available without creating a dependency to + * either. Finally falling back to AWT as a last resort. + * + * @author Toni Helenius + */ +public class DisplayModeUtils implements DisplayModeProvider { + + /** + * The delegate for the actual provider + */ + private final DisplayModeProvider displayModeProvider; + + private static class SingletonHelper { + + private static final DisplayModeUtils INSTANCE = new DisplayModeUtils(); + } + + public static DisplayModeUtils getInstance() { + return SingletonHelper.INSTANCE; + } + + private DisplayModeUtils() { + displayModeProvider = getDisplayModeProvider(); + } + + private DisplayModeProvider getDisplayModeProvider() { + + // LWJGL 3 + try { + Class.forName("com.jme3.system.lwjgl.LwjglWindow"); + return new Lwjgl3DisplayModeProvider(); + } catch (ClassNotFoundException exception) { + } + + // LWJGL 2 + try { + Class.forName("org.lwjgl.opengl.Display"); + return new Lwjgl2DisplayModeProvider(); + } catch (ClassNotFoundException exception) { + } + + // Fallback + return new AwtDisplayModeProvider(); + } + + @Override + public List getDisplayModes() { + return displayModeProvider.getDisplayModes(); + } + + @Override + public boolean isFullScreenSupported() { + return displayModeProvider.isFullScreenSupported(); + } + +} diff --git a/src/toniarts/openkeeper/utils/Lwjgl2DisplayModeProvider.java b/src/toniarts/openkeeper/utils/Lwjgl2DisplayModeProvider.java new file mode 100644 index 000000000..c7ffd3ff9 --- /dev/null +++ b/src/toniarts/openkeeper/utils/Lwjgl2DisplayModeProvider.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014-2024 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.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * Extracts display modes using LWJGL 2 + * + * @author Toni Helenius + */ +class Lwjgl2DisplayModeProvider extends DefaultDisplayModeProvider { + + private final Method getBitsPerPixel; + private final Method getFrequency; + private final Method getModeHeight; + private final Method getModeWidth; + private final Method getModes; + + public Lwjgl2DisplayModeProvider() { + try { + Class displayClass = Class.forName("org.lwjgl.opengl.Display"); + getModes = displayClass.getDeclaredMethod("getAvailableDisplayModes"); + Class displayModeClass = Class.forName("org.lwjgl.opengl.DisplayMode"); + getBitsPerPixel = displayModeClass.getDeclaredMethod("getBitsPerPixel"); + getFrequency = displayModeClass.getDeclaredMethod("getFrequency"); + getModeHeight = displayModeClass.getDeclaredMethod("getHeight"); + getModeWidth = displayModeClass.getDeclaredMethod("getWidth"); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) { + throw new RuntimeException("Failed to instantiate LWJGL 2 display mode provider. Has API been changed?", ex); + } + } + + @Override + public List getDisplayModes() { + try { + Object[] glModes = (Object[]) getModes.invoke(null); + + List displayModes = new ArrayList<>(); + for (Object glMode : glModes) { + DisplayMode mode = getDisplayMode(glMode); + addDisplayMode(displayModes, mode); + } + + return displayModes; + } catch (IllegalAccessException | InvocationTargetException ex) { + throw new RuntimeException("Failed to get display modes from LWJGL 2", ex); + } + } + + private DisplayMode getDisplayMode(Object glMode) + throws IllegalAccessException, InvocationTargetException { + int width = (Integer) getModeWidth.invoke(glMode); + int height = (Integer) getModeHeight.invoke(glMode); + int bitDepth = (Integer) getBitsPerPixel.invoke(glMode); + int rate = (Integer) getFrequency.invoke(glMode); + + DisplayMode dm = new DisplayMode(width, height); + dm.addRefreshRate(rate); + dm.addBitDepth(bitDepth); + + return dm; + } + + @Override + public boolean isFullScreenSupported() { + return true; + } + +} diff --git a/src/toniarts/openkeeper/utils/Lwjgl3DisplayModeProvider.java b/src/toniarts/openkeeper/utils/Lwjgl3DisplayModeProvider.java new file mode 100644 index 000000000..887552736 --- /dev/null +++ b/src/toniarts/openkeeper/utils/Lwjgl3DisplayModeProvider.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2014-2024 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.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Extracts display modes using LWJGL 3 + * + * @author Toni Helenius + */ +class Lwjgl3DisplayModeProvider extends DefaultDisplayModeProvider { + + private final Method get; + private final Method getBlueBits; + private final Method getFrequency; + private final Method getGreenBits; + private final Method getModeHeight; + private final Method getModeWidth; + private final Method getModes; + private final Method getPrimaryMonitor; + private final Method getRedBits; + private final Method hasRemaining; + + public Lwjgl3DisplayModeProvider() { + try { + Class glfwClass = Class.forName("org.lwjgl.glfw.GLFW"); + getModes = glfwClass.getDeclaredMethod("glfwGetVideoModes", long.class); + getPrimaryMonitor = glfwClass.getDeclaredMethod("glfwGetPrimaryMonitor"); + + Class vidModeClass = Class.forName("org.lwjgl.glfw.GLFWVidMode"); + getBlueBits = vidModeClass.getDeclaredMethod("blueBits"); + getFrequency = vidModeClass.getDeclaredMethod("refreshRate"); + getGreenBits = vidModeClass.getDeclaredMethod("greenBits"); + getModeHeight = vidModeClass.getDeclaredMethod("height"); + getModeWidth = vidModeClass.getDeclaredMethod("width"); + getRedBits = vidModeClass.getDeclaredMethod("redBits"); + + Class[] vmInnerClasses = vidModeClass.getDeclaredClasses(); + assert vmInnerClasses.length == 1 : vmInnerClasses.length; + Class vmBufferClass = vmInnerClasses[0]; + get = vmBufferClass.getMethod("get"); + hasRemaining = vmBufferClass.getMethod("hasRemaining"); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) { + throw new RuntimeException("Failed to instantiate LWJGL 3 display mode provider. Has API been changed?", ex); + } + } + + @Override + public List getDisplayModes() { + try { + Object monitorId = getPrimaryMonitor.invoke(null); + + if (monitorId == null || 0L == (Long) monitorId) { + return Collections.emptyList(); + } + + Object buf = getModes.invoke(null, monitorId); + + List displayModes = new ArrayList<>(); + while ((Boolean) hasRemaining.invoke(buf)) { + Object vidMode = get.invoke(buf); + + DisplayMode mode = getDisplayMode(vidMode); + addDisplayMode(displayModes, mode); + } + + return displayModes; + } catch (IllegalAccessException | InvocationTargetException ex) { + throw new RuntimeException("Failed to get display modes from LWJGL 3", ex); + } + } + + private DisplayMode getDisplayMode(Object glfwVidMode) + throws IllegalAccessException, InvocationTargetException { + int width = (Integer) getModeWidth.invoke(glfwVidMode); + int height = (Integer) getModeHeight.invoke(glfwVidMode); + int redBits = (Integer) getRedBits.invoke(glfwVidMode); + int greenBits = (Integer) getGreenBits.invoke(glfwVidMode); + int blueBits = (Integer) getBlueBits.invoke(glfwVidMode); + int rate = (Integer) getFrequency.invoke(glfwVidMode); + int bitDepth = redBits + greenBits + blueBits; + + DisplayMode dm = new DisplayMode(width, height); + dm.addRefreshRate(rate); + dm.addBitDepth(bitDepth); + + return dm; + } + + @Override + public boolean isFullScreenSupported() { + return true; + } + +} From ddfe4e1a57c7c09df31dbd4f6239fe7d85858b57 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 18:07:12 +0300 Subject: [PATCH 11/70] Reformat --- .../game/task/creature/Research.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/toniarts/openkeeper/game/task/creature/Research.java b/src/toniarts/openkeeper/game/task/creature/Research.java index e68f7f876..45f78c229 100644 --- a/src/toniarts/openkeeper/game/task/creature/Research.java +++ b/src/toniarts/openkeeper/game/task/creature/Research.java @@ -69,21 +69,23 @@ protected ObjectType getRoomObjectType() { public void executeTask(ICreatureController creature, float executionDuration) { // TODO: is this a general case or even smart to do this like this...? - if (executionDuration - getExecutionDuration(creature) >= 1.0f) { - setExecutionDuration(creature, executionDuration - getExecutionDuration(creature)); + if (executionDuration - getExecutionDuration(creature) < 1.0f) { + return; + } + + setExecutionDuration(creature, executionDuration - getExecutionDuration(creature)); - // Advance players spell research - ResearchableEntity researchableEntity = researchControl.research(creature.getResearchPerSecond()); - if (researchableEntity != null) { + // Advance players spell research + ResearchableEntity researchableEntity = researchControl.research(creature.getResearchPerSecond()); + if (researchableEntity != null) { - // Create a spell book - EntityId entityId = objectsController.addRoomSpellBook((short) 0, getTaskLocation().x, getTaskLocation().y, researchableEntity); - entityId = (EntityId) getRoomObjectControl().addItem(entityId, null); - if (entityId != null) { + // Create a spell book + EntityId entityId = objectsController.addRoomSpellBook((short) 0, getTaskLocation().x, getTaskLocation().y, researchableEntity); + entityId = (EntityId) getRoomObjectControl().addItem(entityId, null); + if (entityId != null) { - // Failed add, wut - objectsController.createController(entityId).remove(); - } + // Failed add, wut + objectsController.createController(entityId).remove(); } } } From c0a36250364f00b50e4367a6e615aa7c13e3ca30 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 18:08:40 +0300 Subject: [PATCH 12/70] Add some data to the task component rather than storing it in the creature controller --- .../game/component/TaskComponent.java | 10 +++++++++- .../creature/CreatureController.java | 18 +++++++++--------- .../openkeeper/game/task/TaskManager.java | 4 ++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/toniarts/openkeeper/game/component/TaskComponent.java b/src/toniarts/openkeeper/game/component/TaskComponent.java index ab07edafe..0b1569493 100644 --- a/src/toniarts/openkeeper/game/component/TaskComponent.java +++ b/src/toniarts/openkeeper/game/component/TaskComponent.java @@ -33,15 +33,23 @@ public class TaskComponent implements EntityComponent { public Point targetLocation; public TaskType taskType; + /** + * How long we have contributed to the task + */ + public float taskDuration; + public boolean taskStarted; + public TaskComponent() { // For serialization } - public TaskComponent(long taskId, EntityId targetEntity, Point targetLocation, TaskType taskType) { + public TaskComponent(long taskId, EntityId targetEntity, Point targetLocation, TaskType taskType, float taskDuration, boolean taskStarted) { this.taskId = taskId; this.targetEntity = targetEntity; this.targetLocation = targetLocation; this.taskType = taskType; + this.taskDuration = taskDuration; + this.taskStarted = taskStarted; } } diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index ad271f142..cfe1a4515 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -105,8 +105,6 @@ public class CreatureController extends EntityController implements ICreatureCon // TODO: All the data is not supposed to be on entities as they become too big, but I don't want these here either private final Creature creature; private final StateMachine stateMachine; - private float taskDuration = 0.0f; - private boolean taskStarted = false; private float motionless = 0; public CreatureController(EntityId entityId, EntityData entityData, Creature creature, INavigationService navigationService, @@ -264,7 +262,6 @@ public void unassingCurrentTask() { assignedTask.unassign(this); entityData.removeComponent(entityId, TaskComponent.class); } - taskStarted = false; } @Override @@ -395,9 +392,12 @@ public boolean isWorker() { @Override public void executeAssignedTask() { - taskStarted = true; + TaskComponent taskComponent = entityData.getComponent(entityId, TaskComponent.class); + if (!taskComponent.taskStarted) { + entityData.setComponent(entityId, new TaskComponent(taskComponent.taskId, taskComponent.targetEntity, taskComponent.targetLocation, taskComponent.taskType, taskComponent.taskDuration, true)); + } if (isAssignedTaskValid()) { - getAssignedTask().executeTask(this, taskDuration); + getAssignedTask().executeTask(this, taskComponent.taskDuration); } } @@ -828,8 +828,9 @@ public void processTick(float tpf, double gameTime) { } // Task timer - if (taskStarted) { - taskDuration += tpf; + TaskComponent taskComponent = entityData.getComponent(entityId, TaskComponent.class); + if (taskComponent != null && taskComponent.taskStarted) { + entityData.setComponent(entityId, new TaskComponent(taskComponent.taskId, taskComponent.targetEntity, taskComponent.targetLocation, taskComponent.taskType, taskComponent.taskDuration + tpf, taskComponent.taskStarted)); } stateMachine.update(); @@ -908,9 +909,8 @@ public void setAssignedTask(Task task) { // Unassign previous task unassingCurrentTask(); - taskDuration = 0.0f; //workNavigationRequired = true; - entityData.setComponent(entityId, new TaskComponent(task.getId(), task.getTaskTarget(), task.getTaskLocation(), task.getTaskType())); + entityData.setComponent(entityId, new TaskComponent(task.getId(), task.getTaskTarget(), task.getTaskLocation(), task.getTaskType(), 0.0f, false)); } @Override diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index 6597105ea..c1b577e61 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -233,6 +233,10 @@ private void processChangedTasks(Set entities) { for (Entity entity : entities) { long taskId = entity.get(TaskComponent.class).taskId; Long oldTaskId = tasksIdsByEntities.put(entity.getId(), taskId); + if (oldTaskId != null && taskId == oldTaskId) { + continue; + } + Task task = tasksByIds.get(oldTaskId); if (task != null) { task.unassign(creaturesController.createController(entity.getId())); From a3ab19c07566cc8b8720d80328229f9aad62bc20 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 18:09:09 +0300 Subject: [PATCH 13/70] Detect experience gain from training --- .../game/logic/CreatureExperienceSystem.java | 146 +++++++++++++----- 1 file changed, 104 insertions(+), 42 deletions(-) diff --git a/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java b/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java index 807d3babb..6984f5ebc 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureExperienceSystem.java @@ -21,15 +21,21 @@ import com.simsilica.es.EntityData; import com.simsilica.es.EntityId; import com.simsilica.es.EntitySet; +import com.simsilica.es.filter.AndFilter; +import com.simsilica.es.filter.FieldFilter; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import toniarts.openkeeper.game.component.CreatureAi; import toniarts.openkeeper.game.component.CreatureComponent; import toniarts.openkeeper.game.component.CreatureExperience; +import toniarts.openkeeper.game.component.TaskComponent; import toniarts.openkeeper.game.controller.ICreaturesController; import toniarts.openkeeper.game.controller.creature.CreatureState; +import toniarts.openkeeper.game.task.TaskType; +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.Utils; @@ -44,11 +50,19 @@ */ public class CreatureExperienceSystem implements IGameLogicUpdatable { + private enum ExperienceGainReason { + NONE, + WORKING_OR_FIGHTING, + TRAINING + } + private final KwdFile kwdFile; private final EntitySet experienceEntities; + private final EntitySet trainingEntities; private final EntityData entityData; private final int impExperienceGainPerSecond; - private final SafeArrayList entityIds; + private final SafeArrayList experienceEntityIds; + private final SafeArrayList trainingEntityIds; private final ICreaturesController creaturesController; private final Map timeWorkingByEntityId = new HashMap<>(); @@ -58,75 +72,97 @@ public CreatureExperienceSystem(EntityData entityData, KwdFile kwdFile, this.kwdFile = kwdFile; this.entityData = entityData; this.creaturesController = creaturesController; - entityIds = new SafeArrayList<>(EntityId.class); + experienceEntityIds = new SafeArrayList<>(EntityId.class); + trainingEntityIds = new SafeArrayList<>(EntityId.class); impExperienceGainPerSecond = (int) gameSettings.get(Variable.MiscVariable.MiscType.IMP_EXPERIENCE_GAIN_PER_SECOND).getValue(); experienceEntities = entityData.getEntities(CreatureComponent.class, CreatureExperience.class, CreatureAi.class); - processAddedEntities(experienceEntities); + trainingEntities = entityData.getEntities(new AndFilter(TaskComponent.class, new FieldFilter(TaskComponent.class, "taskStarted", true), new FieldFilter(TaskComponent.class, "taskType", TaskType.TRAIN)), CreatureComponent.class, CreatureExperience.class, CreatureAi.class, TaskComponent.class); + processAddedEntities(experienceEntities, experienceEntityIds); + processAddedEntities(trainingEntities, trainingEntityIds); } @Override public void processTick(float tpf, double gameTime) { if (experienceEntities.applyChanges()) { - processAddedEntities(experienceEntities.getAddedEntities()); + processAddedEntities(experienceEntities.getAddedEntities(), experienceEntityIds); - processDeletedEntities(experienceEntities.getRemovedEntities()); + processDeletedEntities(experienceEntities.getRemovedEntities(), experienceEntityIds); + } + + if (trainingEntities.applyChanges()) { + + processAddedEntities(trainingEntities.getAddedEntities(), trainingEntityIds); + + processDeletedEntities(trainingEntities.getRemovedEntities(), trainingEntityIds); } // Increase the experience level of those who are worthy - for (EntityId entityId : entityIds.getArray()) { + for (EntityId entityId : experienceEntityIds.getArray()) { Entity entity = experienceEntities.getEntity(entityId); - CreatureExperience creatureExperience = entity.get(CreatureExperience.class); - if (creatureExperience.level >= Utils.MAX_CREATURE_LEVEL) { - continue; - } + handleEntity(entity, entityId, tpf, () -> { + return isEntityWorkingOrFighting(entity) ? ExperienceGainReason.WORKING_OR_FIGHTING : ExperienceGainReason.NONE; + }); + } + for (EntityId entityId : trainingEntityIds.getArray()) { + Entity entity = trainingEntities.getEntity(entityId); + handleEntity(entity, entityId, tpf, () -> { + return ExperienceGainReason.TRAINING; + }); + } + } - // Check if we can gain exp - if (isEntityWorkingOrFighting(entity)) { - double timeWorking = timeWorkingByEntityId.compute(entityId, (k, v) -> { - if (v == null) { - return 0.0; - } - return v + tpf; - }); - - // Increase the exp - if (timeWorking >= 1) { - timeWorkingByEntityId.merge(entityId, -1.0, Double::sum); - - CreatureComponent creatureComponent = entity.get(CreatureComponent.class); - int experience = creatureExperience.experience; - if (kwdFile.getImp().getId() == creatureComponent.creatureId) { - experience += impExperienceGainPerSecond; - } else { - experience += creatureExperience.experiencePerSecond; - } - - // See if we gained a level - if (experience >= creatureExperience.experienceToNextLevel) { - experience -= creatureExperience.experienceToNextLevel; - creaturesController.levelUpCreature(entityId, creatureExperience.level + 1, experience); - } else { - entityData.setComponent(entityId, new CreatureExperience(creatureExperience.level, experience, creatureExperience.experienceToNextLevel, creatureExperience.experiencePerSecond, creatureExperience.experiencePerSecondTraining)); - } - } + private void handleEntity(Entity entity, EntityId entityId, float tpf, Supplier reasonSupplier) { + CreatureExperience creatureExperience = entity.get(CreatureExperience.class); + if (creatureExperience.level >= Utils.MAX_CREATURE_LEVEL) { + return; + } + + // Check if we can gain exp + ExperienceGainReason reason = reasonSupplier.get(); + if (reason == ExperienceGainReason.NONE) { + return; + } + + double timeWorking = timeWorkingByEntityId.compute(entityId, (k, v) -> { + if (v == null) { + return 0.0; } + return v + tpf; + }); + + // Increase the exp + if (timeWorking < 1) { + return; + } + + timeWorkingByEntityId.merge(entityId, -1.0, Double::sum); + int experience = creatureExperience.experience + getCreatureExperienceGain(entity, reason); + + // See if we gained a level + if (experience >= creatureExperience.experienceToNextLevel) { + experience -= creatureExperience.experienceToNextLevel; + creaturesController.levelUpCreature(entityId, creatureExperience.level + 1, experience); + } else { + entityData.setComponent(entityId, new CreatureExperience(creatureExperience.level, experience, creatureExperience.experienceToNextLevel, creatureExperience.experiencePerSecond, creatureExperience.experiencePerSecondTraining)); } } - private void processAddedEntities(Set entities) { + private void processAddedEntities(Set entities, SafeArrayList entityIds) { for (Entity entity : entities) { int index = Collections.binarySearch(entityIds, entity.getId()); entityIds.add(~index, entity.getId()); } } - private void processDeletedEntities(Set entities) { + private void processDeletedEntities(Set entities, SafeArrayList entityIds) { for (Entity entity : entities) { int index = Collections.binarySearch(entityIds, entity.getId()); entityIds.remove(index); + + // Logically it can't be on the two lists at the same time timeWorkingByEntityId.remove(entity.getId()); } } @@ -142,6 +178,30 @@ private boolean isWorker(Entity entity) { return creatureComponent.worker; } + private int getCreatureExperienceGain(Entity entity, ExperienceGainReason reason) { + switch (reason) { + case WORKING_OR_FIGHTING -> { + CreatureComponent creatureComponent = entity.get(CreatureComponent.class); + if (kwdFile.getImp().getId() == creatureComponent.creatureId) { + return impExperienceGainPerSecond; + } else { + return entity.get(CreatureExperience.class).experiencePerSecond; + } + } + case TRAINING -> { + Creature creature = kwdFile.getCreature(entity.get(CreatureComponent.class).creatureId); + Map stats = kwdFile.getCreatureStats(entity.get(CreatureExperience.class).level); + + return stats != null + ? stats.get(Variable.CreatureStats.StatType.EXPERIENCE_POINTS_FROM_TRAINING_PER_SECOND).getValue() + : creature.getAttributes().getExpPerSecondTraining(); + } + default -> { + return 0; + } + } + } + @Override public void start() { @@ -150,8 +210,10 @@ public void start() { @Override public void stop() { experienceEntities.release(); + trainingEntities.release(); timeWorkingByEntityId.clear(); - entityIds.clear(); + experienceEntityIds.clear(); + trainingEntityIds.clear(); } } From 4eabb68f8ac32bdb66a14de3c86d6b2df92269b0 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 18:52:56 +0300 Subject: [PATCH 14/70] Better exit condition --- src/toniarts/openkeeper/game/task/TaskManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index c1b577e61..1200c1526 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -233,7 +233,7 @@ private void processChangedTasks(Set entities) { for (Entity entity : entities) { long taskId = entity.get(TaskComponent.class).taskId; Long oldTaskId = tasksIdsByEntities.put(entity.getId(), taskId); - if (oldTaskId != null && taskId == oldTaskId) { + if (oldTaskId == null || taskId == oldTaskId) { continue; } From 1ecf31269b26d5e4dd1b153fa641435b4934eb76 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 19:46:48 +0300 Subject: [PATCH 15/70] Better syntax --- src/toniarts/openkeeper/utils/WorldUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/utils/WorldUtils.java b/src/toniarts/openkeeper/utils/WorldUtils.java index 1c43557d4..6d6d0f65c 100644 --- a/src/toniarts/openkeeper/utils/WorldUtils.java +++ b/src/toniarts/openkeeper/utils/WorldUtils.java @@ -146,7 +146,7 @@ public static Point[] getSurroundingTiles(IMapDataInformation mapData, Point poi addIfValidCoordinate(mapData, point.x + 1, point.y + 1, tileCoords); // SE } - return tileCoords.toArray(new Point[0]); + return tileCoords.toArray(Point[]::new); } private static void addIfValidCoordinate(IMapDataInformation mapData, final int x, final int y, List tileCoords) { From 42804c627fc0f9a7939f8ae2435dd41d634a1094 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 19:47:54 +0300 Subject: [PATCH 16/70] Allow other tasks use the finding nearby tile algorithm --- .../openkeeper/game/task/AbstractTask.java | 29 +++++++++++++++++++ .../game/task/worker/DigTileTask.java | 19 +----------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/toniarts/openkeeper/game/task/AbstractTask.java b/src/toniarts/openkeeper/game/task/AbstractTask.java index fb6b66e70..32b8824a6 100644 --- a/src/toniarts/openkeeper/game/task/AbstractTask.java +++ b/src/toniarts/openkeeper/game/task/AbstractTask.java @@ -158,6 +158,35 @@ protected boolean isReachable(ICreatureController creature, Vector2f target) { return (navigationService.findPath(WorldUtils.vectorToPoint(creature.getPosition()), targetTile, creature) != null); } + /** + * Tries to find a accessible target, a tile next to a tile where the task + * is supposed to happen + * + * @param creature the creature trying to reach this + * @return task location next to the wanted tile + */ + protected Vector2f getAccessibleTargetNextToLocation(ICreatureController creature) { + for (Point taskPerformLocation : WorldUtils.getSurroundingTiles(mapController.getMapData(), getTaskLocation(), false)) { + for (Point p : WorldUtils.getSurroundingTiles(mapController.getMapData(), getTaskLocation(), false)) { + if (p.equals(getTaskLocation())) { + continue; + } + + if (!navigationService.isAccessible(mapController.getMapData().getTile(p), mapController.getMapData().getTile(taskPerformLocation), creature)) { + continue; + } + + // TODO: intelligent coordinates? + Vector2f target = new Vector2f(p.x, p.y); + if (isReachable(creature, target)) { + return target; + } + } + } + + return null; + } + @Override public boolean isFaceTarget() { return false; diff --git a/src/toniarts/openkeeper/game/task/worker/DigTileTask.java b/src/toniarts/openkeeper/game/task/worker/DigTileTask.java index a8499d6cc..b538bea8b 100644 --- a/src/toniarts/openkeeper/game/task/worker/DigTileTask.java +++ b/src/toniarts/openkeeper/game/task/worker/DigTileTask.java @@ -24,7 +24,6 @@ import toniarts.openkeeper.game.navigation.INavigationService; import toniarts.openkeeper.game.task.AbstractTileTask; import toniarts.openkeeper.game.task.TaskType; -import toniarts.openkeeper.utils.WorldUtils; /** * Dig a tile task, for workers @@ -45,23 +44,7 @@ public int getMaxAllowedNumberOfAsignees() { @Override public Vector2f getTarget(ICreatureController creature) { - - // Find an accessible target - // TODO: entity's location? - for (Point taskPerformLocation : WorldUtils.getSurroundingTiles(mapController.getMapData(), getTaskLocation(), false)) { - for (Point p : WorldUtils.getSurroundingTiles(mapController.getMapData(), getTaskLocation(), false)) { - if (navigationService.isAccessible(mapController.getMapData().getTile(p), mapController.getMapData().getTile(taskPerformLocation), creature)) { - - // TODO: intelligent coordinates? - Vector2f target = new Vector2f(p.x, p.y); - if (isReachable(creature, target)) { - return target; - } - } - } - } - - return null; + return getAccessibleTargetNextToLocation(creature); } @Override From fc95a1602123b8a66a6afed8380778b58114d722 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 19:48:55 +0300 Subject: [PATCH 17/70] Have the creatures face training equipment instead of going inside them --- .../openkeeper/game/task/creature/Train.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/toniarts/openkeeper/game/task/creature/Train.java b/src/toniarts/openkeeper/game/task/creature/Train.java index 9ea327f71..b0135874f 100644 --- a/src/toniarts/openkeeper/game/task/creature/Train.java +++ b/src/toniarts/openkeeper/game/task/creature/Train.java @@ -32,7 +32,6 @@ import toniarts.openkeeper.tools.convert.map.Variable; import toniarts.openkeeper.tools.convert.map.Variable.MiscVariable; import toniarts.openkeeper.tools.convert.map.Variable.MiscVariable.MiscType; -import toniarts.openkeeper.utils.WorldUtils; /** * Trains creature @@ -66,7 +65,12 @@ public boolean isValid(ICreatureController creature) { @Override public Vector2f getTarget(ICreatureController creature) { - return WorldUtils.pointToVector2f(getTaskLocation()); // FIXME 0.5f not needed? + return getAccessibleTargetNextToLocation(creature); + } + + @Override + public boolean isFaceTarget() { + return true; } @Override @@ -91,4 +95,8 @@ public TaskType getTaskType() { return TaskType.TRAIN; } + @Override + public boolean isRemovable() { + return true; + } } From c45f5ba64ba727a8dbfeb316e9309d6007dbb185 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 19:57:38 +0300 Subject: [PATCH 18/70] Animate research and training --- src/toniarts/openkeeper/game/logic/CreatureViewSystem.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/toniarts/openkeeper/game/logic/CreatureViewSystem.java b/src/toniarts/openkeeper/game/logic/CreatureViewSystem.java index 76723b711..e81d97b57 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureViewSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureViewSystem.java @@ -32,6 +32,7 @@ import toniarts.openkeeper.game.component.Unconscious; import toniarts.openkeeper.game.controller.creature.CreatureState; import toniarts.openkeeper.game.task.TaskType; +import static toniarts.openkeeper.game.task.TaskType.TRAIN; import toniarts.openkeeper.tools.convert.map.Creature; /** @@ -161,6 +162,10 @@ private static Creature.AnimationType getAnimation(TaskType taskType) { return Creature.AnimationType.SLEEPING; case DIG_TILE: return Creature.AnimationType.MELEE_ATTACK; + case TRAIN: + return Creature.AnimationType.MELEE_ATTACK; + case RESEARCH: + return Creature.AnimationType.RESEARCHING; default: return Creature.AnimationType.STAND_STILL; } From 0221603e6baf5ae1cec3ba12a4a4990e25b686ee Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 23:04:19 +0300 Subject: [PATCH 19/70] Fixed some coding issues --- .../room/storage/RoomTraineeControl.java | 2 +- .../openkeeper/game/task/TaskManager.java | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/room/storage/RoomTraineeControl.java b/src/toniarts/openkeeper/game/controller/room/storage/RoomTraineeControl.java index a0f23c398..8f2fd7b60 100644 --- a/src/toniarts/openkeeper/game/controller/room/storage/RoomTraineeControl.java +++ b/src/toniarts/openkeeper/game/controller/room/storage/RoomTraineeControl.java @@ -36,7 +36,7 @@ */ public abstract class RoomTraineeControl extends AbstractRoomObjectControl { - public RoomTraineeControl(KwdFile kwdFile, IRoomController parent, IObjectsController objectsController, IGameTimer gameTimer) { + protected RoomTraineeControl(KwdFile kwdFile, IRoomController parent, IObjectsController objectsController, IGameTimer gameTimer) { super(kwdFile, parent, objectsController, gameTimer); } diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index 1200c1526..977a57763 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -656,27 +656,29 @@ private static Integer getShortestDistance(Point currentPosition, Point... coord private AbstractTask getRoomTask(ObjectType objectType, Point target, EntityId targetEntity, ICreatureController creature, IRoomController room) { switch (objectType) { - case GOLD: { + case GOLD -> { return new CarryGoldToTreasuryTask(navigationService, mapController, target, creature.getOwnerId(), room, gameWorldController); } - case LAIR: { + case LAIR -> { return new ClaimLair(navigationService, mapController, target, creature.getOwnerId(), room, this); } - case RESEARCHER: { + case RESEARCHER -> { return new Research(navigationService, mapController, target, creature.getOwnerId(), room, this, playerControllers.get(creature.getOwnerId()).getResearchControl(), objectsController); } - case PRISONER: { + case PRISONER -> { return new CarryEnemyCreatureToPrison(navigationService, mapController, target, creature.getOwnerId(), room, this, creaturesController.createController(targetEntity)); } - case SPECIAL: - case SPELL_BOOK: { + case SPECIAL, SPELL_BOOK -> { return new CarryObjectToStorageTask(navigationService, mapController, target, creature.getOwnerId(), room, this, objectsController.createController(targetEntity)); } - case TRAINEE: { + case TRAINEE -> { return new Train(navigationService, mapController, target, creature.getOwnerId(), room, this, gameWorldController, gameSettings, playerControllers.get(creature.getOwnerId())); } + default -> { + logger.log(Level.DEBUG, "No task defined for " + objectType); + return null; + } } - return null; } protected void removeRoomTask(AbstractCapacityCriticalRoomTask task) { From 62029d5c06e66f5f179ef0deb0fd20452443cabb Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 5 Jul 2024 23:14:03 +0300 Subject: [PATCH 20/70] Refactor to try to reduce complexity --- .../controller/RoomControllerFactory.java | 103 +++++++++--------- .../view/text/CreatureTextParser.java | 60 ++++++---- 2 files changed, 91 insertions(+), 72 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/RoomControllerFactory.java b/src/toniarts/openkeeper/game/controller/RoomControllerFactory.java index 8b177fd18..8d3b132f4 100644 --- a/src/toniarts/openkeeper/game/controller/RoomControllerFactory.java +++ b/src/toniarts/openkeeper/game/controller/RoomControllerFactory.java @@ -55,69 +55,70 @@ private RoomControllerFactory() { public static IRoomController constructRoom(KwdFile kwdFile, RoomInstance roomInstance, IObjectsController objectsController, Map gameSettings, IGameTimer gameTimer) { - String roomName = roomInstance.getRoom().getName(); switch (roomInstance.getRoom().getTileConstruction()) { - case _3_BY_3: + case _3_BY_3 -> { return new ThreeByThreeController(kwdFile, roomInstance, objectsController); - - case HERO_GATE: - //return new HeroGateConstructor(assetManager, roomInstance); - case HERO_GATE_FRONT_END: + } + case HERO_GATE, HERO_GATE_FRONT_END -> { return new HeroGateFrontEndController(kwdFile, roomInstance, objectsController); - case HERO_GATE_2_BY_2: - //return new HeroGateTwoByTwoConstructor(assetManager, roomInstance); - // case HERO_GATE_3_BY_1: + } + case HERO_GATE_2_BY_2 -> { return new NormalRoomController(kwdFile, roomInstance, objectsController); - //return new HeroGateThreeByOneConstructor(assetManager, roomInstance); - case _5_BY_5_ROTATED: + } + case _5_BY_5_ROTATED -> { return new FiveByFiveRotatedController(kwdFile, roomInstance, objectsController, gameSettings, gameTimer); + } + case NORMAL -> { + return constructNormal(roomName, kwdFile, roomInstance, objectsController, gameTimer, gameSettings); + } + case DOUBLE_QUAD -> { + return constructDoubleQuad(roomName, kwdFile, roomInstance, objectsController, gameTimer); + } + default -> { + // TODO + logger.log(Level.WARNING, "Room {0} not exist", roomName); + return new NormalRoomController(kwdFile, roomInstance, objectsController); + } + } + } + + private static IRoomController constructDoubleQuad(String roomName, KwdFile kwdFile, RoomInstance roomInstance, IObjectsController objectsController, IGameTimer gameTimer) { + if (roomName.equalsIgnoreCase("Prison")) { + return new PrisonController(kwdFile, roomInstance, objectsController, gameTimer); + } else if (roomName.equalsIgnoreCase("Combat Pit")) { + return new CombatPitController(kwdFile, roomInstance, objectsController); + } else if (roomName.equalsIgnoreCase("Temple")) { + return new TempleController(kwdFile, roomInstance, objectsController); + } - case NORMAL: - if (roomName.equalsIgnoreCase("Lair")) { - return new LairController(kwdFile, roomInstance, objectsController, gameTimer); - } else if (roomName.equalsIgnoreCase("Library")) { - return new LibraryController(kwdFile, roomInstance, objectsController, gameTimer); - } else if (roomName.equalsIgnoreCase("Training Room")) { - return new TrainingRoomController(kwdFile, roomInstance, objectsController, gameTimer); - } else if (roomName.equalsIgnoreCase("Work Shop")) { - return new WorkshopController(kwdFile, roomInstance, objectsController); + return new DoubleQuadController(kwdFile, roomInstance, objectsController); + } + + private static IRoomController constructNormal(String roomName, KwdFile kwdFile, RoomInstance roomInstance, IObjectsController objectsController, IGameTimer gameTimer, Map gameSettings) { + if (roomName.equalsIgnoreCase("Lair")) { + return new LairController(kwdFile, roomInstance, objectsController, gameTimer); + } else if (roomName.equalsIgnoreCase("Library")) { + return new LibraryController(kwdFile, roomInstance, objectsController, gameTimer); + } else if (roomName.equalsIgnoreCase("Training Room")) { + return new TrainingRoomController(kwdFile, roomInstance, objectsController, gameTimer); + } else if (roomName.equalsIgnoreCase("Work Shop")) { + return new WorkshopController(kwdFile, roomInstance, objectsController); // } else if (roomName.equalsIgnoreCase("Guard Room")) { // return new GuardRoom(assetManager, roomInstance, objectLoader, worldState, effectManager); - } else if (roomName.equalsIgnoreCase("Casino")) { - return new CasinoController(kwdFile, roomInstance, objectsController); + } else if (roomName.equalsIgnoreCase("Casino")) { + return new CasinoController(kwdFile, roomInstance, objectsController); // } else if (roomName.equalsIgnoreCase("Graveyard")) { // return new Graveyard(assetManager, roomInstance, objectLoader, worldState, effectManager); - } else if (roomName.equalsIgnoreCase("Torture Chamber")) { - return new TortureChamberController(kwdFile, roomInstance, objectsController, gameTimer); - } else if (roomName.equalsIgnoreCase("Treasury")) { - return new TreasuryController(kwdFile, roomInstance, objectsController, gameSettings, gameTimer); - } else if (roomName.equalsIgnoreCase("Hatchery")) { - return new HatcheryController(kwdFile, roomInstance, objectsController, gameTimer); - } - return new NormalRoomController(kwdFile, roomInstance, objectsController); - -// case QUAD: -// if (roomName.equalsIgnoreCase("Hero Stone Bridge") || roomName.equalsIgnoreCase("Stone Bridge")) { -// return new StoneBridge(assetManager, roomInstance, objectLoader, worldState, effectManager); -// } - // return new QuadConstructor(assetManager, roomInstance); -// - case DOUBLE_QUAD: - if (roomName.equalsIgnoreCase("Prison")) { - return new PrisonController(kwdFile, roomInstance, objectsController, gameTimer); - } else if (roomName.equalsIgnoreCase("Combat Pit")) { - return new CombatPitController(kwdFile, roomInstance, objectsController); - } else if (roomName.equalsIgnoreCase("Temple")) { - return new TempleController(kwdFile, roomInstance, objectsController); - } - return new DoubleQuadController(kwdFile, roomInstance, objectsController); - default: - - // TODO - logger.log(Level.WARNING, "Room {0} not exist", roomName); - return new NormalRoomController(kwdFile, roomInstance, objectsController); + } else if (roomName.equalsIgnoreCase("Torture Chamber")) { + return new TortureChamberController(kwdFile, roomInstance, objectsController, gameTimer); + } else if (roomName.equalsIgnoreCase("Treasury")) { + return new TreasuryController(kwdFile, roomInstance, objectsController, gameSettings, gameTimer); + } else if (roomName.equalsIgnoreCase("Hatchery")) { + return new HatcheryController(kwdFile, roomInstance, objectsController, gameTimer); } + + return new NormalRoomController(kwdFile, roomInstance, objectsController); } } diff --git a/src/toniarts/openkeeper/view/text/CreatureTextParser.java b/src/toniarts/openkeeper/view/text/CreatureTextParser.java index c81d9f5f6..d226e0a82 100644 --- a/src/toniarts/openkeeper/view/text/CreatureTextParser.java +++ b/src/toniarts/openkeeper/view/text/CreatureTextParser.java @@ -137,50 +137,68 @@ private static String getStatusText(Entity entity, CreatureAi creatureAi, private static String getTaskTooltip(TaskComponent taskComponent, IMapInformation mapInformation) { switch (taskComponent.taskType) { - case CLAIM_LAIR: + case CLAIM_LAIR -> { return Utils.getMainTextResourceBundle().getString("2627"); - case CAPTURE_ENEMY_CREATURE: + } + case CAPTURE_ENEMY_CREATURE -> { return Utils.getMainTextResourceBundle().getString("2621"); - case CARRY_CREATURE_TO_JAIL: + } + case CARRY_CREATURE_TO_JAIL -> { return Utils.getMainTextResourceBundle().getString("2619"); - case CARRY_CREATURE_TO_LAIR: + } + case CARRY_CREATURE_TO_LAIR -> { return Utils.getMainTextResourceBundle().getString("2619"); - case CARRY_GOLD_TO_TREASURY: + } + case CARRY_GOLD_TO_TREASURY -> { return Utils.getMainTextResourceBundle().getString("2786"); - case CARRY_OBJECT_TO_STORAGE: + } + case CARRY_OBJECT_TO_STORAGE -> { return Utils.getMainTextResourceBundle().getString("2609"); // TODO: these are really awfully specific, we need to fine grain this - case CLAIM_ROOM: + } + case CLAIM_ROOM -> { return Utils.getMainTextResourceBundle().getString("2602"); - case CLAIM_TILE: + } + case CLAIM_TILE -> { return Utils.getMainTextResourceBundle().getString("2601"); - case CLAIM_WALL: + } + case CLAIM_WALL -> { return Utils.getMainTextResourceBundle().getString("2603"); - case DIG_TILE: + } + case DIG_TILE -> { IMapTileInformation tile = mapInformation.getMapData().getTile(taskComponent.targetLocation); return Utils.getMainTextResourceBundle().getString(tile.getGold() > 0 ? "2605" : "2600"); - case FETCH_OBJECT: + } + case FETCH_OBJECT -> { return Utils.getMainTextResourceBundle().getString("2608"); - case GO_TO_LOCATION: + } + case GO_TO_LOCATION -> { return Utils.getMainTextResourceBundle().getString("2670"); - case GO_TO_SLEEP: + } + case GO_TO_SLEEP -> { return Utils.getMainTextResourceBundle().getString("2671"); - case KILL_PLAYER: + } + case KILL_PLAYER -> { return Utils.getMainTextResourceBundle().getString("2645"); - case REPAIR_WALL: + } + case REPAIR_WALL -> { return Utils.getMainTextResourceBundle().getString("2604"); - case RESCUE_CREATURE: + } + case RESCUE_CREATURE -> { return Utils.getMainTextResourceBundle().getString("2617"); - case RESEARCH: + } + case RESEARCH -> { return Utils.getMainTextResourceBundle().getString("2625"); - case GO_TO_EAT: { + } + case GO_TO_EAT -> { return Utils.getMainTextResourceBundle().getString("2668"); } - case TRAIN: { + case TRAIN -> { return Utils.getMainTextResourceBundle().getString("2633"); } + default -> { + return ""; + } } - - return ""; } @Override From 3d6f5c3e7dae50d686cd5465ec56f45a88793303 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sat, 6 Jul 2024 10:36:10 +0300 Subject: [PATCH 21/70] Check if the wanted coordinate is actually valid for dropping gold --- .../game/controller/room/storage/RoomGoldControl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/controller/room/storage/RoomGoldControl.java b/src/toniarts/openkeeper/game/controller/room/storage/RoomGoldControl.java index 6577147f7..31c2bc5e7 100644 --- a/src/toniarts/openkeeper/game/controller/room/storage/RoomGoldControl.java +++ b/src/toniarts/openkeeper/game/controller/room/storage/RoomGoldControl.java @@ -48,7 +48,7 @@ public RoomGoldControl(KwdFile kwdFile, IRoomController parent, IObjectsControll @Override public Integer addItem(Integer sum, Point p) { - if (p != null) { + if (p != null && parent.isTileAccessible(null, p)) { sum = putGold(sum, p); } From bc8af864781909e0e87102f27c1d269fd95d23fc Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 8 Jul 2024 22:21:29 +0300 Subject: [PATCH 22/70] We are not using this and it might hinder MacOS --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6f310f76d..2d16d1850 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,6 @@ dependencies { implementation fileTree(dir: 'lib', include: ['*.jar']) implementation "org.jmonkeyengine:jme3-core:$jmonkeyengine_version" implementation "org.jmonkeyengine:jme3-desktop:$jmonkeyengine_version" - implementation "org.jmonkeyengine:jme3-awt-dialogs:$jmonkeyengine_version" implementation "org.jmonkeyengine:jme3-plugins:$jmonkeyengine_version" implementation "org.jmonkeyengine:jme3-effects:$jmonkeyengine_version" implementation "org.jmonkeyengine:jme3-networking:$jmonkeyengine_version" From b2a0609979fe50abb14cf29b0713632ac739cc15 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 8 Jul 2024 22:22:54 +0300 Subject: [PATCH 23/70] Add some random to loose gold positioning --- .../openkeeper/game/controller/ObjectsController.java | 8 +++++++- src/toniarts/openkeeper/utils/Utils.java | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/controller/ObjectsController.java b/src/toniarts/openkeeper/game/controller/ObjectsController.java index 303af5e1d..386bac988 100644 --- a/src/toniarts/openkeeper/game/controller/ObjectsController.java +++ b/src/toniarts/openkeeper/game/controller/ObjectsController.java @@ -55,6 +55,7 @@ import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Thing; import toniarts.openkeeper.tools.convert.map.Variable; +import toniarts.openkeeper.utils.Utils; import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.MapViewController; @@ -244,7 +245,12 @@ public EntityId addRoomGold(short ownerId, int x, int y, int money, int maxMoney @Override public EntityId addLooseGold(short ownerId, int x, int y, int money, int maxMoney) { - return loadObject(OBJECT_GOLD_ID, ownerId, x, y, 0, money, null, null, null, maxMoney); + Vector3f pos = WorldUtils.pointToVector3f(x, y); + + // Add a slight offset to make things look nicer, stuff not clumping together + pos.x = pos.x + Utils.getRandom().nextFloat(MapViewController.TILE_WIDTH) - (MapViewController.TILE_WIDTH / 2f); + pos.z = pos.z + Utils.getRandom().nextFloat(MapViewController.TILE_WIDTH) - (MapViewController.TILE_WIDTH / 2f); + return loadObject(OBJECT_GOLD_ID, ownerId, pos, 0, money, null, null, null, maxMoney); } @Override diff --git a/src/toniarts/openkeeper/utils/Utils.java b/src/toniarts/openkeeper/utils/Utils.java index c040187ab..0a9b7f0a0 100644 --- a/src/toniarts/openkeeper/utils/Utils.java +++ b/src/toniarts/openkeeper/utils/Utils.java @@ -29,6 +29,7 @@ import java.util.Optional; import java.util.Random; import java.util.ResourceBundle; +import java.util.random.RandomGenerator; import javax.management.MBeanServer; import javax.management.ObjectName; import toniarts.openkeeper.Main; @@ -173,6 +174,15 @@ public static Optional getRandomItem(Collection collection) { return collection.stream().skip(RANDOM.nextInt(collection.size())).findFirst(); } + /** + * Returns the random generator used by the game + * + * @return random generator + */ + public static RandomGenerator getRandom() { + return RANDOM; + } + /** * Get the game main text resource bundle * From e106880e8c3a07bb21fc1a51cb019b0cffe7df28 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 8 Jul 2024 22:23:53 +0300 Subject: [PATCH 24/70] Make creatures drop extra gold they cant handle --- .../controller/creature/CreatureController.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index cfe1a4515..9d1e11439 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -858,7 +858,21 @@ public void resetReEvaluationTimer() { @Override public void addGold(int amount) { Gold gold = entityData.getComponent(entityId, Gold.class); - entityData.setComponent(entityId, new Gold(gold.gold + amount, gold.maxGold)); + + // Drop excess gold, we can only carry so much + boolean hasMaxGold = gold.maxGold > 0; + int maxGoldCanAdd = hasMaxGold ? gold.maxGold - gold.gold : Integer.MAX_VALUE; + int goldToAdd = Math.min(amount, maxGoldCanAdd); + int looseGold = amount - goldToAdd; + + if (goldToAdd > 0) { + entityData.setComponent(entityId, new Gold(gold.gold + goldToAdd, gold.maxGold)); + } + + if (looseGold > 0) { + Point coordinates = getCreatureCoordinates(); + objectsController.addLooseGold(getOwnerId(), coordinates.x, coordinates.y, looseGold, (int) gameSettings.get(Variable.MiscVariable.MiscType.MAX_GOLD_PILE_OUTSIDE_TREASURY).getValue()); + } } @Override From 4df262e90f711d120f77733bb8a9a3798a5d673b Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 8 Jul 2024 22:24:42 +0300 Subject: [PATCH 25/70] Make fleeing last at least the imp revaluation time --- .../creature/CreatureController.java | 35 ++++++++++++------- .../controller/creature/CreatureState.java | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index 9d1e11439..8709f0415 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -1136,29 +1136,38 @@ public void imprison(short playerId) { @Override public boolean isStateTimeExceeded() { double timeSpent = gameTimer.getGameTime() - entityData.getComponent(entityId, CreatureAi.class).stateStartTime; + double stateTargetTime; switch (stateMachine.getCurrentState()) { - case STUNNED: { + case STUNNED -> { // Hmm, this might actually be the level variable, the stun seems to be the time fallen when dropped - return timeSpent >= entityData.getComponent(entityId, CreatureComponent.class).stunDuration; + stateTargetTime = entityData.getComponent(entityId, CreatureComponent.class).stunDuration; } - case FALLEN: { - return timeSpent >= entityData.getComponent(entityId, CreatureComponent.class).stunDuration; + case FALLEN -> { + stateTargetTime = entityData.getComponent(entityId, CreatureComponent.class).stunDuration; } - case GETTING_UP: { - return timeSpent >= getAnimationTime(creature, Creature.AnimationType.GET_UP); + case GETTING_UP -> { + stateTargetTime = getAnimationTime(creature, Creature.AnimationType.GET_UP); } - case ENTERING_DUNGEON: { - return timeSpent >= getAnimationTime(creature, Creature.AnimationType.ENTRANCE); + case ENTERING_DUNGEON -> { + stateTargetTime = getAnimationTime(creature, Creature.AnimationType.ENTRANCE); } - case MELEE_ATTACK: { - return timeSpent >= getAnimationTime(creature, Creature.AnimationType.MELEE_ATTACK); + case MELEE_ATTACK -> { + stateTargetTime = getAnimationTime(creature, Creature.AnimationType.MELEE_ATTACK); } - case EATING: { - return timeSpent >= getAnimationTime(creature, Creature.AnimationType.EATING); + case EATING -> { + stateTargetTime = getAnimationTime(creature, Creature.AnimationType.EATING); + } + case FLEE -> { + // I couldn't find a variable for this, so lets use this for now + stateTargetTime = gameSettings.get(Variable.MiscVariable.MiscType.IMP_IDLE_DELAY_BEFORE_REEVALUATION_SECONDS).getValue(); + } + default -> { + stateTargetTime = Double.MAX_VALUE; } } - return false; + + return stateTargetTime < timeSpent; } private static double getAnimationTime(Creature creature, Creature.AnimationType animation) { diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureState.java b/src/toniarts/openkeeper/game/controller/creature/CreatureState.java index e2e777f32..e9eaf75a0 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureState.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureState.java @@ -367,7 +367,7 @@ public void enter(ICreatureController entity) { @Override public void update(ICreatureController entity) { - if (!entity.shouldFleeOrAttack()) { + if (entity.isStateTimeExceeded() && !entity.shouldFleeOrAttack()) { entity.getStateMachine().changeState(IDLE); } } From f50c52cd37e2ee26804ac1754192540bc12486a6 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 9 Jul 2024 13:46:09 +0300 Subject: [PATCH 26/70] Creatures always have max gold set --- .../game/controller/creature/CreatureController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index 8709f0415..286f41c1d 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -860,8 +860,7 @@ public void addGold(int amount) { Gold gold = entityData.getComponent(entityId, Gold.class); // Drop excess gold, we can only carry so much - boolean hasMaxGold = gold.maxGold > 0; - int maxGoldCanAdd = hasMaxGold ? gold.maxGold - gold.gold : Integer.MAX_VALUE; + int maxGoldCanAdd = gold.maxGold - gold.gold; int goldToAdd = Math.min(amount, maxGoldCanAdd); int looseGold = amount - goldToAdd; From 626d5097bb2ce3d73f02cdf5ed4423764468fb86 Mon Sep 17 00:00:00 2001 From: Andreas Hollandt Date: Mon, 23 Oct 2023 12:04:31 +0200 Subject: [PATCH 27/70] enable GL debug names for RenderDoc --- src/toniarts/openkeeper/Main.java | 4 ++++ src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/toniarts/openkeeper/Main.java b/src/toniarts/openkeeper/Main.java index fa6583360..b3c35747b 100644 --- a/src/toniarts/openkeeper/Main.java +++ b/src/toniarts/openkeeper/Main.java @@ -33,6 +33,7 @@ import com.jme3.post.FilterPostProcessor; import com.jme3.post.ssao.SSAOFilter; import com.jme3.renderer.RenderManager; +import com.jme3.renderer.opengl.GLRenderer; import com.jme3.system.AppSettings; import com.jme3.system.JmeSystem; import de.lessvoid.nifty.Nifty; @@ -331,6 +332,9 @@ public void windowClosing(WindowEvent arg0) { @Override public void simpleInitApp() { + if (debug) + ((GLRenderer)renderer).setDebugEnabled(true); // get debug names for GL objects + // Distribution locator getAssetManager().registerLocator(AssetsConverter.getAssetsFolder(), FileLocator.class); diff --git a/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java b/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java index a2b8d3f69..c042d610c 100644 --- a/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java +++ b/src/toniarts/openkeeper/tools/modelviewer/ModelViewer.java @@ -36,6 +36,7 @@ import com.jme3.math.Vector3f; import com.jme3.niftygui.NiftyJmeDisplay; import com.jme3.renderer.RenderManager; +import com.jme3.renderer.opengl.GLRenderer; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; @@ -203,6 +204,8 @@ public ModelViewer() { @Override public void simpleInitApp() { + ((GLRenderer)renderer).setDebugEnabled(true); // get debug names for GL objects + // Distribution locator assetManager.registerLocator(AssetsConverter.getAssetsFolder(), FileLocator.class); assetManager.registerLoader(MP2Loader.class, "mp2"); From 78d579ddaf0b04bbda74a69feaa5d142dbf08db3 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Wed, 17 Jul 2024 17:35:20 +0300 Subject: [PATCH 28/70] Move some map constants to WorldUtil so that they can be used by the server as well --- .../openkeeper/cinematics/Cinematic.java | 6 ++--- .../game/controller/DoorsController.java | 2 +- .../game/controller/ObjectsController.java | 2 +- .../game/controller/TrapsController.java | 2 +- .../controller/room/TempleController.java | 16 ++++++------ .../game/logic/CreatureFallSystem.java | 5 ++-- .../game/logic/CreatureSpawnSystem.java | 12 ++++----- .../openkeeper/game/state/MainMenuState.java | 2 +- .../game/trigger/TriggerControl.java | 2 +- .../trigger/player/PlayerTriggerControl.java | 2 +- src/toniarts/openkeeper/utils/AssetUtils.java | 2 +- src/toniarts/openkeeper/utils/WorldUtils.java | 26 ++++++++++++------- .../openkeeper/view/PlayerCamera.java | 9 ++++--- .../view/map/MapViewController.java | 16 +++--------- src/toniarts/openkeeper/view/map/Water.java | 17 ++++++------ .../construction/DoubleQuadConstructor.java | 9 ++++--- .../HeroGateFrontEndConstructor.java | 3 ++- .../map/construction/QuadConstructor.java | 9 ++++--- .../map/construction/RoomConstructor.java | 17 ++++++------ .../construction/SingleQuadConstructor.java | 10 +++---- .../room/CombatPitConstructor.java | 5 ++-- .../construction/room/PrisonConstructor.java | 3 ++- .../room/StoneBridgeConstructor.java | 9 ++++--- .../view/selection/SelectionHandler.java | 14 +++++----- 24 files changed, 104 insertions(+), 96 deletions(-) diff --git a/src/toniarts/openkeeper/cinematics/Cinematic.java b/src/toniarts/openkeeper/cinematics/Cinematic.java index f9ef6bf68..18b80825a 100644 --- a/src/toniarts/openkeeper/cinematics/Cinematic.java +++ b/src/toniarts/openkeeper/cinematics/Cinematic.java @@ -79,7 +79,7 @@ public Cinematic(final AssetManager assetManager, Camera cam, Listener audioList public Cinematic(final Main app, String cameraSweepFile, final Vector3f start) { this(app.getAssetManager(), app.getCamera(), app.getListener(), - start.addLocal(0, MapViewController.FLOOR_HEIGHT, 0), + start.addLocal(0, WorldUtils.FLOOR_HEIGHT, 0), cameraSweepFile, app.getRootNode(), app.getStateManager()); } @@ -167,7 +167,7 @@ private void initializeCinematic(final Node scene, final Camera cam, final Vecto // The waypoints for (CameraSweepDataEntry entry : cameraSweepData.getEntries()) { - path.addWayPoint(entry.getPosition().mult(MapViewController.TILE_WIDTH).addLocal(startLocation)); + path.addWayPoint(entry.getPosition().mult(WorldUtils.TILE_WIDTH).addLocal(startLocation)); } //path.setCurveTension(0.5f); if (IS_DEBUG) { @@ -232,7 +232,7 @@ public static void applyCameraSweepEntry(final Camera cam, final Vector3f startL final CameraSweepDataEntry entry, Listener audioListener) { // Set Position - cam.setLocation(startLocation.add(entry.getPosition().mult(MapViewController.TILE_WIDTH))); + cam.setLocation(startLocation.add(entry.getPosition().mult(WorldUtils.TILE_WIDTH))); // Set the rotation cam.setRotation(entry.getRotation()); diff --git a/src/toniarts/openkeeper/game/controller/DoorsController.java b/src/toniarts/openkeeper/game/controller/DoorsController.java index a63a71a65..cc7718e7e 100644 --- a/src/toniarts/openkeeper/game/controller/DoorsController.java +++ b/src/toniarts/openkeeper/game/controller/DoorsController.java @@ -112,7 +112,7 @@ private EntityId loadDoor(int x, int y, short doorId, short ownerId, Integer tri // Move to the center of the tile Vector3f pos = WorldUtils.pointToVector3f(x, y); - pos.y = MapViewController.FLOOR_HEIGHT; + pos.y = WorldUtils.FLOOR_HEIGHT; float rotation = 0; if (canTileSupportDoor(x, y - 1, ownerId) && canTileSupportDoor(x, y + 1, ownerId)) { rotation = -FastMath.HALF_PI; diff --git a/src/toniarts/openkeeper/game/controller/ObjectsController.java b/src/toniarts/openkeeper/game/controller/ObjectsController.java index 303af5e1d..b8b675c76 100644 --- a/src/toniarts/openkeeper/game/controller/ObjectsController.java +++ b/src/toniarts/openkeeper/game/controller/ObjectsController.java @@ -181,7 +181,7 @@ private void loadObject(EntityId entity, short objectId, short ownerId, Vector3f entityData.setComponent(entity, new Owner(ownerId, ownerId)); // Move to the center of the tile - pos.y = (objectId == OBJECT_HEART_ID || objectId == FiveByFiveRotatedController.OBJECT_BIG_STEPS_ID || objectId == FiveByFiveRotatedController.OBJECT_ARCHES_ID || objectId == TempleController.OBJECT_TEMPLE_HAND_ID ? MapViewController.UNDERFLOOR_HEIGHT : MapViewController.FLOOR_HEIGHT); // FIXME: no + pos.y = (objectId == OBJECT_HEART_ID || objectId == FiveByFiveRotatedController.OBJECT_BIG_STEPS_ID || objectId == FiveByFiveRotatedController.OBJECT_ARCHES_ID || objectId == TempleController.OBJECT_TEMPLE_HAND_ID ? WorldUtils.UNDERFLOOR_HEIGHT : WorldUtils.FLOOR_HEIGHT); // FIXME: no entityData.setComponent(entity, new Position(rotation, pos)); // Add additional components diff --git a/src/toniarts/openkeeper/game/controller/TrapsController.java b/src/toniarts/openkeeper/game/controller/TrapsController.java index e4d73a030..41b2f40fa 100644 --- a/src/toniarts/openkeeper/game/controller/TrapsController.java +++ b/src/toniarts/openkeeper/game/controller/TrapsController.java @@ -105,7 +105,7 @@ private EntityId loadTrap(int x, int y, short trapId, short ownerId, boolean blu // Move to the center of the tile Vector3f pos = WorldUtils.pointToVector3f(x, y); - pos.y = MapViewController.FLOOR_HEIGHT; + pos.y = WorldUtils.FLOOR_HEIGHT; entityData.setComponent(entity, new Position(0, pos)); Trap trap = kwdFile.getTrapById(trapId); diff --git a/src/toniarts/openkeeper/game/controller/room/TempleController.java b/src/toniarts/openkeeper/game/controller/room/TempleController.java index 4a92621fd..7440034c7 100644 --- a/src/toniarts/openkeeper/game/controller/room/TempleController.java +++ b/src/toniarts/openkeeper/game/controller/room/TempleController.java @@ -85,26 +85,26 @@ protected List constructPillars() { // The model is in the center, rotation will not do any good, nudge them over the small sub quads, or corners of the tile if (freeDirections.contains(WallSection.WallDirection.NORTH) && freeDirections.contains(WallSection.WallDirection.EAST)) { Vector3f pos = WorldUtils.pointToVector3f(p.x, p.y); - pos.x = pos.x + MapViewController.TILE_WIDTH / 4; - pos.z = pos.z - MapViewController.TILE_WIDTH / 4; + pos.x = pos.x + WorldUtils.TILE_WIDTH / 4; + pos.z = pos.z - WorldUtils.TILE_WIDTH / 4; pillars.add(constructPillar(pos)); } if (freeDirections.contains(WallSection.WallDirection.SOUTH) && freeDirections.contains(WallSection.WallDirection.EAST)) { Vector3f pos = WorldUtils.pointToVector3f(p.x, p.y); - pos.x = pos.x + MapViewController.TILE_WIDTH / 4; - pos.z = pos.z + MapViewController.TILE_WIDTH / 4; + pos.x = pos.x + WorldUtils.TILE_WIDTH / 4; + pos.z = pos.z + WorldUtils.TILE_WIDTH / 4; pillars.add(constructPillar(pos)); } if (freeDirections.contains(WallSection.WallDirection.SOUTH) && freeDirections.contains(WallSection.WallDirection.WEST)) { Vector3f pos = WorldUtils.pointToVector3f(p.x, p.y); - pos.x = pos.x - MapViewController.TILE_WIDTH / 4; - pos.z = pos.z + MapViewController.TILE_WIDTH / 4; + pos.x = pos.x - WorldUtils.TILE_WIDTH / 4; + pos.z = pos.z + WorldUtils.TILE_WIDTH / 4; pillars.add(constructPillar(pos)); } if (freeDirections.contains(WallSection.WallDirection.NORTH) && freeDirections.contains(WallSection.WallDirection.WEST)) { Vector3f pos = WorldUtils.pointToVector3f(p.x, p.y); - pos.x = pos.x - MapViewController.TILE_WIDTH / 4; - pos.z = pos.z - MapViewController.TILE_WIDTH / 4; + pos.x = pos.x - WorldUtils.TILE_WIDTH / 4; + pos.z = pos.z - WorldUtils.TILE_WIDTH / 4; pillars.add(constructPillar(pos)); } } diff --git a/src/toniarts/openkeeper/game/logic/CreatureFallSystem.java b/src/toniarts/openkeeper/game/logic/CreatureFallSystem.java index ba5d87eba..3235dcf92 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureFallSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureFallSystem.java @@ -24,6 +24,7 @@ import toniarts.openkeeper.game.component.CreatureFall; import toniarts.openkeeper.game.component.Position; import toniarts.openkeeper.game.controller.creature.CreatureState; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.MapViewController; /** @@ -54,9 +55,9 @@ public void processTick(float tpf, double gameTime) { for (Entity entity : fallEntities) { Position position = entity.get(Position.class); Position newPosition = new Position(position.rotation, position.position); - newPosition.position.y = Math.max(newPosition.position.y - tpf * GRAVITY, MapViewController.FLOOR_HEIGHT); + newPosition.position.y = Math.max(newPosition.position.y - tpf * GRAVITY, WorldUtils.FLOOR_HEIGHT); entity.set(newPosition); - if (newPosition.position.y == MapViewController.FLOOR_HEIGHT) { + if (newPosition.position.y == WorldUtils.FLOOR_HEIGHT) { // We'll just remove this and add the AI entityData.removeComponent(entity.getId(), CreatureFall.class); diff --git a/src/toniarts/openkeeper/game/logic/CreatureSpawnSystem.java b/src/toniarts/openkeeper/game/logic/CreatureSpawnSystem.java index d88acdd76..293be1dc5 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureSpawnSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureSpawnSystem.java @@ -127,7 +127,7 @@ private void evaluateAndSpawnCreature(ICreatureEntrance entrance, double gameTim // Spawn imp Point entranceCoordinate = entrance.getEntranceCoordinate(); - entityId = creaturesController.spawnCreature(kwdFile.getImp().getCreatureId(), player.getKeeper().getId(), 1, new Vector2f(entranceCoordinate.x, entranceCoordinate.y), false); + entityId = creaturesController.spawnCreature(kwdFile.getImp().getCreatureId(), player.getKeeper().getId(), 1, new Vector2f(entranceCoordinate.x, entranceCoordinate.y), ICreaturesController.SpawnType.PLACE); spawned = true; } } else if (timeSinceLastSpawn >= Math.max(entranceCooldownTime, entranceCooldownTime * player.getCreatureControl().getTypeCount() * 0.5) @@ -152,7 +152,7 @@ private void evaluateAndSpawnCreature(ICreatureEntrance entrance, double gameTim if (!possibleCreatures.isEmpty()) { short creatureId = Utils.getRandomItem(possibleCreatures).getCreatureId(); Point entranceCoordinate = entrance.getEntranceCoordinate(); - entityId = creaturesController.spawnCreature(creatureId, player.getKeeper().getId(), 1, new Vector2f(entranceCoordinate.x, entranceCoordinate.y), true); + entityId = creaturesController.spawnCreature(creatureId, player.getKeeper().getId(), 1, new Vector2f(entranceCoordinate.x, entranceCoordinate.y), ICreaturesController.SpawnType.ENTRANCE); spawned = true; } } @@ -252,14 +252,14 @@ public void onSold(IRoomController room) { } private void addRoom(IRoomController room) { - if (room instanceof ICreatureEntrance) { - entrances.add((ICreatureEntrance) room); + if (room instanceof ICreatureEntrance iCreatureEntrance) { + entrances.add(iCreatureEntrance); } } private void removeRoom(IRoomController room) { - if (room instanceof ICreatureEntrance) { - entrances.remove((ICreatureEntrance) room); + if (room instanceof ICreatureEntrance iCreatureEntrance) { + entrances.remove(iCreatureEntrance); } } diff --git a/src/toniarts/openkeeper/game/state/MainMenuState.java b/src/toniarts/openkeeper/game/state/MainMenuState.java index d7a615571..e5142924c 100644 --- a/src/toniarts/openkeeper/game/state/MainMenuState.java +++ b/src/toniarts/openkeeper/game/state/MainMenuState.java @@ -185,7 +185,7 @@ public void initialize(AppStateManager stateManager, Application app) { private void loadCameraStartLocation() { Player player = kwdFile.getPlayer(Player.KEEPER1_ID); startLocation = WorldUtils.pointToVector3f(player.getStartingCameraX(), player.getStartingCameraY()); - startLocation.addLocal(0, MapViewController.FLOOR_HEIGHT, 0); + startLocation.addLocal(0, WorldUtils.FLOOR_HEIGHT, 0); // Set the actual camera location loadCameraStartLocation("EnginePath250"); diff --git a/src/toniarts/openkeeper/game/trigger/TriggerControl.java b/src/toniarts/openkeeper/game/trigger/TriggerControl.java index 2a11e0e15..464ec9f31 100644 --- a/src/toniarts/openkeeper/game/trigger/TriggerControl.java +++ b/src/toniarts/openkeeper/game/trigger/TriggerControl.java @@ -199,7 +199,7 @@ protected void doAction(TriggerActionData trigger) { Point p = new Point(trigger.getUserData("posX", int.class) - 1, trigger.getUserData("posY", int.class) - 1); // TODO: flags! - creaturesController.spawnCreature(creatureId, playerId, level, WorldUtils.pointToVector2f(p), false); + creaturesController.spawnCreature(creatureId, playerId, level, WorldUtils.pointToVector2f(p), ICreaturesController.SpawnType.PLACE); break; case MAKE: diff --git a/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerControl.java b/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerControl.java index 0b255df17..3572dcc1d 100644 --- a/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerControl.java +++ b/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerControl.java @@ -286,7 +286,7 @@ protected void doAction(TriggerActionData trigger) { break; } ICreatureEntrance room = ((ICreatureEntrance) rooms.iterator().next()); - creaturesController.spawnCreature(creatureId, playerId, level, WorldUtils.pointToVector2f(room.getEntranceCoordinate()), true); + creaturesController.spawnCreature(creatureId, playerId, level, WorldUtils.pointToVector2f(room.getEntranceCoordinate()), ICreaturesController.SpawnType.ENTRANCE); break; case SET_PORTAL_STATUS: // Creature part. Only for keeper x diff --git a/src/toniarts/openkeeper/utils/AssetUtils.java b/src/toniarts/openkeeper/utils/AssetUtils.java index 2756e8085..a46fabb02 100644 --- a/src/toniarts/openkeeper/utils/AssetUtils.java +++ b/src/toniarts/openkeeper/utils/AssetUtils.java @@ -645,7 +645,7 @@ public static void translateToTile(final Spatial spatial, final Point tile) { } public static void scale(final Spatial spatial) { - spatial.scale(MapViewController.TILE_WIDTH, MapViewController.TILE_HEIGHT, MapViewController.TILE_WIDTH); + spatial.scale(WorldUtils.TILE_WIDTH, WorldUtils.TILE_HEIGHT, WorldUtils.TILE_WIDTH); } /** diff --git a/src/toniarts/openkeeper/utils/WorldUtils.java b/src/toniarts/openkeeper/utils/WorldUtils.java index 6d6d0f65c..d529f4188 100644 --- a/src/toniarts/openkeeper/utils/WorldUtils.java +++ b/src/toniarts/openkeeper/utils/WorldUtils.java @@ -25,7 +25,6 @@ import toniarts.openkeeper.game.data.ActionPoint; import toniarts.openkeeper.game.map.IMapDataInformation; import toniarts.openkeeper.game.map.IMapTileInformation; -import toniarts.openkeeper.view.map.MapViewController; /** * Contains transforms from tile indexes and world coordinates @@ -34,6 +33,15 @@ */ public class WorldUtils { + public static final float TILE_HEIGHT = 1; + public static final float TILE_WIDTH = 1; + public static final float UNDERFLOOR_HEIGHT = 0 * TILE_HEIGHT; + public static final float TORCH_HEIGHT = 3 * TILE_HEIGHT / 2; // FIXME use Terrain Torch Height + public static final float FLOOR_HEIGHT = 1 * TILE_HEIGHT; + public static final float WATER_LEVEL = FLOOR_HEIGHT - 0.07F; + public static final float TOP_HEIGHT = 2 * TILE_HEIGHT; + public static final float DROP_HEIGHT = TOP_HEIGHT + 1; + private WorldUtils() { // Nope } @@ -46,7 +54,7 @@ private WorldUtils() { * @return position on 3D world with y = 0 */ public static Vector3f pointToVector3f(final int x, final int y) { - return new Vector3f(x * MapViewController.TILE_WIDTH, 0, y * MapViewController.TILE_WIDTH); + return new Vector3f(x * TILE_WIDTH, 0, y * TILE_WIDTH); } /** @@ -58,9 +66,9 @@ public static Vector3f pointToVector3f(final int x, final int y) { public static Vector3f ActionPointToVector3f(final ActionPoint ap) { return new Vector3f( - (ap.getStart().x + ap.getEnd().x) / 2.0f * MapViewController.TILE_WIDTH, + (ap.getStart().x + ap.getEnd().x) / 2.0f * TILE_WIDTH, 0, - (ap.getStart().y + ap.getEnd().y) / 2.0f * MapViewController.TILE_WIDTH); + (ap.getStart().y + ap.getEnd().y) / 2.0f * TILE_WIDTH); } /** @@ -72,8 +80,8 @@ public static Vector3f ActionPointToVector3f(final ActionPoint ap) { public static Vector2f ActionPointToVector2f(final ActionPoint ap) { return new Vector2f( - (ap.getStart().x + ap.getEnd().x) / 2.0f * MapViewController.TILE_WIDTH, - (ap.getStart().y + ap.getEnd().y) / 2.0f * MapViewController.TILE_WIDTH); + (ap.getStart().x + ap.getEnd().x) / 2.0f * TILE_WIDTH, + (ap.getStart().y + ap.getEnd().y) / 2.0f * TILE_WIDTH); } /** @@ -86,7 +94,7 @@ public static Vector3f pointToVector3f(final Point p) { } public static Vector2f pointToVector2f(final int x, final int y) { - return new Vector2f(x * MapViewController.TILE_WIDTH, y * MapViewController.TILE_WIDTH); + return new Vector2f(x * TILE_WIDTH, y * TILE_WIDTH); } public static Vector2f pointToVector2f(final Point p) { @@ -94,7 +102,7 @@ public static Vector2f pointToVector2f(final Point p) { } public static Vector2 pointToVector2(final int x, final int y) { - return new Vector2(x * MapViewController.TILE_WIDTH, y * MapViewController.TILE_WIDTH); + return new Vector2(x * TILE_WIDTH, y * TILE_WIDTH); } public static Vector2 pointToVector2(final Point p) { @@ -114,7 +122,7 @@ public static Point vectorToPoint(final Vector3f v) { } public static Point vectorToPoint(final float x, final float y) { - return new Point(Math.round(x / MapViewController.TILE_WIDTH), Math.round(y / MapViewController.TILE_WIDTH)); + return new Point(Math.round(x / TILE_WIDTH), Math.round(y / TILE_WIDTH)); } public static Vector2 vector3fToVector2(Vector3f v) { diff --git a/src/toniarts/openkeeper/view/PlayerCamera.java b/src/toniarts/openkeeper/view/PlayerCamera.java index 8fa15fbfe..e2c247827 100644 --- a/src/toniarts/openkeeper/view/PlayerCamera.java +++ b/src/toniarts/openkeeper/view/PlayerCamera.java @@ -23,6 +23,7 @@ import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import toniarts.openkeeper.tools.convert.map.Thing; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.MapViewController; /** @@ -96,14 +97,14 @@ protected void move(float dx, float dz) { look.addLocal(vel); // check limit - if (look.getX() > limit.x * MapViewController.TILE_WIDTH) { - look.setX(limit.x * MapViewController.TILE_WIDTH); + if (look.getX() > limit.x * WorldUtils.TILE_WIDTH) { + look.setX(limit.x * WorldUtils.TILE_WIDTH); } else if (look.getX() < 0) { look.setX(0); } - if (look.getZ() > limit.y * MapViewController.TILE_WIDTH) { - look.setZ(limit.y * MapViewController.TILE_WIDTH); + if (look.getZ() > limit.y * WorldUtils.TILE_WIDTH) { + look.setZ(limit.y * WorldUtils.TILE_WIDTH); } else if (look.getZ() < 0) { look.setZ(0); } diff --git a/src/toniarts/openkeeper/view/map/MapViewController.java b/src/toniarts/openkeeper/view/map/MapViewController.java index 5b4a69a1e..335261fe2 100644 --- a/src/toniarts/openkeeper/view/map/MapViewController.java +++ b/src/toniarts/openkeeper/view/map/MapViewController.java @@ -73,14 +73,6 @@ public abstract class MapViewController implements ILoader { private static final Logger logger = System.getLogger(MapViewController.class.getName()); - public final static float TILE_WIDTH = 1; - public final static float TILE_HEIGHT = 1; - public final static float TORCH_HEIGHT = 3 * TILE_HEIGHT / 2; // FIXME use Terrain Torch Height - public final static float TOP_HEIGHT = 2 * TILE_HEIGHT; - public final static float FLOOR_HEIGHT = 1 * TILE_HEIGHT; - public final static float UNDERFLOOR_HEIGHT = 0 * TILE_HEIGHT; - public final static float WATER_LEVEL = MapViewController.FLOOR_HEIGHT - 0.07f; - public final static ColorRGBA COLOR_FLASH = new ColorRGBA(0.8f, 0, 0, 1); public final static ColorRGBA COLOR_TAG = new ColorRGBA(0, 0, 0.8f, 1); private final static int PAGE_SQUARE_SIZE = 8; // Divide the terrain to square "pages" @@ -510,21 +502,21 @@ private void handleTorch(IMapTileInformation tile, Node pageNode) { if (tile.getY() % 2 == 0 && tile.getX() % 2 != 0 && canPlaceTorch(tile.getX(), tile.getY() - 1)) { // North name = "Torch1"; angleY = -FastMath.HALF_PI; - position = new Vector3f(0, TORCH_HEIGHT, -TILE_WIDTH / 2); + position = new Vector3f(0, WorldUtils.TORCH_HEIGHT, -WorldUtils.TILE_WIDTH / 2); } else if (tile.getX() % 2 == 0 && tile.getY() % 2 == 0 && canPlaceTorch(tile.getX() - 1, tile.getY())) { // West name = "Torch1"; - position = new Vector3f(-TILE_WIDTH / 2, TORCH_HEIGHT, 0); + position = new Vector3f(-WorldUtils.TILE_WIDTH / 2, WorldUtils.TORCH_HEIGHT, 0); } else if (tile.getY() % 2 == 0 && tile.getX() % 2 != 0 && canPlaceTorch(tile.getX(), tile.getY() + 1)) { // South name = "Torch1"; angleY = FastMath.HALF_PI; - position = new Vector3f(0, TORCH_HEIGHT, TILE_WIDTH / 2); + position = new Vector3f(0, WorldUtils.TORCH_HEIGHT, WorldUtils.TILE_WIDTH / 2); } else if (tile.getX() % 2 == 0 && tile.getY() % 2 == 0 && canPlaceTorch(tile.getX() + 1, tile.getY())) { // East name = "Torch1"; angleY = FastMath.PI; - position = new Vector3f(TILE_WIDTH / 2, TORCH_HEIGHT, 0); + position = new Vector3f(WorldUtils.TILE_WIDTH / 2, WorldUtils.TORCH_HEIGHT, 0); } // Move to tile and right height diff --git a/src/toniarts/openkeeper/view/map/Water.java b/src/toniarts/openkeeper/view/map/Water.java index cc201c3c1..0407b2b22 100644 --- a/src/toniarts/openkeeper/view/map/Water.java +++ b/src/toniarts/openkeeper/view/map/Water.java @@ -41,6 +41,7 @@ import toniarts.openkeeper.tools.convert.map.ArtResource; import toniarts.openkeeper.tools.convert.map.Terrain; import toniarts.openkeeper.utils.AssetUtils; +import toniarts.openkeeper.utils.WorldUtils; /** * Don't let the name fool you, this bad boy also handles lava construction. The @@ -160,14 +161,14 @@ private static Mesh createMesh(List> entityInstances, bo Vector2f textureCoord4 = new Vector2f(1, 1); // Vertices - Vector3f vertice1 = new Vector3f((tile.x - 0.5f) * MapViewController.TILE_WIDTH, - MapViewController.WATER_LEVEL, (tile.y - 0.5f) * MapViewController.TILE_WIDTH); - Vector3f vertice2 = new Vector3f((tile.x + 0.5f) * MapViewController.TILE_WIDTH, - MapViewController.WATER_LEVEL, (tile.y - 0.5f) * MapViewController.TILE_WIDTH); - Vector3f vertice3 = new Vector3f((tile.x - 0.5f) * MapViewController.TILE_WIDTH, - MapViewController.WATER_LEVEL, (tile.y + 0.5f) * MapViewController.TILE_WIDTH); - Vector3f vertice4 = new Vector3f((tile.x + 0.5f) * MapViewController.TILE_WIDTH, - MapViewController.WATER_LEVEL, (tile.y + 0.5f) * MapViewController.TILE_WIDTH); + Vector3f vertice1 = new Vector3f((tile.x - 0.5f) * WorldUtils.TILE_WIDTH, + WorldUtils.WATER_LEVEL, (tile.y - 0.5f) * WorldUtils.TILE_WIDTH); + Vector3f vertice2 = new Vector3f((tile.x + 0.5f) * WorldUtils.TILE_WIDTH, + WorldUtils.WATER_LEVEL, (tile.y - 0.5f) * WorldUtils.TILE_WIDTH); + Vector3f vertice3 = new Vector3f((tile.x - 0.5f) * WorldUtils.TILE_WIDTH, + WorldUtils.WATER_LEVEL, (tile.y + 0.5f) * WorldUtils.TILE_WIDTH); + Vector3f vertice4 = new Vector3f((tile.x + 0.5f) * WorldUtils.TILE_WIDTH, + WorldUtils.WATER_LEVEL, (tile.y + 0.5f) * WorldUtils.TILE_WIDTH); int vertice1Index = addVertice(verticeHash, vertice1, vertices, textureCoord1, textureCoordinates, normals, shareVertices); diff --git a/src/toniarts/openkeeper/view/map/construction/DoubleQuadConstructor.java b/src/toniarts/openkeeper/view/map/construction/DoubleQuadConstructor.java index 43eb379b1..cc5a21c7f 100644 --- a/src/toniarts/openkeeper/view/map/construction/DoubleQuadConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/DoubleQuadConstructor.java @@ -25,6 +25,7 @@ import java.awt.Point; import toniarts.openkeeper.common.RoomInstance; import toniarts.openkeeper.utils.AssetUtils; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.MapViewController; /** @@ -158,7 +159,7 @@ public static Node constructQuad(AssetManager assetManager, String modelName, bo piece = 0; yAngle = FastMath.HALF_PI; } - movement = new Vector3f(-MapViewController.TILE_WIDTH / 4, 0, -MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); } else if (i == 1 && k == 0) { // North east corner if (inside) { piece = 13; @@ -189,7 +190,7 @@ public static Node constructQuad(AssetManager assetManager, String modelName, bo piece = 0; yAngle = -FastMath.HALF_PI; } - movement = new Vector3f(MapViewController.TILE_WIDTH / 4, 0, -MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); } else if (i == 0 && k == 1) { // South west corner if (inside) { piece = 13; @@ -223,7 +224,7 @@ public static Node constructQuad(AssetManager assetManager, String modelName, bo piece = 0; yAngle = FastMath.PI; } - movement = new Vector3f(-MapViewController.TILE_WIDTH / 4, 0, MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, WorldUtils.TILE_WIDTH / 4); } else { // South east corner if (i == 1 && k == 1) if (inside) { piece = 13; @@ -258,7 +259,7 @@ public static Node constructQuad(AssetManager assetManager, String modelName, bo piece = 0; yAngle = FastMath.PI; } - movement = new Vector3f(MapViewController.TILE_WIDTH / 4, 0, MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, WorldUtils.TILE_WIDTH / 4); } // Load the piece Spatial part = AssetUtils.loadModel(assetManager, modelName + piece, null); diff --git a/src/toniarts/openkeeper/view/map/construction/HeroGateFrontEndConstructor.java b/src/toniarts/openkeeper/view/map/construction/HeroGateFrontEndConstructor.java index 234550b10..2ecfccd8f 100644 --- a/src/toniarts/openkeeper/view/map/construction/HeroGateFrontEndConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/HeroGateFrontEndConstructor.java @@ -29,6 +29,7 @@ import toniarts.openkeeper.tools.convert.map.ArtResource; import toniarts.openkeeper.utils.AssetUtils; import toniarts.openkeeper.utils.FullMoon; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.MapViewController; import toniarts.openkeeper.view.map.WallSection; import toniarts.openkeeper.world.room.control.FrontEndLevelControl; @@ -180,7 +181,7 @@ private Spatial loadObject(String model, AssetManager assetManager, Point start, // Reset moveSpatial(object, start, p); - object.move(0, MapViewController.FLOOR_HEIGHT, 0); + object.move(0, WorldUtils.FLOOR_HEIGHT, 0); return object; } diff --git a/src/toniarts/openkeeper/view/map/construction/QuadConstructor.java b/src/toniarts/openkeeper/view/map/construction/QuadConstructor.java index 9d8e86bf4..a321f57d0 100644 --- a/src/toniarts/openkeeper/view/map/construction/QuadConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/QuadConstructor.java @@ -26,6 +26,7 @@ import toniarts.openkeeper.common.RoomInstance; import toniarts.openkeeper.tools.convert.map.ArtResource; import toniarts.openkeeper.utils.AssetUtils; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.MapViewController; /** @@ -90,7 +91,7 @@ public static Node constructQuad(AssetManager assetManager, String modelName, Ar piece = 1; yAngle = FastMath.HALF_PI; } - movement = new Vector3f(-MapViewController.TILE_WIDTH / 4, 0, -MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); } else if (i == 1 && k == 0) { // North east corner if (N && E && NE) { piece = 3; @@ -106,7 +107,7 @@ public static Node constructQuad(AssetManager assetManager, String modelName, Ar } else { piece = 1; } - movement = new Vector3f(MapViewController.TILE_WIDTH / 4, 0, -MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); } else if (i == 0 && k == 1) { // South west corner if (S && W && SW) { piece = 3; @@ -122,7 +123,7 @@ public static Node constructQuad(AssetManager assetManager, String modelName, Ar piece = 1; yAngle = FastMath.PI; } - movement = new Vector3f(-MapViewController.TILE_WIDTH / 4, 0, MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, WorldUtils.TILE_WIDTH / 4); } else { // South east corner if (i == 1 && k == 1) if (S && E && SE) { piece = 3; @@ -140,7 +141,7 @@ public static Node constructQuad(AssetManager assetManager, String modelName, Ar piece = 1; yAngle = -FastMath.HALF_PI; } - movement = new Vector3f(MapViewController.TILE_WIDTH / 4, 0, MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, WorldUtils.TILE_WIDTH / 4); } // Load the piece Spatial part = AssetUtils.loadModel(assetManager, modelName + (base + piece), artResource); diff --git a/src/toniarts/openkeeper/view/map/construction/RoomConstructor.java b/src/toniarts/openkeeper/view/map/construction/RoomConstructor.java index a9424066c..fdf18161c 100644 --- a/src/toniarts/openkeeper/view/map/construction/RoomConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/RoomConstructor.java @@ -17,6 +17,7 @@ import toniarts.openkeeper.view.map.MapViewController; import toniarts.openkeeper.common.RoomInstance; import toniarts.openkeeper.tools.convert.map.ArtResource; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.WallSection; /** @@ -151,11 +152,11 @@ public Spatial getWallSpatial(Point p, WallSection.WallDirection direction) { Vector3f moveSecond; if (section.direction() == WallSection.WallDirection.WEST || section.direction() == WallSection.WallDirection.SOUTH) { - moveFirst = new Vector3f(MapViewController.TILE_WIDTH / 4, 0, -3 * MapViewController.TILE_WIDTH / 4); - moveSecond = new Vector3f(-MapViewController.TILE_WIDTH / 4, 0, -3 * MapViewController.TILE_WIDTH / 4); + moveFirst = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, -3 * WorldUtils.TILE_WIDTH / 4); + moveSecond = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, -3 * WorldUtils.TILE_WIDTH / 4); } else { // NORTH, EAST - moveFirst = new Vector3f(-MapViewController.TILE_WIDTH / 4, 0, -3 * MapViewController.TILE_WIDTH / 4); - moveSecond = new Vector3f(MapViewController.TILE_WIDTH / 4, 0, -3 * MapViewController.TILE_WIDTH / 4); + moveFirst = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, -3 * WorldUtils.TILE_WIDTH / 4); + moveSecond = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, -3 * WorldUtils.TILE_WIDTH / 4); } spatial = new BatchNode(); @@ -191,17 +192,17 @@ public Spatial getWallSpatial(Point p, WallSection.WallDirection direction) { switch (section.direction()) { case WEST: - spatial.move(-MapViewController.TILE_WIDTH, 0, 0); + spatial.move(-WorldUtils.TILE_WIDTH, 0, 0); break; case SOUTH: - spatial.move(0, 0, MapViewController.TILE_WIDTH); + spatial.move(0, 0, WorldUtils.TILE_WIDTH); break; case EAST: - spatial.move(MapViewController.TILE_WIDTH, 0, 0); + spatial.move(WorldUtils.TILE_WIDTH, 0, 0); break; default: // NORTH - spatial.move(0, 0, -MapViewController.TILE_WIDTH); + spatial.move(0, 0, -WorldUtils.TILE_WIDTH); break; } } diff --git a/src/toniarts/openkeeper/view/map/construction/SingleQuadConstructor.java b/src/toniarts/openkeeper/view/map/construction/SingleQuadConstructor.java index a5380de2a..447aa7b5d 100644 --- a/src/toniarts/openkeeper/view/map/construction/SingleQuadConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/SingleQuadConstructor.java @@ -25,7 +25,7 @@ import toniarts.openkeeper.game.map.IMapTileInformation; import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Terrain; -import static toniarts.openkeeper.view.map.MapViewController.TILE_WIDTH; +import toniarts.openkeeper.utils.WorldUtils; /** * @@ -94,7 +94,7 @@ public Spatial construct(IMapDataInformation mapData, int x, int y, final Terrai } yAngle = FastMath.PI; - movement = new Vector3f(-TILE_WIDTH / 4, 0, -TILE_WIDTH / 4); + movement = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); } else if (i == 1 && k == 0) { // North east corner if (N && E && NE) { @@ -108,7 +108,7 @@ public Spatial construct(IMapDataInformation mapData, int x, int y, final Terrai } yAngle = FastMath.HALF_PI; - movement = new Vector3f(TILE_WIDTH / 4, 0, -TILE_WIDTH / 4); + movement = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); } else if (i == 0 && k == 1) { // South west corner if (S && W && SW) { @@ -122,7 +122,7 @@ public Spatial construct(IMapDataInformation mapData, int x, int y, final Terrai } yAngle = -FastMath.HALF_PI; - movement = new Vector3f(-TILE_WIDTH / 4, 0, TILE_WIDTH / 4); + movement = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, WorldUtils.TILE_WIDTH / 4); } else { // (i == 1 && k == 1) South east corner if (S && E && SE) { @@ -136,7 +136,7 @@ public Spatial construct(IMapDataInformation mapData, int x, int y, final Terrai } yAngle = 0; - movement = new Vector3f(TILE_WIDTH / 4, 0, TILE_WIDTH / 4); + movement = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, WorldUtils.TILE_WIDTH / 4); } // Load the piece diff --git a/src/toniarts/openkeeper/view/map/construction/room/CombatPitConstructor.java b/src/toniarts/openkeeper/view/map/construction/room/CombatPitConstructor.java index 4becfe3bc..28cdab6cf 100644 --- a/src/toniarts/openkeeper/view/map/construction/room/CombatPitConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/room/CombatPitConstructor.java @@ -23,10 +23,9 @@ import java.awt.Point; import toniarts.openkeeper.tools.convert.map.ArtResource; import toniarts.openkeeper.utils.AssetUtils; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.construction.DoubleQuadConstructor; -import static toniarts.openkeeper.view.map.MapViewController.TILE_WIDTH; - /** * Manages combat pit door placement, currently it is decoupled from the actual * door. But the rules are pretty static, so... And now one so visibly uses this @@ -80,7 +79,7 @@ protected BatchNode constructFloor() { // This is true, the door is always like this, it might not look correct visually (the opposite quads of the door...) but it is Spatial part = AssetUtils.loadModel(assetManager, modelName + "14", artResource); AssetUtils.translateToTile(part, new Point(x, y)); - part.move(-TILE_WIDTH / 4, 0, -TILE_WIDTH / 4); + part.move(-WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); root.attachChild(part); diff --git a/src/toniarts/openkeeper/view/map/construction/room/PrisonConstructor.java b/src/toniarts/openkeeper/view/map/construction/room/PrisonConstructor.java index 2744a755d..263bcf231 100644 --- a/src/toniarts/openkeeper/view/map/construction/room/PrisonConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/room/PrisonConstructor.java @@ -23,6 +23,7 @@ import java.awt.Point; import toniarts.openkeeper.tools.convert.map.ArtResource; import toniarts.openkeeper.utils.AssetUtils; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.MapViewController; import toniarts.openkeeper.view.map.construction.DoubleQuadConstructor; @@ -77,7 +78,7 @@ protected BatchNode constructFloor() { // This is true, the door is always like this, it might not look correct visually (the opposite quads of the door...) but it is Spatial part = AssetUtils.loadModel(assetManager, modelName + "14", artResource); AssetUtils.translateToTile(part, new Point(x, y)); - part.move(-MapViewController.TILE_WIDTH / 4, 0, -MapViewController.TILE_WIDTH / 4); + part.move(-WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); root.attachChild(part); diff --git a/src/toniarts/openkeeper/view/map/construction/room/StoneBridgeConstructor.java b/src/toniarts/openkeeper/view/map/construction/room/StoneBridgeConstructor.java index 761d09b54..96cf4d141 100644 --- a/src/toniarts/openkeeper/view/map/construction/room/StoneBridgeConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/room/StoneBridgeConstructor.java @@ -25,6 +25,7 @@ import java.awt.Point; import toniarts.openkeeper.tools.convert.map.ArtResource; import toniarts.openkeeper.utils.AssetUtils; +import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.MapViewController; import toniarts.openkeeper.view.map.construction.QuadConstructor; @@ -85,7 +86,7 @@ protected BatchNode constructFloor() { piece = 1; yAngle = FastMath.HALF_PI; } - movement = new Vector3f(-MapViewController.TILE_WIDTH / 4, 0, -MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); } else if (i == 1 && k == 0) { // North east corner if (N && E && NE) { piece = 3; @@ -101,7 +102,7 @@ protected BatchNode constructFloor() { } else { piece = 1; } - movement = new Vector3f(MapViewController.TILE_WIDTH / 4, 0, -MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, -WorldUtils.TILE_WIDTH / 4); } else if (i == 0 && k == 1) { // South west corner if (S && W && SW) { piece = 3; @@ -117,7 +118,7 @@ protected BatchNode constructFloor() { piece = 1; yAngle = FastMath.PI; } - movement = new Vector3f(-MapViewController.TILE_WIDTH / 4, 0, MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(-WorldUtils.TILE_WIDTH / 4, 0, WorldUtils.TILE_WIDTH / 4); } else { // South east corner if (i == 1 && k == 1) if (S && E && SE) { piece = 3; @@ -135,7 +136,7 @@ protected BatchNode constructFloor() { piece = 1; yAngle = -FastMath.HALF_PI; } - movement = new Vector3f(MapViewController.TILE_WIDTH / 4, 0, MapViewController.TILE_WIDTH / 4); + movement = new Vector3f(WorldUtils.TILE_WIDTH / 4, 0, WorldUtils.TILE_WIDTH / 4); } // Load the piece diff --git a/src/toniarts/openkeeper/view/selection/SelectionHandler.java b/src/toniarts/openkeeper/view/selection/SelectionHandler.java index 2ba1ef455..200483cd5 100644 --- a/src/toniarts/openkeeper/view/selection/SelectionHandler.java +++ b/src/toniarts/openkeeper/view/selection/SelectionHandler.java @@ -55,7 +55,7 @@ public ColorRGBA getColor() { public SelectionHandler(Main app) { this.app = app; - this.selectionArea = new SelectionArea(MapViewController.TILE_WIDTH); + this.selectionArea = new SelectionArea(WorldUtils.TILE_WIDTH); setupVisualsForSelection(); } @@ -73,7 +73,7 @@ public boolean update(Vector2f mousePosition) { Vector3f tmp = cam.getWorldCoordinates(this.mousePosition, 0f).clone(); Vector3f dir = cam.getWorldCoordinates(this.mousePosition, 1f).subtractLocal(tmp).normalizeLocal(); - dir.multLocal((MapViewController.TOP_HEIGHT - pos.getY()) / dir.getY()).addLocal(pos); + dir.multLocal((WorldUtils.TOP_HEIGHT - pos.getY()) / dir.getY()).addLocal(pos); pointedPosition.set(dir.getX(), dir.getZ()); pointedTileIndex = WorldUtils.vectorToPoint(pointedPosition); @@ -163,11 +163,11 @@ public void updateSelectionBox() { float delta = 0.01f; Vector2f position = selectionArea.getCenter(); - wireBoxGeo.setLocalTranslation(position.x, MapViewController.FLOOR_HEIGHT, position.y); + wireBoxGeo.setLocalTranslation(position.x, WorldUtils.FLOOR_HEIGHT, position.y); - wireBox.updatePositions(MapViewController.TILE_WIDTH / 2 * dx + delta, - MapViewController.FLOOR_HEIGHT + delta, - MapViewController.TILE_WIDTH / 2 * dy + delta); + wireBox.updatePositions(WorldUtils.TILE_WIDTH / 2 * dx + delta, + WorldUtils.FLOOR_HEIGHT + delta, + WorldUtils.TILE_WIDTH / 2 * dy + delta); // Selection color indicator ColorIndicator newSelectionColor = getColorIndicator(); @@ -188,7 +188,7 @@ private void setupVisualsForSelection() { matWireBox.setColor("Color", selectionColor.getColor()); matWireBox.getAdditionalRenderState().setLineWidth(6); - this.wireBox = new WireBox(MapViewController.TILE_WIDTH, MapViewController.TILE_WIDTH, MapViewController.TILE_WIDTH); + this.wireBox = new WireBox(WorldUtils.TILE_WIDTH, WorldUtils.TILE_WIDTH, WorldUtils.TILE_WIDTH); this.wireBox.setDynamic(); this.wireBoxGeo = new Geometry("wireBox", wireBox); From 86b06f2f241bdbabe90eca5b7526bf64b063e012 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Wed, 17 Jul 2024 17:35:39 +0300 Subject: [PATCH 29/70] Can get shots by ID --- src/toniarts/openkeeper/tools/convert/map/KwdFile.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/tools/convert/map/KwdFile.java b/src/toniarts/openkeeper/tools/convert/map/KwdFile.java index 4de0d43b5..84b864c35 100644 --- a/src/toniarts/openkeeper/tools/convert/map/KwdFile.java +++ b/src/toniarts/openkeeper/tools/convert/map/KwdFile.java @@ -82,7 +82,7 @@ public final class KwdFile { private static final Logger logger = System.getLogger(KwdFile.class.getName()); - // These are needed in various places, I don't know how to else regognize these + // These are needed in various places, I don't know how to else recognize these private final static short ROOM_PORTAL_ID = 3; private final static short ROOM_DUNGEON_HEART_ID = 5; private final static short TRIGGER_GENERIC = 213; @@ -3138,6 +3138,10 @@ public List getShots() { return c; } + public Shot getShotById(short shotId) { + return shots.get(shotId); + } + public GameMap getMap() { return map; } From 9e7a5252a8538b45f107c8108f245fd47eab012b Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Wed, 17 Jul 2024 17:36:13 +0300 Subject: [PATCH 30/70] Initial spell casting --- .../game/controller/CreaturesController.java | 42 ++++++++---- .../game/controller/GameController.java | 1 + .../game/controller/GameWorldController.java | 68 ++++++++++++------- .../game/controller/ICreaturesController.java | 11 ++- 4 files changed, 83 insertions(+), 39 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/CreaturesController.java b/src/toniarts/openkeeper/game/controller/CreaturesController.java index 312b7a6d6..db7acd817 100644 --- a/src/toniarts/openkeeper/game/controller/CreaturesController.java +++ b/src/toniarts/openkeeper/game/controller/CreaturesController.java @@ -36,6 +36,7 @@ import toniarts.openkeeper.game.component.CreatureComponent; import toniarts.openkeeper.game.component.CreatureEfficiency; import toniarts.openkeeper.game.component.CreatureExperience; +import toniarts.openkeeper.game.component.CreatureFall; import toniarts.openkeeper.game.component.CreatureHunger; import toniarts.openkeeper.game.component.CreatureImprisoned; import toniarts.openkeeper.game.component.CreatureMeleeAttack; @@ -73,7 +74,6 @@ import toniarts.openkeeper.tools.convert.map.Variable; import toniarts.openkeeper.utils.Utils; import toniarts.openkeeper.utils.WorldUtils; -import toniarts.openkeeper.view.map.MapViewController; /** * This is a controller that controls all the game objects in the world TODO: @@ -214,30 +214,30 @@ public EntityId spawnCreature(Thing.Creature creature, Vector2f position) { ownerId = deadBody.getPlayerId(); } return loadCreature(creature.getCreatureId(), ownerId, level, position.getX(), position.getY(), 0f, healthPercentage, creature.getGoldHeld(), - triggerId != null && triggerId != 0 ? triggerId : null, false, objective, objectiveTargetPlayerId, objectiveTargetActionPointId); + triggerId != null && triggerId != 0 ? triggerId : null, SpawnType.PLACE, objective, objectiveTargetPlayerId, objectiveTargetActionPointId); } @Override - public EntityId spawnCreature(short creatureId, short playerId, int level, Vector2f position, boolean entrance) { - return loadCreature(creatureId, playerId, level, position.x, position.y, 0, 100, 0, null, entrance, null, (short) 0, 0); + public EntityId spawnCreature(short creatureId, short playerId, int level, Vector2f position, SpawnType spawnType) { + return loadCreature(creatureId, playerId, level, position.x, position.y, 0, 100, 0, null, spawnType, null, (short) 0, 0); } private EntityId loadCreature(short creatureId, short ownerId, int level, float x, float y, float rotation, Integer healthPercentage, int money, - Integer triggerId, boolean entrance, Thing.HeroParty.Objective objective, short objectiveTargetPlayerId, int objectiveTargetActionPointId) { + Integer triggerId, SpawnType spawnType, Thing.HeroParty.Objective objective, short objectiveTargetPlayerId, int objectiveTargetActionPointId) { EntityId entity = entityData.createEntity(); Creature creature = kwdFile.getCreature(creatureId); - return loadCreature(entity, creature, healthPercentage, money, level, entrance, x, y, ownerId, rotation, objective, objectiveTargetPlayerId, objectiveTargetActionPointId, triggerId); + return loadCreature(entity, creature, healthPercentage, money, level, spawnType, x, y, ownerId, rotation, objective, objectiveTargetPlayerId, objectiveTargetActionPointId, triggerId); } - private EntityId loadCreature(EntityId entity, Creature creature, Integer healthPercentage, int money, int level, boolean entrance, float x, float y, short ownerId, float rotation, Thing.HeroParty.Objective objective, short objectiveTargetPlayerId, int objectiveTargetActionPointId, Integer triggerId) { + private EntityId loadCreature(EntityId entity, Creature creature, Integer healthPercentage, int money, int level, SpawnType spawnType, float x, float y, short ownerId, float rotation, Thing.HeroParty.Objective objective, short objectiveTargetPlayerId, int objectiveTargetActionPointId, Integer triggerId) { String name = Utils.generateCreatureName(); String bloodType = Utils.generateBloodType(); - return loadCreature(entity, creature, name, bloodType, healthPercentage, money, level, entrance, x, y, ownerId, rotation, objective, objectiveTargetPlayerId, objectiveTargetActionPointId, triggerId); + return loadCreature(entity, creature, name, bloodType, healthPercentage, money, level, spawnType, x, y, ownerId, rotation, objective, objectiveTargetPlayerId, objectiveTargetActionPointId, triggerId); } - private EntityId loadCreature(EntityId entity, Creature creature, String name, String bloodType, Integer healthPercentage, int money, int level, boolean entrance, float x, float y, short ownerId, float rotation, Thing.HeroParty.Objective objective, short objectiveTargetPlayerId, int objectiveTargetActionPointId, Integer triggerId) { + private EntityId loadCreature(EntityId entity, Creature creature, String name, String bloodType, Integer healthPercentage, int money, int level, SpawnType spawnType, float x, float y, short ownerId, float rotation, Thing.HeroParty.Objective objective, short objectiveTargetPlayerId, int objectiveTargetActionPointId, Integer triggerId) { short creatureId = creature.getId(); // Create health, unless dead body @@ -279,8 +279,24 @@ private EntityId loadCreature(EntityId entity, Creature creature, String name, S entityData.setComponent(entity, new CreatureHunger(gameTimer.getGameTime(), 0)); } - CreatureState creatureState = entrance ? CreatureState.ENTERING_DUNGEON : getCreatureStateByMapLocation(WorldUtils.vectorToPoint(x, y), ownerId, entity); - entityData.setComponent(entity, new CreatureAi(gameTimer.getGameTime(), creatureState, creatureId)); + CreatureState creatureState; + switch (spawnType) { + case ENTRANCE -> { + creatureState = CreatureState.ENTERING_DUNGEON; + } + case PLACE -> { + creatureState = getCreatureStateByMapLocation(WorldUtils.vectorToPoint(x, y), ownerId, entity); + } + case CONJURE -> { + creatureState = null; + entityData.setComponent(entity, new CreatureFall()); + } + default -> + throw new RuntimeException("SpawnType " + spawnType + " not handled!"); + } + if (creatureState != null) { + entityData.setComponent(entity, new CreatureAi(gameTimer.getGameTime(), creatureState, creatureId)); + } // Regeneration Regeneration regeneration = new Regeneration(); @@ -320,7 +336,7 @@ private EntityId loadCreature(EntityId entity, Creature creature, String name, S // Position // FIXME: no floor height - entityData.setComponent(entity, new Position(rotation, new Vector3f(x, MapViewController.FLOOR_HEIGHT, y))); + entityData.setComponent(entity, new Position(rotation, new Vector3f(x, spawnType == SpawnType.CONJURE ? WorldUtils.DROP_HEIGHT : WorldUtils.FLOOR_HEIGHT, y))); // Mobility entityData.setComponent(entity, new Mobile(creature.getFlags().contains(Creature.CreatureFlag.CAN_FLY), @@ -568,7 +584,7 @@ public void turnCreatureIntoAnother(EntityId entityId, short playerId, short cre EntityId newEntityId = entityData.createEntity(); // Load the creature anew - loadCreature(newEntityId, kwdFile.getCreature(creatureId), creatureComponent.name, creatureComponent.bloodType, 100, 0, 1, false, position.position.x, position.position.z, playerId, position.rotation, null, (short) 0, 0, trigger != null ? trigger.triggerId : null); + loadCreature(newEntityId, kwdFile.getCreature(creatureId), creatureComponent.name, creatureComponent.bloodType, 100, 0, 1, SpawnType.PLACE, position.position.x, position.position.z, playerId, position.rotation, null, (short) 0, 0, trigger != null ? trigger.triggerId : null); } } diff --git a/src/toniarts/openkeeper/game/controller/GameController.java b/src/toniarts/openkeeper/game/controller/GameController.java index b2788e9de..eab5756bb 100644 --- a/src/toniarts/openkeeper/game/controller/GameController.java +++ b/src/toniarts/openkeeper/game/controller/GameController.java @@ -218,6 +218,7 @@ public void createNewGame() { gameWorldController.createNewGame(this, this); positionSystem = new PositionSystem(gameWorldController.getMapController(), entityData, gameWorldController.getCreaturesController(), gameWorldController.getDoorsController(), gameWorldController.getObjectsController()); + gameWorldController.setEntityPositionLookup(positionSystem); // Navigation navigationService = new NavigationService(gameWorldController.getMapController(), positionSystem); diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index dd1fbd3bb..fd10593c9 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -68,6 +68,7 @@ import toniarts.openkeeper.game.controller.room.storage.IRoomObjectControl; import toniarts.openkeeper.game.controller.room.storage.RoomGoldControl; import toniarts.openkeeper.game.data.Keeper; +import toniarts.openkeeper.game.data.ResearchableEntity; import toniarts.openkeeper.game.listener.PlayerActionListener; import toniarts.openkeeper.game.logic.IEntityPositionLookup; import toniarts.openkeeper.game.map.IMapTileController; @@ -79,6 +80,7 @@ import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Player; import toniarts.openkeeper.tools.convert.map.Room; +import toniarts.openkeeper.tools.convert.map.Shot; import toniarts.openkeeper.tools.convert.map.Terrain; import toniarts.openkeeper.tools.convert.map.Tile; import toniarts.openkeeper.tools.convert.map.Trap; @@ -92,7 +94,7 @@ */ public class GameWorldController implements IGameWorldController, IPlayerActions { - private static final Logger LOGGER = Logger.getLogger(GameWorldController.class.getName()); + private static final Logger logger = Logger.getLogger(GameWorldController.class.getName()); /** * When dealing with gold... We currently better lock it. Logic stuff @@ -126,7 +128,6 @@ public GameWorldController(KwdFile kwdFile, EntityData entityData, Map { + } + case NONE -> { return; - case ALL_CREATURES: { + } + case ALL_CREATURES -> { if (creature == null) { return; } - break; } - case ENEMY_CREATURES: { + case ENEMY_CREATURES -> { if (owner == null) { return; } if (!player.isEnemy(owner)) { return; } - break; } - case OWN_CREATURES: - case POSESSION: { + case OWN_CREATURES, POSESSION -> { if (creature == null || owner == null || owner != playerId) { return; } } } + // Cast the spell + Shot shot = kwdFile.getShotById(keeperSpell.getShotTypeId()); + int shotData1 = researchableEntity.isUpgraded() ? keeperSpell.getBonusShotData1() : keeperSpell.getShotData1(); + int shotData2 = researchableEntity.isUpgraded() ? keeperSpell.getBonusShotData2() : keeperSpell.getShotData2(); + switch (shot.getProcessType()) { + case CREATE_CREATURE -> { + creaturesController.spawnCreature((short) shotData1, playerId, shotData2, position, ICreaturesController.SpawnType.CONJURE); + } + case MODIFY_HEALTH -> { + + } + default -> + logger.log(Level.WARNING, "Shot type {0} not implemented", shot.getProcessType()); + } } @Override public void placeDoor(short doorId, Point tile, short playerId) { Door door = kwdFile.getDoorById(doorId); if (door == null) { - LOGGER.log(Level.WARNING, "Invalid door ID for door placement received, was: {0}", door); + logger.log(Level.WARNING, "Invalid door ID for door placement received, was: {0}", door); return; } IMapTileInformation mapTile = mapController.getMapData().getTile(tile); if (mapTile == null) { - LOGGER.log(Level.WARNING, "Invalid map location for door placement received, was: {0}", tile); + logger.log(Level.WARNING, "Invalid map location for door placement received, was: {0}", tile); return; } Keeper player = players.get(playerId); if (player == null) { - LOGGER.log(Level.WARNING, "Invalid player for door placement received, was: {0}", playerId); + logger.log(Level.WARNING, "Invalid player for door placement received, was: {0}", playerId); return; } @@ -1016,19 +1034,19 @@ public void placeDoor(short doorId, Point tile, short playerId) { public void placeTrap(short trapId, Point tile, short playerId) { Trap trap = kwdFile.getTrapById(trapId); if (trap == null) { - LOGGER.log(Level.WARNING, "Invalid trap ID for trap placement received, was: {0}", trap); + logger.log(Level.WARNING, "Invalid trap ID for trap placement received, was: {0}", trap); return; } IMapTileInformation mapTile = mapController.getMapData().getTile(tile); if (mapTile == null) { - LOGGER.log(Level.WARNING, "Invalid map location for trap placement received, was: {0}", tile); + logger.log(Level.WARNING, "Invalid map location for trap placement received, was: {0}", tile); return; } Keeper player = players.get(playerId); if (player == null) { - LOGGER.log(Level.WARNING, "Invalid player for trap placement received, was: {0}", playerId); + logger.log(Level.WARNING, "Invalid player for trap placement received, was: {0}", playerId); return; } @@ -1052,4 +1070,8 @@ public IObjectsController getObjectsController() { public ITrapsController getTrapsController() { return trapsController; } + + public void setEntityPositionLookup(IEntityPositionLookup entityPositionLookup) { + this.entityPositionLookup = entityPositionLookup; + } } diff --git a/src/toniarts/openkeeper/game/controller/ICreaturesController.java b/src/toniarts/openkeeper/game/controller/ICreaturesController.java index b17bb7812..f3d169398 100644 --- a/src/toniarts/openkeeper/game/controller/ICreaturesController.java +++ b/src/toniarts/openkeeper/game/controller/ICreaturesController.java @@ -30,6 +30,12 @@ */ public interface ICreaturesController extends IEntityWrapper { + public enum SpawnType { + PLACE, + ENTRANCE, + CONJURE + } + /** * Spawn a creature * @@ -46,11 +52,10 @@ public interface ICreaturesController extends IEntityWrapper Date: Wed, 17 Jul 2024 18:45:31 +0300 Subject: [PATCH 31/70] Deduct mana cost from player --- .../openkeeper/game/controller/GameWorldController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index fd10593c9..3de711bef 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -990,6 +990,8 @@ public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Ve } } + // Deduct the mana + playerControllers.get(playerId).getManaControl().updateMana(0, keeperSpell.getManaCost()); // Cast the spell Shot shot = kwdFile.getShotById(keeperSpell.getShotTypeId()); @@ -1004,7 +1006,7 @@ public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Ve } default -> logger.log(Level.WARNING, "Shot type {0} not implemented", shot.getProcessType()); - } + } } @Override From b7c2293e6fab1ab87fa8cda7f2724d6d7940791d Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Wed, 17 Jul 2024 20:01:24 +0300 Subject: [PATCH 32/70] Use local variable --- .../openkeeper/game/controller/GameWorldController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index 3de711bef..f48e4caaa 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -995,8 +995,9 @@ public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Ve // Cast the spell Shot shot = kwdFile.getShotById(keeperSpell.getShotTypeId()); - int shotData1 = researchableEntity.isUpgraded() ? keeperSpell.getBonusShotData1() : keeperSpell.getShotData1(); - int shotData2 = researchableEntity.isUpgraded() ? keeperSpell.getBonusShotData2() : keeperSpell.getShotData2(); + boolean spellUpgraded = researchableEntity.isUpgraded(); + int shotData1 = spellUpgraded ? keeperSpell.getBonusShotData1() : keeperSpell.getShotData1(); + int shotData2 = spellUpgraded ? keeperSpell.getBonusShotData2() : keeperSpell.getShotData2(); switch (shot.getProcessType()) { case CREATE_CREATURE -> { creaturesController.spawnCreature((short) shotData1, playerId, shotData2, position, ICreaturesController.SpawnType.CONJURE); From 634205e5241e87dfc9c44e01bbfc649967092b15 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 18 Jul 2024 12:53:05 +0300 Subject: [PATCH 33/70] Instanceof pattern --- src/toniarts/openkeeper/game/controller/GameController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/GameController.java b/src/toniarts/openkeeper/game/controller/GameController.java index eab5756bb..6426ec3cc 100644 --- a/src/toniarts/openkeeper/game/controller/GameController.java +++ b/src/toniarts/openkeeper/game/controller/GameController.java @@ -183,8 +183,8 @@ public GameController(GeneralLevel selectedLevel, EntityData entityData, Map Date: Thu, 18 Jul 2024 12:53:58 +0300 Subject: [PATCH 34/70] Use correct logging --- .../openkeeper/game/controller/GameWorldController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index f48e4caaa..0503646b9 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -22,6 +22,7 @@ import com.simsilica.es.EntityData; import com.simsilica.es.EntityId; import java.awt.Point; +import java.lang.System.Logger.Level; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; @@ -32,8 +33,6 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; import toniarts.openkeeper.common.RoomInstance; import toniarts.openkeeper.game.component.AttackTarget; @@ -94,7 +93,7 @@ */ public class GameWorldController implements IGameWorldController, IPlayerActions { - private static final Logger logger = Logger.getLogger(GameWorldController.class.getName()); + private static final System.Logger logger = System.getLogger(GameWorldController.class.getName()); /** * When dealing with gold... We currently better lock it. Logic stuff From 6df2163f81d78e802f971a0164ab37465e69bbfb Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 18 Jul 2024 12:54:21 +0300 Subject: [PATCH 35/70] IsSolid, prevents duplication --- src/toniarts/openkeeper/game/map/IMapInformation.java | 8 ++++++++ src/toniarts/openkeeper/game/map/MapInformation.java | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/toniarts/openkeeper/game/map/IMapInformation.java b/src/toniarts/openkeeper/game/map/IMapInformation.java index a7fad05d5..8f23744b4 100644 --- a/src/toniarts/openkeeper/game/map/IMapInformation.java +++ b/src/toniarts/openkeeper/game/map/IMapInformation.java @@ -148,4 +148,12 @@ public interface IMapInformation { */ public boolean isLava(Point p); + /** + * Is tile at the coordinates solid + * + * @param p coordinate + * @return is the tile solid + */ + public boolean isSolid(Point p); + } diff --git a/src/toniarts/openkeeper/game/map/MapInformation.java b/src/toniarts/openkeeper/game/map/MapInformation.java index 8d1802dd1..f006f2888 100644 --- a/src/toniarts/openkeeper/game/map/MapInformation.java +++ b/src/toniarts/openkeeper/game/map/MapInformation.java @@ -238,4 +238,14 @@ public Terrain getTerrain(IMapTileInformation tile) { return kwdFile.getTerrain(tile.getTerrainId()); } + @Override + public boolean isSolid(Point p) { + S tile = getMapData().getTile(p); + if (tile == null) { + return false; + } + Terrain terrain = kwdFile.getTerrain(tile.getTerrainId()); + return terrain.getFlags().contains(Terrain.TerrainFlag.SOLID); + } + } From 624ef2dc10f254c1242285bac9a3d24a6eb654aa Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 18 Jul 2024 12:54:47 +0300 Subject: [PATCH 36/70] Implement isSolid --- src/toniarts/openkeeper/game/controller/MapController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/toniarts/openkeeper/game/controller/MapController.java b/src/toniarts/openkeeper/game/controller/MapController.java index 91b1943f5..8ae705ad2 100644 --- a/src/toniarts/openkeeper/game/controller/MapController.java +++ b/src/toniarts/openkeeper/game/controller/MapController.java @@ -800,4 +800,9 @@ public int getPlayerSkeletonCapacity(short playerId) { return capacity; } + + @Override + public boolean isSolid(Point p) { + return mapInformation.isSolid(p); + } } From 6b33d026dd362f3cdaaac64982269e054dce609d Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 18 Jul 2024 12:57:36 +0300 Subject: [PATCH 37/70] Refactor to ShotsController --- .../game/controller/GameWorldController.java | 19 ++--- .../game/controller/IObjectsController.java | 3 + .../game/controller/IShotsController.java | 42 ++++++++++ .../game/controller/ObjectsController.java | 7 +- .../game/controller/ShotsController.java | 84 +++++++++++++++++++ 5 files changed, 141 insertions(+), 14 deletions(-) create mode 100644 src/toniarts/openkeeper/game/controller/IShotsController.java create mode 100644 src/toniarts/openkeeper/game/controller/ShotsController.java diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index 0503646b9..e8c1a4f9c 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -79,7 +79,6 @@ import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Player; import toniarts.openkeeper.tools.convert.map.Room; -import toniarts.openkeeper.tools.convert.map.Shot; import toniarts.openkeeper.tools.convert.map.Terrain; import toniarts.openkeeper.tools.convert.map.Tile; import toniarts.openkeeper.tools.convert.map.Trap; @@ -108,6 +107,7 @@ public class GameWorldController implements IGameWorldController, IPlayerActions private ICreaturesController creaturesController; private IDoorsController doorsController; private ITrapsController trapsController; + private IShotsController shotsController; private IEntityPositionLookup entityPositionLookup; private final Map playerControllers; private final SortedMap players; @@ -143,6 +143,9 @@ public void createNewGame(IGameController gameController, ILevelInfo levelInfo) // Load the traps trapsController = new TrapsController(kwdFile, entityData, gameSettings, gameController, levelInfo); + // Init handlers + shotsController = new ShotsController(kwdFile, entityData, gameSettings, gameTimer, gameController, mapController, levelInfo, objectsController, creaturesController); + // Setup player stuff initPlayerMoney(); initPlayerRooms(); @@ -921,7 +924,7 @@ public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Ve } // Where allowed to cast - boolean isSolid = kwdFile.getTerrain(mapTile.getTerrainId()).getFlags().contains(Terrain.TerrainFlag.SOLID); + boolean isSolid = mapController.isSolid(tile); switch (keeperSpell.getCastRule()) { case OWN_LAND: { if (isSolid || mapTile.getOwnerId() != playerId) { @@ -993,20 +996,10 @@ public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Ve playerControllers.get(playerId).getManaControl().updateMana(0, keeperSpell.getManaCost()); // Cast the spell - Shot shot = kwdFile.getShotById(keeperSpell.getShotTypeId()); boolean spellUpgraded = researchableEntity.isUpgraded(); int shotData1 = spellUpgraded ? keeperSpell.getBonusShotData1() : keeperSpell.getShotData1(); int shotData2 = spellUpgraded ? keeperSpell.getBonusShotData2() : keeperSpell.getShotData2(); - switch (shot.getProcessType()) { - case CREATE_CREATURE -> { - creaturesController.spawnCreature((short) shotData1, playerId, shotData2, position, ICreaturesController.SpawnType.CONJURE); - } - case MODIFY_HEALTH -> { - - } - default -> - logger.log(Level.WARNING, "Shot type {0} not implemented", shot.getProcessType()); - } + shotsController.createShot(keeperSpell.getShotTypeId(), shotData1, shotData2, playerId, position, target); } @Override diff --git a/src/toniarts/openkeeper/game/controller/IObjectsController.java b/src/toniarts/openkeeper/game/controller/IObjectsController.java index e40abea28..65b628b11 100644 --- a/src/toniarts/openkeeper/game/controller/IObjectsController.java +++ b/src/toniarts/openkeeper/game/controller/IObjectsController.java @@ -16,6 +16,7 @@ */ package toniarts.openkeeper.game.controller; +import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.simsilica.es.EntityData; import com.simsilica.es.EntityId; @@ -37,6 +38,8 @@ public interface IObjectsController extends IEntityWrapper { public EntityId loadObject(short objectId, short ownerId, Vector3f pos, float rotation); + public EntityId loadObject(short objectId, short ownerId, Vector2f pos, float rotation); + public EntityId loadObject(short objectId, short ownerId, int x, int y, Integer money, ResearchableType researchableType, Short researchTypeId); public EntityId addRoomGold(short ownerId, int x, int y, int money, int maxMoney); diff --git a/src/toniarts/openkeeper/game/controller/IShotsController.java b/src/toniarts/openkeeper/game/controller/IShotsController.java new file mode 100644 index 000000000..3cd60c5d1 --- /dev/null +++ b/src/toniarts/openkeeper/game/controller/IShotsController.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014-2024 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 com.jme3.math.Vector2f; +import com.simsilica.es.EntityId; + +/** + * Handles shots. Shots are spells and weapons cast by traps, creatures and + * keepers + * + * @author Toni Helenius + */ +public interface IShotsController { + + /** + * Creates a shot + * + * @param shotTypeId shot type to create + * @param shotData1 arbitrary value, interpreted per shot + * @param shotData2 arbitrary value, interpreted per shot + * @param playerId owher of the shot + * @param position 2D coordinate of the shot origin + * @param target shot target, can be null + */ + public void createShot(short shotTypeId, int shotData1, int shotData2, short playerId, Vector2f position, EntityId target); + +} diff --git a/src/toniarts/openkeeper/game/controller/ObjectsController.java b/src/toniarts/openkeeper/game/controller/ObjectsController.java index b8b675c76..753dd7a93 100644 --- a/src/toniarts/openkeeper/game/controller/ObjectsController.java +++ b/src/toniarts/openkeeper/game/controller/ObjectsController.java @@ -16,6 +16,7 @@ */ package toniarts.openkeeper.game.controller; +import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.simsilica.es.EntityData; import com.simsilica.es.EntityId; @@ -56,7 +57,6 @@ import toniarts.openkeeper.tools.convert.map.Thing; import toniarts.openkeeper.tools.convert.map.Variable; import toniarts.openkeeper.utils.WorldUtils; -import toniarts.openkeeper.view.map.MapViewController; /** * This is a controller that controls all the game objects in the world TODO: @@ -158,6 +158,11 @@ public EntityId loadObject(short objectId, short ownerId, Vector3f pos, float ro return loadObject(objectId, ownerId, pos, rotation, null, null, null, null, null); } + @Override + public EntityId loadObject(short objectId, short ownerId, Vector2f pos, float rotation) { + return loadObject(objectId, ownerId, new Vector3f(pos.x, WorldUtils.FLOOR_HEIGHT, pos.y), rotation); + } + @Override public EntityId loadObject(short objectId, short ownerId, int x, int y, Integer money, ResearchableType researchableType, Short researchTypeId) { return loadObject(objectId, ownerId, x, y, 0, money, researchableType, researchTypeId, null, null); diff --git a/src/toniarts/openkeeper/game/controller/ShotsController.java b/src/toniarts/openkeeper/game/controller/ShotsController.java new file mode 100644 index 000000000..80907f51b --- /dev/null +++ b/src/toniarts/openkeeper/game/controller/ShotsController.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014-2024 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 com.jme3.math.Vector2f; +import com.simsilica.es.EntityData; +import com.simsilica.es.EntityId; +import java.lang.System.Logger; +import java.util.Map; +import toniarts.openkeeper.tools.convert.map.KwdFile; +import toniarts.openkeeper.tools.convert.map.Shot; +import static toniarts.openkeeper.tools.convert.map.Shot.ProcessType.CREATE_CREATURE; +import static toniarts.openkeeper.tools.convert.map.Shot.ProcessType.CREATE_OBJECT; +import static toniarts.openkeeper.tools.convert.map.Shot.ProcessType.MODIFY_HEALTH; +import static toniarts.openkeeper.tools.convert.map.Shot.ProcessType.POSSESS_CREATURE; +import toniarts.openkeeper.tools.convert.map.Variable; + +/** + * + * @author Toni Helenius + */ +public class ShotsController implements IShotsController { + + private static final Logger logger = System.getLogger(ShotsController.class.getName()); + + private final KwdFile kwdFile; + private final EntityData entityData; + private final Map gameSettings; + private final IGameTimer gameTimer; + private final IGameController gameController; + private final IMapController mapController; + private final ILevelInfo levelInfo; + private final IObjectsController objectsController; + private final ICreaturesController creaturesController; + + public ShotsController(KwdFile kwdFile, EntityData entityData, Map gameSettings, IGameTimer gameTimer, + IGameController gameController, IMapController mapController, ILevelInfo levelInfo, IObjectsController objectsController, ICreaturesController creaturesController) { + this.kwdFile = kwdFile; + this.entityData = entityData; + this.gameSettings = gameSettings; + this.gameTimer = gameTimer; + this.gameController = gameController; + this.mapController = mapController; + this.levelInfo = levelInfo; + this.objectsController = objectsController; + this.creaturesController = creaturesController; + } + + @Override + public void createShot(short shotTypeId, int shotData1, int shotData2, short playerId, Vector2f position, EntityId target) { + Shot shot = kwdFile.getShotById(shotTypeId); + switch (shot.getProcessType()) { + case CREATE_CREATURE -> { + creaturesController.spawnCreature((short) shotData1, playerId, shotData2, position, ICreaturesController.SpawnType.CONJURE); + } + case CREATE_OBJECT -> { + objectsController.loadObject((short) shotData1, playerId, position, 0); + } + case POSSESS_CREATURE -> { + + } + case MODIFY_HEALTH -> { + + } + default -> + logger.log(Logger.Level.WARNING, "Shot type {0} not implemented", shot.getProcessType()); + } + } + +} From cfdc92e37e7deeab19844b9969d81af40d2f07a9 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 18 Jul 2024 13:24:00 +0300 Subject: [PATCH 38/70] Refactor keeper casting validation to a util class --- .../game/controller/GameWorldController.java | 70 +-------- .../controller/KeeperSpellCastValidator.java | 144 ++++++++++++++++++ 2 files changed, 146 insertions(+), 68 deletions(-) create mode 100644 src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index e8c1a4f9c..7c37ca1d7 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -72,7 +72,6 @@ import toniarts.openkeeper.game.logic.IEntityPositionLookup; import toniarts.openkeeper.game.map.IMapTileController; import toniarts.openkeeper.game.map.IMapTileInformation; -import toniarts.openkeeper.tools.convert.map.Creature; import toniarts.openkeeper.tools.convert.map.Door; import toniarts.openkeeper.tools.convert.map.GameObject; import toniarts.openkeeper.tools.convert.map.KeeperSpell; @@ -923,73 +922,8 @@ public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Ve return; } - // Where allowed to cast - boolean isSolid = mapController.isSolid(tile); - switch (keeperSpell.getCastRule()) { - case OWN_LAND: { - if (isSolid || mapTile.getOwnerId() != playerId) { - return; - } - break; - } - case OWN_AND_NEUTRAL_LAND: { - if (isSolid || mapTile.getOwnerId() != playerId || mapTile.getOwnerId() != Player.NEUTRAL_PLAYER_ID) { - return; - } - break; - } - case ENEMY_LAND: { - if (isSolid || !players.get(playerId).isEnemy(mapTile.getOwnerId())) { - return; - } - } - case ANY_LAND: - if (isSolid) { - return; - } - case ANYWHERE: - break; - case NONE: - return; - } - - // The target cast upon - Creature creature = null; - Short owner = null; - if (target != null) { - CreatureComponent creatureComponent = entityData.getComponent(target, CreatureComponent.class); - if (creatureComponent != null) { - creature = kwdFile.getCreature(creatureComponent.creatureId); - } - Owner ownerComponent = entityData.getComponent(target, Owner.class); - if (ownerComponent != null) { - owner = ownerComponent.ownerId; - } - } - switch (keeperSpell.getTargetRule()) { - case ALL, LAND -> { - } - case NONE -> { - return; - } - case ALL_CREATURES -> { - if (creature == null) { - return; - } - } - case ENEMY_CREATURES -> { - if (owner == null) { - return; - } - if (!player.isEnemy(owner)) { - return; - } - } - case OWN_CREATURES, POSESSION -> { - if (creature == null || owner == null || owner != playerId) { - return; - } - } + if (!KeeperSpellCastValidator.isValidCast(keeperSpell, kwdFile, mapController, mapTile, player, entityData, target)) { + return; } // Deduct the mana diff --git a/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java b/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java new file mode 100644 index 000000000..4f9a8fac7 --- /dev/null +++ b/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2014-2024 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 com.simsilica.es.EntityData; +import com.simsilica.es.EntityId; +import toniarts.openkeeper.game.component.CreatureComponent; +import toniarts.openkeeper.game.component.Owner; +import toniarts.openkeeper.game.data.Keeper; +import toniarts.openkeeper.game.map.IMapInformation; +import toniarts.openkeeper.game.map.IMapTileInformation; +import toniarts.openkeeper.tools.convert.map.Creature; +import toniarts.openkeeper.tools.convert.map.KeeperSpell; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.CastRule.ANYWHERE; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.CastRule.ANY_LAND; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.CastRule.ENEMY_LAND; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.CastRule.OWN_AND_NEUTRAL_LAND; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.CastRule.OWN_LAND; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.TargetRule.ALL; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.TargetRule.ALL_CREATURES; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.TargetRule.ENEMY_CREATURES; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.TargetRule.LAND; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.TargetRule.NONE; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.TargetRule.OWN_CREATURES; +import static toniarts.openkeeper.tools.convert.map.KeeperSpell.TargetRule.POSESSION; +import toniarts.openkeeper.tools.convert.map.KwdFile; +import toniarts.openkeeper.tools.convert.map.Player; + +/** + * Simple util class to check whether casting is allowed + * + * @author Toni Helenius + */ +public final class KeeperSpellCastValidator { + + private KeeperSpellCastValidator() { + // Nope + } + + public static boolean isValidCast( + KeeperSpell keeperSpell, + KwdFile kwdFile, + IMapInformation mapInformation, + IMapTileInformation mapTile, + Keeper player, + EntityData entityData, + EntityId target) { + return checkCastRule(mapInformation, mapTile, keeperSpell, player) + && checkTargetRule(target, entityData, kwdFile, keeperSpell, player); + } + + private static boolean checkTargetRule(EntityId target, EntityData entityData, KwdFile kwdFile, KeeperSpell keeperSpell, Keeper player) { + short playerId = player.getId(); + Creature creature = null; + Short owner = null; + if (target != null) { + CreatureComponent creatureComponent = entityData.getComponent(target, CreatureComponent.class); + if (creatureComponent != null) { + creature = kwdFile.getCreature(creatureComponent.creatureId); + } + Owner ownerComponent = entityData.getComponent(target, Owner.class); + if (ownerComponent != null) { + owner = ownerComponent.ownerId; + } + } + + switch (keeperSpell.getTargetRule()) { + case ALL, LAND -> { + } + case NONE -> { + return false; + } + case ALL_CREATURES -> { + if (creature == null) { + return false; + } + } + case ENEMY_CREATURES -> { + if (owner == null) { + return false; + } + if (!player.isEnemy(owner)) { + return false; + } + } + case OWN_CREATURES, POSESSION -> { + if (creature == null || owner == null || owner != playerId) { + return false; + } + } + } + + return true; + } + + private static boolean checkCastRule(IMapInformation mapInformation, IMapTileInformation mapTile, KeeperSpell keeperSpell, Keeper player) { + short playerId = player.getId(); + boolean isSolid = mapInformation.isSolid(mapTile.getLocation()); + switch (keeperSpell.getCastRule()) { + case OWN_LAND: { + if (isSolid || mapTile.getOwnerId() != playerId) { + return false; + } + break; + } + case OWN_AND_NEUTRAL_LAND: { + if (isSolid || mapTile.getOwnerId() != playerId || mapTile.getOwnerId() != Player.NEUTRAL_PLAYER_ID) { + return false; + } + break; + } + case ENEMY_LAND: { + if (isSolid || !player.isEnemy(mapTile.getOwnerId())) { + return false; + } + } + case ANY_LAND: + if (isSolid) { + return false; + } + case ANYWHERE: + break; + case NONE: + return false; + } + + return true; + } + +} From d3199815e44163b9a893e2aa469525c865140739 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 18 Jul 2024 13:26:54 +0300 Subject: [PATCH 39/70] Add also the mana check to the validator --- .../openkeeper/game/controller/GameWorldController.java | 5 ----- .../game/controller/KeeperSpellCastValidator.java | 7 ++++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index 7c37ca1d7..0eb48bdd9 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -917,11 +917,6 @@ public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Ve } // Validate - // Mana... - if (keeperSpell.getManaCost() > player.getMana()) { - return; - } - if (!KeeperSpellCastValidator.isValidCast(keeperSpell, kwdFile, mapController, mapTile, player, entityData, target)) { return; } diff --git a/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java b/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java index 4f9a8fac7..379107989 100644 --- a/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java +++ b/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java @@ -59,10 +59,15 @@ public static boolean isValidCast( Keeper player, EntityData entityData, EntityId target) { - return checkCastRule(mapInformation, mapTile, keeperSpell, player) + return checkPlayerMana(keeperSpell, player) + && checkCastRule(mapInformation, mapTile, keeperSpell, player) && checkTargetRule(target, entityData, kwdFile, keeperSpell, player); } + private static boolean checkPlayerMana(KeeperSpell keeperSpell, Keeper player) { + return keeperSpell.getManaCost() <= player.getMana(); + } + private static boolean checkTargetRule(EntityId target, EntityData entityData, KwdFile kwdFile, KeeperSpell keeperSpell, Keeper player) { short playerId = player.getId(); Creature creature = null; From bfff5a9bb70f884546b52eb71a609727019bdbc0 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 18 Jul 2024 13:50:02 +0300 Subject: [PATCH 40/70] Use control ID instead so we can heal enemies in prison/torture chamber --- .../openkeeper/game/controller/KeeperSpellCastValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java b/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java index 379107989..85524110c 100644 --- a/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java +++ b/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java @@ -79,7 +79,7 @@ private static boolean checkTargetRule(EntityId target, EntityData entityData, K } Owner ownerComponent = entityData.getComponent(target, Owner.class); if (ownerComponent != null) { - owner = ownerComponent.ownerId; + owner = ownerComponent.controlId; } } From f34f51f8cdd07b9378a1d3c636bde1b4c900b6b4 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Thu, 18 Jul 2024 13:50:19 +0300 Subject: [PATCH 41/70] Implement healing spell --- src/toniarts/openkeeper/game/controller/ShotsController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/controller/ShotsController.java b/src/toniarts/openkeeper/game/controller/ShotsController.java index 80907f51b..d54dbd98e 100644 --- a/src/toniarts/openkeeper/game/controller/ShotsController.java +++ b/src/toniarts/openkeeper/game/controller/ShotsController.java @@ -21,6 +21,7 @@ import com.simsilica.es.EntityId; import java.lang.System.Logger; import java.util.Map; +import toniarts.openkeeper.game.controller.entity.EntityController; import toniarts.openkeeper.tools.convert.map.KwdFile; import toniarts.openkeeper.tools.convert.map.Shot; import static toniarts.openkeeper.tools.convert.map.Shot.ProcessType.CREATE_CREATURE; @@ -74,7 +75,7 @@ public void createShot(short shotTypeId, int shotData1, int shotData2, short pla } case MODIFY_HEALTH -> { - + EntityController.setDamage(entityData, target, -shotData1); } default -> logger.log(Logger.Level.WARNING, "Shot type {0} not implemented", shot.getProcessType()); From d6683c5576661d0e3b401eeedb7689cdf1aa21f8 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 21 Jul 2024 15:22:24 +0300 Subject: [PATCH 42/70] Stub creature possession --- .../openkeeper/game/component/Possessed.java | 44 +++++++ .../game/controller/GameController.java | 7 ++ .../game/controller/IGameController.java | 9 ++ .../game/controller/ShotsController.java | 2 +- .../creature/CreatureController.java | 35 ++++++ .../creature/ICreatureController.java | 7 ++ .../controller/player/PlayerManaControl.java | 4 + src/toniarts/openkeeper/game/data/Keeper.java | 9 ++ .../game/logic/PossessedSystem.java | 107 ++++++++++++++++++ .../game/network/game/GameClientService.java | 7 ++ .../game/network/game/GameHostedService.java | 15 +++ .../game/state/GameClientState.java | 5 + .../openkeeper/game/state/MainMenuState.java | 27 +++-- .../openkeeper/game/state/PlayerState.java | 11 +- .../state/session/GameSessionListener.java | 3 + .../game/state/session/LocalGameSession.java | 10 +- .../game/state/session/PlayerService.java | 8 ++ .../tools/modelviewer/MapLoaderAppState.java | 19 ++-- .../view/PlayerInteractionState.java | 4 +- .../view/PossessionCameraState.java | 19 ++-- .../view/PossessionInteractionState.java | 14 +-- 21 files changed, 323 insertions(+), 43 deletions(-) create mode 100644 src/toniarts/openkeeper/game/component/Possessed.java create mode 100644 src/toniarts/openkeeper/game/logic/PossessedSystem.java diff --git a/src/toniarts/openkeeper/game/component/Possessed.java b/src/toniarts/openkeeper/game/component/Possessed.java new file mode 100644 index 000000000..5c3ef7189 --- /dev/null +++ b/src/toniarts/openkeeper/game/component/Possessed.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014-2024 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.component; + +import com.simsilica.es.EntityComponent; + +/** + * Simple tagging component for entity being possessed + * + * @author Toni Helenius + */ +public class Possessed implements EntityComponent { + + /** + * We mark the mana drain here so that we can easily get the information. + * Mana component should be used in other calculations + */ + public int manaDrain; + public double manaCheckTime; + + public Possessed() { + // For serialization + } + + public Possessed(int manaDrain, double manaCheckTime) { + this.manaDrain = manaDrain; + this.manaCheckTime = manaCheckTime; + } + +} diff --git a/src/toniarts/openkeeper/game/controller/GameController.java b/src/toniarts/openkeeper/game/controller/GameController.java index 6426ec3cc..81264fbe5 100644 --- a/src/toniarts/openkeeper/game/controller/GameController.java +++ b/src/toniarts/openkeeper/game/controller/GameController.java @@ -19,6 +19,7 @@ import com.badlogic.gdx.ai.GdxAI; import com.jme3.util.SafeArrayList; import com.simsilica.es.EntityData; +import com.simsilica.es.EntityId; import java.io.IOException; import java.lang.System.Logger; import java.lang.System.Logger.Level; @@ -650,6 +651,12 @@ public GameResult getGameResult() { return gameResult; } + @Override + public void setPossession(EntityId target, short playerId) { + players.get(playerId).setPossession(target != null); + playerService.setPossession(target, playerId); + } + @Override public void close() { if (steeringCalculatorLoop != null) { diff --git a/src/toniarts/openkeeper/game/controller/IGameController.java b/src/toniarts/openkeeper/game/controller/IGameController.java index 4922a4bf7..52dbb0d71 100644 --- a/src/toniarts/openkeeper/game/controller/IGameController.java +++ b/src/toniarts/openkeeper/game/controller/IGameController.java @@ -16,6 +16,7 @@ */ package toniarts.openkeeper.game.controller; +import com.simsilica.es.EntityId; import java.util.Collection; import toniarts.openkeeper.game.data.GameResult; import toniarts.openkeeper.game.logic.IEntityPositionLookup; @@ -71,4 +72,12 @@ public interface IGameController { public IGameWorldController getGameWorldController(); + /** + * Set player possession mode on/off + * + * @param target possession target, null if possession ends + * @param playerId player ID that posesses + */ + public void setPossession(EntityId target, short playerId); + } diff --git a/src/toniarts/openkeeper/game/controller/ShotsController.java b/src/toniarts/openkeeper/game/controller/ShotsController.java index d54dbd98e..7362163b9 100644 --- a/src/toniarts/openkeeper/game/controller/ShotsController.java +++ b/src/toniarts/openkeeper/game/controller/ShotsController.java @@ -72,7 +72,7 @@ public void createShot(short shotTypeId, int shotData1, int shotData2, short pla objectsController.loadObject((short) shotData1, playerId, position, 0); } case POSSESS_CREATURE -> { - + creaturesController.createController(target).setPossession(true); } case MODIFY_HEALTH -> { EntityController.setDamage(entityData, target, -shotData1); diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index cfe1a4515..ce68be39f 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -47,6 +47,7 @@ import toniarts.openkeeper.game.component.Gold; import toniarts.openkeeper.game.component.Health; import toniarts.openkeeper.game.component.InHand; +import toniarts.openkeeper.game.component.Mana; import toniarts.openkeeper.game.component.Mobile; import toniarts.openkeeper.game.component.Navigation; import toniarts.openkeeper.game.component.Objective; @@ -55,6 +56,7 @@ import toniarts.openkeeper.game.component.PlayerObjective; import toniarts.openkeeper.game.component.PortalGem; import toniarts.openkeeper.game.component.Position; +import toniarts.openkeeper.game.component.Possessed; import toniarts.openkeeper.game.component.RoomStorage; import toniarts.openkeeper.game.component.Slapped; import toniarts.openkeeper.game.component.TaskComponent; @@ -1253,4 +1255,37 @@ public boolean isRecuperating() { return entityData.getComponent(entityId, CreatureRecuperating.class) != null; } + @Override + public void setPossession(boolean possessed) { + if (possessed) { + startPossession(); + } else { + endPossession(); + } + } + + private void startPossession() { + CreatureComponent creatureComponent = entityData.getComponent(entityId, CreatureComponent.class); + int manaDrain = creatureComponent != null ? creatureComponent.posessionManaCost : 0; + Mana mana = entityData.getComponent(entityId, Mana.class); + entityData.setComponent(entityId, new Mana(mana != null ? -manaDrain - mana.manaGeneration : -manaDrain)); + entityData.setComponent(entityId, new Possessed(manaDrain, gameTimer.getGameTime())); + entityData.removeComponent(entityId, CreatureAi.class); + entityData.removeComponent(entityId, Navigation.class); + } + + private void endPossession() { + entityData.setComponent(entityId, new CreatureAi(gameTimer.getGameTime(), CreatureState.IDLE, getCreature().getCreatureId())); + + // Return the mana flow + Possessed possessed = entityData.getComponent(entityId, Possessed.class); + Mana mana = entityData.getComponent(entityId, Mana.class); + int manaGeneration = mana.manaGeneration + possessed.manaDrain; + if (manaGeneration == 0) { + entityData.removeComponent(entityId, Mana.class); + } else { + entityData.setComponent(entityId, new Mana(manaGeneration)); + } + } + } diff --git a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java index 1998d35d9..3f973a4bd 100644 --- a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java @@ -247,4 +247,11 @@ public interface ICreatureController extends IGameLogicUpdatable, INavigable, IE */ public boolean isRecuperating(); + /** + * Sets creature possession on/off + * + * @param possessed should the creature be posessed or AI driven + */ + public void setPossession(boolean possessed); + } diff --git a/src/toniarts/openkeeper/game/controller/player/PlayerManaControl.java b/src/toniarts/openkeeper/game/controller/player/PlayerManaControl.java index dc8d117d2..8993c90ff 100644 --- a/src/toniarts/openkeeper/game/controller/player/PlayerManaControl.java +++ b/src/toniarts/openkeeper/game/controller/player/PlayerManaControl.java @@ -74,4 +74,8 @@ public void addListener(PlayerManaListener listener) { public void removeListener(PlayerManaListener listener) { listeners.remove(listener); } + + public boolean hasEnoughMana(int manaDrain) { + return keeper.getMana() - manaDrain + keeper.getManaGain() - keeper.getManaLoose() > 0; + } } diff --git a/src/toniarts/openkeeper/game/data/Keeper.java b/src/toniarts/openkeeper/game/data/Keeper.java index 246312cf7..3e9c6ee8f 100644 --- a/src/toniarts/openkeeper/game/data/Keeper.java +++ b/src/toniarts/openkeeper/game/data/Keeper.java @@ -48,6 +48,7 @@ public class Keeper implements Comparable, IIndexable, Savable { private int manaLoose; private int maxMana; private Point dungeonHeartLocation; + private boolean possession; private List availableRooms = new ArrayList<>(); private List availableSpells = new ArrayList<>(); private List availableCreatures = new ArrayList<>(); @@ -219,6 +220,14 @@ public void setDungeonHeartLocation(Point dungeonHeartLocation) { this.dungeonHeartLocation = dungeonHeartLocation; } + public boolean isPossession() { + return possession; + } + + public void setPossession(boolean possession) { + this.possession = possession; + } + public List getAvailableRooms() { return availableRooms; } diff --git a/src/toniarts/openkeeper/game/logic/PossessedSystem.java b/src/toniarts/openkeeper/game/logic/PossessedSystem.java new file mode 100644 index 000000000..26e124b42 --- /dev/null +++ b/src/toniarts/openkeeper/game/logic/PossessedSystem.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014-2024 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.logic; + +import com.simsilica.es.Entity; +import com.simsilica.es.EntityData; +import com.simsilica.es.EntitySet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import toniarts.openkeeper.game.component.Owner; +import toniarts.openkeeper.game.component.Possessed; +import toniarts.openkeeper.game.controller.IGameController; +import toniarts.openkeeper.game.controller.IPlayerController; +import toniarts.openkeeper.game.controller.player.PlayerManaControl; + +/** + * Maintains possessed state, sees when we need to stop possessing + * + * @author Toni Helenius + */ +public class PossessedSystem implements IGameLogicUpdatable { + + private final EntitySet possessedEntities; + private final Map manaControls; + private final EntityData entityData; + private final IGameController gameController; + + public PossessedSystem(Collection playerControllers, EntityData entityData, IGameController gameController) { + this.gameController = gameController; + this.entityData = entityData; + manaControls = HashMap.newHashMap(playerControllers.size()); + for (IPlayerController playerController : playerControllers) { + PlayerManaControl manaControl = playerController.getManaControl(); + if (manaControl != null) { + manaControls.put(playerController.getKeeper().getId(), manaControl); + } + } + + // Listen for posessed entities + possessedEntities = entityData.getEntities(Possessed.class, Owner.class); + processAddedEntities(possessedEntities); + } + + @Override + public void processTick(float tpf, double gameTime) { + if (possessedEntities.applyChanges()) { + + processAddedEntities(possessedEntities.getAddedEntities()); + + processDeletedEntities(possessedEntities.getRemovedEntities()); + } + + for (Entity entity : possessedEntities) { + + // 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) { + 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)); + } + } + } + + @Override + public void start() { + + } + + @Override + public void stop() { + possessedEntities.release(); + } + + private void processAddedEntities(Set entities) { + for (Entity entity : entities) { + gameController.setPossession(entity.getId(), entity.get(Owner.class).ownerId); + } + } + + private void processDeletedEntities(Set entities) { + for (Entity entity : entities) { + gameController.setPossession(null, entity.get(Owner.class).ownerId); + } + } +} diff --git a/src/toniarts/openkeeper/game/network/game/GameClientService.java b/src/toniarts/openkeeper/game/network/game/GameClientService.java index 4b5d3212c..2fa91e898 100644 --- a/src/toniarts/openkeeper/game/network/game/GameClientService.java +++ b/src/toniarts/openkeeper/game/network/game/GameClientService.java @@ -417,5 +417,12 @@ public void onResearchStatusChanged(short keeperId, ResearchableEntity researcha l.onResearchStatusChanged(keeperId, researchableEntity); } } + + @Override + public void setPossession(EntityId target) { + for (GameSessionListener l : listeners.getArray()) { + l.setPossession(target); + } + } } } diff --git a/src/toniarts/openkeeper/game/network/game/GameHostedService.java b/src/toniarts/openkeeper/game/network/game/GameHostedService.java index 580b74f65..d8f1aa3b3 100644 --- a/src/toniarts/openkeeper/game/network/game/GameHostedService.java +++ b/src/toniarts/openkeeper/game/network/game/GameHostedService.java @@ -376,6 +376,16 @@ public void onResearchStatusChanged(short keeperId, ResearchableEntity researcha } } + @Override + public void setPossession(EntityId target, short playerId) { + for (Map.Entry gameSession : players.entrySet()) { + if (gameSession.getKey().getKeeper().getId() == playerId) { + gameSession.getValue().setPossession(target); + break; + } + } + } + private class ServerMessageListener implements MessageListener { public ServerMessageListener() { @@ -698,5 +708,10 @@ public void onResearchStatusChanged(short keeperId, ResearchableEntity researcha getCallback().onResearchStatusChanged(keeperId, researchableEntity); } + @Override + public void setPossession(EntityId target) { + getCallback().setPossession(target); + } + } } diff --git a/src/toniarts/openkeeper/game/state/GameClientState.java b/src/toniarts/openkeeper/game/state/GameClientState.java index b078cf6bf..3d0b3f3fd 100644 --- a/src/toniarts/openkeeper/game/state/GameClientState.java +++ b/src/toniarts/openkeeper/game/state/GameClientState.java @@ -620,6 +620,11 @@ private Comparable getResearchableEntityType(KwdFile kwdFile, ResearchableType r return null; } + + @Override + public void setPossession(EntityId target) { + playerState.setPossession(target); + } } public IMapInformation getMapClientService() { diff --git a/src/toniarts/openkeeper/game/state/MainMenuState.java b/src/toniarts/openkeeper/game/state/MainMenuState.java index e5142924c..c536d0fc0 100644 --- a/src/toniarts/openkeeper/game/state/MainMenuState.java +++ b/src/toniarts/openkeeper/game/state/MainMenuState.java @@ -589,57 +589,62 @@ private static class MainMenuPlayerService implements PlayerService { @Override public void setWidescreen(boolean enable, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void playSpeech(int speechId, boolean showText, boolean introduction, int pathId, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public boolean isInTransition() { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void doTransition(short pathId, Vector3f start, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void flashButton(TriggerAction.MakeType buttonType, short targetId, TriggerAction.ButtonType targetButtonType, boolean enabled, int time, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void rotateViewAroundPoint(Vector3f point, boolean relative, int angle, int time, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void showMessage(int textId, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void zoomViewToPoint(Vector3f point, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void zoomViewToEntity(EntityId entityId, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void setGamePaused(boolean paused) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void showUnitFlower(EntityId entityId, int interval, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setPossession(EntityId target, short playerId) { + throw new UnsupportedOperationException("Not supported yet."); } } diff --git a/src/toniarts/openkeeper/game/state/PlayerState.java b/src/toniarts/openkeeper/game/state/PlayerState.java index c5be00c5e..5d2f4645e 100644 --- a/src/toniarts/openkeeper/game/state/PlayerState.java +++ b/src/toniarts/openkeeper/game/state/PlayerState.java @@ -55,7 +55,6 @@ import toniarts.openkeeper.view.control.EntityViewControl; import toniarts.openkeeper.world.MapLoader; import toniarts.openkeeper.world.WorldState; -import toniarts.openkeeper.world.creature.CreatureControl; import toniarts.openkeeper.world.room.GenericRoom; import toniarts.openkeeper.world.room.RoomInstance; @@ -193,7 +192,7 @@ protected void onInteractionStateChange(InteractionState interactionState) { } @Override - protected void onPossession(CreatureControl creature) { + protected void onPossession(EntityId entityId) { // Disable states for (AbstractAppState state : appStates) { if (state instanceof PossessionInteractionState @@ -203,7 +202,7 @@ protected void onPossession(CreatureControl creature) { state.setEnabled(false); } // Enable state - possessionState.setTarget(creature); + possessionState.setTarget(entityId); possessionState.setEnabled(true); screen.goToScreen(PlayerScreenController.SCREEN_POSSESSION_ID); @@ -463,7 +462,7 @@ public short getPlayerId() { } protected Creature getPossessionCreature() { - return possessionState.getTarget().getCreature(); + return null/*possessionState.getTarget().getCreature()*/; } protected InteractionState getInteractionState() { @@ -570,4 +569,8 @@ public void onResearchStatusChanged(short keeperId, ResearchableEntity researcha screen.updateEntityResearch(researchableEntity); } + void setPossession(EntityId target) { + + } + } diff --git a/src/toniarts/openkeeper/game/state/session/GameSessionListener.java b/src/toniarts/openkeeper/game/state/session/GameSessionListener.java index 054f5e920..f8a92a08f 100644 --- a/src/toniarts/openkeeper/game/state/session/GameSessionListener.java +++ b/src/toniarts/openkeeper/game/state/session/GameSessionListener.java @@ -121,4 +121,7 @@ public interface GameSessionListener extends MapListener, PlayerListener { @Asynchronous public void onShowUnitFlower(EntityId entityId, int interval); + @Asynchronous + public void setPossession(EntityId target); + } diff --git a/src/toniarts/openkeeper/game/state/session/LocalGameSession.java b/src/toniarts/openkeeper/game/state/session/LocalGameSession.java index 5560616fa..fe884e70a 100644 --- a/src/toniarts/openkeeper/game/state/session/LocalGameSession.java +++ b/src/toniarts/openkeeper/game/state/session/LocalGameSession.java @@ -24,7 +24,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import toniarts.openkeeper.Main; @@ -461,4 +460,13 @@ public void onResearchStatusChanged(short keeperId, ResearchableEntity researcha } } + @Override + public void setPossession(EntityId target, short playerId) { + if (playerId == PLAYER_ID) { + for (GameSessionListener listener : listeners.getArray()) { + listener.setPossession(target); + } + } + } + } diff --git a/src/toniarts/openkeeper/game/state/session/PlayerService.java b/src/toniarts/openkeeper/game/state/session/PlayerService.java index e5760685a..912f11167 100644 --- a/src/toniarts/openkeeper/game/state/session/PlayerService.java +++ b/src/toniarts/openkeeper/game/state/session/PlayerService.java @@ -127,4 +127,12 @@ public interface PlayerService { */ public void showUnitFlower(EntityId entityId, int interval, short playerId); + /** + * Set player possession mode on/off + * + * @param target possession target, null if possession ends + * @param playerId player ID that posesses + */ + public void setPossession(EntityId target, short playerId); + } diff --git a/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java b/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java index 77644b1f3..22dcaf779 100644 --- a/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java +++ b/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java @@ -145,37 +145,42 @@ public void doTransition(short pathId, Vector3f start, short playerId) { @Override public void flashButton(TriggerAction.MakeType buttonType, short targetId, TriggerAction.ButtonType targetButtonType, boolean enabled, int time, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void rotateViewAroundPoint(Vector3f point, boolean relative, int angle, int time, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void showMessage(int textId, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void zoomViewToPoint(Vector3f point, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void zoomViewToEntity(EntityId entityId, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void setGamePaused(boolean paused) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void showUnitFlower(EntityId entityId, int interval, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setPossession(EntityId target, short playerId) { + throw new UnsupportedOperationException("Not supported yet."); } } diff --git a/src/toniarts/openkeeper/view/PlayerInteractionState.java b/src/toniarts/openkeeper/view/PlayerInteractionState.java index a40e4adf0..cb0067a23 100644 --- a/src/toniarts/openkeeper/view/PlayerInteractionState.java +++ b/src/toniarts/openkeeper/view/PlayerInteractionState.java @@ -36,6 +36,7 @@ import com.jme3.scene.Node; import com.jme3.scene.control.AbstractControl; import com.simsilica.es.EntityData; +import com.simsilica.es.EntityId; import de.lessvoid.nifty.controls.Label; import de.lessvoid.nifty.elements.Element; import java.awt.Point; @@ -66,7 +67,6 @@ import toniarts.openkeeper.view.selection.SelectionArea; import toniarts.openkeeper.view.selection.SelectionHandler; import toniarts.openkeeper.view.text.TextParser; -import toniarts.openkeeper.world.creature.CreatureControl; /** * State for managing player interactions in the world. Heavily drawn from @@ -775,7 +775,7 @@ public boolean isKeeperHandFull() { */ protected abstract void onInteractionStateChange(InteractionState interactionState); - protected abstract void onPossession(CreatureControl creature); + protected abstract void onPossession(EntityId creature); public static class InteractionState { diff --git a/src/toniarts/openkeeper/view/PossessionCameraState.java b/src/toniarts/openkeeper/view/PossessionCameraState.java index cdeee7715..832f2aa33 100644 --- a/src/toniarts/openkeeper/view/PossessionCameraState.java +++ b/src/toniarts/openkeeper/view/PossessionCameraState.java @@ -27,14 +27,13 @@ import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; -import java.awt.Point; +import com.simsilica.es.EntityId; import java.lang.System.Logger; import toniarts.openkeeper.Main; import toniarts.openkeeper.game.FunnyCameraContol; import toniarts.openkeeper.game.data.Settings; import toniarts.openkeeper.game.state.AbstractPauseAwareState; import toniarts.openkeeper.tools.convert.map.Creature; -import toniarts.openkeeper.world.creature.CreatureControl; /** * @@ -47,7 +46,7 @@ public class PossessionCameraState extends AbstractPauseAwareState implements Ac private Main app; private InputManager inputManager; - private CreatureControl target; + private EntityId target; private Creature creature; public Vector2f mousePosition = Vector2f.ZERO; @@ -115,7 +114,7 @@ public void setEnabled(boolean enabled) { camera = new PossessionCamera(app.getCamera(), creature.getAttributes().getSpeed(), creature.getFirstPersonOscillateScale()); loadCameraStartLocation(); - FunnyCameraContol fcc = new FunnyCameraContol(app.getCamera(), target.getSpatial()); + FunnyCameraContol fcc = new FunnyCameraContol(app.getCamera(), null/*target.getSpatial()*/); fcc.setLookAtOffset(new Vector3f(0, creature.getAttributes().getEyeHeight(), 0)); fcc.setHeight(creature.getAttributes().getHeight()); fcc.setDistance(1.5f); @@ -124,7 +123,7 @@ public void setEnabled(boolean enabled) { registerInput(); } else { unregisterInput(); - target.getSpatial().removeControl(FunnyCameraContol.class); + //target.getSpatial().removeControl(FunnyCameraContol.class); target = null; } } @@ -133,10 +132,10 @@ public void setEnabled(boolean enabled) { * Load the initial camera position */ private void loadCameraStartLocation() { - Point p = target.getCreatureCoordinates(); - Vector3f startLocation = new Vector3f(p.x, target.getHeight(), p.y); + //Point p = target.getCreatureCoordinates(); + //Vector3f startLocation = new Vector3f(p.x, target.getHeight(), p.y); Camera cam = app.getCamera(); - cam.setLocation(startLocation.addLocal(0, creature.getAttributes().getEyeHeight(), 0)); + //cam.setLocation(startLocation.addLocal(0, creature.getAttributes().getEyeHeight(), 0)); //cam.setFrustumPerspective(45, cam.getWidth() / cam.getHeight(), 0.1f, creature.getDistanceCanSee() * 10); cam.setAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); } @@ -237,8 +236,8 @@ public void update(float tpf) { app.getListener().setRotation(app.getCamera().getRotation()); } - public void setTarget(CreatureControl target) { + public void setTarget(EntityId target) { this.target = target; - creature = this.target.getCreature(); + //creature = this.target.getCreature(); } } diff --git a/src/toniarts/openkeeper/view/PossessionInteractionState.java b/src/toniarts/openkeeper/view/PossessionInteractionState.java index 93de945d8..f0dcc7912 100644 --- a/src/toniarts/openkeeper/view/PossessionInteractionState.java +++ b/src/toniarts/openkeeper/view/PossessionInteractionState.java @@ -28,12 +28,12 @@ import com.jme3.input.event.MouseMotionEvent; import com.jme3.input.event.TouchEvent; import com.jme3.math.Vector2f; +import com.simsilica.es.EntityId; import java.lang.System.Logger; import toniarts.openkeeper.Main; import toniarts.openkeeper.game.data.Settings; import toniarts.openkeeper.game.state.AbstractPauseAwareState; import toniarts.openkeeper.view.PossessionCameraControl.Direction; -import toniarts.openkeeper.world.creature.CreatureControl; /** * State for managing player interactions in the world while possessing a @@ -57,7 +57,7 @@ public enum Action { private AppStateManager stateManager; private InputManager inputManager; - private CreatureControl target; + private EntityId target; private RawInputListener inputListener; public Vector2f mousePosition = Vector2f.ZERO; private Action action; @@ -97,21 +97,21 @@ public void onExit() { stateManager.getState(PossessionCameraState.class).setEnabled(true); } }; - target.getSpatial().addControl(pcc); + //target.getSpatial().addControl(pcc); } else { stateManager.getState(PossessionCameraState.class).setEnabled(false); app.getInputManager().removeRawInputListener(inputListener); PlayerCamera pc = stateManager.getState(PlayerCameraState.class).getCamera(); pc.initialize(); - pc.setLookAt(target.getSpatial().getLocalTranslation()); + //pc.setLookAt(target.getSpatial().getLocalTranslation()); PossessionCameraControl pcc = new PossessionCameraControl(app.getCamera(), Direction.EXIT) { @Override public void onExit() { PossessionInteractionState.this.onExit(); } }; - target.getSpatial().addControl(pcc); + //target.getSpatial().addControl(pcc); target = null; } } @@ -144,11 +144,11 @@ private void changeAction(Action action) { } } - public CreatureControl getTarget() { + public EntityId getTarget() { return target; } - public void setTarget(CreatureControl target) { + public void setTarget(EntityId target) { this.target = target; stateManager.getState(PossessionCameraState.class).setTarget(target); } From 6d39db2ab44a6260285eb6061ab821970c6aa2a2 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 21 Jul 2024 15:34:35 +0300 Subject: [PATCH 43/70] Remove some autogenerated comments --- .../openkeeper/tools/modelviewer/MapLoaderAppState.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java b/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java index 22dcaf779..6c9a2f651 100644 --- a/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java +++ b/src/toniarts/openkeeper/tools/modelviewer/MapLoaderAppState.java @@ -125,22 +125,22 @@ public MapPlayerService() { @Override public void setWidescreen(boolean enable, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void playSpeech(int speechId, boolean showText, boolean introduction, int pathId, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public boolean isInTransition() { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override public void doTransition(short pathId, Vector3f start, short playerId) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + throw new UnsupportedOperationException("Not supported yet."); } @Override From f407bfd94f50e8255250414d1b596098c5fd8132 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 21 Jul 2024 15:50:29 +0300 Subject: [PATCH 44/70] Better mana checks --- .../openkeeper/game/controller/GameWorldController.java | 2 +- .../openkeeper/game/controller/KeeperSpellCastValidator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index 0eb48bdd9..581f0938a 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -922,7 +922,7 @@ public void castKeeperSpell(short keeperSpellId, EntityId target, Point tile, Ve } // Deduct the mana - playerControllers.get(playerId).getManaControl().updateMana(0, keeperSpell.getManaCost()); + playerControllers.get(playerId).getManaControl().addMana(-keeperSpell.getManaCost()); // Cast the spell boolean spellUpgraded = researchableEntity.isUpgraded(); diff --git a/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java b/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java index 85524110c..5b3da3223 100644 --- a/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java +++ b/src/toniarts/openkeeper/game/controller/KeeperSpellCastValidator.java @@ -65,7 +65,7 @@ && checkCastRule(mapInformation, mapTile, keeperSpell, player) } private static boolean checkPlayerMana(KeeperSpell keeperSpell, Keeper player) { - return keeperSpell.getManaCost() <= player.getMana(); + return keeperSpell.getManaCost() <= player.getMana() - player.getManaLoose(); } private static boolean checkTargetRule(EntityId target, EntityData entityData, KwdFile kwdFile, KeeperSpell keeperSpell, Keeper player) { From 5d3f669d9f4619247dcb9311de7d74db2d2e4187 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 21 Jul 2024 16:13:51 +0300 Subject: [PATCH 45/70] Validate spell casting locally --- src/toniarts/openkeeper/view/PlayerInteractionState.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/toniarts/openkeeper/view/PlayerInteractionState.java b/src/toniarts/openkeeper/view/PlayerInteractionState.java index cb0067a23..db7f57515 100644 --- a/src/toniarts/openkeeper/view/PlayerInteractionState.java +++ b/src/toniarts/openkeeper/view/PlayerInteractionState.java @@ -44,6 +44,7 @@ import java.util.Set; import toniarts.openkeeper.Main; import toniarts.openkeeper.game.console.ConsoleState; +import toniarts.openkeeper.game.controller.KeeperSpellCastValidator; import toniarts.openkeeper.game.data.Settings; import toniarts.openkeeper.game.map.IMapInformation; import toniarts.openkeeper.game.map.IMapTileInformation; @@ -730,7 +731,7 @@ public void onTouchEvent(TouchEvent evt) { * @return true if spell can be cast */ private boolean castSpell(KeeperSpell keeperSpell, IEntityViewControl object, Point tile, Vector2f position) { - if (!canCastSpell(keeperSpell, object, tile, position)) { + if (!canCastSpell(keeperSpell, object, tile)) { return false; } gameClientState.getGameClientService().castKeeperSpell(keeperSpell.getId(), object != null ? object.getEntityId() : null, tile, position); @@ -738,8 +739,8 @@ private boolean castSpell(KeeperSpell keeperSpell, IEntityViewControl object, Po return true; } - private boolean canCastSpell(KeeperSpell keeperSpell, IEntityViewControl object, Point tile, Vector2f position) { - return true; + private boolean canCastSpell(KeeperSpell keeperSpell, IEntityViewControl object, Point tile) { + return KeeperSpellCastValidator.isValidCast(keeperSpell, kwdFile, mapInformation, mapInformation.getMapData().getTile(tile), gameClientState.getPlayer(), entityData, object != null ? object.getEntityId() : null); } /** From 6de316965827f30c6f24e2217f429073ff7788f4 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 21 Jul 2024 16:34:21 +0300 Subject: [PATCH 46/70] Use WorldUtils --- .../openkeeper/game/controller/ObjectsController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/ObjectsController.java b/src/toniarts/openkeeper/game/controller/ObjectsController.java index 14268a48e..f6ce2e665 100644 --- a/src/toniarts/openkeeper/game/controller/ObjectsController.java +++ b/src/toniarts/openkeeper/game/controller/ObjectsController.java @@ -253,8 +253,8 @@ public EntityId addLooseGold(short ownerId, int x, int y, int money, int maxMone Vector3f pos = WorldUtils.pointToVector3f(x, y); // Add a slight offset to make things look nicer, stuff not clumping together - pos.x = pos.x + Utils.getRandom().nextFloat(MapViewController.TILE_WIDTH) - (MapViewController.TILE_WIDTH / 2f); - pos.z = pos.z + Utils.getRandom().nextFloat(MapViewController.TILE_WIDTH) - (MapViewController.TILE_WIDTH / 2f); + pos.x = pos.x + Utils.getRandom().nextFloat(WorldUtils.TILE_WIDTH) - (WorldUtils.TILE_WIDTH / 2f); + pos.z = pos.z + Utils.getRandom().nextFloat(WorldUtils.TILE_WIDTH) - (WorldUtils.TILE_WIDTH / 2f); return loadObject(OBJECT_GOLD_ID, ownerId, pos, 0, money, null, null, null, maxMoney); } From 7144e8fe01c542507eb08a40accc0f2fcc55bdde Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Mon, 22 Jul 2024 22:03:09 +0300 Subject: [PATCH 47/70] Convert creatures via torture --- .../game/component/CreatureTortured.java | 8 +++-- .../game/controller/CreaturesController.java | 2 +- .../game/controller/GameController.java | 2 +- .../game/controller/GameWorldController.java | 2 +- .../creature/CreatureController.java | 21 ++++++++++++++ .../creature/ICreatureController.java | 7 +++++ .../game/logic/CreatureTorturingSystem.java | 29 ++++++++++++++----- .../openkeeper/game/logic/HealthSystem.java | 2 +- 8 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/toniarts/openkeeper/game/component/CreatureTortured.java b/src/toniarts/openkeeper/game/component/CreatureTortured.java index 3441685ce..458cad228 100644 --- a/src/toniarts/openkeeper/game/component/CreatureTortured.java +++ b/src/toniarts/openkeeper/game/component/CreatureTortured.java @@ -25,15 +25,17 @@ */ public class CreatureTortured implements EntityComponent { - public double startTime; + public double timeTortured; + public double tortureCheckTime; public double healthCheckTime; public CreatureTortured() { // For serialization } - public CreatureTortured(double startTime, double healthCheckTime) { - this.startTime = startTime; + public CreatureTortured(double timeTortured, double tortureCheckTime, double healthCheckTime) { + this.timeTortured = timeTortured; + this.tortureCheckTime = tortureCheckTime; this.healthCheckTime = healthCheckTime; } diff --git a/src/toniarts/openkeeper/game/controller/CreaturesController.java b/src/toniarts/openkeeper/game/controller/CreaturesController.java index db7acd817..b6e355340 100644 --- a/src/toniarts/openkeeper/game/controller/CreaturesController.java +++ b/src/toniarts/openkeeper/game/controller/CreaturesController.java @@ -395,7 +395,7 @@ private CreatureState getCreatureStateByMapLocation(Point location, short ownerI } if (room.hasObjectControl(AbstractRoomController.ObjectType.TORTUREE)) { room.getObjectControl(AbstractRoomController.ObjectType.TORTUREE).addItem(entityId, location); - entityData.setComponent(entityId, new CreatureTortured(gameTimer.getGameTime(), gameTimer.getGameTime())); + entityData.setComponent(entityId, new CreatureTortured(0, gameTimer.getGameTime(), gameTimer.getGameTime())); return CreatureState.TORTURED; } } diff --git a/src/toniarts/openkeeper/game/controller/GameController.java b/src/toniarts/openkeeper/game/controller/GameController.java index 81264fbe5..cfe45495e 100644 --- a/src/toniarts/openkeeper/game/controller/GameController.java +++ b/src/toniarts/openkeeper/game/controller/GameController.java @@ -257,7 +257,7 @@ public void createNewGame() { 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, gameSettings), + new CreatureTorturingSystem(entityData, gameWorldController.getCreaturesController(), gameWorldController.getMapController()), new DeathSystem(entityData, gameSettings, positionSystem), new PlayerCreatureSystem(entityData, kwdFile, playerControllers.values()), new PlayerSpellbookSystem(entityData, kwdFile, playerControllers.values()), diff --git a/src/toniarts/openkeeper/game/controller/GameWorldController.java b/src/toniarts/openkeeper/game/controller/GameWorldController.java index 581f0938a..9b739ab57 100644 --- a/src/toniarts/openkeeper/game/controller/GameWorldController.java +++ b/src/toniarts/openkeeper/game/controller/GameWorldController.java @@ -761,7 +761,7 @@ private void dropCreature(IRoomController roomController, EntityId entity, Point // Set the component, continue the torturing time if such is possible CreatureTortured tortured = entityData.getComponent(entity, CreatureTortured.class); - entityData.setComponent(entity, new CreatureTortured(tortured != null ? tortured.startTime : gameTimer.getGameTime(), gameTimer.getGameTime())); + entityData.setComponent(entity, new CreatureTortured(tortured != null ? tortured.timeTortured : 0.0d, gameTimer.getGameTime(), gameTimer.getGameTime())); } } tortureOrImprisonment = imprison || torture; diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index 2bce99768..171601882 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -1310,4 +1310,25 @@ private void endPossession() { } } + @Override + public void convertCreature(short playerId) { + + // Remove our lair from the last player + CreatureSleep creatureSleep = entityData.getComponent(entityId, CreatureSleep.class); + if (creatureSleep != null && creatureSleep.lairObjectId != null) { + entityData.removeEntity(creatureSleep.lairObjectId); + entityData.setComponent(entityId, new CreatureSleep(null, creatureSleep.lastSleepTime, creatureSleep.sleepStartTime)); + } + + // Set new owner + entityData.setComponent(entityId, new Owner(playerId, playerId)); + + // Remove good player stuff + entityData.removeComponent(entityId, Objective.class); + entityData.removeComponent(entityId, Party.class); + + // Reset AI + getStateMachine().changeState(CreatureState.IDLE); + } + } diff --git a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java index 3f973a4bd..c45deecbb 100644 --- a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java @@ -254,4 +254,11 @@ public interface ICreatureController extends IGameLogicUpdatable, INavigable, IE */ public void setPossession(boolean possessed); + /** + * Converts creature to another player's bidding + * + * @param playerId the new player ID + */ + public void convertCreature(short playerId); + } diff --git a/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java b/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java index f0c187234..12aafb920 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java @@ -19,12 +19,12 @@ import com.simsilica.es.Entity; import com.simsilica.es.EntityData; import com.simsilica.es.EntitySet; -import java.util.Map; import toniarts.openkeeper.game.component.CreatureComponent; import toniarts.openkeeper.game.component.CreatureTortured; -import toniarts.openkeeper.game.component.Health; import toniarts.openkeeper.game.component.Position; -import toniarts.openkeeper.tools.convert.map.Variable; +import toniarts.openkeeper.game.controller.ICreaturesController; +import toniarts.openkeeper.game.map.IMapInformation; +import toniarts.openkeeper.utils.WorldUtils; /** * Handles creatures torturing. When enough... persuasion has been received... @@ -35,10 +35,14 @@ public class CreatureTorturingSystem implements IGameLogicUpdatable { private final EntityData entityData; + private final ICreaturesController creaturesController; + private final IMapInformation mapInformation; private final EntitySet torturedEntities; - public CreatureTorturingSystem(EntityData entityData, Map gameSettings) { + public CreatureTorturingSystem(EntityData entityData, ICreaturesController creaturesController, IMapInformation mapInformation) { this.entityData = entityData; + this.creaturesController = creaturesController; + this.mapInformation = mapInformation; // Have the position also here, since the player may move tortured entities between torture rooms, kinda still tortured but not counting towards death at the time torturedEntities = entityData.getEntities(CreatureTortured.class, CreatureComponent.class, Position.class); @@ -52,11 +56,22 @@ public void processTick(float tpf, double gameTime) { // Process ticks for (Entity entity : torturedEntities) { - Health health = entity.get(Health.class); + CreatureTortured creatureTortured = entity.get(CreatureTortured.class); + if (creatureTortured.tortureCheckTime >= gameTime) { + continue; + } - // TODO: Join the persuating player army!! + CreatureComponent creatureComponent = entity.get(CreatureComponent.class); + if (creatureTortured.timeTortured + tpf >= creatureComponent.tortureTimeToConvert) { - // TODO: Mood (to MoodSystem) + // Convert! + short playerId = mapInformation.getMapData().getTile(WorldUtils.vectorToPoint(entity.get(Position.class).position)).getOwnerId(); + entityData.removeComponent(entity.getId(), CreatureTortured.class); + creaturesController.createController(entity.getId()).convertCreature(playerId); + continue; + } + + entity.set(new CreatureTortured(creatureTortured.timeTortured + tpf, gameTime, creatureTortured.healthCheckTime)); } } diff --git a/src/toniarts/openkeeper/game/logic/HealthSystem.java b/src/toniarts/openkeeper/game/logic/HealthSystem.java index 290001cc0..74fb4a751 100644 --- a/src/toniarts/openkeeper/game/logic/HealthSystem.java +++ b/src/toniarts/openkeeper/game/logic/HealthSystem.java @@ -284,7 +284,7 @@ private int calculateHealthChange(EntityId entityId, Health health, double gameT CreatureTortured tortured = entity.get(CreatureTortured.class); if (gameTime - tortured.healthCheckTime >= 1) { int tortureHealthRegeneratePerSecond = levelInfo.getLevelData().getCreature(entity.get(CreatureComponent.class).creatureId).getAttributes().getTortureHpChange(); - entityData.setComponent(entity.getId(), new CreatureTortured(tortured.startTime, tortured.healthCheckTime + 1)); + entityData.setComponent(entity.getId(), new CreatureTortured(tortured.timeTortured, tortured.tortureCheckTime, tortured.healthCheckTime + 1)); delta += tortureHealthRegeneratePerSecond; return delta; // Assume no other states can be From c9cdafd5a77b8ce39e63031b28be1c77d13781c9 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 19:43:47 +0300 Subject: [PATCH 48/70] Only trigger conversion for foes --- .../game/logic/CreatureTorturingSystem.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java b/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java index 12aafb920..4bdb2043c 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureTorturingSystem.java @@ -21,6 +21,7 @@ import com.simsilica.es.EntitySet; import toniarts.openkeeper.game.component.CreatureComponent; import toniarts.openkeeper.game.component.CreatureTortured; +import toniarts.openkeeper.game.component.Owner; import toniarts.openkeeper.game.component.Position; import toniarts.openkeeper.game.controller.ICreaturesController; import toniarts.openkeeper.game.map.IMapInformation; @@ -45,7 +46,7 @@ public CreatureTorturingSystem(EntityData entityData, ICreaturesController creat this.mapInformation = mapInformation; // Have the position also here, since the player may move tortured entities between torture rooms, kinda still tortured but not counting towards death at the time - torturedEntities = entityData.getEntities(CreatureTortured.class, CreatureComponent.class, Position.class); + torturedEntities = entityData.getEntities(CreatureTortured.class, CreatureComponent.class, Position.class, Owner.class); } @Override @@ -64,11 +65,15 @@ public void processTick(float tpf, double gameTime) { CreatureComponent creatureComponent = entity.get(CreatureComponent.class); if (creatureTortured.timeTortured + tpf >= creatureComponent.tortureTimeToConvert) { - // Convert! + Owner owner = entity.get(Owner.class); short playerId = mapInformation.getMapData().getTile(WorldUtils.vectorToPoint(entity.get(Position.class).position)).getOwnerId(); - entityData.removeComponent(entity.getId(), CreatureTortured.class); - creaturesController.createController(entity.getId()).convertCreature(playerId); - continue; + if (owner.ownerId != playerId) { + + // Convert! + entityData.removeComponent(entity.getId(), CreatureTortured.class); + creaturesController.createController(entity.getId()).convertCreature(playerId); + continue; + } } entity.set(new CreatureTortured(creatureTortured.timeTortured + tpf, gameTime, creatureTortured.healthCheckTime)); From 57bb54a8765ac54c0e2b70e1b9ffaa184b7faf85 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 19:45:01 +0300 Subject: [PATCH 49/70] Release attack target when exiting fight state --- .../game/controller/creature/CreatureController.java | 4 +++- .../openkeeper/game/controller/creature/CreatureState.java | 4 ++-- .../game/controller/creature/ICreatureController.java | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index 171601882..f138cf557 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -515,6 +515,7 @@ && isEnemy(entity) } else { attackTargetController = creaturesController.createController(attackTarget.entityId); } + return attackTargetController; } @@ -525,7 +526,8 @@ private boolean hasPathToEntity(EntityId entity) { return ourPos.equals(theirPos) || navigationService.findPath(ourPos, theirPos, this) != null; } - private void setAttackTarget(EntityId entity) { + @Override + public void setAttackTarget(EntityId entity) { if (entity == null) { entityData.removeComponent(entityId, AttackTarget.class); } else { diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureState.java b/src/toniarts/openkeeper/game/controller/creature/CreatureState.java index e9eaf75a0..fd6c12443 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureState.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureState.java @@ -256,8 +256,8 @@ public void update(ICreatureController entity) { } @Override - public void exit(ICreatureController entity) { - + public void exit(ICreatureController entity) { + entity.setAttackTarget(null); } @Override diff --git a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java index c45deecbb..6e55a38bd 100644 --- a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java @@ -86,6 +86,8 @@ public interface ICreatureController extends IGameLogicUpdatable, INavigable, IE public ICreatureController getAttackTarget(); + public void setAttackTarget(EntityId entity); + public boolean isWithinAttackDistance(EntityId attackTarget); public void stopCreature(); From 0e44d69e70a0d2f66cbaaae677096ab552c5ceca Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 19:45:38 +0300 Subject: [PATCH 50/70] Revert "Release attack target when exiting fight state" This reverts commit 57bb54a8765ac54c0e2b70e1b9ffaa184b7faf85. --- .../game/controller/creature/CreatureController.java | 4 +--- .../openkeeper/game/controller/creature/CreatureState.java | 4 ++-- .../game/controller/creature/ICreatureController.java | 2 -- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index f138cf557..171601882 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -515,7 +515,6 @@ && isEnemy(entity) } else { attackTargetController = creaturesController.createController(attackTarget.entityId); } - return attackTargetController; } @@ -526,8 +525,7 @@ private boolean hasPathToEntity(EntityId entity) { return ourPos.equals(theirPos) || navigationService.findPath(ourPos, theirPos, this) != null; } - @Override - public void setAttackTarget(EntityId entity) { + private void setAttackTarget(EntityId entity) { if (entity == null) { entityData.removeComponent(entityId, AttackTarget.class); } else { diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureState.java b/src/toniarts/openkeeper/game/controller/creature/CreatureState.java index fd6c12443..e9eaf75a0 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureState.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureState.java @@ -256,8 +256,8 @@ public void update(ICreatureController entity) { } @Override - public void exit(ICreatureController entity) { - entity.setAttackTarget(null); + public void exit(ICreatureController entity) { + } @Override diff --git a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java index 6e55a38bd..c45deecbb 100644 --- a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java @@ -86,8 +86,6 @@ public interface ICreatureController extends IGameLogicUpdatable, INavigable, IE public ICreatureController getAttackTarget(); - public void setAttackTarget(EntityId entity); - public boolean isWithinAttackDistance(EntityId attackTarget); public void stopCreature(); From b56efe6c30b27e8e159da13ce2cceab47a8ec5b5 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 19:48:56 +0300 Subject: [PATCH 51/70] Remove attack target when fall unconscious --- src/toniarts/openkeeper/game/logic/HealthSystem.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/toniarts/openkeeper/game/logic/HealthSystem.java b/src/toniarts/openkeeper/game/logic/HealthSystem.java index 74fb4a751..f390a9cb5 100644 --- a/src/toniarts/openkeeper/game/logic/HealthSystem.java +++ b/src/toniarts/openkeeper/game/logic/HealthSystem.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import toniarts.openkeeper.game.component.AttackTarget; import toniarts.openkeeper.game.component.ChickenAi; import toniarts.openkeeper.game.component.CreatureAi; import toniarts.openkeeper.game.component.CreatureComponent; @@ -241,6 +242,7 @@ private boolean canRiseAsSkeleton(Owner owner, short creatureId) { } private void processUnconscious(EntityId entityId, Health health, double gameTime) { + entityData.removeComponent(entityId, AttackTarget.class); entityData.setComponent(entityId, new Health(0, health.maxHealth)); entityData.setComponent(entityId, new Unconscious(gameTime)); //entityData.setComponent(entityId, new CreatureAi(gameTime, CreatureState.UNCONSCIOUS, creatureComponent.creatureId)); // Hmm From 8ad7c03d3fece343aaf248ea2dd100cd87631c3a Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 19:50:31 +0300 Subject: [PATCH 52/70] Also use the convert method for the neutral players --- .../creature/CreatureController.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index 171601882..f69a92167 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -172,12 +172,14 @@ public void checkSurroundings() { // Scan for neutral creatures to claim short ownerId = getOwnerId(); - if (ownerId != Player.NEUTRAL_PLAYER_ID && ownerId != Player.GOOD_PLAYER_ID) { - for (EntityId entity : entityPositionLookup.getSensedEntities(entityId)) { - Owner owner = entityData.getComponent(entity, Owner.class); - if (owner != null && owner.ownerId == Player.NEUTRAL_PLAYER_ID) { - entityData.setComponent(entity, new Owner(ownerId, ownerId)); - } + if (ownerId == Player.NEUTRAL_PLAYER_ID || ownerId == Player.GOOD_PLAYER_ID) { + return; + } + + for (EntityId entity : entityPositionLookup.getSensedEntities(entityId)) { + Owner owner = entityData.getComponent(entity, Owner.class); + if (owner != null && owner.ownerId == Player.NEUTRAL_PLAYER_ID) { + convertCreature(entity, ownerId); } } } @@ -1312,20 +1314,24 @@ private void endPossession() { @Override public void convertCreature(short playerId) { + convertCreature(entityId, playerId); + } + + private void convertCreature(EntityId entity, short playerId) { // Remove our lair from the last player - CreatureSleep creatureSleep = entityData.getComponent(entityId, CreatureSleep.class); + CreatureSleep creatureSleep = entityData.getComponent(entity, CreatureSleep.class); if (creatureSleep != null && creatureSleep.lairObjectId != null) { entityData.removeEntity(creatureSleep.lairObjectId); - entityData.setComponent(entityId, new CreatureSleep(null, creatureSleep.lastSleepTime, creatureSleep.sleepStartTime)); + entityData.setComponent(entity, new CreatureSleep(null, creatureSleep.lastSleepTime, creatureSleep.sleepStartTime)); } // Set new owner - entityData.setComponent(entityId, new Owner(playerId, playerId)); + entityData.setComponent(entity, new Owner(playerId, playerId)); // Remove good player stuff - entityData.removeComponent(entityId, Objective.class); - entityData.removeComponent(entityId, Party.class); + entityData.removeComponent(entity, Objective.class); + entityData.removeComponent(entity, Party.class); // Reset AI getStateMachine().changeState(CreatureState.IDLE); From 760f7944879502d60c9ebf018e66392b2488e799 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 20:19:22 +0300 Subject: [PATCH 53/70] React to all owner changes --- src/toniarts/openkeeper/view/control/CreatureFlowerControl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toniarts/openkeeper/view/control/CreatureFlowerControl.java b/src/toniarts/openkeeper/view/control/CreatureFlowerControl.java index bb9713129..8b45dc94d 100644 --- a/src/toniarts/openkeeper/view/control/CreatureFlowerControl.java +++ b/src/toniarts/openkeeper/view/control/CreatureFlowerControl.java @@ -225,7 +225,7 @@ protected void onMaterialGenerated(Material material) { } // Set new owner - if (currentDrawnOwnerId == Player.NEUTRAL_PLAYER_ID && currentDrawnOwnerId != getOwnerId()) { + if (currentDrawnOwnerId != getOwnerId()) { material.setBoolean("FlashColors", false); currentDrawnOwnerId = getOwnerId(); setFlowerColor(getPlayerColor(currentDrawnOwnerId)); From da569eb9f3948596d4bf258c0be884bc5f683812 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 20:32:56 +0300 Subject: [PATCH 54/70] Remove unused import --- src/toniarts/openkeeper/game/controller/TrapsController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/controller/TrapsController.java b/src/toniarts/openkeeper/game/controller/TrapsController.java index 41b2f40fa..037d5d03d 100644 --- a/src/toniarts/openkeeper/game/controller/TrapsController.java +++ b/src/toniarts/openkeeper/game/controller/TrapsController.java @@ -37,7 +37,6 @@ import toniarts.openkeeper.tools.convert.map.Trap; import toniarts.openkeeper.tools.convert.map.Variable; import toniarts.openkeeper.utils.WorldUtils; -import toniarts.openkeeper.view.map.MapViewController; /** * This is a controller that controls all the traps in the world TODO: From b99f88e863d2044e8a49dce160fdfc386b4d1bab Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 20:52:17 +0300 Subject: [PATCH 55/70] Implement is creature captured trigger --- .../trigger/AbstractThingTriggerControl.java | 4 -- .../game/trigger/TriggerControl.java | 21 +++------ .../game/trigger/TriggerLoader.java | 43 ++++++++++--------- .../ActionPointTriggerControl.java | 8 +--- .../creature/CreatureTriggerControl.java | 2 +- .../trigger/party/PartyTriggerControl.java | 6 +-- .../trigger/player/PlayerTriggerControl.java | 16 ++----- 7 files changed, 38 insertions(+), 62 deletions(-) diff --git a/src/toniarts/openkeeper/game/trigger/AbstractThingTriggerControl.java b/src/toniarts/openkeeper/game/trigger/AbstractThingTriggerControl.java index 9f5109ac7..6564de8c4 100644 --- a/src/toniarts/openkeeper/game/trigger/AbstractThingTriggerControl.java +++ b/src/toniarts/openkeeper/game/trigger/AbstractThingTriggerControl.java @@ -34,10 +34,6 @@ public abstract class AbstractThingTriggerControl extends PlayerTriggerContro protected T instanceControl; - public AbstractThingTriggerControl() { // empty serialization constructor - super(); - } - public AbstractThingTriggerControl(final IGameController gameController, final ILevelInfo levelInfo, final IGameTimer gameTimer, final IMapController mapController, final ICreaturesController creaturesController, final int triggerId, final short playerId, final PlayerService playerService) { diff --git a/src/toniarts/openkeeper/game/trigger/TriggerControl.java b/src/toniarts/openkeeper/game/trigger/TriggerControl.java index 464ec9f31..a54c5bbb8 100644 --- a/src/toniarts/openkeeper/game/trigger/TriggerControl.java +++ b/src/toniarts/openkeeper/game/trigger/TriggerControl.java @@ -51,20 +51,13 @@ public class TriggerControl extends Control { private static final short TIME_LIMIT_TIMER_ID = 16; protected TriggerGenericData trigger; - protected TriggerGenericData root; - - protected ILevelInfo levelInfo; - protected IGameTimer gameTimer; - protected IGameController gameController; - protected IMapController mapController; - protected ICreaturesController creaturesController; - - /** - * empty serialization constructor - */ - public TriggerControl() { - super(); - } + protected final TriggerGenericData root; + + protected final ILevelInfo levelInfo; + protected final IGameTimer gameTimer; + protected final IGameController gameController; + protected final IMapController mapController; + protected final ICreaturesController creaturesController; public TriggerControl(final IGameController gameController, final ILevelInfo levelInfo, final IGameTimer gameTimer, final IMapController mapController, final ICreaturesController creaturesController, final int triggerId) { diff --git a/src/toniarts/openkeeper/game/trigger/TriggerLoader.java b/src/toniarts/openkeeper/game/trigger/TriggerLoader.java index 946f21063..912334b3a 100644 --- a/src/toniarts/openkeeper/game/trigger/TriggerLoader.java +++ b/src/toniarts/openkeeper/game/trigger/TriggerLoader.java @@ -50,33 +50,36 @@ private void parse(TriggerGenericData parent, int id) { Trigger temp = triggers.get(id); TriggerData trigger; - if (temp instanceof TriggerGeneric) { - trigger = new TriggerGenericData(id, temp.getRepeatTimes()); - ((TriggerGenericData) trigger).setType(((TriggerGeneric) temp).getType()); - ((TriggerGenericData) trigger).setComparison(((TriggerGeneric) temp).getTargetValueComparison()); + switch (temp) { + case TriggerGeneric triggerGeneric -> { + trigger = new TriggerGenericData(id, temp.getRepeatTimes()); + ((TriggerGenericData) trigger).setType(triggerGeneric.getType()); + ((TriggerGenericData) trigger).setComparison(triggerGeneric.getTargetValueComparison()); - for (String key : temp.getUserDataKeys()) { - trigger.setUserData(key, temp.getUserData(key)); - } - - parent.attachChild(trigger); + for (String key : temp.getUserDataKeys()) { + trigger.setUserData(key, temp.getUserData(key)); + } - if (temp.hasChildren()) { - parse(((TriggerGenericData) trigger), temp.getIdChild()); - } + parent.attachChild(trigger); - } else if (temp instanceof TriggerAction) { - trigger = new TriggerActionData(id); - ((TriggerActionData) trigger).setType(((TriggerAction) temp).getType()); + if (temp.hasChildren()) { + parse(((TriggerGenericData) trigger), temp.getIdChild()); + } - for (String key : temp.getUserDataKeys()) { - trigger.setUserData(key, temp.getUserData(key)); } + case TriggerAction triggerAction -> { + trigger = new TriggerActionData(id); + ((TriggerActionData) trigger).setType(triggerAction.getType()); - parent.attachChild(trigger); + for (String key : temp.getUserDataKeys()) { + trigger.setUserData(key, temp.getUserData(key)); + } - } else { - throw new RuntimeException("Unexpected class " + temp + "!"); + parent.attachChild(trigger); + + } + default -> + throw new RuntimeException("Unexpected class " + temp + "!"); } if (temp.hasNext()) { diff --git a/src/toniarts/openkeeper/game/trigger/actionpoint/ActionPointTriggerControl.java b/src/toniarts/openkeeper/game/trigger/actionpoint/ActionPointTriggerControl.java index 264b3ab17..178834919 100644 --- a/src/toniarts/openkeeper/game/trigger/actionpoint/ActionPointTriggerControl.java +++ b/src/toniarts/openkeeper/game/trigger/actionpoint/ActionPointTriggerControl.java @@ -41,12 +41,8 @@ public class ActionPointTriggerControl extends TriggerControl { private static final Logger logger = System.getLogger(ActionPointTriggerControl.class.getName()); - private ActionPoint ap; - private IEntityPositionLookup entityPositionLookup; - - public ActionPointTriggerControl() { // empty serialization constructor - super(); - } + private final ActionPoint ap; + private final IEntityPositionLookup entityPositionLookup; public ActionPointTriggerControl(final IGameController gameController, final ILevelInfo levelInfo, final IGameTimer gameTimer, final IMapController mapController, final ICreaturesController creaturesController, int triggerId, ActionPoint ap, diff --git a/src/toniarts/openkeeper/game/trigger/creature/CreatureTriggerControl.java b/src/toniarts/openkeeper/game/trigger/creature/CreatureTriggerControl.java index 7a4de0033..fe48776c1 100644 --- a/src/toniarts/openkeeper/game/trigger/creature/CreatureTriggerControl.java +++ b/src/toniarts/openkeeper/game/trigger/creature/CreatureTriggerControl.java @@ -80,7 +80,7 @@ protected boolean isActive(TriggerGenericData trigger) { } return false; case CREATURE_CONVERTED: - return false; + return instanceControl.getOwnerId() != getPlayerId(); case CREATURE_CLAIMED: if (instanceControl != null) { return instanceControl.isClaimed(); diff --git a/src/toniarts/openkeeper/game/trigger/party/PartyTriggerControl.java b/src/toniarts/openkeeper/game/trigger/party/PartyTriggerControl.java index 7950f127a..5e2e41e49 100644 --- a/src/toniarts/openkeeper/game/trigger/party/PartyTriggerControl.java +++ b/src/toniarts/openkeeper/game/trigger/party/PartyTriggerControl.java @@ -29,11 +29,7 @@ public class PartyTriggerControl extends TriggerControl { - private IPartyController partyController; - - public PartyTriggerControl() { // empty serialization constructor - super(); - } + private final IPartyController partyController; public PartyTriggerControl(final IGameController gameController, final ILevelInfo levelInfo, final IGameTimer gameTimer, final IMapController mapController, final ICreaturesController creaturesController, int triggerId, diff --git a/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerControl.java b/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerControl.java index 3572dcc1d..772c5f280 100644 --- a/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerControl.java +++ b/src/toniarts/openkeeper/game/trigger/player/PlayerTriggerControl.java @@ -48,15 +48,8 @@ public class PlayerTriggerControl extends TriggerControl { private static final Logger logger = System.getLogger(PlayerTriggerControl.class.getName()); - private short playerId; - private PlayerService playerService; - - /** - * empty serialization constructor - */ - public PlayerTriggerControl() { - super(); - } + private final short playerId; + private final PlayerService playerService; public PlayerTriggerControl(final IGameController gameController, final ILevelInfo levelInfo, final IGameTimer gameTimer, final IMapController mapController, final ICreaturesController creaturesController, final int triggerId, final short playerId, @@ -66,8 +59,8 @@ public PlayerTriggerControl(final IGameController gameController, final ILevelIn this.playerService = playerService; } - public void setPlayer(short playerId) { - this.playerId = playerId; + public short getPlayerId() { + return playerId; } @Override @@ -246,7 +239,6 @@ protected boolean isActive(TriggerGenericData trigger) { case GUI_TRANSITION_ENDS: return !playerService.isInTransition(); -// return playerState.isTransitionEnd(); case GUI_BUTTON_PRESSED: return false; From f71f6e9b5f4e68b4078ecb52678b9e110cdc06d4 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 23 Jul 2024 20:54:57 +0300 Subject: [PATCH 56/70] Don't remove party component just yet --- .../openkeeper/game/controller/creature/CreatureController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index f69a92167..4378ba003 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -1331,7 +1331,6 @@ private void convertCreature(EntityId entity, short playerId) { // Remove good player stuff entityData.removeComponent(entity, Objective.class); - entityData.removeComponent(entity, Party.class); // Reset AI getStateMachine().changeState(CreatureState.IDLE); From a2661fe02ff26afc34a95fd5690a2d6d424f761f Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Wed, 24 Jul 2024 20:00:38 +0300 Subject: [PATCH 57/70] Process unemployed workers in one go --- .../game/controller/GameController.java | 2 +- .../creature/CreatureController.java | 14 ++++-- .../controller/creature/CreatureState.java | 48 ++++++++++++------- .../creature/ICreatureController.java | 3 +- .../game/logic/CreatureAiSystem.java | 8 +++- .../openkeeper/game/task/ITaskManager.java | 15 ++++++ .../openkeeper/game/task/TaskManager.java | 23 ++++++++- 7 files changed, 85 insertions(+), 28 deletions(-) diff --git a/src/toniarts/openkeeper/game/controller/GameController.java b/src/toniarts/openkeeper/game/controller/GameController.java index 81264fbe5..b5f08e467 100644 --- a/src/toniarts/openkeeper/game/controller/GameController.java +++ b/src/toniarts/openkeeper/game/controller/GameController.java @@ -265,7 +265,7 @@ public void createNewGame() { new CreatureSpawnSystem(gameWorldController.getCreaturesController(), playerControllers.values(), gameSettings, this, gameWorldController.getMapController()), new ChickenSpawnSystem(gameWorldController.getObjectsController(), playerControllers.values(), gameSettings, this, gameWorldController.getMapController()), new ManaCalculatorLogic(playerControllers.values(), entityData), - new CreatureAiSystem(entityData, gameWorldController.getCreaturesController()), + new CreatureAiSystem(entityData, gameWorldController.getCreaturesController(), taskManager), new ChickenAiSystem(entityData, gameWorldController.getObjectsController()), new CreatureViewSystem(entityData), new DoorViewSystem(entityData, positionSystem), diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java index 2bce99768..5760bf6ef 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureController.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import toniarts.openkeeper.game.component.Attack; import toniarts.openkeeper.game.component.AttackTarget; import toniarts.openkeeper.game.component.CreatureAi; @@ -338,18 +339,20 @@ public boolean goToSleep() { } @Override - public boolean findWork() { + public void findWork(Consumer workResult) { // See if we have some available work if (isWorker()) { - return (taskManager.assignTask(this, false)); + taskManager.addToUnemployedWorkerQueue(this, workResult); + return; } short owner = getOwnerId(); if (owner == Player.NEUTRAL_PLAYER_ID || owner == Player.GOOD_PLAYER_ID) { // Currently no creature specific jobs offered for neutral or good players - return false; + workResult.accept(false); + return; } // See that is there a prefered job for us @@ -365,10 +368,11 @@ public boolean findWork() { // Choose if (!jobs.isEmpty()) { - return (taskManager.assignTask(this, chooseOnWeight(jobs).getJobType())); + workResult.accept(taskManager.assignTask(this, chooseOnWeight(jobs).getJobType())); + return; } - return false; + workResult.accept(false); } private static Creature.JobPreference chooseOnWeight(List items) { diff --git a/src/toniarts/openkeeper/game/controller/creature/CreatureState.java b/src/toniarts/openkeeper/game/controller/creature/CreatureState.java index e9eaf75a0..b3692ee03 100644 --- a/src/toniarts/openkeeper/game/controller/creature/CreatureState.java +++ b/src/toniarts/openkeeper/game/controller/creature/CreatureState.java @@ -18,6 +18,7 @@ import com.badlogic.gdx.ai.fsm.State; import com.badlogic.gdx.ai.msg.Telegram; +import java.util.function.Consumer; /** * State machine for creature AI. TODO: needs to be hierarchial so that this @@ -39,51 +40,60 @@ public void enter(ICreatureController entity) { // Idling is the last resort entity.unassingCurrentTask(); - if (!findStuffToDo(entity)) { - entity.navigateToRandomPoint(); - } + findStuffToDo(entity, (foundWork) -> { + if (!foundWork) { + entity.navigateToRandomPoint(); + } + }); } - private boolean findStuffToDo(ICreatureController entity) { + private static void findStuffToDo(ICreatureController entity, Consumer workResult) { // See if we should just follow if (entity.getParty() != null && !entity.getParty().isPartyLeader(entity)) { entity.setFollowTarget(entity.getParty().getPartyLeader().getEntityId()); entity.getStateMachine().changeState(CreatureState.FOLLOW); - return true; + workResult.accept(true); + return; } // See if we have an objective if (entity.hasObjective() && entity.followObjective()) { entity.getStateMachine().changeState(CreatureState.WORK); - return true; + workResult.accept(true); + return; } // See lair need if (entity.needsLair() && !entity.hasLair() && entity.findLair()) { entity.getStateMachine().changeState(CreatureState.WORK); - return true; // Found work + workResult.accept(true); + return; // Found work } // See basic needs if (entity.hasLair() && entity.isNeedForSleep() && entity.goToSleep()) { entity.getStateMachine().changeState(CreatureState.WORK); - return true; // Found work + workResult.accept(true); + return; // Found work } // See hunger if (entity.isHungry() && entity.goToEat()) { entity.getStateMachine().changeState(CreatureState.WORK); - return true; // Found work + workResult.accept(true); + return; // Found work } // Find work - if (entity.findWork() || (entity.isWorker() && entity.isTooMuchGold() && entity.dropGoldToTreasury())) { - entity.getStateMachine().changeState(CreatureState.WORK); - return true; // Found work - } - - return false; + entity.findWork((foundWork) -> { + if (foundWork || (entity.isWorker() && entity.isTooMuchGold() && entity.dropGoldToTreasury())) { + entity.getStateMachine().changeState(CreatureState.WORK); + workResult.accept(true); // Found work + } else { + workResult.accept(false); + } + }); } @Override @@ -96,9 +106,11 @@ public void update(ICreatureController entity) { if (entity.isTimeToReEvaluate()) { entity.resetReEvaluationTimer(); - if (!findStuffToDo(entity) && entity.isStopped()) { - entity.navigateToRandomPoint(); - } + findStuffToDo(entity, (foundWork) -> { + if (!foundWork && entity.isStopped()) { + entity.navigateToRandomPoint(); + } + }); } } diff --git a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java index 3f973a4bd..39d89a88d 100644 --- a/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java +++ b/src/toniarts/openkeeper/game/controller/creature/ICreatureController.java @@ -19,6 +19,7 @@ import com.badlogic.gdx.ai.fsm.StateMachine; import com.simsilica.es.EntityId; import java.awt.Point; +import java.util.function.Consumer; import toniarts.openkeeper.game.controller.entity.IEntityController; import toniarts.openkeeper.game.controller.object.IObjectController; import toniarts.openkeeper.game.data.ObjectiveType; @@ -64,7 +65,7 @@ public interface ICreatureController extends IGameLogicUpdatable, INavigable, IE public boolean goToSleep(); - public boolean findWork(); + public void findWork(Consumer workResult); public boolean isWorker(); diff --git a/src/toniarts/openkeeper/game/logic/CreatureAiSystem.java b/src/toniarts/openkeeper/game/logic/CreatureAiSystem.java index 9a837ab05..00ac8cee5 100644 --- a/src/toniarts/openkeeper/game/logic/CreatureAiSystem.java +++ b/src/toniarts/openkeeper/game/logic/CreatureAiSystem.java @@ -28,6 +28,7 @@ import toniarts.openkeeper.game.component.CreatureAi; import toniarts.openkeeper.game.controller.ICreaturesController; import toniarts.openkeeper.game.controller.creature.ICreatureController; +import toniarts.openkeeper.game.task.ITaskManager; /** * Handles creature logic updates, the creature AI updates that is. The AI is @@ -43,9 +44,11 @@ public class CreatureAiSystem implements IGameLogicUpdatable { private final SafeArrayList creatureControllers; private final Map creatureControllersByEntityId; private final ICreaturesController creaturesController; + private final ITaskManager taskManager; - public CreatureAiSystem(EntityData entityData, ICreaturesController creaturesController) { + public CreatureAiSystem(EntityData entityData, ICreaturesController creaturesController, ITaskManager taskManager) { this.creaturesController = creaturesController; + this.taskManager = taskManager; creatureEntities = entityData.getEntities(CreatureAi.class); creatureControllers = new SafeArrayList<>(ICreatureController.class); @@ -67,6 +70,9 @@ public void processTick(float tpf, double gameTime) { for (ICreatureController creatureController : creatureControllers.getArray()) { creatureController.processTick(tpf, gameTime); } + + // We have a specialty here, process creature worker queue + taskManager.processUnemployedWorkerQueue(); } private void processAddedEntities(Set entities) { diff --git a/src/toniarts/openkeeper/game/task/ITaskManager.java b/src/toniarts/openkeeper/game/task/ITaskManager.java index 2a2408793..87f526e6f 100644 --- a/src/toniarts/openkeeper/game/task/ITaskManager.java +++ b/src/toniarts/openkeeper/game/task/ITaskManager.java @@ -17,6 +17,7 @@ package toniarts.openkeeper.game.task; import com.simsilica.es.EntityId; +import java.util.function.Consumer; import toniarts.openkeeper.game.controller.creature.ICreatureController; import toniarts.openkeeper.game.controller.room.AbstractRoomController; import toniarts.openkeeper.tools.convert.map.Creature; @@ -109,4 +110,18 @@ public interface ITaskManager { */ boolean assignEatTask(ICreatureController creature); + /** + * Adds a creature to unemployment queue. You need to separately process the + * queue + * + * @param creature the unemployed creature + * @param workResult result callback + */ + void addToUnemployedWorkerQueue(ICreatureController creature, Consumer workResult); + + /** + * Processes the unemployed workers + */ + void processUnemployedWorkerQueue(); + } diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index 977a57763..2cb78ddee 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -37,6 +37,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; +import java.util.function.Consumer; import toniarts.openkeeper.game.component.CreatureComponent; import toniarts.openkeeper.game.component.Death; import toniarts.openkeeper.game.component.Food; @@ -123,6 +124,7 @@ public class TaskManager implements ITaskManager, IGameLogicUpdatable { private final Map tasksIdsByEntities = new HashMap<>(); private final Map playerControllers; private final Map> roomTasks = new HashMap<>(); + private final Map> unemployedCreatures = new HashMap<>(); public TaskManager(EntityData entityData, IGameWorldController gameWorldController, IMapController mapController, IObjectsController objectsController, ICreaturesController creaturesController, INavigationService navigationService, @@ -797,8 +799,8 @@ public Task getTaskById(long taskId) { Task task = tasksByIds.get(taskId); // For nested task, return the actual task - if (task != null && task instanceof AbstractObjectiveTask) { - return ((AbstractObjectiveTask) task).getCurrentTask(); + if (task != null && task instanceof AbstractObjectiveTask nestedTask) { + return nestedTask.getCurrentTask(); } return task; @@ -815,4 +817,21 @@ private IMapTileInformation getEntityPosition(Entity entity) { return mapTile; } + @Override + public void addToUnemployedWorkerQueue(ICreatureController creature, Consumer workResult) { + unemployedCreatures.put(creature, workResult); + } + + @Override + public void processUnemployedWorkerQueue() { + if (unemployedCreatures.isEmpty()) { + return; + } + + unemployedCreatures.forEach((creature, workResult) -> { + workResult.accept(assignTask(creature, false)); + }); + unemployedCreatures.clear(); + } + } From a63fc9cc1d3925f10c09cbde4a4cc71224416d7c Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 26 Jul 2024 19:47:47 +0300 Subject: [PATCH 58/70] Try to fill worker tasks as efficiently as possible --- .../openkeeper/game/task/AbstractTask.java | 7 +- src/toniarts/openkeeper/game/task/Task.java | 7 ++ .../openkeeper/game/task/TaskManager.java | 90 ++++++++++++++++++- .../objective/ObjectiveTaskDecorator.java | 5 ++ 4 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/toniarts/openkeeper/game/task/AbstractTask.java b/src/toniarts/openkeeper/game/task/AbstractTask.java index 32b8824a6..5b5f32c01 100644 --- a/src/toniarts/openkeeper/game/task/AbstractTask.java +++ b/src/toniarts/openkeeper/game/task/AbstractTask.java @@ -110,9 +110,14 @@ public int getAssigneeCount() { return assignees.size(); } + @Override + public boolean isFull() { + return getAssigneeCount() >= getMaxAllowedNumberOfAsignees(); + } + @Override public boolean canAssign(ICreatureController creature) { - return (assignees.size() < getMaxAllowedNumberOfAsignees() && isValid(creature) && isReachable(creature)); + return (!isFull() && isValid(creature) && isReachable(creature)); } @Override diff --git a/src/toniarts/openkeeper/game/task/Task.java b/src/toniarts/openkeeper/game/task/Task.java index 967826f02..254a0d49f 100644 --- a/src/toniarts/openkeeper/game/task/Task.java +++ b/src/toniarts/openkeeper/game/task/Task.java @@ -77,6 +77,13 @@ public interface Task { */ int getMaxAllowedNumberOfAsignees(); + /** + * Is the task assignee count already full + * + * @return {@code true} if additional workers can't be assigned anymore + */ + boolean isFull(); + /** * Task priority, added to distance when evaluating tasks to give out. The * bigger the number, the less urgent the task is diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index 2cb78ddee..42e4365d2 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -38,6 +38,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.function.Consumer; +import java.util.stream.Collectors; import toniarts.openkeeper.game.component.CreatureComponent; import toniarts.openkeeper.game.component.Death; import toniarts.openkeeper.game.component.Food; @@ -828,10 +829,93 @@ public void processUnemployedWorkerQueue() { return; } - unemployedCreatures.forEach((creature, workResult) -> { - workResult.accept(assignTask(creature, false)); - }); + unemployedCreatures + .entrySet() + .stream() + .collect(Collectors.groupingBy((creature) -> creature.getKey().getOwnerId())) + .entrySet() + .forEach((creaturesByOwnerId) -> { + processUnemployedWorkers(taskQueues.getOrDefault(creaturesByOwnerId.getKey(), Collections.emptySet()), creaturesByOwnerId.getValue()); + }); unemployedCreatures.clear(); } + private void processUnemployedWorkers(Set tasks, List>> workers) { + Set taskQueue = new HashSet<>(tasks); + Map> unemployedWorkers = workers + .stream() + .collect(Collectors.toMap((entry) -> entry.getKey(), (entry) -> entry.getValue())); + + // Process each available task for each available worker + List priorizedTasks = new ArrayList<>(workers.size() + tasks.size()); + for (Entry> workerEntry : unemployedWorkers.entrySet()) { + ICreatureController creature = workerEntry.getKey(); + Consumer workResult = workerEntry.getValue(); + final Point currentLocation = creature.getCreatureCoordinates(); + + // Give points by distance and priority, no need to know can we do the task as this point as it might be a heavy check + for (Task task : taskQueue) { + int points = WorldUtils.calculateDistance(currentLocation, task.getTaskLocation()) + task.getPriority(); + priorizedTasks.add(new TaskWorkerPriority(task, creature, points, workResult)); + } + } + + // Go through the tasks and assign workers + while (!unemployedWorkers.isEmpty() && !priorizedTasks.isEmpty()) { + + // With each iteration we sort to fill the jobs as even as possible (assignee count changes) + Collections.sort(priorizedTasks); + + Iterator iter = priorizedTasks.iterator(); + while (iter.hasNext()) { + TaskWorkerPriority taskWorkerPriority = iter.next(); + + // See if the task is full or the worker is not unemployed anymore or that the task can't actually be handled by this creature + if (!unemployedWorkers.containsKey(taskWorkerPriority.creature) + || taskWorkerPriority.task.isFull() + || !taskWorkerPriority.task.canAssign(taskWorkerPriority.creature)) { + iter.remove(); + continue; + } + + // Assign task + taskWorkerPriority.task.assign(taskWorkerPriority.creature, true); + taskWorkerPriority.workResult.accept(Boolean.TRUE); + + // Prune list and re-sort tasks + unemployedWorkers.remove(taskWorkerPriority.creature); + iter.remove(); + break; + } + } + + // Finally anybody without assigment will get feedback + unemployedWorkers.forEach((t, u) -> { + u.accept(Boolean.FALSE); + }); + } + + private static record TaskWorkerPriority(Task task, ICreatureController creature, int points, Consumer workResult) implements Comparable { + + @Override + public int compareTo(TaskWorkerPriority taskWorkerPriority) { + + // Fill in jobs that have no workers first + int result = Integer.compare(task.getAssigneeCount(), taskWorkerPriority.task.getAssigneeCount()); + if (result != 0) { + return result; + } + + // Closest creature gets the job + result = Integer.compare(points, taskWorkerPriority.points); + if (result != 0) { + return result; + } + + // If the same, compare by date added + return task.getTaskCreated().compareTo(taskWorkerPriority.task.getTaskCreated()); + } + + } + } diff --git a/src/toniarts/openkeeper/game/task/objective/ObjectiveTaskDecorator.java b/src/toniarts/openkeeper/game/task/objective/ObjectiveTaskDecorator.java index a81b50e5a..1035031b1 100644 --- a/src/toniarts/openkeeper/game/task/objective/ObjectiveTaskDecorator.java +++ b/src/toniarts/openkeeper/game/task/objective/ObjectiveTaskDecorator.java @@ -150,4 +150,9 @@ public long getId() { return taskId; } + @Override + public boolean isFull() { + return task.isFull(); + } + } From 8b4757a3ec680308d396b72c2a57d764fa66d88d Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 26 Jul 2024 19:48:10 +0300 Subject: [PATCH 59/70] Organise imports --- .../openkeeper/view/map/construction/RoomConstructor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/toniarts/openkeeper/view/map/construction/RoomConstructor.java b/src/toniarts/openkeeper/view/map/construction/RoomConstructor.java index fdf18161c..5b9170cf9 100644 --- a/src/toniarts/openkeeper/view/map/construction/RoomConstructor.java +++ b/src/toniarts/openkeeper/view/map/construction/RoomConstructor.java @@ -13,10 +13,9 @@ import com.jme3.scene.Node; import com.jme3.scene.Spatial; import java.awt.Point; -import toniarts.openkeeper.utils.AssetUtils; -import toniarts.openkeeper.view.map.MapViewController; import toniarts.openkeeper.common.RoomInstance; import toniarts.openkeeper.tools.convert.map.ArtResource; +import toniarts.openkeeper.utils.AssetUtils; import toniarts.openkeeper.utils.WorldUtils; import toniarts.openkeeper.view.map.WallSection; From 60ef9d608a977bdddda85f7f163a1198cce5ba61 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 26 Jul 2024 20:01:23 +0300 Subject: [PATCH 60/70] Remove unused method --- .../openkeeper/game/task/ITaskManager.java | 10 ----- .../openkeeper/game/task/TaskManager.java | 41 ------------------- 2 files changed, 51 deletions(-) diff --git a/src/toniarts/openkeeper/game/task/ITaskManager.java b/src/toniarts/openkeeper/game/task/ITaskManager.java index 87f526e6f..114075a6a 100644 --- a/src/toniarts/openkeeper/game/task/ITaskManager.java +++ b/src/toniarts/openkeeper/game/task/ITaskManager.java @@ -66,16 +66,6 @@ public interface ITaskManager { */ boolean assignSleepTask(ICreatureController creature); - /** - * Assign a task to a creature - * - * @param creature the creature to assign a task to - * @param byDistance whether we should assign the closest task (i.e. if a - * player drops the creature somewhere) - * @return true if a task was assigned - */ - boolean assignTask(ICreatureController creature, boolean byDistance); - /** * Assigns a creature to given task type * diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index 42e4365d2..6df2ad69c 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -507,47 +507,6 @@ private void createFetchObjectTask(Entity entity, short playerId) { } } - @Override - public boolean assignTask(ICreatureController creature, boolean byDistance) { - - Set taskQueue = taskQueues.get(creature.getOwnerId()); - if (taskQueue == null) { - return false; -// throw new IllegalArgumentException("This task manager instance is not for the given player!"); - } - - // Sort by distance & priority - final Point currentLocation = creature.getCreatureCoordinates(); - List prioritisedTaskQueue = new ArrayList<>(taskQueue); - Collections.sort(prioritisedTaskQueue, (Task t, Task t1) -> { - int result = Integer.compare(t.getAssigneeCount(), t1.getAssigneeCount()); - if (result == 0) { - result = Integer.compare( - WorldUtils.calculateDistance(currentLocation, t.getTaskLocation()) + t.getPriority(), - WorldUtils.calculateDistance(currentLocation, t1.getTaskLocation()) + t1.getPriority() - ); - - if (result == 0) { - // If the same, compare by date added - return t.getTaskCreated().compareTo(t1.getTaskCreated()); - } - } - return result; - }); - - // Take the first available task from the sorted queue - for (Task task : prioritisedTaskQueue) { - if (task.canAssign(creature)) { - - // Assign to first task - task.assign(creature, true); - return true; - } - } - - return false; - } - public void addTask(short playerId, Task task) { Set tasks = taskQueues.get(playerId); if (!tasks.contains(task)) { From 06cb4a9ec10cc8f3f3e77ee4b1e30f20cc883913 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Fri, 26 Jul 2024 21:10:34 +0300 Subject: [PATCH 61/70] Try to take the closest task location for the worker --- .../openkeeper/game/task/AbstractTask.java | 25 +++++++++++++++---- .../openkeeper/game/task/TaskManager.java | 10 +++++++- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/toniarts/openkeeper/game/task/AbstractTask.java b/src/toniarts/openkeeper/game/task/AbstractTask.java index 5b5f32c01..f262bac2a 100644 --- a/src/toniarts/openkeeper/game/task/AbstractTask.java +++ b/src/toniarts/openkeeper/game/task/AbstractTask.java @@ -22,7 +22,9 @@ import java.lang.System.Logger; import java.lang.System.Logger.Level; import java.time.Instant; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import toniarts.openkeeper.game.controller.IMapController; @@ -171,6 +173,7 @@ protected boolean isReachable(ICreatureController creature, Vector2f target) { * @return task location next to the wanted tile */ protected Vector2f getAccessibleTargetNextToLocation(ICreatureController creature) { + List accessibleTiles = new ArrayList<>(); for (Point taskPerformLocation : WorldUtils.getSurroundingTiles(mapController.getMapData(), getTaskLocation(), false)) { for (Point p : WorldUtils.getSurroundingTiles(mapController.getMapData(), getTaskLocation(), false)) { if (p.equals(getTaskLocation())) { @@ -181,11 +184,23 @@ protected Vector2f getAccessibleTargetNextToLocation(ICreatureController creatur continue; } - // TODO: intelligent coordinates? - Vector2f target = new Vector2f(p.x, p.y); - if (isReachable(creature, target)) { - return target; - } + accessibleTiles.add(p); + } + } + + if (accessibleTiles.isEmpty()) { + return null; + } + + // Sort by manhattan distance to the creature and get the first reachable one + Point startingPoint = WorldUtils.vectorToPoint(creature.getPosition()); + accessibleTiles.sort((o1, o2) -> { + return Integer.compare(WorldUtils.calculateDistance(startingPoint, o1), WorldUtils.calculateDistance(startingPoint, o2)); + }); + for (Point p : accessibleTiles) { + Vector2f target = new Vector2f(p.x, p.y); + if (isReachable(creature, target)) { + return target; } } diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index 6df2ad69c..3fe2637fc 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -17,6 +17,7 @@ package toniarts.openkeeper.game.task; import com.badlogic.gdx.ai.pfa.GraphPath; +import com.jme3.math.Vector2f; import com.simsilica.es.Entity; import com.simsilica.es.EntityData; import com.simsilica.es.EntityId; @@ -814,7 +815,14 @@ private void processUnemployedWorkers(Set tasks, List Date: Fri, 26 Jul 2024 21:46:53 +0300 Subject: [PATCH 62/70] Make sort as separate method rather than natural property of the record --- .../openkeeper/game/task/TaskManager.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/toniarts/openkeeper/game/task/TaskManager.java b/src/toniarts/openkeeper/game/task/TaskManager.java index 3fe2637fc..43ca6bc31 100644 --- a/src/toniarts/openkeeper/game/task/TaskManager.java +++ b/src/toniarts/openkeeper/game/task/TaskManager.java @@ -831,7 +831,7 @@ private void processUnemployedWorkers(Set tasks, List sortByTaskStatusAndDistance(o1, o2)); Iterator iter = priorizedTasks.iterator(); while (iter.hasNext()) { @@ -862,26 +862,25 @@ private void processUnemployedWorkers(Set tasks, List workResult) implements Comparable { + private int sortByTaskStatusAndDistance(TaskWorkerPriority o1, TaskWorkerPriority o2) { - @Override - public int compareTo(TaskWorkerPriority taskWorkerPriority) { + // Fill in jobs that have no workers first + int result = Integer.compare(o1.task.getAssigneeCount(), o2.task.getAssigneeCount()); + if (result != 0) { + return result; + } - // Fill in jobs that have no workers first - int result = Integer.compare(task.getAssigneeCount(), taskWorkerPriority.task.getAssigneeCount()); - if (result != 0) { - return result; - } + // Closest creature gets the job + result = Integer.compare(o1.points, o2.points); + if (result != 0) { + return result; + } - // Closest creature gets the job - result = Integer.compare(points, taskWorkerPriority.points); - if (result != 0) { - return result; - } + // If the same, compare by date added + return o1.task.getTaskCreated().compareTo(o2.task.getTaskCreated()); + } - // If the same, compare by date added - return task.getTaskCreated().compareTo(taskWorkerPriority.task.getTaskCreated()); - } + private static record TaskWorkerPriority(Task task, ICreatureController creature, int points, Consumer workResult) { } From 46b614558e8a2e23c721b46b0849d00185796c4c Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sat, 27 Jul 2024 11:16:37 +0300 Subject: [PATCH 63/70] Gradle 8.6 -> 8.9 --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +++++-- gradlew.bat | 2 ++ 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 2d16d1850..81dce5bf4 100644 --- a/build.gradle +++ b/build.gradle @@ -123,5 +123,5 @@ compileJava { } wrapper { - gradleVersion = '8.6' + gradleVersion = '8.9' } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch delta 34463 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 SXRcJ{65?fGI57uQ5&!`B?F@7P delta 34554 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce5..09523c0e5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e46..9b42019c7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From 814946e712eafd0a6d7d5796b266309926d49c3a Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 28 Jul 2024 12:32:41 +0300 Subject: [PATCH 64/70] Replace SimpleHint with our custom SimpleHintEx --- assets/Interface/GameHUD.xml | 44 +++---- assets/Interface/MyControls.xml | 56 ++++----- .../gui/nifty/effect/SimpleHintEx.java | 118 ++++++++++++++++++ 3 files changed, 169 insertions(+), 49 deletions(-) create mode 100644 src/toniarts/openkeeper/gui/nifty/effect/SimpleHintEx.java diff --git a/assets/Interface/GameHUD.xml b/assets/Interface/GameHUD.xml index a33644b45..f589d9993 100644 --- a/assets/Interface/GameHUD.xml +++ b/assets/Interface/GameHUD.xml @@ -11,7 +11,7 @@ - + @@ -27,6 +27,8 @@ + + @@ -46,8 +48,8 @@ - - + + @@ -56,8 +58,8 @@ - - + + @@ -81,8 +83,8 @@ - - + + @@ -91,8 +93,8 @@ - - + + @@ -227,8 +229,8 @@ - - + + @@ -240,8 +242,8 @@ - - + + @@ -252,8 +254,8 @@ - - + + @@ -264,8 +266,8 @@ - - + + @@ -280,8 +282,8 @@ - - + + @@ -290,8 +292,8 @@ - - + + diff --git a/assets/Interface/MyControls.xml b/assets/Interface/MyControls.xml index fd02b0a06..be4995bbf 100644 --- a/assets/Interface/MyControls.xml +++ b/assets/Interface/MyControls.xml @@ -40,29 +40,29 @@ - - + + - - + + - - + + - - + + @@ -75,8 +75,8 @@ - - + + - - + + - - + + - - + + @@ -123,8 +123,8 @@ - - + + @@ -134,8 +134,8 @@ - - + + @@ -145,8 +145,8 @@ - - + + @@ -156,8 +156,8 @@ - - + + @@ -446,8 +446,8 @@ - - + + @@ -470,8 +470,8 @@ - - + + diff --git a/src/toniarts/openkeeper/gui/nifty/effect/SimpleHintEx.java b/src/toniarts/openkeeper/gui/nifty/effect/SimpleHintEx.java new file mode 100644 index 000000000..419060ef5 --- /dev/null +++ b/src/toniarts/openkeeper/gui/nifty/effect/SimpleHintEx.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2014-2024 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.gui.nifty.effect; + +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.controls.Label; +import de.lessvoid.nifty.effects.EffectImpl; +import de.lessvoid.nifty.effects.EffectProperties; +import de.lessvoid.nifty.effects.Falloff; +import de.lessvoid.nifty.elements.Element; +import de.lessvoid.nifty.elements.render.TextRenderer; +import de.lessvoid.nifty.render.NiftyRenderEngine; +import de.lessvoid.nifty.screen.Screen; +import de.lessvoid.nifty.tools.SizeValue; +import de.lessvoid.nifty.tools.TargetElementResolver; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * This is a simple extension to the Nifty {@link SimpleHint}. SimpleHint + * requires a {@link TextRenderer} to be present. Rework this so that we can + * deal also with {@link Label} + * + * @author Toni Helenius + */ +public class SimpleHintEx implements EffectImpl { + + /** + * target element. + */ + @Nullable + private Element targetElement; + + /** + * hint text. + */ + @Nullable + private String hintText; + + /** + * initialize. + * + * @param nifty Nifty + * @param element Element + * @param parameter Parameter + */ + @Override + public void activate( + @Nonnull final Nifty nifty, + @Nonnull final Element element, + @Nonnull final EffectProperties parameter) { + Screen screen = nifty.getCurrentScreen(); + if (screen == null) { + return; + } + TargetElementResolver resolver = new TargetElementResolver(nifty.getCurrentScreen(), element); + targetElement = resolver.resolve(parameter.getProperty("targetElement")); + + String text = parameter.getProperty("hintText"); + if (text != null) { + hintText = text; + } + } + + /** + * execute the effect. + * + * @param element the Element + * @param normalizedTime TimeInterpolator to use + * @param falloff falloff value + * @param r RenderDevice to use + */ + @Override + public void execute( + @Nonnull final Element element, + final float normalizedTime, + @Nullable final Falloff falloff, + @Nonnull final NiftyRenderEngine r) { + if (targetElement == null) { + return; + } + + Label label = targetElement.getNiftyControl(Label.class); + String hint = hintText == null ? "Missing Hint Text!" : hintText; + if (label != null) { + label.setText(hint); + } else { + TextRenderer textRenderer = targetElement.getRenderer(TextRenderer.class); + if (textRenderer != null) { + textRenderer.setText(hint); + targetElement.setConstraintWidth(SizeValue.px(textRenderer.getTextWidth())); + element.getParent().layoutElements(); + } + } + } + + /** + * deactivate the effect. + */ + @Override + public void deactivate() { + } + +} From 2101e6cf881f46ebf67b1cf467a9ec97b12bd8d9 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 28 Jul 2024 12:33:11 +0300 Subject: [PATCH 65/70] Code cleanup --- .../openkeeper/view/SystemMessageState.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/toniarts/openkeeper/view/SystemMessageState.java b/src/toniarts/openkeeper/view/SystemMessageState.java index d6028d8de..99802f98b 100644 --- a/src/toniarts/openkeeper/view/SystemMessageState.java +++ b/src/toniarts/openkeeper/view/SystemMessageState.java @@ -123,12 +123,14 @@ public void addMessageIcon(MessageType type, String text) { @Override public void update(float tpf) { - if (systemMessagesQueue != null) { - for (Element child : systemMessagesQueue.getChildren()) { - SystemMessageControl control = child.getControl(SystemMessageControl.class); - if (control != null && System.currentTimeMillis() - control.getCreatedAt() > MESSAGE_LIFETIME) { - child.markForRemoval(); - } + if (systemMessagesQueue == null) { + return; + } + + for (Element child : systemMessagesQueue.getChildren()) { + SystemMessageControl control = child.getControl(SystemMessageControl.class); + if (control != null && System.currentTimeMillis() - control.getCreatedAt() > MESSAGE_LIFETIME) { + child.markForRemoval(); } } } From bdbf817575397f3bb4a928c83dacbb8add388cc1 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 28 Jul 2024 12:34:13 +0300 Subject: [PATCH 66/70] Implement auto scroll component --- assets/Interface/MyControls.xml | 12 ++ .../autoscrolltext/AutoScrollTextControl.java | 126 ++++++++++++++++++ .../nifty/effect/HorizontalAutoScroll.java | 95 +++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 src/toniarts/openkeeper/gui/nifty/autoscrolltext/AutoScrollTextControl.java create mode 100644 src/toniarts/openkeeper/gui/nifty/effect/HorizontalAutoScroll.java diff --git a/assets/Interface/MyControls.xml b/assets/Interface/MyControls.xml index be4995bbf..c1252f93a 100644 --- a/assets/Interface/MyControls.xml +++ b/assets/Interface/MyControls.xml @@ -558,4 +558,16 @@ + + + + + + + + + + + + diff --git a/src/toniarts/openkeeper/gui/nifty/autoscrolltext/AutoScrollTextControl.java b/src/toniarts/openkeeper/gui/nifty/autoscrolltext/AutoScrollTextControl.java new file mode 100644 index 000000000..2de751825 --- /dev/null +++ b/src/toniarts/openkeeper/gui/nifty/autoscrolltext/AutoScrollTextControl.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2014-2024 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.gui.nifty.autoscrolltext; + +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.controls.AbstractController; +import de.lessvoid.nifty.controls.Label; +import de.lessvoid.nifty.controls.Parameters; +import de.lessvoid.nifty.effects.Effect; +import de.lessvoid.nifty.effects.EffectEventId; +import de.lessvoid.nifty.elements.Element; +import de.lessvoid.nifty.elements.render.TextRenderer; +import de.lessvoid.nifty.input.NiftyInputEvent; +import de.lessvoid.nifty.layout.align.HorizontalAlign; +import de.lessvoid.nifty.screen.Screen; +import de.lessvoid.nifty.tools.Color; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.Objects; +import toniarts.openkeeper.gui.nifty.effect.HorizontalAutoScroll; + +/** + * Label that autoscrolls if it doesn't fit its constraints + * + * @author Toni Helenius + */ +public class AutoScrollTextControl extends AbstractController implements Label { + + private static final Logger logger = System.getLogger(AutoScrollTextControl.class.getName()); + + private static final String TEXT_FIELD = "#text"; + + private Label textControl; + private Effect autoScroll; + private Nifty nifty; + + /** + * Default constructor. + */ + public AutoScrollTextControl() { + } + + @Override + public final void bind( + final Nifty niftyParam, + final Screen screenParam, + final Element newElement, + final Parameters properties) { + super.bind(newElement); + logger.log(Level.DEBUG, "Binding auto scroll text control"); + nifty = niftyParam; + + textControl = newElement.findNiftyControl(TEXT_FIELD, Label.class); + autoScroll = textControl.getElement().getEffects(EffectEventId.onCustom, HorizontalAutoScroll.class).getFirst(); + } + + + @Override + public final void onStartScreen() { + } + + @Override + public boolean inputEvent(NiftyInputEvent inputEvent) { + return false; + } + + @Override + public void setText(String text) { + + // Effects trigger this constantly, check if the text really changed + if (Objects.equals(getText(), text)) { + return; + } + + textControl.setText(text); + + calculateAutoScroll(); + } + + private void calculateAutoScroll() { + TextRenderer textRenderer = textControl.getElement().getRenderer(TextRenderer.class); + int textWidth = textRenderer.getTextWidth(); + int elementWidth = getElement().getWidth(); + if (textWidth > elementWidth) { + textRenderer.setTextHAlign(HorizontalAlign.left); + + autoScroll.getParameters().setProperty("start", Integer.toString(elementWidth - textWidth)); + autoScroll.getParameters().setProperty("end", Integer.toString(0)); + autoScroll.getParameters().setProperty("delay", Integer.toString(2)); + textControl.getElement().startEffect(EffectEventId.onCustom); + } else { + textRenderer.setTextHAlign(HorizontalAlign.center); + textControl.getElement().stopEffect(EffectEventId.onCustom); + } + } + + @Override + public String getText() { + return textControl.getText(); + } + + @Override + public void setColor(Color color) { + textControl.setColor(color); + } + + @Override + public Color getColor() { + return textControl.getColor(); + } + +} diff --git a/src/toniarts/openkeeper/gui/nifty/effect/HorizontalAutoScroll.java b/src/toniarts/openkeeper/gui/nifty/effect/HorizontalAutoScroll.java new file mode 100644 index 000000000..52752c06c --- /dev/null +++ b/src/toniarts/openkeeper/gui/nifty/effect/HorizontalAutoScroll.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2014-2024 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.gui.nifty.effect; + +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.effects.EffectImpl; +import de.lessvoid.nifty.effects.EffectProperties; +import de.lessvoid.nifty.effects.Falloff; +import de.lessvoid.nifty.elements.Element; +import de.lessvoid.nifty.render.NiftyRenderEngine; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * This is like a marquee effect, constantly scrolling the view back and forth + * + * @author Toni Helenius + */ +public class HorizontalAutoScroll implements EffectImpl { + + private static final float EFFECT_SPEED = 2500f; + private float end = 100; + private float start = 0; + private float timeWhenChangedDirection = 0; + private float delay; + private float delayStart; + private float currentXOffset; + private boolean reverse = false; + + @Override + public void activate( + @Nonnull final Nifty nifty, + @Nonnull final Element element, + @Nonnull final EffectProperties parameter) { + end = Integer.parseInt(parameter.getProperty("end", "0")); + start = Integer.parseInt(parameter.getProperty("start", "0")); + delay = Integer.parseInt(parameter.getProperty("delay", "0")) / 100f; + delayStart = 0; + reverse = start < end; + timeWhenChangedDirection = 0; + currentXOffset = 0; + } + + @Override + public void execute( + @Nonnull final Element element, + final float normalizedTime, + @Nullable final Falloff falloff, + @Nonnull final NiftyRenderEngine r) { + if (normalizedTime - delayStart < delay) { + timeWhenChangedDirection = normalizedTime; + + // Keep us always "on the go", if we don't apply the moveTo, posission will be reset + r.moveTo(currentXOffset, 0); + + return; + } + + if (reverse) { + currentXOffset = Math.max(end - (normalizedTime - timeWhenChangedDirection) * EFFECT_SPEED, start); + } else { + currentXOffset = Math.min(start + (normalizedTime - timeWhenChangedDirection) * EFFECT_SPEED, end); + } + r.moveTo(currentXOffset, 0); + + if (currentXOffset >= end) { + reverse = true; + timeWhenChangedDirection = normalizedTime; + delayStart = normalizedTime; + } else if (currentXOffset <= start) { + reverse = false; + timeWhenChangedDirection = normalizedTime; + delayStart = normalizedTime; + } + } + + @Override + public void deactivate() { + } + +} From 63c1ff4fefb5aa32643a964244bf3debbb1a477b Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 28 Jul 2024 12:34:55 +0300 Subject: [PATCH 67/70] Use the auto scroll component on the tooltip --- assets/Interface/GameHUD.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/assets/Interface/GameHUD.xml b/assets/Interface/GameHUD.xml index f589d9993..db0659891 100644 --- a/assets/Interface/GameHUD.xml +++ b/assets/Interface/GameHUD.xml @@ -29,6 +29,7 @@ + @@ -104,8 +105,8 @@ - - + + From 6c44a6bcba26e820c409efc15a296cc0a7cbd2f4 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Sun, 28 Jul 2024 12:55:03 +0300 Subject: [PATCH 68/70] Cleanup --- assets/Interface/MyControls.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/assets/Interface/MyControls.xml b/assets/Interface/MyControls.xml index c1252f93a..3fb2fd9de 100644 --- a/assets/Interface/MyControls.xml +++ b/assets/Interface/MyControls.xml @@ -562,11 +562,9 @@ - + - - From 545cbffd0254c0ca5fda7514d889c02ff6f2d018 Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 30 Jul 2024 20:50:30 +0300 Subject: [PATCH 69/70] Denormalize the elapsed time so that our values can be constant in relation to effect length --- .../nifty/effect/HorizontalAutoScroll.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/toniarts/openkeeper/gui/nifty/effect/HorizontalAutoScroll.java b/src/toniarts/openkeeper/gui/nifty/effect/HorizontalAutoScroll.java index 52752c06c..578b46db7 100644 --- a/src/toniarts/openkeeper/gui/nifty/effect/HorizontalAutoScroll.java +++ b/src/toniarts/openkeeper/gui/nifty/effect/HorizontalAutoScroll.java @@ -32,7 +32,7 @@ */ public class HorizontalAutoScroll implements EffectImpl { - private static final float EFFECT_SPEED = 2500f; + private static final float EFFECT_SPEED = 0.085f; private float end = 100; private float start = 0; private float timeWhenChangedDirection = 0; @@ -41,6 +41,13 @@ public class HorizontalAutoScroll implements EffectImpl { private float currentXOffset; private boolean reverse = false; + /** + * Keeps constant speed. Normalized time in + * {@link #execute(de.lessvoid.nifty.elements.Element, float, de.lessvoid.nifty.effects.Falloff, de.lessvoid.nifty.render.NiftyRenderEngine)} + * is relative to the effect length. + */ + private float speedFactor; + @Override public void activate( @Nonnull final Nifty nifty, @@ -48,11 +55,12 @@ public void activate( @Nonnull final EffectProperties parameter) { end = Integer.parseInt(parameter.getProperty("end", "0")); start = Integer.parseInt(parameter.getProperty("start", "0")); - delay = Integer.parseInt(parameter.getProperty("delay", "0")) / 100f; + delay = Integer.parseInt(parameter.getProperty("delay", "0")) * 1000f; delayStart = 0; reverse = start < end; timeWhenChangedDirection = 0; currentXOffset = 0; + speedFactor = Integer.parseInt(parameter.getProperty("length", "1000")); } @Override @@ -61,30 +69,31 @@ public void execute( final float normalizedTime, @Nullable final Falloff falloff, @Nonnull final NiftyRenderEngine r) { - if (normalizedTime - delayStart < delay) { - timeWhenChangedDirection = normalizedTime; + float denormalizedTime = normalizedTime * speedFactor; + if (denormalizedTime - delayStart < delay) { + timeWhenChangedDirection = denormalizedTime; - // Keep us always "on the go", if we don't apply the moveTo, posission will be reset + // Keep us always "on the go", if we don't apply the moveTo, position will be reset r.moveTo(currentXOffset, 0); return; } if (reverse) { - currentXOffset = Math.max(end - (normalizedTime - timeWhenChangedDirection) * EFFECT_SPEED, start); + currentXOffset = Math.max(end - (denormalizedTime - timeWhenChangedDirection) * EFFECT_SPEED, start); } else { - currentXOffset = Math.min(start + (normalizedTime - timeWhenChangedDirection) * EFFECT_SPEED, end); + currentXOffset = Math.min(start + (denormalizedTime - timeWhenChangedDirection) * EFFECT_SPEED, end); } r.moveTo(currentXOffset, 0); if (currentXOffset >= end) { reverse = true; - timeWhenChangedDirection = normalizedTime; - delayStart = normalizedTime; + timeWhenChangedDirection = denormalizedTime; + delayStart = denormalizedTime; } else if (currentXOffset <= start) { reverse = false; - timeWhenChangedDirection = normalizedTime; - delayStart = normalizedTime; + timeWhenChangedDirection = denormalizedTime; + delayStart = denormalizedTime; } } From 39b6f93af5a31f833572267586563d51130511ff Mon Sep 17 00:00:00 2001 From: Toni Helenius Date: Tue, 30 Jul 2024 20:50:57 +0300 Subject: [PATCH 70/70] Just in case, increase the effect length --- assets/Interface/MyControls.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/Interface/MyControls.xml b/assets/Interface/MyControls.xml index 3fb2fd9de..a51952143 100644 --- a/assets/Interface/MyControls.xml +++ b/assets/Interface/MyControls.xml @@ -562,7 +562,7 @@ - +