Skip to content

Commit

Permalink
[Patch]: Added StatusEffect handler tests (#986)
Browse files Browse the repository at this point in the history
Added Minecraft-backed JUnit tests to the common module
Started with codec testing for the StatusEffect handler
  • Loading branch information
SigmundGranaas committed Sep 12, 2024
1 parent 0675913 commit 5a1c4d3
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,39 @@ public void testStatusEffectHandler(TestContext context) {
});
}

@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, batchId = "StatusEffectHandlerTest", tickLimit = 120)
public void testStatusAttackerEffectHandler(TestContext context) {
TestPos playerPos = TestPos.of(new BlockPos(3, 1, 3), context);
createFloor(context);

PlayerEntity player = PlayerFactory.of(context, playerPos);

// Spawn a target entity (e.g., a zombie) that will receive the status effect
LivingEntity target = context.spawnEntity(EntityType.PIG, playerPos.offset(1, 0, 1).relative());

// Create an instance of StatusEffectHandler from JSON
StatusEffectHandler statusEffectHandler = statusEffectAttackerHandler();

// Trigger the onHit method simulating the player hitting the target
statusEffectHandler.onHit(player, context.getWorld(), target);

// Verify the status effect is applied correctly
context.runAtTick(1, () -> {
// PLayer did no get ticked by the game world. It needs a single manual tick.
player.baseTick();
boolean hasEffect = player.hasStatusEffect(StatusEffects.POISON);
context.assertTrue(hasEffect, "Player does not have the expected Poison effect.");

if (hasEffect) {
StatusEffectInstance effectInstance = player.getStatusEffect(StatusEffects.POISON);
context.assertTrue(0 == effectInstance.getAmplifier(), "Poison effect level is not as expected.");
context.assertTrue(599 == effectInstance.getDuration(), "Poison effect duration is not as expected.");
}

context.complete();
});
}

public static StatusEffectHandler statusEffectHandler() {
String json = """
{
Expand All @@ -401,4 +434,17 @@ public static StatusEffectHandler statusEffectHandler() {
""";
return Utils.handlerFromString(json, StatusEffectHandler.BUILDER);
}

public static StatusEffectHandler statusEffectAttackerHandler() {
String json = """
{
"type": "minecraft:status_effect",
"target": "minecraft:attacker",
"effect": "minecraft:poison",
"level": 1,
"duration": 600
}
""";
return Utils.handlerFromString(json, StatusEffectHandler.BUILDER);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.sigmundgranaas.forgero.fabric.gametest.handler;

public class StatusEffectTest {
}
18 changes: 17 additions & 1 deletion fabric/minecraft-common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,25 @@

dependencies {
// Fabric Loader
modCompileOnly "net.fabricmc:fabric-loader:${project.loader_version}"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}"
}

loom {
accessWidenerPath = file("src/main/resources/minecraft-common.accesswidener")
}

sourceSets {
test {
compileClasspath += main.compileClasspath
runtimeClasspath += main.runtimeClasspath
}
}

test {
useJUnitPlatform()
maxParallelForks = 32
testLogging.events("failed")
testLogging.info.events = ["failed", "skipped"]
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import static com.sigmundgranaas.forgero.minecraft.common.feature.FeatureUtils.compute;

import java.util.Objects;

import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.sigmundgranaas.forgero.core.Forgero;
import com.sigmundgranaas.forgero.core.property.Attribute;
import com.sigmundgranaas.forgero.core.property.attribute.BaseAttribute;
import lombok.Getter;
Expand Down Expand Up @@ -68,11 +71,12 @@ public class StatusEffectHandler implements EntityTargetHandler {
* @param duration Duration in ticks for the effect.
* @param target The target entity.
*/
// Add null checks in the constructor
public StatusEffectHandler(StatusEffect effect, Attribute level, Attribute duration, String target) {
this.effect = effect;
this.level = level;
this.duration = duration;
this.target = target;
this.effect = Objects.requireNonNull(effect, "Effect cannot be null");
this.level = Objects.requireNonNull(level, "Level cannot be null");
this.duration = Objects.requireNonNull(duration, "Duration cannot be null");
this.target = Objects.requireNonNull(target, "Target cannot be null");
}

public static Codec<StatusEffectHandler> codec() {
Expand All @@ -94,10 +98,20 @@ public static Codec<StatusEffectHandler> codec() {
*/
@Override
public void onHit(Entity source, World world, Entity targetEntity) {
if ("minecraft:targeted_entity".equals(target) && targetEntity instanceof LivingEntity livingTarget) {
livingTarget.addStatusEffect(new StatusEffectInstance(effect, duration(source), level(source) - 1));
} else if ("minecraft:attacker".equals(target) && source instanceof LivingEntity livingSource) {
livingSource.addStatusEffect(new StatusEffectInstance(effect, duration(source), level(source) - 1));
if ("minecraft:targeted_entity".equals(target)) {
if (targetEntity instanceof LivingEntity livingTarget) {
livingTarget.addStatusEffect(new StatusEffectInstance(effect, duration(source), level(source) - 1));
} else {
Forgero.LOGGER.warn("Targeted entity is not a living entity!");
}
} else if ("minecraft:attacker".equals(target)) {
if (source instanceof LivingEntity livingSource) {
livingSource.addStatusEffect(new StatusEffectInstance(effect, duration(source), level(source) - 1));
} else {
Forgero.LOGGER.warn("Targeted entity is not a living entity!");
}
} else {
throw new IllegalArgumentException("Invalid target: " + target);
}
}

Expand All @@ -106,7 +120,7 @@ private int duration(Entity source) {
}

private int level(Entity source) {
return compute(level, source).asInt();
return Math.max(1, compute(level, source).asInt());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sigmundgranaas.forgero.minecraft.common.handler;

import org.junit.jupiter.api.BeforeAll;

import net.minecraft.Bootstrap;
import net.minecraft.SharedConstants;

public interface Bootstrapped {
@BeforeAll
static void bootStrap() {
SharedConstants.createGameVersion();
Bootstrap.initialize();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.sigmundgranaas.forgero.minecraft.common.handler;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.serialization.JsonOps;
import com.sigmundgranaas.forgero.core.property.Attribute;
import com.sigmundgranaas.forgero.core.property.attribute.BaseAttribute;
import com.sigmundgranaas.forgero.minecraft.common.handler.targeted.onHitEntity.StatusEffectHandler;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffects;
import net.minecraft.registry.Registries;
import net.minecraft.util.Identifier;

class StatusEffectHandlerTest implements Bootstrapped {
private StatusEffectHandler handler;
private StatusEffect effect;
private Attribute level;
private Attribute duration;

@BeforeEach
void setUp() {
effect = StatusEffects.POISON;
level = BaseAttribute.of(2, StatusEffectHandler.EFFECT_LEVEL_ATTRIBUTE_TYPE);
duration = BaseAttribute.of(100, StatusEffectHandler.EFFECT_DURATION_TYPE);
handler = new StatusEffectHandler(effect, level, duration, "minecraft:targeted_entity");
}

@Test
void testCodecEncode() {
JsonObject json = StatusEffectHandler.BUILDER.encodeStart(JsonOps.INSTANCE, handler)
.result()
.map(JsonElement::getAsJsonObject)
.orElseThrow();

assertEquals("minecraft:poison", json.get("effect").getAsString());
assertEquals(2, json.get("level").getAsJsonObject().get("value").getAsInt());
assertEquals(100, json.get("duration").getAsJsonObject().get("value").getAsInt());
assertEquals("minecraft:targeted_entity", json.get("target").getAsString());
}

@Test
void testCodecDecode() {
JsonObject json = new JsonObject();
json.addProperty("effect", "minecraft:poison");
json.addProperty("level", 2);
json.addProperty("duration", 100);
json.addProperty("target", "minecraft:targeted_entity");

StatusEffectHandler decodedHandler = StatusEffectHandler.BUILDER.parse(JsonOps.INSTANCE, json)
.result()
.orElseThrow();

assertEquals(Registries.STATUS_EFFECT.get(new Identifier("minecraft:poison")), decodedHandler.effect());
assertEquals(2, decodedHandler.level().compute().asInt());
assertEquals(100, decodedHandler.duration().compute().asInt());
assertEquals("minecraft:targeted_entity", decodedHandler.target());
}

@Test
void testCodecDecodeDefaultValues() {
JsonObject json = new JsonObject();
json.addProperty("effect", "minecraft:poison");
json.addProperty("target", "minecraft:targeted_entity");

StatusEffectHandler decodedHandler = StatusEffectHandler.BUILDER.parse(JsonOps.INSTANCE, json)
.result()
.orElseThrow();

assertEquals(Registries.STATUS_EFFECT.get(new Identifier("minecraft:poison")), decodedHandler.effect());
assertEquals(1, decodedHandler.level().compute().asInt());
assertEquals(20 * 30, decodedHandler.duration().compute().asInt());
assertEquals("minecraft:targeted_entity", decodedHandler.target());
}

@Test
void testInvalidTarget() {
handler = new StatusEffectHandler(effect, level, duration, "invalid_target");
assertThrows(IllegalArgumentException.class, () -> handler.onHit(null, null, null));
}

@Test
void testNullEffect() {
assertThrows(NullPointerException.class, () -> new StatusEffectHandler(null, level, duration, "minecraft:targeted_entity"));
}

@Test
void testNullTarget() {
assertThrows(NullPointerException.class, () -> new StatusEffectHandler(effect, level, duration, null));
}
}

0 comments on commit 5a1c4d3

Please sign in to comment.