diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java index 796f558..5c4e8c9 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/IntVector2.java @@ -120,6 +120,20 @@ public class IntVector2 implements Serializable { return distance(v.x, v.y); } + public static float distanceManhattan(int x1, int y1, int x2, int y2) { + final float x_d = x2 - x1; + final float y_d = y2 - y1; + return Math.abs(x_d) + Math.abs(y_d); + } + + public float distanceManhattan(int x, int y) { + return IntVector2.distanceManhattan(this.x, this.y, x, y); + } + + public float distanceManhattan(IntVector2 v) { + return distanceManhattan(v.x, v.y); + } + public static float distance2(int x1, int y1, int x2, int y2) { final float x_d = x2 - x1; final float y_d = y2 - y1; diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/StatType.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/StatType.java index 4d93ab2..006a67b 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/StatType.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/entities/StatType.java @@ -6,6 +6,6 @@ public enum StatType { HP, /** Represents the movement points of a character */ MP, - /** Represents thr action points of a character */ + /** Represents the action points of a character */ AP } diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/EntityManager.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/EntityManager.java index 47e903d..fa68046 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/EntityManager.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/EntityManager.java @@ -85,6 +85,34 @@ public class EntityManager { return found; } + /** + * Checks if any entity on a position blocks vision. + * @param pos The position to check on + * @return Whether or not anything on the position blocks vision + */ + public boolean blocksVision(IntVector2 pos) { + for(Entity entity: entities) { + if(entity.getPosition() == pos && entity.blocksVision()) { + return true; + } + } + return false; + } + + /** + * Checks if any entity on a position blocks movement. + * @param pos The position to check on + * @return Whether or not anything on the position blocks movement + */ + public boolean blocksMovement(IntVector2 pos) { + for(Entity entity: entities) { + if(entity.getPosition() == pos && entity.blocksMovement()) { + return true; + } + } + return false; + } + /** * Iterates over all entities inside the list. * @return An iterator over every {@link Entity} diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java index 3ac0ee9..90ae8a7 100644 --- a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java @@ -1,13 +1,20 @@ package uulm.teamname.marvelous.gamelibrary.gamelogic; +import uulm.teamname.marvelous.gamelibrary.IntVector2; import uulm.teamname.marvelous.gamelibrary.Tuple; import uulm.teamname.marvelous.gamelibrary.entities.Entity; import uulm.teamname.marvelous.gamelibrary.entities.Character; +import uulm.teamname.marvelous.gamelibrary.entities.EntityID; +import uulm.teamname.marvelous.gamelibrary.entities.StoneType; +import uulm.teamname.marvelous.gamelibrary.entities.StatType; import uulm.teamname.marvelous.gamelibrary.events.*; +import uulm.teamname.marvelous.gamelibrary.events.Event; import uulm.teamname.marvelous.gamelibrary.requests.CharacterRequest; import uulm.teamname.marvelous.gamelibrary.requests.Request; import uulm.teamname.marvelous.gamelibrary.requests.RequestType; +import java.awt.*; +import java.awt.geom.Line2D; import java.util.ArrayList; /** Contains game logic handling. */ @@ -80,8 +87,7 @@ class GameLogic { .setTargetField(data.originField) .setAmount(1) .type(EventType.ConsumedAPEvent)); - //TODO: add infinity stone use effect in GameLogic.executeRequest - + //TODO: add infinity stone usage effect in GameLogic.executeRequest } case DisconnectRequest -> { result.add(new GameEvent() @@ -99,70 +105,51 @@ class GameLogic { * @return Whether or not the request is valid */ public static boolean checkRequest(GameState state, Request request) { - //TODO: refactor this using errors try { switch(request.type) { case MeleeAttackRequest, RangedAttackRequest -> { CharacterRequest data = (CharacterRequest)request; - Entity originEntity = state.entities.findEntity(data.originEntity); - if(originEntity == null || originEntity.getPosition() != data.originField || !(originEntity instanceof Character)) { - return false; - } - Character origin = (Character)originEntity; - Entity targetEntity = state.entities.findEntity(data.targetEntity); - if(targetEntity == null || targetEntity.getPosition() != data.targetField || !(targetEntity instanceof Character)) { - return false; - } - Character target = (Character)targetEntity; + Character origin = getCharacter(state, data.originField, data.originEntity); + Character target = getCharacter(state, data.targetField, data.targetEntity); - if(origin.hp.getValue() <= 0 || !origin.isActive()) { - return false; - } - if(origin.ap.getValue() < 1) { - return false; - } - if(request.type == RequestType.MeleeAttackRequest && origin.meleeDamage != data.value) { - return false; - }else if(request.type == RequestType.RangedAttackRequest && origin.rangedDamage != data.value) { - return false; - } - if(target.hp.getValue() <= 0 || !target.isActive()) { - return false; - } + requireAlive(origin); + requireAlive(target); + requireAP(origin, 1); - if(request.type == RequestType.RangedAttackRequest && origin.getPosition().distance(target.getPosition()) > origin.attackRange) { - return false; + if(request.type == RequestType.MeleeAttackRequest) { + if(origin.meleeDamage != data.value) { + throw new InvalidRequestException(); + } + if(data.originField.distanceManhattan(data.targetField) > 1) { + throw new InvalidRequestException(); + } + }else if(request.type == RequestType.RangedAttackRequest) { + if(origin.rangedDamage != data.value) { + throw new InvalidRequestException(); + } + if(data.originField.distanceManhattan(data.targetField) > origin.attackRange) { + throw new InvalidRequestException(); + } + if(data.originField.distanceManhattan(data.targetField) <= 1) { + throw new InvalidRequestException(); + } + requireLineOfSight(state, data.originField, data.targetField); } - //TODO: check line of sight in GameLogic.checkRequest - return true; } case MoveRequest -> { CharacterRequest data = (CharacterRequest)request; - Entity originEntity = state.entities.findEntity(data.originEntity); - if(originEntity == null || originEntity.getPosition() != data.originField || !(originEntity instanceof Character)) { - return false; - } - Character origin = (Character)originEntity; + Character origin = getCharacter(state, data.originField, data.originEntity); - if(data.targetField.getX() < 0 || data.targetField.getX() >= state.mapSize.getX() || data.targetField.getY() < 0 || data.targetField.getY() >= state.mapSize.getY()) { - return false; - } + requireAlive(origin); + requireMP(origin, 1); + verifyCoordinates(state, data.targetField); - for(Entity entity: state.entities.findByPosition(data.targetField)) { - if(entity.blocksMovement()) { - return false; - } - } - - if(origin.hp.getValue() <= 0 || !origin.isActive()) { - return false; - } - if(origin.mp.getValue() < 1) { - return false; + if(state.entities.blocksMovement(data.targetField)) { + throw new InvalidRequestException(); } return true; @@ -170,59 +157,29 @@ class GameLogic { case ExchangeInfinityStoneRequest -> { CharacterRequest data = (CharacterRequest)request; - Entity originEntity = state.entities.findEntity(data.originEntity); - if(originEntity == null || originEntity.getPosition() != data.originField || !(originEntity instanceof Character)) { - return false; - } - Character origin = (Character)originEntity; - Entity targetEntity = state.entities.findEntity(data.targetEntity); - if(targetEntity == null || targetEntity.getPosition() != data.targetField || !(targetEntity instanceof Character)) { - return false; - } - Character target = (Character)targetEntity; + Character origin = getCharacter(state, data.originField, data.originEntity); + Character target = getCharacter(state, data.targetField, data.targetEntity); - if(origin.hp.getValue() <= 0 || !origin.isActive()) { - return false; - } - if(origin.ap.getValue() < 1) { - return false; - } - if(target.hp.getValue() <= 0 || !target.isActive()) { - return false; - } - - if(!origin.inventory.hasStone(data.stoneType)) { - return false; - } + requireAlive(origin); + requireAlive(target); + requireAP(origin, 1); + requireInfinityStone(origin, data.stoneType); return true; } case UseInfinityStoneRequest -> { CharacterRequest data = (CharacterRequest)request; - Entity originEntity = state.entities.findEntity(data.originEntity); - if(originEntity == null || originEntity.getPosition() != data.originField || !(originEntity instanceof Character)) { - return false; - } - Character origin = (Character)originEntity; - Entity targetEntity = state.entities.findEntity(data.targetEntity); - if(targetEntity == null || targetEntity.getPosition() != data.targetField || !(targetEntity instanceof Character)) { - return false; - } - Character target = (Character)targetEntity; + Character origin = getCharacter(state, data.originField, data.originEntity); + Character target = getCharacter(state, data.targetField, data.targetEntity); - if(origin.hp.getValue() <= 0 || !origin.isActive()) { - return false; - } - if(origin.ap.getValue() < 1) { - return false; - } + requireAlive(origin); + requireAP(origin, 1); + requireInfinityStone(origin, data.stoneType); + + //TODO: properly verify UseInfinityStoneRequest in GameLogic.checkRequest if(!target.isActive()) { - return false; - } - - if(!origin.inventory.hasStone(data.stoneType)) { - return false; + throw new InvalidRequestException(); } return true; @@ -234,13 +191,98 @@ class GameLogic { return true; } } - }catch(Exception e) { + }catch(Exception ignored) { return false; } return false; } + /** + * Retrieves a {@link Character} for a {@link Request}. + * @param state The game state to use + * @param position The requested position + * @param entityID The requested {@link EntityID} + * @return The found character + * @throws InvalidRequestException if the character is invalid or not found + */ + private static Character getCharacter(GameState state, IntVector2 position, EntityID entityID) throws InvalidRequestException { + Entity entity = state.entities.findEntity(entityID); + if(entity == null || entity.getPosition() != position || !(entity instanceof Character)) { + throw new InvalidRequestException(); + } + try { + return (Character)entity; + }catch(Exception ignored) { + throw new InvalidRequestException(); + } + } + + /** + * Verifies that a {@link Character} is alive. + */ + private static void requireAlive(Character entity) throws InvalidRequestException { + if(entity.hp.getValue() <= 0 || !entity.isActive()) { + throw new InvalidRequestException(); + } + } + + /** + * Verifies that a {@link Character} has enough {@link StatType#AP}. + */ + private static void requireAP(Character entity, int ap) throws InvalidRequestException { + if(entity.ap.getValue() < ap) { + throw new InvalidRequestException(); + } + } + + /** + * Verifies that a {@link Character} has enough {@link StatType#MP}. + */ + private static void requireMP(Character entity, int mp) throws InvalidRequestException { + if(entity.mp.getValue() < mp) { + throw new InvalidRequestException(); + } + } + + /** + * Verifies that a {@link Character} has the required {@link StoneType}. + */ + private static void requireInfinityStone(Character entity, StoneType stone) throws InvalidRequestException { + if(!entity.inventory.hasStone(stone)) { + throw new InvalidRequestException(); + } + } + + /** + * Verifies that coordinates are within the playing area. + */ + private static void verifyCoordinates(GameState state, IntVector2 position) throws InvalidRequestException { + if(position.getX() < 0 || position.getX() >= state.mapSize.getX() || position.getY() < 0 || position.getY() >= state.mapSize.getY()) { + throw new InvalidRequestException(); + } + } + + /** + * Verifies that there is a line of sight between two positions. + */ + private static void requireLineOfSight(GameState state, IntVector2 start, IntVector2 end) throws InvalidRequestException { + //naive code for the win!!! \o/ + //at least its early exit and probably only O(ln(n*m)) + //TODO: implement proper line rasterization algorithm in GameLogic.requireLineOfSight + Line2D line = new Line2D.Float(start.getX(), start.getY(), end.getX(), end.getY()); + for(int i = start.getX(); i <= end.getX(); i++) { + for(int j = start.getY(); j <= end.getY(); j++) { + var cell = new Rectangle.Float(i - 0.5f, j - 0.5f, 1, 1); + if(line.intersects(cell)) { + if(state.entities.blocksVision(new IntVector2(i, j))) { + throw new InvalidRequestException(); + } + } + } + } + } + /** * Applies an {@link Event} to a {@link GameState}. * @param state The game state to apply to diff --git a/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/InvalidRequestException.java b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/InvalidRequestException.java new file mode 100644 index 0000000..9421c5c --- /dev/null +++ b/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/InvalidRequestException.java @@ -0,0 +1,8 @@ +package uulm.teamname.marvelous.gamelibrary.gamelogic; + +import uulm.teamname.marvelous.gamelibrary.requests.Request; + +/** Represents an exception thrown when a {@link Request} is invalid. */ +public class InvalidRequestException extends Exception { + +}