Compare commits
9 Commits
9f5303ba74
...
gamelib
Author | SHA1 | Date | |
---|---|---|---|
fed9f8d077 | |||
cf035dedac | |||
a28741ac3d | |||
12f4d7592b | |||
d2942341e2 | |||
673ba9f89d | |||
a9acbda343 | |||
6e249280c1 | |||
ebd7d2a12b |
@ -58,6 +58,37 @@ public class Character extends Entity {
|
|||||||
this.meleeDamage = meleeDamage;
|
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.
|
* Checks if the character is still alive.
|
||||||
* @return Whether or not the characters hp is greater than 0
|
* @return Whether or not the characters hp is greater than 0
|
||||||
|
@ -32,8 +32,8 @@ public class Stat {
|
|||||||
*/
|
*/
|
||||||
public Stat(StatType type, int value, int max) {
|
public Stat(StatType type, int value, int max) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.max = max;
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.max = max;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +42,7 @@ public class Stat {
|
|||||||
* @param toCopy is the {@link Stat} to copy
|
* @param toCopy is the {@link Stat} to copy
|
||||||
*/
|
*/
|
||||||
public Stat(Stat toCopy) {
|
public Stat(Stat toCopy) {
|
||||||
this(toCopy.type, toCopy.value, toCopy.max);
|
this(toCopy.type, toCopy.getValue(), toCopy.getMax());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getValue() {
|
public int getValue() {
|
||||||
|
@ -119,6 +119,7 @@ public class GameInstance {
|
|||||||
manager.applyEvent(gameStateEvent);
|
manager.applyEvent(gameStateEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public GameState getGameStateUnsafe() {
|
public GameState getGameStateUnsafe() {
|
||||||
return _state;
|
return _state;
|
||||||
}
|
}
|
||||||
|
@ -136,22 +136,52 @@ public class GameLogic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case MindStone -> {
|
case MindStone -> {
|
||||||
|
verifyCoordinates(state, data.targetField);
|
||||||
|
|
||||||
requireLineOfSight(state, data.originField, data.targetField);
|
requireLineOfSight(state, data.originField, data.targetField);
|
||||||
|
|
||||||
|
getAttackableWithoutID(state, data.targetField);
|
||||||
}
|
}
|
||||||
case RealityStone -> {
|
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 -> {
|
case PowerStone -> {
|
||||||
Character target = getCharacter(state, data.targetField, data.targetEntity);
|
verifyCoordinates(state, data.targetField);
|
||||||
|
|
||||||
requireAlive(target);
|
Entity target = getAttackableWithoutID(state, data.targetField);
|
||||||
requireOppositeTeam(origin, target);
|
|
||||||
|
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 -> {
|
case TimeStone -> {
|
||||||
// "👍 i approve" - the server
|
// "👍 i approve" - the server
|
||||||
}
|
}
|
||||||
case SoulStone -> {
|
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)) {
|
if(data.originEntity.equals(data.targetEntity)) {
|
||||||
throw new InvalidRequestException("Invalid soul stone target (same as origin)");
|
throw new InvalidRequestException("Invalid soul stone target (same as origin)");
|
||||||
@ -199,6 +229,21 @@ public class GameLogic {
|
|||||||
return entity;
|
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}.
|
* Retrieves a {@link Character} for a {@link Request}.
|
||||||
* @param state The game state to use
|
* @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.
|
* Verifies that a {@link Character} has a turn.
|
||||||
*/
|
*/
|
||||||
@ -332,36 +396,7 @@ public class GameLogic {
|
|||||||
.buildEntityEvent());
|
.buildEntityEvent());
|
||||||
|
|
||||||
Entity targetEntity = state.entities.findEntity(data.targetEntity);
|
Entity targetEntity = state.entities.findEntity(data.targetEntity);
|
||||||
if(targetEntity instanceof Character) {
|
result.addAll(checkDeath(state, targetEntity, data.value));
|
||||||
Character target = (Character)targetEntity;
|
|
||||||
if(target.hp.getValue() <= data.value) {
|
|
||||||
|
|
||||||
List<StoneType> stones = Arrays.asList(target.inventory.getStonesAsArray());
|
|
||||||
Collections.shuffle(stones); // required by documents
|
|
||||||
|
|
||||||
ArrayList<IntVector2> used = new ArrayList<>();
|
|
||||||
for(StoneType stone: stones) {
|
|
||||||
ArrayList<IntVector2> options = getFreeNeighbour(state, target.getPosition(), used);
|
|
||||||
IntVector2 picked = options.get(rand.nextInt(options.size()));
|
|
||||||
used.add(picked);
|
|
||||||
result.add(new EventBuilder(EventType.SpawnEntityEvent)
|
|
||||||
.withEntity(new InfinityStone(
|
|
||||||
new EntityID(EntityType.InfinityStones, stone.getID()),
|
|
||||||
picked,
|
|
||||||
stone
|
|
||||||
))
|
|
||||||
.buildEntityEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else if(targetEntity instanceof Rock) {
|
|
||||||
Rock target = (Rock)targetEntity;
|
|
||||||
if(target.getHp() <= data.value) {
|
|
||||||
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
|
|
||||||
.withTargetField(data.targetField)
|
|
||||||
.withTargetEntity(target.id)
|
|
||||||
.buildEntityEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case MoveRequest -> {
|
case MoveRequest -> {
|
||||||
CharacterRequest data = (CharacterRequest)request;
|
CharacterRequest data = (CharacterRequest)request;
|
||||||
@ -449,7 +484,8 @@ public class GameLogic {
|
|||||||
.buildCharacterEvent());
|
.buildCharacterEvent());
|
||||||
}
|
}
|
||||||
case MindStone -> {
|
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)
|
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
||||||
.withTargetEntity(targetEntity.id)
|
.withTargetEntity(targetEntity.id)
|
||||||
@ -457,36 +493,7 @@ public class GameLogic {
|
|||||||
.withAmount(state.partyConfig.mindStoneDMG)
|
.withAmount(state.partyConfig.mindStoneDMG)
|
||||||
.buildEntityEvent());
|
.buildEntityEvent());
|
||||||
|
|
||||||
if(targetEntity instanceof Character) {
|
result.addAll(checkDeath(state, targetEntity, state.partyConfig.mindStoneDMG));
|
||||||
Character target = (Character)targetEntity;
|
|
||||||
if(target.hp.getValue() <= data.value) {
|
|
||||||
|
|
||||||
List<StoneType> stones = Arrays.asList(target.inventory.getStonesAsArray());
|
|
||||||
Collections.shuffle(stones); // required by documents
|
|
||||||
|
|
||||||
ArrayList<IntVector2> used = new ArrayList<>();
|
|
||||||
for(StoneType stone: stones) {
|
|
||||||
ArrayList<IntVector2> options = getFreeNeighbour(state, target.getPosition(), used);
|
|
||||||
IntVector2 picked = options.get(rand.nextInt(options.size()));
|
|
||||||
used.add(picked);
|
|
||||||
result.add(new EventBuilder(EventType.SpawnEntityEvent)
|
|
||||||
.withEntity(new InfinityStone(
|
|
||||||
new EntityID(EntityType.InfinityStones, stone.getID()),
|
|
||||||
picked,
|
|
||||||
stone
|
|
||||||
))
|
|
||||||
.buildEntityEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else if(targetEntity instanceof Rock) {
|
|
||||||
Rock target = (Rock)targetEntity;
|
|
||||||
if(target.getHp() <= data.value) {
|
|
||||||
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
|
|
||||||
.withTargetField(data.targetField)
|
|
||||||
.withTargetEntity(target.id)
|
|
||||||
.buildEntityEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case RealityStone -> {
|
case RealityStone -> {
|
||||||
EntityID target = null;
|
EntityID target = null;
|
||||||
@ -509,6 +516,9 @@ public class GameLogic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case PowerStone -> {
|
case PowerStone -> {
|
||||||
|
ArrayList<Entity> found = state.entities.findByPosition(data.targetField);
|
||||||
|
Entity targetEntity = found.get(0);
|
||||||
|
|
||||||
Character origin = (Character)state.entities.findEntity(data.originEntity);
|
Character origin = (Character)state.entities.findEntity(data.originEntity);
|
||||||
int dmg = (int)Math.round(origin.hp.getMax() * 0.1);
|
int dmg = (int)Math.round(origin.hp.getMax() * 0.1);
|
||||||
//this is ugly ... but also easy to understand
|
//this is ugly ... but also easy to understand
|
||||||
@ -523,10 +533,12 @@ public class GameLogic {
|
|||||||
.buildEntityEvent());
|
.buildEntityEvent());
|
||||||
}
|
}
|
||||||
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
||||||
.withTargetEntity(data.targetEntity)
|
.withTargetEntity(targetEntity.id)
|
||||||
.withTargetField(data.targetField)
|
.withTargetField(data.targetField)
|
||||||
.withAmount(origin.meleeDamage * 2)
|
.withAmount(origin.meleeDamage * 2)
|
||||||
.buildEntityEvent());
|
.buildEntityEvent());
|
||||||
|
|
||||||
|
result.addAll(checkDeath(state, targetEntity, origin.meleeDamage * 2));
|
||||||
}
|
}
|
||||||
case TimeStone -> {
|
case TimeStone -> {
|
||||||
Character origin = (Character)state.entities.findEntity(data.originEntity);
|
Character origin = (Character)state.entities.findEntity(data.originEntity);
|
||||||
@ -548,9 +560,11 @@ public class GameLogic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case SoulStone -> {
|
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)
|
result.add(new EventBuilder(EventType.HealedEvent)
|
||||||
.withTargetEntity(data.targetEntity)
|
.withTargetEntity(target.id)
|
||||||
.withTargetField(data.targetField)
|
.withTargetField(data.targetField)
|
||||||
.withAmount(target.hp.getMax())
|
.withAmount(target.hp.getMax())
|
||||||
.buildEntityEvent());
|
.buildEntityEvent());
|
||||||
@ -569,6 +583,49 @@ public class GameLogic {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for death of a Character or Rock and returns the resulting {@link Event Events}.
|
||||||
|
* @param state The game state to apply to
|
||||||
|
* @param targetEntity The entity to check
|
||||||
|
* @param damage The damage taken
|
||||||
|
* @return The resulting events
|
||||||
|
*/
|
||||||
|
private static ArrayList<Event> checkDeath(GameState state, Entity targetEntity, int damage) {
|
||||||
|
ArrayList<Event> result = new ArrayList<>();
|
||||||
|
|
||||||
|
if(targetEntity instanceof Character) {
|
||||||
|
Character target = (Character)targetEntity;
|
||||||
|
if(target.hp.getValue() <= damage) {
|
||||||
|
|
||||||
|
List<StoneType> stones = Arrays.asList(target.inventory.getStonesAsArray());
|
||||||
|
Collections.shuffle(stones); // required by documents
|
||||||
|
|
||||||
|
ArrayList<IntVector2> used = new ArrayList<>();
|
||||||
|
for(StoneType stone: stones) {
|
||||||
|
ArrayList<IntVector2> options = getFreeNeighbour(state, target.getPosition(), used);
|
||||||
|
IntVector2 picked = options.get(rand.nextInt(options.size()));
|
||||||
|
used.add(picked);
|
||||||
|
result.add(new EventBuilder(EventType.SpawnEntityEvent)
|
||||||
|
.withEntity(new InfinityStone(
|
||||||
|
new EntityID(EntityType.InfinityStones, stone.getID()),
|
||||||
|
picked,
|
||||||
|
stone
|
||||||
|
))
|
||||||
|
.buildEntityEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else if(targetEntity instanceof Rock) {
|
||||||
|
Rock target = (Rock)targetEntity;
|
||||||
|
if(target.getHp() <= damage) {
|
||||||
|
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
|
||||||
|
.withTargetField(target.getPosition())
|
||||||
|
.withTargetEntity(target.id)
|
||||||
|
.buildEntityEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies an {@link Event} to a {@link GameState}.
|
* Applies an {@link Event} to a {@link GameState}.
|
||||||
|
@ -43,6 +43,9 @@ public class EntityDeserializer extends JsonDeserializer<Entity> {
|
|||||||
id,
|
id,
|
||||||
codec.treeToValue(node.get("position"), IntVector2.class),
|
codec.treeToValue(node.get("position"), IntVector2.class),
|
||||||
characterName,
|
characterName,
|
||||||
|
properties.HP,
|
||||||
|
properties.MP,
|
||||||
|
properties.AP,
|
||||||
node.get("HP").asInt(),
|
node.get("HP").asInt(),
|
||||||
node.get("MP").asInt(),
|
node.get("MP").asInt(),
|
||||||
node.get("AP").asInt(),
|
node.get("AP").asInt(),
|
||||||
|
Reference in New Issue
Block a user