Compare commits
No commits in common. "gamelib" and "3070b9864c04447d8216925c7467c52ed2305339" have entirely different histories.
gamelib
...
3070b9864c
79
build.gradle
79
build.gradle
@ -2,7 +2,6 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
id 'idea'
|
id 'idea'
|
||||||
id "org.sonarqube" version "3.2.0"
|
id "org.sonarqube" version "3.2.0"
|
||||||
id 'jacoco'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@ -18,76 +17,10 @@ test {
|
|||||||
maxParallelForks = 1
|
maxParallelForks = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Fancy live test output (from https://stackoverflow.com/questions/3963708/gradle-how-to-display-test-results-in-the-console-in-real-time)
|
|
||||||
|
|
||||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
|
||||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
|
||||||
|
|
||||||
tasks.withType(Test) {
|
|
||||||
testLogging {
|
|
||||||
// set options for log level LIFECYCLE
|
|
||||||
events TestLogEvent.FAILED,
|
|
||||||
TestLogEvent.PASSED,
|
|
||||||
TestLogEvent.SKIPPED
|
|
||||||
// TestLogEvent.STANDARD_OUT
|
|
||||||
exceptionFormat TestExceptionFormat.FULL
|
|
||||||
showExceptions true
|
|
||||||
showCauses true
|
|
||||||
showStackTraces true
|
|
||||||
|
|
||||||
// set options for log level DEBUG and INFO
|
|
||||||
debug {
|
|
||||||
events TestLogEvent.STARTED,
|
|
||||||
TestLogEvent.FAILED,
|
|
||||||
TestLogEvent.PASSED,
|
|
||||||
TestLogEvent.SKIPPED,
|
|
||||||
TestLogEvent.STANDARD_ERROR
|
|
||||||
TestLogEvent.STANDARD_OUT
|
|
||||||
exceptionFormat TestExceptionFormat.FULL
|
|
||||||
}
|
|
||||||
// info.events = debug.events
|
|
||||||
// info.exceptionFormat = debug.exceptionFormat
|
|
||||||
|
|
||||||
afterSuite { desc, result ->
|
|
||||||
if (!desc.parent) { // will match the outermost suite
|
|
||||||
def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
|
|
||||||
def startItem = '| ', endItem = ' |'
|
|
||||||
def repeatLength = startItem.length() + output.length() + endItem.length()
|
|
||||||
println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
jacocoTestReport {
|
|
||||||
reports {
|
|
||||||
html.enabled true
|
|
||||||
xml.enabled true
|
|
||||||
xml.destination file("${buildDir}/reports/jacoco.xml")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins.withType(JacocoPlugin) {
|
|
||||||
tasks["test"].finalizedBy 'jacocoTestReport'
|
|
||||||
}
|
|
||||||
|
|
||||||
sonarqube {
|
|
||||||
properties {
|
|
||||||
property "sonar.java.coveragePlugin", "jacoco"
|
|
||||||
property "sonar.host.url", "https://sonarqube.yandrik.dev"
|
|
||||||
property "sonar.coverage.jacoco.xmlReportPath", "${buildDir}/reports/jacoco.xml"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.4'
|
implementation "com.fasterxml.jackson.core:jackson-core:2.12.3"
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.12.4'
|
implementation "com.fasterxml.jackson.core:jackson-annotations:2.12.3"
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.4'
|
implementation "com.fasterxml.jackson.core:jackson-databind:2.12.3"
|
||||||
|
|
||||||
implementation "org.hibernate.validator:hibernate-validator:7.0.1.Final"
|
implementation "org.hibernate.validator:hibernate-validator:7.0.1.Final"
|
||||||
implementation "org.glassfish:jakarta.el:4.0.1"
|
implementation "org.glassfish:jakarta.el:4.0.1"
|
||||||
@ -95,10 +28,10 @@ dependencies {
|
|||||||
implementation "org.tinylog:tinylog-api:2.4.0-M1"
|
implementation "org.tinylog:tinylog-api:2.4.0-M1"
|
||||||
implementation "org.tinylog:tinylog-impl:2.4.0-M1"
|
implementation "org.tinylog:tinylog-impl:2.4.0-M1"
|
||||||
|
|
||||||
testImplementation 'net.jqwik:jqwik:1.5.3'
|
testImplementation "net.jqwik:jqwik:1.5.1"
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.0-M1"
|
testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.0-M1"
|
||||||
testImplementation 'org.mockito:mockito-core:3.11.2'
|
testImplementation "org.mockito:mockito-core:3.+"
|
||||||
testImplementation 'org.assertj:assertj-core:3.20.2'
|
testImplementation "org.assertj:assertj-core:3.19.0"
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.0-M1"
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.0-M1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,6 @@ public class IntVector2 implements Serializable {
|
|||||||
return dot(v.x, v.y);
|
return dot(v.x, v.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This returns a new Vector for now, but should scale the actually modified vector
|
|
||||||
public IntVector2 scale(float x, float y) {
|
public IntVector2 scale(float x, float y) {
|
||||||
return new IntVector2(Math.round(this.x * x), Math.round(this.y * y));
|
return new IntVector2(Math.round(this.x * x), Math.round(this.y * y));
|
||||||
}
|
}
|
||||||
|
@ -58,37 +58,6 @@ 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,17 +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.value = value;
|
|
||||||
this.max = max;
|
this.max = max;
|
||||||
}
|
this.value = value;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@link Stat} with the same values as the
|
|
||||||
* given {@link Stat}
|
|
||||||
* @param toCopy is the {@link Stat} to copy
|
|
||||||
*/
|
|
||||||
public Stat(Stat toCopy) {
|
|
||||||
this(toCopy.type, toCopy.getValue(), toCopy.getMax());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getValue() {
|
public int getValue() {
|
||||||
|
@ -119,7 +119,6 @@ public class GameInstance {
|
|||||||
manager.applyEvent(gameStateEvent);
|
manager.applyEvent(gameStateEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public GameState getGameStateUnsafe() {
|
public GameState getGameStateUnsafe() {
|
||||||
return _state;
|
return _state;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ public class GameLogic {
|
|||||||
* Checks a {@link Request} for validity for a {@link GameState}.
|
* Checks a {@link Request} for validity for a {@link GameState}.
|
||||||
* @param state The game state to check on
|
* @param state The game state to check on
|
||||||
* @param request The request to validate
|
* @param request The request to validate
|
||||||
* @return Whether the request is valid
|
* @return Whether or not the request is valid
|
||||||
*/
|
*/
|
||||||
protected static boolean checkRequest(GameState state, Request request) {
|
protected static boolean checkRequest(GameState state, Request request) {
|
||||||
try {
|
try {
|
||||||
@ -36,20 +36,15 @@ public class GameLogic {
|
|||||||
CharacterRequest data = (CharacterRequest)request;
|
CharacterRequest data = (CharacterRequest)request;
|
||||||
|
|
||||||
Character origin = getCharacter(state, data.originField, data.originEntity);
|
Character origin = getCharacter(state, data.originField, data.originEntity);
|
||||||
Entity target = getAttackable(state, data.targetField, data.targetEntity);
|
Character target = getCharacter(state, data.targetField, data.targetEntity);
|
||||||
|
|
||||||
requireTurn(state, origin);
|
requireTurn(state, origin);
|
||||||
|
requireOppositeTeam(origin, target);
|
||||||
|
|
||||||
requireAlive(origin);
|
requireAlive(origin);
|
||||||
|
requireAlive(target);
|
||||||
requireAP(origin, 1);
|
requireAP(origin, 1);
|
||||||
|
|
||||||
if(target instanceof Character) {
|
|
||||||
Character targetCharacter = (Character)target;
|
|
||||||
|
|
||||||
requireOppositeTeam(origin, targetCharacter);
|
|
||||||
requireAlive(targetCharacter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(request.type == RequestType.MeleeAttackRequest) {
|
if(request.type == RequestType.MeleeAttackRequest) {
|
||||||
if(origin.meleeDamage != data.value) {
|
if(origin.meleeDamage != data.value) {
|
||||||
throw new InvalidRequestException("Invalid melee damage");
|
throw new InvalidRequestException("Invalid melee damage");
|
||||||
@ -131,57 +126,32 @@ public class GameLogic {
|
|||||||
case SpaceStone -> {
|
case SpaceStone -> {
|
||||||
verifyCoordinates(state, data.targetField);
|
verifyCoordinates(state, data.targetField);
|
||||||
|
|
||||||
if(state.entities.findByPosition(data.targetField).size() != 0) {
|
if(state.entities.blocksMovement(data.targetField)) {
|
||||||
throw new InvalidRequestException("Using space stone onto non-free field");
|
throw new InvalidRequestException("Using space stone onto blocked field");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case MindStone -> {
|
case MindStone -> {
|
||||||
verifyCoordinates(state, data.targetField);
|
if(data.originField == data.targetField) {
|
||||||
|
throw new InvalidRequestException("Invalid mind stone target field");
|
||||||
requireLineOfSight(state, data.originField, data.targetField);
|
}
|
||||||
|
if(data.value != state.partyConfig.mindStoneDMG) {
|
||||||
getAttackableWithoutID(state, data.targetField);
|
throw new InvalidRequestException("Invalid mind stone damage");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case RealityStone -> {
|
case RealityStone -> {
|
||||||
verifyCoordinates(state, data.targetField);
|
// no check done
|
||||||
|
|
||||||
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 -> {
|
||||||
verifyCoordinates(state, data.targetField);
|
Character target = getCharacter(state, data.targetField, data.targetEntity);
|
||||||
|
|
||||||
Entity target = getAttackableWithoutID(state, data.targetField);
|
requireAlive(target);
|
||||||
|
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 -> {
|
||||||
verifyCoordinates(state, data.targetField);
|
Character target = getCharacter(state, data.targetField, data.targetEntity);
|
||||||
|
|
||||||
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)");
|
||||||
@ -213,37 +183,6 @@ public class GameLogic {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* @param entityID The requested {@link EntityID}
|
|
||||||
* @return The found entity
|
|
||||||
* @throws InvalidRequestException if the entity is invalid or not found
|
|
||||||
*/
|
|
||||||
private static Entity getAttackable(GameState state, IntVector2 position, EntityID entityID) throws InvalidRequestException {
|
|
||||||
Entity entity = state.entities.findEntity(entityID);
|
|
||||||
if(entity == null || !entity.getPosition().equals(position) || (!(entity instanceof Character) && !(entity instanceof Rock)) || entity.id.type == EntityType.NPC) {
|
|
||||||
throw new InvalidRequestException("Invalid target character or rock");
|
|
||||||
}
|
|
||||||
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
|
||||||
@ -264,25 +203,6 @@ 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.
|
||||||
*/
|
*/
|
||||||
@ -395,8 +315,26 @@ public class GameLogic {
|
|||||||
.withAmount(data.value)
|
.withAmount(data.value)
|
||||||
.buildEntityEvent());
|
.buildEntityEvent());
|
||||||
|
|
||||||
Entity targetEntity = state.entities.findEntity(data.targetEntity);
|
Character target = (Character)state.entities.findEntity(data.targetEntity);
|
||||||
result.addAll(checkDeath(state, targetEntity, data.value));
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case MoveRequest -> {
|
case MoveRequest -> {
|
||||||
CharacterRequest data = (CharacterRequest)request;
|
CharacterRequest data = (CharacterRequest)request;
|
||||||
@ -484,16 +422,18 @@ public class GameLogic {
|
|||||||
.buildCharacterEvent());
|
.buildCharacterEvent());
|
||||||
}
|
}
|
||||||
case MindStone -> {
|
case MindStone -> {
|
||||||
ArrayList<Entity> found = state.entities.findByPosition(data.targetField);
|
EntityType target = data.originEntity.type == EntityType.P1 ? EntityType.P2 : EntityType.P1;
|
||||||
Entity targetEntity = found.get(0);
|
for(IntVector2 pos: rasterizeInfinity(data.originField, data.targetField, state.mapSize, false)) {
|
||||||
|
for(Entity entity: state.entities.findByPosition(pos)) {
|
||||||
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
if(entity.id.isSameType(target)) {
|
||||||
.withTargetEntity(targetEntity.id)
|
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
||||||
.withTargetField(data.targetField)
|
.withTargetEntity(entity.id)
|
||||||
.withAmount(state.partyConfig.mindStoneDMG)
|
.withTargetField(pos)
|
||||||
.buildEntityEvent());
|
.withAmount(data.value)
|
||||||
|
.buildEntityEvent());
|
||||||
result.addAll(checkDeath(state, targetEntity, state.partyConfig.mindStoneDMG));
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case RealityStone -> {
|
case RealityStone -> {
|
||||||
EntityID target = null;
|
EntityID target = null;
|
||||||
@ -516,29 +456,20 @@ 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
|
if(origin.hp.getValue() != 1 && dmg > 0) {
|
||||||
int hp1 = origin.hp.getValue();
|
|
||||||
int hp2 = Math.max(1, origin.hp.getValue() - dmg);
|
|
||||||
int actualDmg = hp1 - hp2;
|
|
||||||
if(actualDmg > 0) {
|
|
||||||
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
||||||
.withTargetEntity(data.originEntity)
|
.withTargetEntity(data.originEntity)
|
||||||
.withTargetField(data.originField)
|
.withTargetField(data.originField)
|
||||||
.withAmount(actualDmg)
|
.withAmount(dmg)
|
||||||
.buildEntityEvent());
|
.buildEntityEvent());
|
||||||
}
|
}
|
||||||
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
result.add(new EventBuilder(EventType.TakenDamageEvent)
|
||||||
.withTargetEntity(targetEntity.id)
|
.withTargetEntity(data.targetEntity)
|
||||||
.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);
|
||||||
@ -560,11 +491,9 @@ public class GameLogic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case SoulStone -> {
|
case SoulStone -> {
|
||||||
ArrayList<Entity> found = state.entities.findByPosition(data.targetField);
|
Character target = (Character)state.entities.findEntity(data.targetEntity);
|
||||||
Character target = (Character)found.get(0);
|
|
||||||
|
|
||||||
result.add(new EventBuilder(EventType.HealedEvent)
|
result.add(new EventBuilder(EventType.HealedEvent)
|
||||||
.withTargetEntity(target.id)
|
.withTargetEntity(data.targetEntity)
|
||||||
.withTargetField(data.targetField)
|
.withTargetField(data.targetField)
|
||||||
.withAmount(target.hp.getMax())
|
.withAmount(target.hp.getMax())
|
||||||
.buildEntityEvent());
|
.buildEntityEvent());
|
||||||
@ -583,49 +512,6 @@ 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}.
|
||||||
@ -640,26 +526,19 @@ public class GameLogic {
|
|||||||
case TakenDamageEvent -> {
|
case TakenDamageEvent -> {
|
||||||
EntityEvent data = (EntityEvent)event;
|
EntityEvent data = (EntityEvent)event;
|
||||||
|
|
||||||
Entity targetEntity = state.entities.findEntity(data.targetEntity);
|
Character target = (Character)state.entities.findEntity(data.targetEntity);
|
||||||
|
target.hp.decreaseValue(data.amount);
|
||||||
|
|
||||||
if(targetEntity instanceof Character) {
|
EntityType opposing = target.id.type == EntityType.P1 ? EntityType.P2 : EntityType.P1;
|
||||||
Character target = (Character)targetEntity;
|
state.winConditions.increaseValue(opposing, WinCondition.TotalDamage, data.amount);
|
||||||
target.hp.decreaseValue(data.amount);
|
if(!target.isAlive()) {
|
||||||
|
target.inventory.clear();
|
||||||
|
state.winConditions.increaseValue(opposing, WinCondition.TotalKnockouts, 1);
|
||||||
|
}
|
||||||
|
|
||||||
EntityType opposing = target.id.type == EntityType.P1 ? EntityType.P2 : EntityType.P1;
|
if(state.activeCharacter != null && state.activeCharacter.type == EntityType.NPC && state.activeCharacter.id == NPCType.Thanos.getID()) {
|
||||||
state.winConditions.increaseValue(opposing, WinCondition.TotalDamage, data.amount);
|
NPC thanos = (NPC)state.entities.findEntity(state.activeCharacter);
|
||||||
if(!target.isAlive()) {
|
target.inventory.transfer(thanos.inventory);
|
||||||
target.inventory.clear();
|
|
||||||
state.winConditions.increaseValue(opposing, WinCondition.TotalKnockouts, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(state.activeCharacter != null && state.activeCharacter.type == EntityType.NPC && state.activeCharacter.id == NPCType.Thanos.getID()) {
|
|
||||||
NPC thanos = (NPC)state.entities.findEntity(state.activeCharacter);
|
|
||||||
target.inventory.transfer(thanos.inventory);
|
|
||||||
}
|
|
||||||
}else if(targetEntity instanceof Rock) {
|
|
||||||
Rock target = (Rock)targetEntity;
|
|
||||||
target.decreaseHp(data.amount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case ConsumedAPEvent -> {
|
case ConsumedAPEvent -> {
|
||||||
|
@ -43,9 +43,6 @@ 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(),
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
package uulm.teamname.marvelous.gamelibrary;
|
|
||||||
|
|
||||||
import net.jqwik.api.*;
|
|
||||||
import net.jqwik.api.Tuple;
|
|
||||||
import net.jqwik.api.lifecycle.BeforeProperty;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
import static org.assertj.core.api.Assertions.*;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class IntVector2Test {
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
@BeforeProperty
|
|
||||||
void beforeAll() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Property
|
|
||||||
@Disabled("This test fails! Therefore,a bug exists here")
|
|
||||||
void settingLengthToOneResultsInCardinalDirections(
|
|
||||||
@ForAll int x,
|
|
||||||
@ForAll int y
|
|
||||||
) {
|
|
||||||
var vec = new IntVector2(x, y).setLength(1);
|
|
||||||
// System.out.printf("Input was (%d, %d). Output is %s\n", x, y, vec);
|
|
||||||
if (!vec.equals(new IntVector2(0, 0))) {
|
|
||||||
assertThat(IntVector2.CardinalDirections)
|
|
||||||
.contains(vec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Property
|
|
||||||
void settingSmallVectorLengthResultsInCardinalDirections(
|
|
||||||
@ForAll("SmallIntegers") Integer x,
|
|
||||||
@ForAll("SmallIntegers") Integer y
|
|
||||||
) {
|
|
||||||
var vec = new IntVector2(x, y).setLength(1);
|
|
||||||
// System.out.printf("Input was (%d, %d). Output is %s\n", x, y, vec);
|
|
||||||
if (!vec.equals(new IntVector2(0, 0))) {
|
|
||||||
assertThat(IntVector2.CardinalDirections)
|
|
||||||
.contains(vec);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void setLengthCardinalTest() {
|
|
||||||
assertThat(new IntVector2(1, 1).setLength(1))
|
|
||||||
.isEqualTo(new IntVector2(1, 1));
|
|
||||||
assertThat(new IntVector2(0, 1).setLength(1))
|
|
||||||
.isEqualTo(new IntVector2(0, 1));
|
|
||||||
assertThat(new IntVector2(1, -1).setLength(1))
|
|
||||||
.isEqualTo(new IntVector2(1, -1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provide("SmallIntegers")
|
|
||||||
Arbitrary<Integer> smallIntegers() {
|
|
||||||
return Arbitraries.integers()
|
|
||||||
.between(-1000, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -120,27 +120,15 @@ class MessageValidationUtilityTest {
|
|||||||
var characterConfig = new CharacterConfig();
|
var characterConfig = new CharacterConfig();
|
||||||
|
|
||||||
assertThat(ValidationUtility.validate(characterConfig).get())
|
assertThat(ValidationUtility.validate(characterConfig).get())
|
||||||
.isIn("characters must not be empty", "characters darf nicht leer sein");
|
.isEqualTo("characters must not be empty");
|
||||||
// .isEqualTo("characters must not be empty");
|
|
||||||
|
|
||||||
characterConfig.characters = new CharacterProperties[] {racoon, quicksilver, hulk, loki, silversurfer};
|
characterConfig.characters = new CharacterProperties[] {racoon, quicksilver, hulk, loki, silversurfer};
|
||||||
|
|
||||||
var results = ValidationUtility.validate(characterConfig).get();
|
assertThat(ValidationUtility.validate(characterConfig).get())
|
||||||
assertThat(results.contains("characters has less than 24 characters") ||
|
.contains("characters has less than 24 characters",
|
||||||
results.contains("characters hat weniger als 24 characters"))
|
"characters[4].rangedDamage must be greater than 0",
|
||||||
.isTrue();
|
"characters[4].name must not be empty",
|
||||||
|
"characters[4].meleeDamage must be greater than 0");
|
||||||
assertThat(results.contains("characters[4].rangedDamage must be greater than 0") ||
|
|
||||||
results.contains("characters[4].rangedDamage muss größer als 0 sein"))
|
|
||||||
.isTrue();
|
|
||||||
|
|
||||||
assertThat(results.contains("characters[4].name must not be empty") ||
|
|
||||||
results.contains("characters[4].name darf nicht leer sein"))
|
|
||||||
.isTrue();
|
|
||||||
|
|
||||||
assertThat(results.contains("characters[4].meleeDamage must be greater than 0") ||
|
|
||||||
results.contains("characters[4].meleeDamage muss größer als 0 sein"))
|
|
||||||
.isTrue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user