Добавить production-каталог опасностей
This commit is contained in:
@@ -6,7 +6,9 @@ import java.util.List;
|
||||
|
||||
/** Immutable, Android-independent level data. */
|
||||
public final class LevelDefinition {
|
||||
public enum HazardType { SPIKES, SAW, FREEZER, ELECTRIC, PIT }
|
||||
public enum HazardType {
|
||||
SPIKES, SAW, FREEZER, ELECTRIC, PIT, CRUSHER, FIRE, PROJECTILE, EXPLOSION
|
||||
}
|
||||
public enum SwitchType { PRESSURE, TOGGLE, TIMED }
|
||||
|
||||
public static final class Box {
|
||||
@@ -54,17 +56,36 @@ public final class LevelDefinition {
|
||||
public final String relayId;
|
||||
public final float impulseX;
|
||||
public final float impulseY;
|
||||
public final float cycleSeconds;
|
||||
public final float activeSeconds;
|
||||
public final float speedX;
|
||||
public final float speedY;
|
||||
public final String visualStyle;
|
||||
public final String audioCue;
|
||||
|
||||
public Hazard(HazardType type, Box box) {
|
||||
this(type, box, null, 0f, 0f);
|
||||
}
|
||||
|
||||
public Hazard(HazardType type, Box box, String relayId, float impulseX, float impulseY) {
|
||||
this(type, box, relayId, impulseX, impulseY,
|
||||
0f, 0f, 0f, 0f, null, null);
|
||||
}
|
||||
|
||||
public Hazard(HazardType type, Box box, String relayId, float impulseX, float impulseY,
|
||||
float cycleSeconds, float activeSeconds, float speedX, float speedY,
|
||||
String visualStyle, String audioCue) {
|
||||
this.type = type;
|
||||
this.box = box;
|
||||
this.relayId = relayId;
|
||||
this.impulseX = impulseX;
|
||||
this.impulseY = impulseY;
|
||||
this.cycleSeconds = cycleSeconds;
|
||||
this.activeSeconds = activeSeconds;
|
||||
this.speedX = speedX;
|
||||
this.speedY = speedY;
|
||||
this.visualStyle = visualStyle;
|
||||
this.audioCue = audioCue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,13 @@ public final class LevelJsonLoader {
|
||||
box(required(value, "box", path), path + ".box"),
|
||||
optionalText(value, "relayId", path),
|
||||
optionalDecimal(value, "impulseX", path, 0f),
|
||||
optionalDecimal(value, "impulseY", path, 0f)));
|
||||
optionalDecimal(value, "impulseY", path, 0f),
|
||||
optionalDecimal(value, "cycleSeconds", path, 0f),
|
||||
optionalDecimal(value, "activeSeconds", path, 0f),
|
||||
optionalDecimal(value, "speedX", path, 0f),
|
||||
optionalDecimal(value, "speedY", path, 0f),
|
||||
optionalText(value, "visualStyle", path),
|
||||
optionalText(value, "audioCue", path)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,47 @@ public final class LevelValidator {
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < level.hazards.size(); i++) {
|
||||
box(level.hazards.get(i).box, level, source + ".hazards[" + i + "].box");
|
||||
LevelDefinition.Hazard hazard = level.hazards.get(i);
|
||||
String path = source + ".hazards[" + i + "]";
|
||||
box(hazard.box, level, path + ".box");
|
||||
if (hazard.type == LevelDefinition.HazardType.CRUSHER) {
|
||||
positive(hazard.cycleSeconds, path + ".cycleSeconds");
|
||||
positive(hazard.activeSeconds, path + ".activeSeconds");
|
||||
if (hazard.activeSeconds > hazard.cycleSeconds) {
|
||||
throw invalid(path + ".activeSeconds", "must not exceed cycleSeconds");
|
||||
}
|
||||
requiredText(hazard.visualStyle, path + ".visualStyle");
|
||||
requiredText(hazard.audioCue, path + ".audioCue");
|
||||
}
|
||||
if (hazard.type == LevelDefinition.HazardType.FIRE) {
|
||||
requiredText(hazard.visualStyle, path + ".visualStyle");
|
||||
requiredText(hazard.audioCue, path + ".audioCue");
|
||||
}
|
||||
if (hazard.type == LevelDefinition.HazardType.PROJECTILE) {
|
||||
positive(hazard.cycleSeconds, path + ".cycleSeconds");
|
||||
if (hazard.speedX == 0f && hazard.speedY == 0f) {
|
||||
throw invalid(path + ".speedX", "or speedY must be non-zero");
|
||||
}
|
||||
LevelDefinition.Box destination = new LevelDefinition.Box(
|
||||
hazard.box.x + hazard.speedX * hazard.cycleSeconds,
|
||||
hazard.box.y + hazard.speedY * hazard.cycleSeconds,
|
||||
hazard.box.width, hazard.box.height);
|
||||
box(destination, level, path + ".destinationBox");
|
||||
requiredText(hazard.visualStyle, path + ".visualStyle");
|
||||
requiredText(hazard.audioCue, path + ".audioCue");
|
||||
}
|
||||
if (hazard.type == LevelDefinition.HazardType.EXPLOSION) {
|
||||
positive(hazard.cycleSeconds, path + ".cycleSeconds");
|
||||
positive(hazard.activeSeconds, path + ".activeSeconds");
|
||||
if (hazard.activeSeconds > hazard.cycleSeconds) {
|
||||
throw invalid(path + ".activeSeconds", "must not exceed cycleSeconds");
|
||||
}
|
||||
if (hazard.impulseX == 0f && hazard.impulseY == 0f) {
|
||||
throw invalid(path + ".impulseX", "or impulseY must be non-zero");
|
||||
}
|
||||
requiredText(hazard.visualStyle, path + ".visualStyle");
|
||||
requiredText(hazard.audioCue, path + ".audioCue");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < level.switches.size(); i++) {
|
||||
LevelDefinition.Switch item = level.switches.get(i);
|
||||
@@ -112,6 +152,10 @@ public final class LevelValidator {
|
||||
if (!Float.isFinite(value) || value <= 0f) throw invalid(path, "must be positive and finite");
|
||||
}
|
||||
|
||||
private static void requiredText(String value, String path) {
|
||||
if (value == null || value.trim().isEmpty()) throw invalid(path, "must not be blank");
|
||||
}
|
||||
|
||||
private static IllegalArgumentException invalid(String path, String message) {
|
||||
return new IllegalArgumentException(path + " " + message);
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"formatVersion":1,"physicsVersion":1,"levels":["level-01.json","level-02.json","level-03.json","level-04.json","level-05.json","level-06.json","level-07.json","level-08.json","level-09.json","level-10.json"]}
|
||||
{"formatVersion":1,"physicsVersion":1,"levels":["level-01.json","level-02.json","level-03.json","level-04.json","level-05.json","level-06.json","level-07.json","level-08.json","level-09.json","level-10.json","level-11.json","level-12.json","level-13.json","level-14.json"]}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"formatVersion":1,"physicsVersion":1,"number":11,
|
||||
"copy":{"en":{"title":"The Brass Breather","hint":"The press has a rhythm. Leave one volunteer, then cross while its jaws rest."},"ru":{"title":"Латунный пресс","hint":"У пресса есть ритм. Оставьте одного добровольца и пройдите, пока створки отдыхают."}},
|
||||
"world":{"width":2400,"height":1080},"spawn":{"x":130,"y":760},
|
||||
"goal":{"x":2180,"y":770,"width":86,"height":110},"targets":{"deaths":1,"seconds":35},
|
||||
"platforms":[{"box":{"x":0,"y":880,"width":2400,"height":200}}],
|
||||
"hazards":[{"type":"CRUSHER","box":{"x":700,"y":690,"width":150,"height":190},
|
||||
"impulseX":80,"impulseY":120,"cycleSeconds":4,"activeSeconds":2.5,
|
||||
"speedX":0,"speedY":0,"visualStyle":"brass-press","audioCue":"crusher-impact"}],
|
||||
"switches":[],"doors":[]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"formatVersion":1,"physicsVersion":1,"number":12,
|
||||
"copy":{"en":{"title":"Blue Funeral","hint":"Flame is final for the living. Sacrifice once, then jump the blue fire."},"ru":{"title":"Синее пламя","hint":"Огонь смертелен для живых. Пожертвуйте одним и перепрыгните синее пламя."}},
|
||||
"world":{"width":2400,"height":1080},"spawn":{"x":130,"y":760},
|
||||
"goal":{"x":2180,"y":770,"width":86,"height":110},"targets":{"deaths":1,"seconds":35},
|
||||
"platforms":[{"box":{"x":0,"y":880,"width":2400,"height":200}}],
|
||||
"hazards":[{"type":"FIRE","box":{"x":700,"y":790,"width":120,"height":90},
|
||||
"impulseX":160,"impulseY":-220,"cycleSeconds":0,"activeSeconds":0,
|
||||
"speedX":0,"speedY":0,"visualStyle":"blue-flame","audioCue":"fire-burst"}],
|
||||
"switches":[],"doors":[]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"formatVersion":1,"physicsVersion":1,"number":13,
|
||||
"copy":{"en":{"title":"Crossfire Clock","hint":"The clockwork bolt always returns. Read its line, then jump through the gap."},"ru":{"title":"Часовой обстрел","hint":"Заводной снаряд всегда возвращается. Прочитайте траекторию и прыгните в окно."}},
|
||||
"world":{"width":2400,"height":1080},"spawn":{"x":130,"y":760},
|
||||
"goal":{"x":2180,"y":770,"width":86,"height":110},"targets":{"deaths":1,"seconds":40},
|
||||
"platforms":[{"box":{"x":0,"y":880,"width":2400,"height":200}}],
|
||||
"hazards":[{"type":"PROJECTILE","box":{"x":1700,"y":790,"width":48,"height":48},
|
||||
"impulseX":420,"impulseY":-120,"cycleSeconds":3,"activeSeconds":0,
|
||||
"speedX":-400,"speedY":0,"visualStyle":"clockwork-bolt","audioCue":"projectile-hit"}],
|
||||
"switches":[],"doors":[]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"formatVersion":1,"physicsVersion":1,"number":14,
|
||||
"copy":{"en":{"title":"Steam Charge","hint":"The blast throws what it takes. Deliver a body to the plate and use the open gate."},"ru":{"title":"Паровой заряд","hint":"Взрыв отбрасывает жертву. Доставьте тело на плиту и пройдите в открытые ворота."}},
|
||||
"world":{"width":2500,"height":1080},"spawn":{"x":130,"y":760},
|
||||
"goal":{"x":2260,"y":770,"width":86,"height":110},"targets":{"deaths":1,"seconds":45},
|
||||
"platforms":[{"box":{"x":0,"y":880,"width":2500,"height":200}}],
|
||||
"hazards":[{"type":"EXPLOSION","box":{"x":620,"y":740,"width":140,"height":140},
|
||||
"impulseX":550,"impulseY":-500,"cycleSeconds":4,"activeSeconds":2,
|
||||
"speedX":0,"speedY":0,"visualStyle":"steam-blast","audioCue":"explosion-blast"}],
|
||||
"switches":[{"type":"PRESSURE","box":{"x":980,"y":860,"width":180,"height":20},
|
||||
"signalId":"blast-gate","durationSeconds":0,"requiredMass":1}],
|
||||
"doors":[{"box":{"x":1400,"y":560,"width":82,"height":320},"requiredSignals":["blast-gate"]}]
|
||||
}
|
||||
Reference in New Issue
Block a user