Добавить испытания и прогрессию по LevelID

This commit is contained in:
2026-08-19 09:25:32 +03:00
parent 259fcd316b
commit 3de8d45039
37 changed files with 968 additions and 63 deletions
@@ -24,6 +24,7 @@ public final class LevelCatalogLoader {
List<LevelDefinition> result = new ArrayList<>();
Set<Integer> numbers = new HashSet<>();
Set<String> levelIds = new HashSet<>();
String directory = directory(index);
for (int i = 0; i < entries.size(); i++) {
String path = directory + entries.get(i);
@@ -37,6 +38,10 @@ public final class LevelCatalogLoader {
throw new IllegalArgumentException(index + ".levels[" + i
+ "] duplicates level number " + level.number);
}
if (!levelIds.add(level.levelId)) {
throw new IllegalArgumentException(index + ".levels[" + i
+ "] duplicates level id '" + level.levelId + "'");
}
int expected = i + 1;
if (level.number != expected) {
throw new IllegalArgumentException(index + ".levels[" + i + "] expected level number "
@@ -89,6 +89,9 @@ public final class LevelContentTool {
}
rect(value, "spawn", 0, new LevelDefinition.Box(level.spawnX, level.spawnY, 62f, 100f), "#22c55e");
rect(value, "goal", 0, level.goal, "#38bdf8");
if (level.collectible != null) {
rect(value, "collectible", 0, level.collectible, "#facc15");
}
value.append("</svg>\n");
return value.toString();
}
@@ -107,6 +110,10 @@ public final class LevelContentTool {
row(value, level.number, "spawn", 0, "PLAYER", new LevelDefinition.Box(
level.spawnX, level.spawnY, 62f, 100f), 0f, 0f, "");
row(value, level.number, "goal", 0, "GOAL", level.goal, 0f, 0f, "");
if (level.collectible != null) {
row(value, level.number, "collectible", 0, "CACHE", level.collectible,
0f, 0f, level.levelId);
}
for (int i = 0; i < level.platforms.size(); i++) {
LevelDefinition.Platform item = level.platforms.get(i);
row(value, level.number, "platform", i, item.isMoving() ? "MOVING" : "STATIC",
@@ -131,6 +131,8 @@ public final class LevelDefinition {
public final int number;
public final int formatVersion;
public final int physicsVersion;
public final String levelId;
public final String worldId;
public final String title;
public final String hint;
public final String titleRu;
@@ -142,6 +144,7 @@ public final class LevelDefinition {
public final Box goal;
public final int targetDeaths;
public final int targetSeconds;
public final Box collectible;
public final List<PhysicalObject> objects;
public final List<Platform> platforms;
public final List<Hazard> hazards;
@@ -213,9 +216,41 @@ public final class LevelDefinition {
List<Hazard> hazards,
List<Switch> switches,
List<Door> doors
) {
this(formatVersion, physicsVersion, defaultLevelId(number), "legacy",
number, title, hint, titleRu, hintRu, worldWidth, worldHeight,
spawnX, spawnY, goal, targetDeaths, targetSeconds, null, objects,
platforms, hazards, switches, doors);
}
public LevelDefinition(
int formatVersion,
int physicsVersion,
String levelId,
String worldId,
int number,
String title,
String hint,
String titleRu,
String hintRu,
float worldWidth,
float worldHeight,
float spawnX,
float spawnY,
Box goal,
int targetDeaths,
int targetSeconds,
Box collectible,
List<PhysicalObject> objects,
List<Platform> platforms,
List<Hazard> hazards,
List<Switch> switches,
List<Door> doors
) {
this.formatVersion = formatVersion;
this.physicsVersion = physicsVersion;
this.levelId = levelId;
this.worldId = worldId;
this.number = number;
this.title = title;
this.hint = hint;
@@ -228,6 +263,7 @@ public final class LevelDefinition {
this.goal = goal;
this.targetDeaths = targetDeaths;
this.targetSeconds = targetSeconds;
this.collectible = collectible;
this.objects = Collections.unmodifiableList(objects);
this.platforms = Collections.unmodifiableList(platforms);
this.hazards = Collections.unmodifiableList(hazards);
@@ -235,6 +271,10 @@ public final class LevelDefinition {
this.doors = Collections.unmodifiableList(doors);
}
public static String defaultLevelId(int number) {
return "legacy.level-" + (number < 10 ? "0" : "") + number;
}
public boolean containsMechanic(HazardType type) {
for (Hazard hazard : hazards) {
if (hazard.type == type) return true;
@@ -41,11 +41,20 @@ public final class LevelJsonLoader {
List<LevelDefinition.Hazard> hazards = hazards(root, source);
List<LevelDefinition.Switch> switches = switches(root, source);
List<LevelDefinition.Door> doors = doors(root, source);
int number = integer(root, "number", source);
String levelId = root.containsKey("id")
? text(root, "id", source) : LevelDefinition.defaultLevelId(number);
String worldId = root.containsKey("worldId")
? text(root, "worldId", source) : "legacy";
LevelDefinition.Box collectible = root.containsKey("collectible")
? box(root.get("collectible"), source + ".collectible") : null;
return LevelValidator.validate(new LevelDefinition(
integer(root, "formatVersion", source),
integer(root, "physicsVersion", source),
integer(root, "number", source),
levelId,
worldId,
number,
text(english, "title", source + ".copy.en"),
text(english, "hint", source + ".copy.en"),
text(russian, "title", source + ".copy.ru"),
@@ -57,6 +66,7 @@ public final class LevelJsonLoader {
box(required(root, "goal", source), source + ".goal"),
integer(targets, "deaths", source + ".targets"),
integer(targets, "seconds", source + ".targets"),
collectible,
objects,
platforms,
hazards,
@@ -109,6 +109,13 @@ public final class LevelValidator {
for (int i = 0; i < level.doors.size(); i++) {
box(level.doors.get(i).box, level, source + ".doors[" + i + "].box");
}
if (level.collectible != null) box(level.collectible, level, source + ".collectible");
if (level.levelId == null || !level.levelId.matches("[a-z0-9][a-z0-9._-]{2,63}")) {
throw invalid(source + ".id", "must be a stable lowercase identifier");
}
if (level.worldId == null || !level.worldId.matches("[a-z0-9][a-z0-9._-]{2,31}")) {
throw invalid(source + ".worldId", "must be a stable lowercase identifier");
}
if (level.targetDeaths < 0) throw invalid(source + ".targets.deaths", "must not be negative");
if (level.targetSeconds <= 0) throw invalid(source + ".targets.seconds", "must be positive");
Set<String> signalSources = new HashSet<>();