Gamelib/src/main/java/uulm/teamname/marvelous/gamelibrary/gamelogic/GameLogic.java

940 lines
40 KiB
Java
Raw Normal View History

package uulm.teamname.marvelous.gamelibrary.gamelogic;
import uulm.teamname.marvelous.gamelibrary.IntVector2;
import uulm.teamname.marvelous.gamelibrary.entities.*;
import uulm.teamname.marvelous.gamelibrary.entities.Character;
import uulm.teamname.marvelous.gamelibrary.events.*;
import uulm.teamname.marvelous.gamelibrary.events.Event;
2021-05-31 19:37:18 +00:00
import uulm.teamname.marvelous.gamelibrary.json.config.FieldType;
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.awt.geom.Rectangle2D;
2021-05-27 15:08:08 +00:00
import java.util.*;
/** Contains game logic handling. */
class GameLogic {
2021-05-27 15:08:08 +00:00
private static final Random rand = new Random();
/**
* Produces resulting {@link Event}s from a given {@link Request}.
* @param state The game state to execute on
* @param request The request to execute
* @return The list of resulting events
*/
public static ArrayList<Event> executeRequest(GameState state, Request request) {
ArrayList<Event> result = new ArrayList<>();
switch(request.type) {
case MeleeAttackRequest, RangedAttackRequest -> {
CharacterRequest data = (CharacterRequest)request;
2021-05-03 17:36:30 +00:00
result.add(new EventBuilder(request.type == RequestType.MeleeAttackRequest ? EventType.MeleeAttackEvent : EventType.RangedAttackEvent)
.withOriginEntity(data.originEntity)
.withTargetEntity(data.targetEntity)
.withOriginField(data.originField)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildCharacterEvent());
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(1)
.buildEntityEvent());
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildEntityEvent());
}
case MoveRequest -> {
CharacterRequest data = (CharacterRequest)request;
2021-05-03 17:36:30 +00:00
result.add(new EventBuilder(EventType.MoveEvent)
.withOriginEntity(data.originEntity)
.withOriginField(data.originField)
.withTargetField(data.targetField)
.buildCharacterEvent());
result.add(new EventBuilder(EventType.ConsumedMPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.targetField) //when this event gets handled, the character already moved to the target field
.withAmount(1)
.buildEntityEvent());
for(Entity entity: state.entities.findByPosition(data.targetField)) {
if(entity instanceof Character) {
2021-05-03 17:36:30 +00:00
result.add(new EventBuilder(EventType.MoveEvent)
.withOriginEntity(entity.id)
.withOriginField(data.targetField)
.withTargetField(data.originField)
.buildCharacterEvent());
break; //we should only have one character per field anyways
}
}
}
case ExchangeInfinityStoneRequest, UseInfinityStoneRequest -> {
CharacterRequest data = (CharacterRequest)request;
2021-05-03 17:36:30 +00:00
result.add(new EventBuilder(request.type == RequestType.ExchangeInfinityStoneRequest ? EventType.ExchangeInfinityStoneEvent : EventType.UseInfinityStoneEvent)
.withOriginEntity(data.originEntity)
.withOriginField(data.originField)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withStoneType(data.stoneType)
.buildCharacterEvent());
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(1)
.buildEntityEvent());
if(request.type == RequestType.UseInfinityStoneRequest) {
switch(((CharacterRequest) request).stoneType) {
case SpaceStone -> {
result.add(new EventBuilder(EventType.MoveEvent)
.withOriginEntity(data.originEntity)
.withOriginField(data.originField)
.withTargetField(data.targetField)
.buildCharacterEvent());
}
case MindStone -> {
EntityType target = data.originEntity.type == EntityType.P1 ? EntityType.P2 : EntityType.P1;
for(IntVector2 pos: rasterize(data.originField, data.targetField)) {
for(Entity entity: state.entities.findByPosition(pos)) {
if(entity.id.isSameType(target)) {
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(entity.id)
.withTargetField(pos)
.withAmount(data.value)
.buildEntityEvent());
}
}
}
}
case RealityStone -> {
if(data.originEntity == data.targetEntity) { // => place stone
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withTargetField(data.targetField)
.withEntity(new Rock(new EntityID(EntityType.Rocks, state.entities.findFreeRockSlot()), data.targetField, 100))
.buildEntityEvent());
}else { // => destroy stone
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
.withTargetField(data.targetField)
.withTargetEntity(data.targetEntity)
.buildEntityEvent());
}
}
case PowerStone -> {
Character origin = (Character)state.entities.findEntity(data.originEntity);
int dmg = (int)Math.round(origin.hp.getValue() * 0.1);
if(origin.hp.getValue() != 1 && dmg > 0) {
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(dmg)
.buildEntityEvent());
}
result.add(new EventBuilder(EventType.TakenDamageEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
.withAmount(data.value)
.buildEntityEvent());
}
case TimeStone -> {
Character origin = (Character)state.entities.findEntity(data.originEntity);
2021-05-31 20:54:13 +00:00
int ap = origin.ap.getMax() - origin.ap.getValue();
if(ap < 0) {
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(ap)
.buildEntityEvent());
}
2021-05-31 20:54:13 +00:00
int mp = origin.mp.getMax() - origin.mp.getValue();
if(mp < 0) {
result.add(new EventBuilder(EventType.ConsumedMPEvent)
.withTargetEntity(data.originEntity)
.withTargetField(data.originField)
.withAmount(mp)
.buildEntityEvent());
}
}
case SoulStone -> {
Character target = (Character)state.entities.findEntity(data.targetEntity);
result.add(new EventBuilder(EventType.HealedEvent)
.withTargetEntity(data.targetEntity)
.withTargetField(data.targetField)
2021-05-31 20:54:13 +00:00
.withAmount(target.hp.getMax())
.buildEntityEvent());
}
}
}
}
case EndRoundRequest -> {
result.addAll(handleTurnEnd(state)); //why is it called end round request when it ends a turn...
}
case Req -> {
result.add(buildGameStateEvent(state));
}
}
return result;
}
/**
* Builds a {@link EventType#GamestateEvent} for the given {@link GameState}.
* @param state The game state to use
* @return The resulting event
*/
public static Event buildGameStateEvent(GameState state) {
return new EventBuilder(EventType.GamestateEvent)
.withEntities(state.entities.export())
.withTurnOrder((EntityID[])state.turnOrder.toArray())
.withMapSize(state.mapSize)
.withActiveCharacter(state.activeCharacter)
.withStoneCooldowns(state.stoneCooldown.export())
.withWinCondition(state.won)
.buildGameStateEvent();
}
/**
* Checks a {@link Request} for validity for a {@link GameState}.
* @param state The game state to check on
* @param request The request to validate
* @return Whether or not the request is valid
*/
public static boolean checkRequest(GameState state, Request request) {
try {
switch(request.type) {
case MeleeAttackRequest, RangedAttackRequest -> {
CharacterRequest data = (CharacterRequest)request;
Character origin = getCharacter(state, data.originField, data.originEntity);
Character target = getCharacter(state, data.targetField, data.targetEntity);
requireAlive(origin);
requireAlive(target);
requireAP(origin, 1);
if(request.type == RequestType.MeleeAttackRequest) {
if(origin.meleeDamage != data.value) {
throw new InvalidRequestException();
}
if(data.originField.distanceChebyshev(data.targetField) > 1) {
throw new InvalidRequestException();
}
}else if(request.type == RequestType.RangedAttackRequest) {
if(origin.rangedDamage != data.value) {
throw new InvalidRequestException();
}
if(data.originField.distanceChebyshev(data.targetField) > origin.attackRange) {
throw new InvalidRequestException();
}
if(data.originField.distanceChebyshev(data.targetField) <= 1) {
throw new InvalidRequestException();
}
requireLineOfSight(state, data.originField, data.targetField);
}
return true;
}
case MoveRequest -> {
CharacterRequest data = (CharacterRequest)request;
Character origin = getCharacter(state, data.originField, data.originEntity);
requireAlive(origin);
requireMP(origin, 1);
verifyCoordinates(state, data.targetField);
if(data.originField.distanceChebyshev(data.targetField) != 1) {
throw new InvalidRequestException();
}
if(state.entities.blocksMovement(data.targetField)) {
throw new InvalidRequestException();
}
return true;
}
case ExchangeInfinityStoneRequest -> {
CharacterRequest data = (CharacterRequest)request;
Character origin = getCharacter(state, data.originField, data.originEntity);
Character target = getCharacter(state, data.targetField, data.targetEntity);
requireAlive(origin);
requireAlive(target);
requireAP(origin, 1);
requireInfinityStone(origin, data.stoneType);
if(data.originField.distanceChebyshev(data.targetField) != 1) {
throw new InvalidRequestException();
}
return true;
}
case UseInfinityStoneRequest -> {
CharacterRequest data = (CharacterRequest)request;
Character origin = getCharacter(state, data.originField, data.originEntity);
requireAlive(origin);
requireAP(origin, 1);
requireInfinityStone(origin, data.stoneType);
2021-05-27 15:26:46 +00:00
if(state.stoneCooldown.onCooldown(data.stoneType)) {
throw new InvalidRequestException();
}
switch(((CharacterRequest) request).stoneType) {
case SpaceStone -> {
verifyCoordinates(state, data.targetField);
if(state.entities.blocksMovement(data.targetField)) {
throw new InvalidRequestException();
}
}
case MindStone -> {
if(data.originField == data.targetField) {
throw new InvalidRequestException();
}
2021-05-31 16:30:26 +00:00
if(data.value != state.partyConfig.mindStoneDMG) {
throw new InvalidRequestException();
}
}
case RealityStone -> {
if(data.originEntity == data.targetEntity) { // => place stone
if(state.entities.findByPosition(data.targetField).size() != 0) {
throw new InvalidRequestException();
}
}else { // => destroy stone
boolean hasRock = false;
for(Entity entity: state.entities.findByPosition(data.targetField)) {
if(entity.id.isSameType(EntityType.Rocks)) {
hasRock = true;
break;
}
}
if(!hasRock) {
throw new InvalidRequestException();
}
}
}
case PowerStone -> {
Character target = getCharacter(state, data.targetField, data.targetEntity);
requireAlive(target);
if(origin.rangedDamage * 2 != data.value) {
throw new InvalidRequestException();
}
}
case TimeStone -> {
// "👍 i approve" - the server
}
case SoulStone -> {
Character target = getCharacter(state, data.targetField, data.targetEntity);
if(target.hp.getValue() != 0) {
throw new InvalidRequestException();
}
}
}
return true;
}
2021-05-27 16:02:30 +00:00
case EndRoundRequest, Req -> {
return true;
}
}
}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);
2021-05-31 20:54:13 +00:00
if(entity == null || entity.getPosition() != position || !(entity instanceof Character) || entity.id.type == EntityType.NPC) {
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 {
2021-05-03 17:36:30 +00:00
if(stone == null || !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 {
2021-05-27 15:08:08 +00:00
if(!checkLineOfSight(state, start, end)) {
throw new InvalidRequestException();
}
}
/**
* Checks if a line of sight exists between the two positions
* @param state The game state to work on
* @param start The first position
* @param end The second position
* @return Whether or not the light of sight exists
*/
private static boolean checkLineOfSight(GameState state, IntVector2 start, IntVector2 end) {
for(IntVector2 pos: rasterize(start, end)) {
if(state.entities.blocksVision(pos)) {
return false;
}
}
2021-05-27 15:08:08 +00:00
return true;
}
/**
* Finds free neighbour options from a starting field.
* @param state The game state to work on
* @param start The starting position
* @return A list of free neighbour field options
*/
private static ArrayList<IntVector2> getFreeNeighbour(GameState state, IntVector2 start) {
ArrayList<IntVector2> options = new ArrayList<>();
if(start.getX() < 0 || start.getX() >= state.mapSize.getX() || start.getY() < 0 || start.getY() >= state.mapSize.getY()) {
return options;
}
for(IntVector2 dir: IntVector2.CardinalDirections) {
if(state.entities.findByPosition(start.add(dir)).size() == 0) {
options.add(start.add(dir));
}
}
if(options.size() == 0) {
return getFreeNeighbour(state, start.add(IntVector2.CardinalDirections[rand.nextInt(IntVector2.CardinalDirections.length)]));
}else {
return options;
}
}
2021-05-31 19:37:18 +00:00
/**
* Finds free field options.
* @param state The game state to work on
* @return A list of free field options
*/
private static ArrayList<IntVector2> getFreeFields(GameState state) {
ArrayList<IntVector2> options = new ArrayList<>();
for(int x = 0; x < state.mapSize.getX(); x++) {
for(int y = 0; y < state.mapSize.getY(); y++) {
IntVector2 pos = new IntVector2(x, y);
if(state.entities.findByPosition(pos).size() == 0) {
options.add(pos);
}
}
}
return options;
}
/**
* Applies an {@link Event} to a {@link GameState}.
* @param state The game state to apply to
* @param event The event to apply
*/
public static void applyEvent(GameState state, Event event) {
2021-05-18 11:55:36 +00:00
switch(event.type) {
case DestroyedEntityEvent -> {
state.entities.removeEntity(((EntityEvent)event).targetEntity);
}
case TakenDamageEvent -> {
Character target = (Character)state.entities.findEntity(((CharacterEvent)event).targetEntity);
target.hp.decreaseValue(((CharacterEvent)event).amount);
EntityType opposing = target.id.type == EntityType.P1 ? EntityType.P2 : EntityType.P1;
state.winConditions.increaseValue(opposing, WinCondition.TotalDamage, ((CharacterEvent)event).amount);
if(target.hp.getValue() == 0) {
state.winConditions.increaseValue(opposing, WinCondition.TotalKnockouts, 1);
}
2021-05-18 11:55:36 +00:00
}
case ConsumedAPEvent -> {
((Character)state.entities.findEntity(((CharacterEvent)event).targetEntity)).ap.decreaseValue(((CharacterEvent)event).amount);
}
case ConsumedMPEvent -> {
((Character)state.entities.findEntity(((CharacterEvent)event).targetEntity)).mp.decreaseValue(((CharacterEvent)event).amount);
}
case SpawnEntityEvent -> {
state.entities.addEntity(((EntityEvent)event).entity);
}
case HealedEvent -> {
((Character)state.entities.findEntity(((CharacterEvent)event).targetEntity)).hp.increaseValue(((CharacterEvent)event).amount);
}
case MoveEvent -> {
Character target = (Character)state.entities.findEntity(((CharacterEvent)event).originEntity);
for(Entity entity: state.entities.findByPosition(((CharacterEvent)event).targetField)) {
if(entity instanceof InfinityStone) {
target.inventory.addStone(((InfinityStone)entity).type);
state.winConditions.updateValue(target.id.type, WinCondition.MaxStones, target.inventory.getSize());
}
}
target.setPosition(((CharacterEvent)event).targetField);
2021-05-18 11:55:36 +00:00
}
2021-05-27 15:26:46 +00:00
case UseInfinityStoneEvent -> {
2021-05-31 16:30:26 +00:00
state.stoneCooldown.setCooldown(((CharacterEvent)event).stoneType);
2021-05-27 15:26:46 +00:00
}
2021-05-18 11:55:36 +00:00
case ExchangeInfinityStoneEvent -> {
((Character)state.entities.findEntity(((CharacterEvent)event).originEntity)).inventory.removeStone(((CharacterEvent)event).stoneType);
Character target = (Character)state.entities.findEntity(((CharacterEvent)event).targetEntity);
target.inventory.addStone(((CharacterEvent)event).stoneType);
state.winConditions.updateValue(target.id.type, WinCondition.MaxStones, target.inventory.getSize());
2021-05-18 11:55:36 +00:00
}
}
}
2021-05-31 19:37:18 +00:00
/**
* Starts the game and initializes all entities.
* @param state The game state to work on
* @param selectedCharacters1 The characters selected by player 1
* @param selectedCharacters2 The characters selected by player 2
* @return The list of resulting {@link Event}s
*/
public static ArrayList<Event> startGame(GameState state, ArrayList<Integer> selectedCharacters1, ArrayList<Integer> selectedCharacters2) {
ArrayList<Event> result = new ArrayList<>();
int rockIndex = 0;
for(int x = 0; x < state.mapSize.getX(); x++) {
for(int y = 0; y < state.mapSize.getY(); y++) {
if(state.scenarioConfig.scenario[y][x] == FieldType.ROCK) {
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withEntity(new Rock(new EntityID(EntityType.Rocks, rockIndex++), new IntVector2(x, y), 100))
.buildEntityEvent());
}
}
}
ArrayList<IntVector2> free = getFreeFields(state);
int p1 = selectedCharacters1.size();
int all = selectedCharacters1.size() + selectedCharacters2.size();
ArrayList<Integer> characters = new ArrayList<>(selectedCharacters1);
characters.addAll(selectedCharacters2);
for(int i = 0; i < all; i++) {
int choice = rand.nextInt(free.size());
IntVector2 position = free.get(choice);
free.remove(choice);
int selected = characters.get(i);
EntityID id = new EntityID(i < p1 ? EntityType.P1 : EntityType.P2, selected);
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withEntity(new Character(
id, position,
state.characterConfig.characters[selected].name,
state.characterConfig.characters[selected].HP,
state.characterConfig.characters[selected].MP,
state.characterConfig.characters[selected].AP,
state.characterConfig.characters[selected].attackRange,
state.characterConfig.characters[selected].rangedDamage,
state.characterConfig.characters[selected].meleeDamage
))
.buildEntityEvent());
state.turnOrder.add(id);
}
return result;
}
2021-05-19 17:59:41 +00:00
/**
* Starts end of round handling if necessary.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
public static ArrayList<Event> checkTurnEnd(GameState state) {
if(
((Character) state.entities.findEntity(state.activeCharacter)).ap.getValue() <= 0 &&
((Character) state.entities.findEntity(state.activeCharacter)).mp.getValue() <= 0
) {
return handleTurnEnd(state);
}
return new ArrayList<>();
}
/**
* Handles everything that happens at the end of a turn, including new rounds.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
2021-05-19 18:04:10 +00:00
public static ArrayList<Event> handleTurnEnd(GameState state) {
2021-05-19 17:59:41 +00:00
ArrayList<Event> result = new ArrayList<>();
ArrayList<EntityID> alive = new ArrayList<>();
for (EntityID id: state.turnOrder) {
2021-05-31 20:54:13 +00:00
if(id.type == EntityType.NPC) {
continue;
}
2021-05-19 17:59:41 +00:00
Character character = ((Character)state.entities.findEntity(id));
if(character.hp.getValue() > 0){
alive.add(id);
}
if(character.inventory.getFreeSlots() == 0) { // no slots => has all infinity stones
result.addAll(handlePlayerWin(state, character.id.type));
2021-05-19 17:59:41 +00:00
return result;
}
}
if(alive.isEmpty()) {
EntityType winner = state.winConditions.getWinner();
if(winner == EntityType.None) {
winner = rand.nextBoolean() ? EntityType.P1 : EntityType.P2;
}
result.addAll(handlePlayerWin(state, winner));
return result;
}
2021-05-19 17:59:41 +00:00
int index = alive.indexOf(state.activeCharacter);
if(index == alive.size() - 1) {
2021-05-27 15:08:08 +00:00
result.addAll(handleRoundStart(state));
2021-05-19 17:59:41 +00:00
}else {
state.activeCharacter = alive.get(index + 1);
}
2021-05-27 15:08:08 +00:00
result.addAll(handleTurnStart(state));
return result;
}
/**
* Handles everything that happens at the beginning of new rounds.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
2021-05-27 15:08:08 +00:00
public static ArrayList<Event> handleRoundStart(GameState state) {
ArrayList<Event> result = new ArrayList<>();
2021-05-19 17:59:41 +00:00
state.roundNumber++;
state.turnNumber = 0;
2021-05-27 15:08:08 +00:00
if(state.roundNumber >= 1 && state.roundNumber <= 6) {
result.addAll(handleGoose(state));
}
HashSet<EntityID> revived = new HashSet<>();
if(state.roundNumber == 7) {
result.addAll(handleStan(state, revived));
}
2021-05-31 20:54:13 +00:00
if(state.roundNumber == state.partyConfig.maxRounds + 1) {
result.addAll(spawnThanos(state));
}
Collections.shuffle(state.turnOrder);
for (EntityID id: state.turnOrder) {
2021-05-27 15:08:08 +00:00
if(revived.contains(id) || ((Character)state.entities.findEntity(id)).hp.getValue() > 0){
state.activeCharacter = id;
break;
2021-05-19 17:59:41 +00:00
}
}
2021-05-27 15:26:46 +00:00
state.stoneCooldown.update();
result.add(new EventBuilder(EventType.RoundSetupEvent)
.withRoundCount(state.roundNumber)
2021-05-27 15:08:08 +00:00
.withCharacterOrder(state.turnOrder.toArray(new EntityID[0]))
.buildGameEvent());
return result;
}
2021-05-27 15:08:08 +00:00
/**
* Handles the actions of Goose at rounds 1-6.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
public static ArrayList<Event> handleGoose(GameState state) {
ArrayList<Event> result = new ArrayList<>();
StoneType[] available = state.unvomitedStones.toArray(new StoneType[0]);
StoneType stone = available[rand.nextInt(available.length)];
state.unvomitedStones.remove(stone);
ArrayList<IntVector2> free = new ArrayList<>();
for(int x = 0; x < state.mapSize.getX(); x++) {
for(int y = 0; y < state.mapSize.getY(); y++) {
IntVector2 pos = new IntVector2(x, y);
if(state.entities.findByPosition(pos).size() == 0) {
free.add(pos);
}
}
}
IntVector2 position = free.get(rand.nextInt(free.size()));
EntityID goose = new EntityID(EntityType.NPC, 0);
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withEntity(new NPC(goose, position))
.buildEntityEvent());
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withEntity(new InfinityStone(new EntityID(EntityType.InfinityStones, stone.getID()), position, stone))
.buildEntityEvent());
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
.withTargetEntity(goose)
.withTargetField(position)
.buildEntityEvent());
return result;
}
/**
* Handles the actions of Stan at round 7.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
public static ArrayList<Event> handleStan(GameState state, HashSet<EntityID> revived) {
ArrayList<Event> result = new ArrayList<>();
ArrayList<Character> characters = new ArrayList<>();
ArrayList<IntVector2> targetOptions = new ArrayList<>();
int lowest = -1;
for(EntityID id: state.turnOrder) {
Character character = (Character)state.entities.findEntity(id);
characters.add(character);
if(lowest == -1 || character.hp.getValue() < lowest) {
lowest = character.hp.getValue();
targetOptions.clear();
}
if(lowest == character.hp.getValue()) {
targetOptions.add(character.getPosition());
}
}
IntVector2 targetPosition = targetOptions.get(rand.nextInt(targetOptions.size()));
ArrayList<IntVector2> spawnOptions = getFreeNeighbour(state, targetPosition);
if(spawnOptions.size() == 0) {
return result;
}
IntVector2 spawnPosition = spawnOptions.get(rand.nextInt(spawnOptions.size()));
EntityID stan = new EntityID(EntityType.NPC, 1);
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withEntity(new NPC(stan, spawnPosition))
.buildEntityEvent());
for(Character character: characters) {
if(checkLineOfSight(state, spawnPosition, character.getPosition())) {
if(character.hp.getValue() == 0) {
revived.add(character.id);
}
2021-05-31 20:54:13 +00:00
if(character.hp.getValue() != character.hp.getMax()) {
2021-05-27 15:08:08 +00:00
result.add(new EventBuilder(EventType.HealedEvent)
.withTargetEntity(character.id)
.withTargetField(character.getPosition())
2021-05-31 20:54:13 +00:00
.withAmount(character.hp.getMax() - character.hp.getValue())
2021-05-27 15:08:08 +00:00
.buildEntityEvent());
}
}
}
result.add(new EventBuilder(EventType.DestroyedEntityEvent)
.withTargetEntity(stan)
.withTargetField(spawnPosition)
.buildEntityEvent());
return result;
}
2021-05-31 20:54:13 +00:00
/**
* Spawns Thanos at the beginning of the first overtime round.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
public static ArrayList<Event> spawnThanos(GameState state) {
ArrayList<Event> result = new ArrayList<>();
ArrayList<IntVector2> free = getFreeFields(state);
IntVector2 position = free.get(rand.nextInt(free.size()));
int maxMP = -1;
for(EntityID id: state.turnOrder) {
Character character = (Character)state.entities.findEntity(id);
if(character.mp.getValue() > maxMP) {
maxMP = character.mp.getValue();
}
}
EntityID thanos = new EntityID(EntityType.NPC, 2);
result.add(new EventBuilder(EventType.SpawnEntityEvent)
.withEntity(new Character(thanos, position, "Thanos", 1, maxMP, 0, 0, 0, 0))
.buildEntityEvent());
state.turnOrder.add(thanos);
return result;
}
/**
* Handles Thanos' AI.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
public static ArrayList<Event> handleThanos(GameState state, Character thanos) {
ArrayList<Event> result = new ArrayList<>();
//TODO: implement thanos ai
return result;
}
/**
* Handles everything that happens at the beginning of a turn.
* @param state The game state to work on
* @return The list of resulting {@link Event}s
*/
2021-05-27 15:08:08 +00:00
public static ArrayList<Event> handleTurnStart(GameState state) {
ArrayList<Event> result = new ArrayList<>();
state.turnNumber++;
Character activeCharacter = (Character)state.entities.findEntity(state.activeCharacter);
2021-05-31 20:54:13 +00:00
boolean isThanos = state.activeCharacter.type == EntityType.NPC && state.activeCharacter.id == 2;
2021-05-19 17:59:41 +00:00
2021-05-31 20:54:13 +00:00
if(isThanos && state.roundNumber > state.partyConfig.maxRounds + 1) {
activeCharacter.mp.setMax(activeCharacter.mp.getMax() + 1);//TODO: use event for this...
}
if(activeCharacter.ap.getValue() != activeCharacter.ap.getMax()) {
2021-05-19 17:59:41 +00:00
result.add(new EventBuilder(EventType.ConsumedAPEvent)
.withTargetEntity(state.activeCharacter)
.withTargetField(activeCharacter.getPosition())
2021-05-31 20:54:13 +00:00
.withAmount(activeCharacter.ap.getValue() - activeCharacter.ap.getMax())
2021-05-19 17:59:41 +00:00
.buildGameEvent());
}
2021-05-31 20:54:13 +00:00
if(activeCharacter.mp.getValue() != activeCharacter.mp.getMax()) {
2021-05-19 17:59:41 +00:00
result.add(new EventBuilder(EventType.ConsumedMPEvent)
.withTargetEntity(state.activeCharacter)
.withTargetField(activeCharacter.getPosition())
2021-05-31 20:54:13 +00:00
.withAmount(activeCharacter.mp.getValue() - activeCharacter.mp.getMax())
2021-05-19 17:59:41 +00:00
.buildGameEvent());
}
result.add(new EventBuilder(EventType.TurnEvent)
2021-05-27 15:08:08 +00:00
.withTurnCount(state.turnOrder.size())
.withNextCharacter(state.activeCharacter)
.buildGameEvent());
2021-05-19 17:59:41 +00:00
2021-05-31 20:54:13 +00:00
if(isThanos) {
result.addAll(handleThanos(state, activeCharacter));
}
return result;
}
/**
* Handles the victory of a player through one character.
* @param state The game state to work on
* @param winner The winning character
* @return The list of resulting {@link Event}s
*/
public static ArrayList<Event> handlePlayerWin(GameState state, EntityType winner) {
ArrayList<Event> result = new ArrayList<>();
state.won = true;
result.add(new EventBuilder(EventType.WinEvent)
.withPlayerWon(winner == EntityType.P1 ? 1 : 2)
.buildGameEvent());
return result;
}
/**
* Computes all fields which intersect the line between the center points of the given two fields.
* @return The list of intersecting positions
*/
public static ArrayList<IntVector2> rasterize(IntVector2 a, IntVector2 b) {
ArrayList<IntVector2> result = new ArrayList<>();
//TODO: implement proper line rasterization algorithm in GameLogic.rasterize
int x1 = Math.min(a.getX(), b.getX());
int x2 = Math.max(a.getX(), b.getX());
int y1 = Math.min(a.getY(), b.getY());
int y2 = Math.max(a.getY(), b.getY());
Line2D line = new Line2D.Float(x1 + 0.5f, y1 + 0.5f, x2 + 0.5f, y2 + 0.5f);
for(int i = x1; i <= x2; i++) {
for(int j = y1; j <= y2; j++) {
Rectangle2D cell = new Rectangle.Float(i, j, 1, 1);
if(line.intersects(cell)) {
result.add(new IntVector2(i, j));
}
}
}
return result;
}
}