Compare commits

...

8 Commits

5 changed files with 118 additions and 13 deletions

View File

@ -58,6 +58,37 @@ public class Character extends Entity {
this.meleeDamage = meleeDamage;
}
/**
* Constructs a new {@link Character} with an empty inventory.
* @param id The {@link EntityID} of the character
* @param position The position of the character
* @param name The name of the character
* @param maxHp The maximum hp of the character
* @param maxMp The maximum mp of the character
* @param maxAp The maximum ap of the character
* @param hp The current hp of the character
* @param mp The current mp of the character
* @param ap The current ap of the character
* @param attackRange The ranged attack range of the character
* @param rangedDamage The ranged damage of the character
* @param meleeDamage The melee damage of the character
*/
public Character(EntityID id, IntVector2 position, String name, int maxHp, int maxMp, int maxAp, int hp, int mp, int ap, int attackRange, int rangedDamage, int meleeDamage) {
super(id, position);
solid = false;
opaque = true;
if(id.type == EntityType.NPC && id.id == NPCType.Thanos.getID()) {
solid = true; //characters cannot walk into thanos
}
this.name = name;
this.hp = new Stat(StatType.HP, hp, maxHp);
this.mp = new Stat(StatType.MP, mp, maxMp);
this.ap = new Stat(StatType.AP, ap, maxAp);
this.attackRange = attackRange;
this.rangedDamage = rangedDamage;
this.meleeDamage = meleeDamage;
}
/**
* Checks if the character is still alive.
* @return Whether or not the characters hp is greater than 0

View File

@ -32,8 +32,8 @@ public class Stat {
*/
public Stat(StatType type, int value, int max) {
this.type = type;
this.max = max;
this.value = value;
this.max = max;
}
/**
@ -42,7 +42,7 @@ public class Stat {
* @param toCopy is the {@link Stat} to copy
*/
public Stat(Stat toCopy) {
this(toCopy.type, toCopy.value, toCopy.max);
this(toCopy.type, toCopy.getValue(), toCopy.getMax());
}
public int getValue() {

View File

@ -119,6 +119,7 @@ public class GameInstance {
manager.applyEvent(gameStateEvent);
}
@Deprecated
public GameState getGameStateUnsafe() {
return _state;
}

View File

@ -136,22 +136,52 @@ public class GameLogic {
}
}
case MindStone -> {
verifyCoordinates(state, data.targetField);
requireLineOfSight(state, data.originField, data.targetField);
getAttackableWithoutID(state, data.targetField);
}
case RealityStone -> {
// no check done
verifyCoordinates(state, data.targetField);
boolean rock = false;
boolean empty = true;
for(Entity entity: state.entities.findByPosition(data.targetField)) {
if(entity.id.type == EntityType.Rocks) {
rock = true;
break;
}else {
empty = false;
}
}
if(!empty && !rock) {
throw new InvalidRequestException("Using reality stone on non-free field without a rock");
}
}
case PowerStone -> {
Character target = getCharacter(state, data.targetField, data.targetEntity);
verifyCoordinates(state, data.targetField);
requireAlive(target);
requireOppositeTeam(origin, target);
Entity target = getAttackableWithoutID(state, data.targetField);
if(target instanceof Character) {
Character targetCharacter = (Character)target;
requireOppositeTeam(origin, targetCharacter);
requireAlive(targetCharacter);
}
if(data.originField.distanceChebyshev(data.targetField) != 1) {
throw new InvalidRequestException("Invalid melee target distance");
}
}
case TimeStone -> {
// "👍 i approve" - the server
}
case SoulStone -> {
Character target = getCharacter(state, data.targetField, data.targetEntity);
verifyCoordinates(state, data.targetField);
Character target = getCharacterWithoutID(state, data.targetField);
if(data.originEntity.equals(data.targetEntity)) {
throw new InvalidRequestException("Invalid soul stone target (same as origin)");
@ -199,6 +229,21 @@ public class GameLogic {
return entity;
}
/**
* Retrieves an attack-able Entity ({@link Character} or {@link Rock}) for a {@link Request}.
* @param state The game state to use
* @param position The requested position
* @return The found entity
* @throws InvalidRequestException if the entity is invalid or not found
*/
private static Entity getAttackableWithoutID(GameState state, IntVector2 position) throws InvalidRequestException {
ArrayList<Entity> entities = state.entities.findByPosition(position);
if(entities.isEmpty() || entities.get(0).id.type == EntityType.NPC) {
throw new InvalidRequestException("Invalid target character or rock");
}
return entities.get(0);
}
/**
* Retrieves a {@link Character} for a {@link Request}.
* @param state The game state to use
@ -219,6 +264,25 @@ public class GameLogic {
}
}
/**
* Retrieves a {@link Character} for a {@link Request}.
* @param state The game state to use
* @param position The requested position
* @return The found character
* @throws InvalidRequestException if the character is invalid or not found
*/
private static Character getCharacterWithoutID(GameState state, IntVector2 position) throws InvalidRequestException {
ArrayList<Entity> entities = state.entities.findByPosition(position);
if(entities.isEmpty() || !(entities.get(0) instanceof Character) || entities.get(0).id.type == EntityType.NPC) {
throw new InvalidRequestException("Invalid origin or target character");
}
try {
return (Character)entities.get(0);
}catch(Exception ignored) {
throw new InvalidRequestException("Invalid origin or target character (cast failed)");
}
}
/**
* Verifies that a {@link Character} has a turn.
*/
@ -449,7 +513,8 @@ public class GameLogic {
.buildCharacterEvent());
}
case MindStone -> {
Entity targetEntity = state.entities.findEntity(data.targetEntity);
ArrayList<Entity> found = state.entities.findByPosition(data.targetField);
Entity targetEntity = found.get(0);
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(targetEntity.id)
@ -459,7 +524,7 @@ public class GameLogic {
if(targetEntity instanceof Character) {
Character target = (Character)targetEntity;
if(target.hp.getValue() <= data.value) {
if(target.hp.getValue() <= state.partyConfig.mindStoneDMG) {
List<StoneType> stones = Arrays.asList(target.inventory.getStonesAsArray());
Collections.shuffle(stones); // required by documents
@ -480,7 +545,7 @@ public class GameLogic {
}
}else if(targetEntity instanceof Rock) {
Rock target = (Rock)targetEntity;
if(target.getHp() <= data.value) {
if(target.getHp() <= state.partyConfig.mindStoneDMG) {
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
.withTargetField(data.targetField)
.withTargetEntity(target.id)
@ -509,6 +574,9 @@ public class GameLogic {
}
}
case PowerStone -> {
ArrayList<Entity> found = state.entities.findByPosition(data.targetField);
Entity targetEntity = found.get(0);
Character origin = (Character)state.entities.findEntity(data.originEntity);
int dmg = (int)Math.round(origin.hp.getMax() * 0.1);
//this is ugly ... but also easy to understand
@ -523,7 +591,7 @@ public class GameLogic {
.buildEntityEvent());
}
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.targetEntity)
.withTargetEntity(targetEntity.id)
.withTargetField(data.targetField)
.withAmount(origin.meleeDamage * 2)
.buildEntityEvent());
@ -548,9 +616,11 @@ public class GameLogic {
}
}
case SoulStone -> {
Character target = (Character)state.entities.findEntity(data.targetEntity);
ArrayList<Entity> found = state.entities.findByPosition(data.targetField);
Character target = (Character)found.get(0);
result.add(new EventBuilder(EventType.HealedEvent)
.withTargetEntity(data.targetEntity)
.withTargetEntity(target.id)
.withTargetField(data.targetField)
.withAmount(target.hp.getMax())
.buildEntityEvent());

View File

@ -43,6 +43,9 @@ public class EntityDeserializer extends JsonDeserializer<Entity> {
id,
codec.treeToValue(node.get("position"), IntVector2.class),
characterName,
properties.HP,
properties.MP,
properties.AP,
node.get("HP").asInt(),
node.get("MP").asInt(),
node.get("AP").asInt(),