Добавить общую систему весовых объектов

This commit is contained in:
2026-08-19 07:40:11 +03:00
parent 19bb64edfb
commit 259fcd316b
32 changed files with 1017 additions and 34 deletions
@@ -80,6 +80,9 @@ public final class CorpseManagerTest {
assertFalse(body.crushed());
assertFalse(body.activatesPressure());
assertTrue(manager.bodies().contains(body));
assertTrue(body instanceof IWeightedObject);
assertEquals(1, body.pressureMass());
assertEquals(body.x(), body.weightedX(), 0f);
}
@Test
@@ -6,6 +6,7 @@ import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -14,7 +15,7 @@ public final class LevelCatalogTest {
@Test
public void productionCatalogContainsHazardsAndTwoCorpseStateTeachingLevels() {
List<LevelDefinition> levels = LevelCatalog.all();
assertEquals(16, levels.size());
assertEquals(17, levels.size());
Set<String> titles = new HashSet<>();
for (int i = 0; i < levels.size(); i++) {
LevelDefinition level = levels.get(i);
@@ -29,6 +30,16 @@ public final class LevelCatalogTest {
assertTrue(levels.get(14).containsMechanic(LevelDefinition.HazardType.CRUSHER));
assertTrue(levels.get(15).containsMechanic(LevelDefinition.HazardType.FIRE));
assertFalse(levels.get(15).platforms.get(1).controlId.isEmpty());
LevelDefinition weighted = levels.get(16);
assertEquals(5, weighted.objects.size());
Set<LevelDefinition.PhysicalObjectType> objectTypes = new HashSet<>();
for (LevelDefinition.PhysicalObject object : weighted.objects) {
objectTypes.add(object.type);
}
assertEquals(new HashSet<>(Arrays.asList(LevelDefinition.PhysicalObjectType.values())),
objectTypes);
assertEquals(9, weighted.switches.get(0).requiredMass);
assertFalse(weighted.doors.isEmpty());
}
@Test
@@ -214,6 +214,43 @@ public final class LevelJsonLoaderTest {
assertEquals("explosion-blast", explosion.audioCue);
}
@Test
public void allApprovedWeightedObjectTypesLoadThroughOneMassContract() {
String objects = "\"objects\":["
+ "{\"type\":\"BOX\",\"box\":{\"x\":300,\"y\":800,\"width\":80,\"height\":80},\"mass\":1},"
+ "{\"type\":\"ROCK\",\"box\":{\"x\":400,\"y\":820,\"width\":60,\"height\":60},\"mass\":2},"
+ "{\"type\":\"BALL\",\"box\":{\"x\":500,\"y\":820,\"width\":60,\"height\":60},\"mass\":1},"
+ "{\"type\":\"ICE_BLOCK\",\"box\":{\"x\":600,\"y\":800,\"width\":80,\"height\":80},\"mass\":2},"
+ "{\"type\":\"COUNTERWEIGHT\",\"box\":{\"x\":700,\"y\":780,\"width\":80,\"height\":100},\"mass\":3}],";
String json = validLevelJson(1).replace("\"platforms\":", objects + "\"platforms\":");
LevelDefinition level = LevelJsonLoader.load(new StringReader(json), "weighted.json");
assertEquals(5, level.objects.size());
assertEquals(LevelDefinition.PhysicalObjectType.BOX, level.objects.get(0).type);
assertEquals(LevelDefinition.PhysicalObjectType.ROCK, level.objects.get(1).type);
assertEquals(LevelDefinition.PhysicalObjectType.BALL, level.objects.get(2).type);
assertEquals(LevelDefinition.PhysicalObjectType.ICE_BLOCK, level.objects.get(3).type);
assertEquals(LevelDefinition.PhysicalObjectType.COUNTERWEIGHT, level.objects.get(4).type);
assertEquals(3, level.objects.get(4).mass);
}
@Test
public void weightedObjectRejectsNonPositiveMassWithItsJsonPath() {
String object = "\"objects\":[{\"type\":\"BOX\","
+ "\"box\":{\"x\":300,\"y\":800,\"width\":80,\"height\":80},\"mass\":0}],";
String json = validLevelJson(1).replace("\"platforms\":", object + "\"platforms\":");
try {
LevelJsonLoader.load(new StringReader(json), "massless.json");
fail("A pressure object with no mass must fail closed");
} catch (IllegalArgumentException error) {
assertTrue(error.getMessage(), error.getMessage().contains(
"massless.json.objects[0].mass"));
assertTrue(error.getMessage(), error.getMessage().contains("must be positive"));
}
}
static String validLevelJson(int formatVersion) {
return "{"
+ "\"formatVersion\":" + formatVersion + ","
@@ -8,8 +8,8 @@ import org.junit.Test;
public final class LevelPageTest {
@Test
public void sixteenLevelsUseTwoBoundedTenCardPages() {
LevelPage page = new LevelPage(16, 10);
public void seventeenLevelsUseTwoBoundedTenCardPages() {
LevelPage page = new LevelPage(17, 10);
assertEquals(2, page.pageCount());
assertEquals(0, page.firstIndex());
@@ -19,7 +19,7 @@ public final class LevelPageTest {
page.next();
assertEquals(10, page.firstIndex());
assertEquals(16, page.endExclusive());
assertEquals(17, page.endExclusive());
assertTrue(page.hasPrevious());
assertFalse(page.hasNext());
@@ -29,5 +29,7 @@ public final class LevelPageTest {
assertEquals(0, page.pageIndex());
page.selectIndex(15);
assertEquals(1, page.pageIndex());
page.selectIndex(16);
assertEquals(1, page.pageIndex());
}
}
@@ -0,0 +1,30 @@
package com.projectsacrifice.game;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public final class PhysicalObjectStateTest {
@Test
public void authoredObjectPublishesStableMassGeometryAndRuntimeState() {
LevelDefinition.PhysicalObject definition = new LevelDefinition.PhysicalObject(
LevelDefinition.PhysicalObjectType.COUNTERWEIGHT,
new LevelDefinition.Box(420f, 760f, 80f, 120f), 3);
PhysicalObjectState state = new PhysicalObjectState(definition);
state.velocity(120f, -30f).grounded(true).supportIndex(2);
assertEquals(LevelDefinition.PhysicalObjectType.COUNTERWEIGHT, state.type());
assertEquals(420f, state.weightedX(), 0f);
assertEquals(760f, state.weightedY(), 0f);
assertEquals(80f, state.weightedWidth(), 0f);
assertEquals(120f, state.weightedHeight(), 0f);
assertEquals(3, state.pressureMass());
assertTrue(state.activatesPressure());
assertEquals(120f, state.velocityX(), 0f);
assertEquals(-30f, state.velocityY(), 0f);
assertTrue(state.grounded());
assertEquals(2, state.supportIndex());
}
}
@@ -29,6 +29,10 @@ public final class VersionedSessionCodecTest {
assertTrue(restored.bodies().get(1).conductive());
assertTrue(restored.bodies().get(0).crushed());
assertTrue(restored.bodies().get(1).burned());
assertEquals(2, restored.objects().size());
assertEquals(LevelDefinition.PhysicalObjectType.BOX,
restored.objects().get(0).type());
assertEquals(3, restored.objects().get(1).mass());
assertEquals(Arrays.asList(1.25f, 3.5f), restored.platformMotionTimes());
assertTrue(restored.toggleSignals().get("gate"));
assertEquals(2.25f, restored.timedSignals().get("clock"), 0f);
@@ -58,6 +62,18 @@ public final class VersionedSessionCodecTest {
assertTrue(decoded.snapshot().previousSwitchContacts().get("gate@420.0"));
assertFalse(decoded.snapshot().bodies().get(0).crushed());
assertFalse(decoded.snapshot().bodies().get(1).burned());
assertTrue(decoded.snapshot().objects().isEmpty());
}
@Test
public void legacyV3MigratesWithSafeEmptyPhysicalObjectRuntime() {
VersionedSessionCodec.DecodeResult decoded = VersionedSessionCodec.decode(
VersionedSessionCodec.encodeLegacyV3(sampleSnapshot()));
assertEquals(VersionedSessionCodec.Status.MIGRATED, decoded.status());
assertTrue(decoded.snapshot().bodies().get(0).crushed());
assertTrue(decoded.snapshot().bodies().get(1).burned());
assertTrue(decoded.snapshot().objects().isEmpty());
}
@Test
@@ -105,6 +121,12 @@ public final class VersionedSessionCodecTest {
720f, 826f, 72f, 54f, 0f, 0f,
false, true, true, true, false, true,
-1, "FIRE"))
.addObject(new GameSessionSnapshot.ObjectData(
LevelDefinition.PhysicalObjectType.BOX,
300f, 800f, 80f, 80f, 20f, 0f, 1, true, -1))
.addObject(new GameSessionSnapshot.ObjectData(
LevelDefinition.PhysicalObjectType.COUNTERWEIGHT,
700f, 780f, 80f, 100f, 0f, 0f, 3, true, 1))
.platformMotionTimes(Arrays.asList(1.25f, 3.5f))
.toggleSignal("gate", true)
.timedSignal("clock", 2.25f)
@@ -0,0 +1,45 @@
package com.projectsacrifice.game;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import org.junit.Test;
public final class WeightedPressureResolverTest {
@Test
public void plateUsesOneMassContractForPlayerCorpseAndPhysicalObject() {
LevelDefinition.Box plate = new LevelDefinition.Box(100f, 200f, 180f, 20f);
IWeightedObject player = item(110f, 150f, 62f, 100f, 1, true);
IWeightedObject corpse = item(170f, 166f, 72f, 54f, 1, true);
IWeightedObject counterweight = item(230f, 140f, 70f, 80f, 3, true);
assertFalse(PressureMassResolver.active(
plate, 6, Arrays.asList(player, corpse, counterweight)));
assertTrue(PressureMassResolver.active(
plate, 5, Arrays.asList(player, corpse, counterweight)));
}
@Test
public void inactiveAndNonIntersectingObjectsNeverContributeMass() {
LevelDefinition.Box plate = new LevelDefinition.Box(100f, 200f, 180f, 20f);
IWeightedObject burnedCorpse = item(120f, 166f, 72f, 54f, 5, false);
IWeightedObject distantRock = item(500f, 160f, 60f, 60f, 5, true);
assertFalse(PressureMassResolver.active(
plate, 1, Arrays.asList(burnedCorpse, distantRock)));
}
private static IWeightedObject item(float x, float y, float width, float height,
int mass, boolean active) {
return new IWeightedObject() {
@Override public float weightedX() { return x; }
@Override public float weightedY() { return y; }
@Override public float weightedWidth() { return width; }
@Override public float weightedHeight() { return height; }
@Override public int pressureMass() { return mass; }
@Override public boolean activatesPressure() { return active; }
};
}
}