Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1be7e90ec2 |
No files matched your search
+1
-1
@@ -12,7 +12,7 @@ mapping_version=2023.09.03-1.20.1
|
|||||||
mod_id=improved_aerodynamics
|
mod_id=improved_aerodynamics
|
||||||
mod_name=VS: Improved Aerodynamics
|
mod_name=VS: Improved Aerodynamics
|
||||||
mod_license=MIT
|
mod_license=MIT
|
||||||
mod_version=0.3.0
|
mod_version=0.4.0
|
||||||
azurelib_version=3.2.7
|
azurelib_version=3.2.7
|
||||||
mod_group_id=dev.lumareth.improvedaerodynamics
|
mod_group_id=dev.lumareth.improvedaerodynamics
|
||||||
mod_authors=Lumareth
|
mod_authors=Lumareth
|
||||||
|
|||||||
@@ -131,6 +131,26 @@ public final class Config {
|
|||||||
.comment("Auto trim: below this airspeed (blocks per second) the trim holds its current value")
|
.comment("Auto trim: below this airspeed (blocks per second) the trim holds its current value")
|
||||||
.defineInRange("trimMinSpeed", 10.0D, 0.0D, 100.0D);
|
.defineInRange("trimMinSpeed", 10.0D, 0.0D, 100.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue FLAP_TAKEOFF_DEGREES = BUILDER
|
||||||
|
.comment("Flaps: deflection in degrees of flaps at the takeoff setting. Flaps need the improved flight model")
|
||||||
|
.defineInRange("flapTakeoffDegrees", 15.0D, 0.0D, 60.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue FLAP_LANDING_DEGREES = BUILDER
|
||||||
|
.comment("Flaps: deflection in degrees of flaps at the landing setting")
|
||||||
|
.defineInRange("flapLandingDegrees", 35.0D, 0.0D, 60.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue FLAP_SLEW_DEGREES = BUILDER
|
||||||
|
.comment("Flaps: how fast they move, in degrees per second")
|
||||||
|
.defineInRange("flapSlewDegrees", 10.0D, 1.0D, 90.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue FLAP_TAKEOFF_MAX_SPEED = BUILDER
|
||||||
|
.comment("Flaps: airspeed in blocks per second above which the air pushes takeoff flaps back in, fully retracted at 25% more")
|
||||||
|
.defineInRange("flapTakeoffMaxSpeed", 45.0D, 1.0D, 1000.0D);
|
||||||
|
|
||||||
|
public static final ForgeConfigSpec.DoubleValue FLAP_LANDING_MAX_SPEED = BUILDER
|
||||||
|
.comment("Flaps: the same for the landing setting")
|
||||||
|
.defineInRange("flapLandingMaxSpeed", 35.0D, 1.0D, 1000.0D);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
BUILDER.pop();
|
BUILDER.pop();
|
||||||
BUILDER.comment("Vectored afterburner nozzle block").push("nozzle");
|
BUILDER.comment("Vectored afterburner nozzle block").push("nozzle");
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ public final class ConfigCommands {
|
|||||||
new Option<>("exhaust_style", Config.EXHAUST_STYLE, StringArgumentType.word(), String.class, List.of("wariumvs", "improved")),
|
new Option<>("exhaust_style", Config.EXHAUST_STYLE, StringArgumentType.word(), String.class, List.of("wariumvs", "improved")),
|
||||||
new Option<>("nozzle_yaw_mix", Config.NOZZLE_YAW_MIX, DoubleArgumentType.doubleArg(), Double.class),
|
new Option<>("nozzle_yaw_mix", Config.NOZZLE_YAW_MIX, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
new Option<>("nozzle_light", Config.NOZZLE_LIGHT, BoolArgumentType.bool(), Boolean.class),
|
new Option<>("nozzle_light", Config.NOZZLE_LIGHT, BoolArgumentType.bool(), Boolean.class),
|
||||||
|
new Option<>("flap_takeoff_degrees", Config.FLAP_TAKEOFF_DEGREES, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("flap_landing_degrees", Config.FLAP_LANDING_DEGREES, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("flap_slew_degrees", Config.FLAP_SLEW_DEGREES, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("flap_takeoff_max_speed", Config.FLAP_TAKEOFF_MAX_SPEED, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
|
new Option<>("flap_landing_max_speed", Config.FLAP_LANDING_MAX_SPEED, DoubleArgumentType.doubleArg(), Double.class),
|
||||||
new Option<>("lookahead_seconds", Config.LOOKAHEAD_SECONDS, DoubleArgumentType.doubleArg(), Double.class));
|
new Option<>("lookahead_seconds", Config.LOOKAHEAD_SECONDS, DoubleArgumentType.doubleArg(), Double.class));
|
||||||
|
|
||||||
private ConfigCommands() {
|
private ConfigCommands() {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class ImprovedAerodynamics {
|
|||||||
MinecraftForge.EVENT_BUS.register(PhysicsBridge.class);
|
MinecraftForge.EVENT_BUS.register(PhysicsBridge.class);
|
||||||
MinecraftForge.EVENT_BUS.register(ConfigCommands.class);
|
MinecraftForge.EVENT_BUS.register(ConfigCommands.class);
|
||||||
MinecraftForge.EVENT_BUS.register(ExhaustSync.class);
|
MinecraftForge.EVENT_BUS.register(ExhaustSync.class);
|
||||||
|
MinecraftForge.EVENT_BUS.register(dev.lumareth.improvedAerodynamics.flight.FlapInteraction.class);
|
||||||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, Config.SPEC);
|
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, Config.SPEC);
|
||||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_SPEC);
|
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, Config.CLIENT_SPEC);
|
||||||
NozzleContent.register(FMLJavaModLoadingContext.get().getModEventBus());
|
NozzleContent.register(FMLJavaModLoadingContext.get().getModEventBus());
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dev.lumareth.improvedAerodynamics.client;
|
|||||||
|
|
||||||
import com.mojang.blaze3d.platform.InputConstants;
|
import com.mojang.blaze3d.platform.InputConstants;
|
||||||
import dev.lumareth.improvedAerodynamics.ImprovedAerodynamics;
|
import dev.lumareth.improvedAerodynamics.ImprovedAerodynamics;
|
||||||
|
import dev.lumareth.improvedAerodynamics.network.FlapPacket;
|
||||||
import dev.lumareth.improvedAerodynamics.network.Network;
|
import dev.lumareth.improvedAerodynamics.network.Network;
|
||||||
import dev.lumareth.improvedAerodynamics.network.ToggleAfterburnerPacket;
|
import dev.lumareth.improvedAerodynamics.network.ToggleAfterburnerPacket;
|
||||||
import dev.lumareth.improvedAerodynamics.network.ToggleAutoTrimPacket;
|
import dev.lumareth.improvedAerodynamics.network.ToggleAutoTrimPacket;
|
||||||
@@ -22,6 +23,12 @@ public final class KeyBindings {
|
|||||||
public static final KeyMapping TOGGLE_AFTERBURNER = new KeyMapping("key.improved_aerodynamics.toggle_afterburner",
|
public static final KeyMapping TOGGLE_AFTERBURNER = new KeyMapping("key.improved_aerodynamics.toggle_afterburner",
|
||||||
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_K, "key.categories.improved_aerodynamics");
|
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_K, "key.categories.improved_aerodynamics");
|
||||||
|
|
||||||
|
public static final KeyMapping FLAPS_DOWN = new KeyMapping("key.improved_aerodynamics.flaps_down",
|
||||||
|
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_G, "key.categories.improved_aerodynamics");
|
||||||
|
|
||||||
|
public static final KeyMapping FLAPS_UP = new KeyMapping("key.improved_aerodynamics.flaps_up",
|
||||||
|
KeyConflictContext.IN_GAME, InputConstants.Type.KEYSYM, GLFW.GLFW_KEY_H, "key.categories.improved_aerodynamics");
|
||||||
|
|
||||||
private KeyBindings() {
|
private KeyBindings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +41,8 @@ public final class KeyBindings {
|
|||||||
public static void onRegisterKeyMappings(RegisterKeyMappingsEvent event) {
|
public static void onRegisterKeyMappings(RegisterKeyMappingsEvent event) {
|
||||||
event.register(TOGGLE_AUTO_TRIM);
|
event.register(TOGGLE_AUTO_TRIM);
|
||||||
event.register(TOGGLE_AFTERBURNER);
|
event.register(TOGGLE_AFTERBURNER);
|
||||||
|
event.register(FLAPS_DOWN);
|
||||||
|
event.register(FLAPS_UP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +70,19 @@ public final class KeyBindings {
|
|||||||
Network.CHANNEL.sendToServer(new ToggleAfterburnerPacket());
|
Network.CHANNEL.sendToServer(new ToggleAfterburnerPacket());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while (FLAPS_DOWN.consumeClick()) {
|
||||||
|
sendFlaps(minecraft, true);
|
||||||
|
}
|
||||||
|
while (FLAPS_UP.consumeClick()) {
|
||||||
|
sendFlaps(minecraft, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendFlaps(Minecraft minecraft, boolean extend) {
|
||||||
|
if (minecraft.player != null && minecraft.player.getVehicle() != null && minecraft.getConnection() != null
|
||||||
|
&& Network.CHANNEL.isRemotePresent(minecraft.getConnection().getConnection())) {
|
||||||
|
Network.CHANNEL.sendToServer(new FlapPacket(extend));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,8 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
double roll;
|
double roll;
|
||||||
double trimIntegral;
|
double trimIntegral;
|
||||||
double trimOutput;
|
double trimOutput;
|
||||||
|
// Deflection of this node's flaps in radians, moves towards what the flap lever and airspeed allow
|
||||||
|
double flapAngle;
|
||||||
// Torque (ship axes) one unit of pitch input on this node's pitch surfaces makes, measured every substep
|
// Torque (ship axes) one unit of pitch input on this node's pitch surfaces makes, measured every substep
|
||||||
final Vector3d pitchResponse = new Vector3d();
|
final Vector3d pitchResponse = new Vector3d();
|
||||||
final Vector3d pitchResponseBuilding = new Vector3d();
|
final Vector3d pitchResponseBuilding = new Vector3d();
|
||||||
@@ -67,6 +69,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
trimIntegral = data.autoTrim() ? data.storedTrim() : 0.0;
|
trimIntegral = data.autoTrim() ? data.storedTrim() : 0.0;
|
||||||
trimOutput = trimIntegral;
|
trimOutput = trimIntegral;
|
||||||
savedTrim = trimIntegral;
|
savedTrim = trimIntegral;
|
||||||
|
flapAngle = Math.toRadians(FlapSetting.of(data.flapSetting()).degrees());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,9 +225,10 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
NodeData node = s.node() != AeroSurface.NO_NODE ? frame.nodes.get(s.node()) : null;
|
NodeData node = s.node() != AeroSurface.NO_NODE ? frame.nodes.get(s.node()) : null;
|
||||||
NodeState state = node != null ? nodeStates.get(node.key()) : null;
|
NodeState state = node != null ? nodeStates.get(node.key()) : null;
|
||||||
double input = s.type().controllable ? input(s, node, state, 0.0) : 0.0;
|
double input = s.type().controllable ? input(s, node, state, 0.0) : 0.0;
|
||||||
|
double flap = s.flap() && state != null ? state.flapAngle : 0.0;
|
||||||
|
|
||||||
if (improved) {
|
if (improved) {
|
||||||
boolean nowStalled = ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], out);
|
boolean nowStalled = ImprovedAero.evaluate(s, v, w, r, input, flap, weight, tuning, stalled[i], out);
|
||||||
if (commit) {
|
if (commit) {
|
||||||
stalled[i] = nowStalled;
|
stalled[i] = nowStalled;
|
||||||
wingStalled |= nowStalled && s.type() == SurfaceType.WING;
|
wingStalled |= nowStalled && s.type() == SurfaceType.WING;
|
||||||
@@ -239,7 +243,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
probe.clear();
|
probe.clear();
|
||||||
double base = 0.0;
|
double base = 0.0;
|
||||||
if (improved) {
|
if (improved) {
|
||||||
ImprovedAero.evaluate(s, v, w, r, input, weight, tuning, stalled[i], probe);
|
ImprovedAero.evaluate(s, v, w, r, input, flap, weight, tuning, stalled[i], probe);
|
||||||
} else {
|
} else {
|
||||||
WariumAero.evaluate(s, v, w, r, input, weight, probe);
|
WariumAero.evaluate(s, v, w, r, input, weight, probe);
|
||||||
}
|
}
|
||||||
@@ -247,7 +251,7 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
probe.clear();
|
probe.clear();
|
||||||
double nudged = input(s, node, state, delta);
|
double nudged = input(s, node, state, delta);
|
||||||
if (improved) {
|
if (improved) {
|
||||||
ImprovedAero.evaluate(s, v, w, r, nudged, weight, tuning, stalled[i], probe);
|
ImprovedAero.evaluate(s, v, w, r, nudged, flap, weight, tuning, stalled[i], probe);
|
||||||
} else {
|
} else {
|
||||||
WariumAero.evaluate(s, v, w, r, nudged, weight, probe);
|
WariumAero.evaluate(s, v, w, r, nudged, weight, probe);
|
||||||
}
|
}
|
||||||
@@ -296,10 +300,25 @@ public final class AeroState implements PhysicsBridge.Publishable {
|
|||||||
state.pitch = moveTowards(state.pitch, node.pitch(), step);
|
state.pitch = moveTowards(state.pitch, node.pitch(), step);
|
||||||
state.yaw = moveTowards(state.yaw, node.yaw(), step);
|
state.yaw = moveTowards(state.yaw, node.yaw(), step);
|
||||||
state.roll = moveTowards(state.roll, node.roll(), step);
|
state.roll = moveTowards(state.roll, node.roll(), step);
|
||||||
|
updateFlaps(state, node, worldVelocity.length(), dt);
|
||||||
AutoTrim.update(state, node, worldVelocity, altitude, rotation, inertia, w, dt, improved && wingStalled);
|
AutoTrim.update(state, node, worldVelocity, altitude, rotation, inertia, w, dt, improved && wingStalled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a node's flaps towards the angle its lever asks for. Above the setting's maximum speed the air pushes them
|
||||||
|
* back in, so a craft that is too fast for its flaps has them half retracted, and all the way at 25% more.
|
||||||
|
*/
|
||||||
|
private static void updateFlaps(NodeState state, NodeData node, double speed, double dt) {
|
||||||
|
FlapSetting setting = FlapSetting.of(node.flapSetting());
|
||||||
|
double target = Math.toRadians(setting.degrees());
|
||||||
|
double limit = setting.maxSpeed();
|
||||||
|
if (speed > limit) {
|
||||||
|
target *= Math.max(0.0, 1.0 - (speed - limit) / (0.25 * limit));
|
||||||
|
}
|
||||||
|
state.flapAngle = moveTowards(state.flapAngle, target, Math.toRadians(Config.FLAP_SLEW_DEGREES.get()) * dt);
|
||||||
|
}
|
||||||
|
|
||||||
static double moveTowards(double value, double target, double maxStep) {
|
static double moveTowards(double value, double target, double maxStep) {
|
||||||
if (Math.abs(target - value) <= maxStep) {
|
if (Math.abs(target - value) <= maxStep) {
|
||||||
return target;
|
return target;
|
||||||
|
|||||||
@@ -14,16 +14,17 @@ package dev.lumareth.improvedAerodynamics.flight;
|
|||||||
* @param brakeInput air brakes only: Warium's input value, they ignore smoothing and trim
|
* @param brakeInput air brakes only: Warium's input value, they ignore smoothing and trim
|
||||||
* @param aspectRatio span squared over area of the wing the block belongs to, 0 if not known yet
|
* @param aspectRatio span squared over area of the wing the block belongs to, 0 if not known yet
|
||||||
* @param wingGroup whether the wing the block belongs to contains WING blocks, not only control surfaces or stabilizers
|
* @param wingGroup whether the wing the block belongs to contains WING blocks, not only control surfaces or stabilizers
|
||||||
|
* @param flap whether the block was made a flap with shift-right-click, it then also follows its node's flap setting
|
||||||
*/
|
*/
|
||||||
public record AeroSurface(long key, SurfaceType type, boolean slab, double x, double y, double z, int facingX, int facingZ,
|
public record AeroSurface(long key, SurfaceType type, boolean slab, double x, double y, double z, int facingX, int facingZ,
|
||||||
double pressure, double fluidFactor, double speedMultiplier,
|
double pressure, double fluidFactor, double speedMultiplier,
|
||||||
long node, ControlAxis axis1, double multiplier1, ControlAxis axis2, double multiplier2,
|
long node, ControlAxis axis1, double multiplier1, ControlAxis axis2, double multiplier2,
|
||||||
double brakeInput, double aspectRatio, boolean wingGroup) {
|
double brakeInput, double aspectRatio, boolean wingGroup, boolean flap) {
|
||||||
public static final long NO_NODE = Long.MIN_VALUE;
|
public static final long NO_NODE = Long.MIN_VALUE;
|
||||||
|
|
||||||
AeroSurface withWingShape(double aspectRatio, boolean wingGroup) {
|
AeroSurface withWingShape(double aspectRatio, boolean wingGroup) {
|
||||||
return new AeroSurface(key, type, slab, x, y, z, facingX, facingZ, pressure, fluidFactor, speedMultiplier,
|
return new AeroSurface(key, type, slab, x, y, z, facingX, facingZ, pressure, fluidFactor, speedMultiplier,
|
||||||
node, axis1, multiplier1, axis2, multiplier2, brakeInput, aspectRatio, wingGroup);
|
node, axis1, multiplier1, axis2, multiplier2, brakeInput, aspectRatio, wingGroup, flap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Warium's "Direction.getClockWise(Axis.Y)" of the facing, the span direction of a horizontal surface. */
|
/** Warium's "Direction.getClockWise(Axis.Y)" of the facing, the span direction of a horizontal surface. */
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.lumareth.improvedAerodynamics.flight;
|
||||||
|
|
||||||
|
import net.mcreator.valkyrienwarium.entity.SeatEntityEntity;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
|
||||||
|
/** Server side of the flap keys: moves the flap lever of the control node the pilot's seat is linked to. */
|
||||||
|
public final class FlapControl {
|
||||||
|
private FlapControl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param step +1 for more flaps, -1 for fewer */
|
||||||
|
public static void step(ServerPlayer player, int step) {
|
||||||
|
Entity vehicle = player.getVehicle();
|
||||||
|
if (!(vehicle instanceof SeatEntityEntity)) {
|
||||||
|
player.displayClientMessage(Component.translatable("message.improved_aerodynamics.flaps.no_seat"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BlockEntity node = AutoTrimToggle.findNode(vehicle.level(), vehicle.blockPosition());
|
||||||
|
if (node == null) {
|
||||||
|
player.displayClientMessage(Component.translatable("message.improved_aerodynamics.flaps.no_node"), true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CompoundTag tag = node.getPersistentData();
|
||||||
|
int current = FlapSetting.of(tag.getInt(WariumCapture.FLAP_SETTING_TAG)).ordinal();
|
||||||
|
int next = Math.max(0, Math.min(FlapSetting.values().length - 1, current + (step > 0 ? 1 : -1)));
|
||||||
|
if (next != current) {
|
||||||
|
tag.putInt(WariumCapture.FLAP_SETTING_TAG, next);
|
||||||
|
node.setChanged();
|
||||||
|
}
|
||||||
|
player.displayClientMessage(Component.translatable("message.improved_aerodynamics.flaps",
|
||||||
|
Component.translatable(FlapSetting.of(next).translationKey())), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package dev.lumareth.improvedAerodynamics.flight;
|
||||||
|
|
||||||
|
import dev.lumareth.improvedAerodynamics.ImprovedAerodynamics;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.tags.TagKey;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift-right-click on a block in the {@code improved_aerodynamics:flappable} tag (Warium's horizontal control
|
||||||
|
* surfaces) with empty hands makes it a flap, or a normal control surface again. A flap follows the flap lever of the
|
||||||
|
* control node it is linked to, on top of the pilot's input.
|
||||||
|
*/
|
||||||
|
public final class FlapInteraction {
|
||||||
|
public static final TagKey<Block> FLAPPABLE = TagKey.create(Registries.BLOCK,
|
||||||
|
new ResourceLocation(ImprovedAerodynamics.MOD_ID, "flappable"));
|
||||||
|
|
||||||
|
private FlapInteraction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) {
|
||||||
|
Player player = event.getEntity();
|
||||||
|
if (event.getHand() != InteractionHand.MAIN_HAND || !player.isShiftKeyDown() || player.isPassenger()
|
||||||
|
|| !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Level level = event.getLevel();
|
||||||
|
BlockPos pos = event.getPos();
|
||||||
|
BlockState state = level.getBlockState(pos);
|
||||||
|
if (!state.is(FLAPPABLE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Handled on both sides so the block's own right-click action does not run on either
|
||||||
|
event.setCanceled(true);
|
||||||
|
event.setCancellationResult(InteractionResult.sidedSuccess(level.isClientSide));
|
||||||
|
if (level.isClientSide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||||
|
if (blockEntity == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CompoundTag tag = blockEntity.getPersistentData();
|
||||||
|
boolean flap = !tag.getBoolean(WariumCapture.FLAP_TAG);
|
||||||
|
tag.putBoolean(WariumCapture.FLAP_TAG, flap);
|
||||||
|
blockEntity.setChanged();
|
||||||
|
if (flap && !tag.contains("ControlX")) {
|
||||||
|
player.displayClientMessage(Component.translatable("message.improved_aerodynamics.flap.unlinked"), true);
|
||||||
|
} else {
|
||||||
|
player.displayClientMessage(Component.translatable(flap
|
||||||
|
? "message.improved_aerodynamics.flap.on" : "message.improved_aerodynamics.flap.off"), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package dev.lumareth.improvedAerodynamics.flight;
|
||||||
|
|
||||||
|
import dev.lumareth.improvedAerodynamics.Config;
|
||||||
|
|
||||||
|
/** Positions of a control node's flap lever. */
|
||||||
|
public enum FlapSetting {
|
||||||
|
UP,
|
||||||
|
TAKEOFF,
|
||||||
|
LANDING;
|
||||||
|
|
||||||
|
/** The setting for a stored number, UP for anything that is not one. */
|
||||||
|
public static FlapSetting of(int ordinal) {
|
||||||
|
FlapSetting[] values = values();
|
||||||
|
return ordinal >= 0 && ordinal < values.length ? values[ordinal] : UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deflection in degrees the flaps move to at this setting. */
|
||||||
|
double degrees() {
|
||||||
|
return switch (this) {
|
||||||
|
case UP -> 0.0;
|
||||||
|
case TAKEOFF -> Config.FLAP_TAKEOFF_DEGREES.get();
|
||||||
|
case LANDING -> Config.FLAP_LANDING_DEGREES.get();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Airspeed in blocks per second above which the air starts to push the flaps back in. Infinite when they are up. */
|
||||||
|
double maxSpeed() {
|
||||||
|
return switch (this) {
|
||||||
|
case UP -> Double.POSITIVE_INFINITY;
|
||||||
|
case TAKEOFF -> Config.FLAP_TAKEOFF_MAX_SPEED.get();
|
||||||
|
case LANDING -> Config.FLAP_LANDING_MAX_SPEED.get();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public String translationKey() {
|
||||||
|
return "message.improved_aerodynamics.flaps." + name().toLowerCase(java.util.Locale.ROOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,12 @@ final class ImprovedAero {
|
|||||||
private static final double MAX_DEFLECTION = Math.toRadians(20.0);
|
private static final double MAX_DEFLECTION = Math.toRadians(20.0);
|
||||||
// Share of the deflection that acts as angle of attack, gives Warium's control authority at the design speed
|
// Share of the deflection that acts as angle of attack, gives Warium's control authority at the design speed
|
||||||
private static final double FLAP_EFFECTIVENESS = 0.75;
|
private static final double FLAP_EFFECTIVENESS = 0.75;
|
||||||
|
// How much of the lift a flap adds raises the stall angle, and the parasitic drag coefficient per radian² of flap
|
||||||
|
// Share of the flap's deflection that acts as angle of attack: 35 degrees of landing flap adds about 1.1 to the
|
||||||
|
// lift coefficient, like a real plain or slotted flap
|
||||||
|
private static final double FLAP_ALPHA_FACTOR = 0.4;
|
||||||
|
private static final double FLAP_STALL_GAIN = 0.7;
|
||||||
|
private static final double FLAP_DRAG = 0.35;
|
||||||
// Angle of attack of a level cambered wing, where K is calibrated
|
// Angle of attack of a level cambered wing, where K is calibrated
|
||||||
private static final double REFERENCE_ANGLE = Math.toRadians(4.0);
|
private static final double REFERENCE_ANGLE = Math.toRadians(4.0);
|
||||||
private static final double REFERENCE_SPEED = 40.0;
|
private static final double REFERENCE_SPEED = 40.0;
|
||||||
@@ -62,10 +68,11 @@ final class ImprovedAero {
|
|||||||
* @param w ship angular velocity, ship axes
|
* @param w ship angular velocity, ship axes
|
||||||
* @param r block center relative to the center of mass, ship axes
|
* @param r block center relative to the center of mass, ship axes
|
||||||
* @param input control input of the surface, -1 to 1
|
* @param input control input of the surface, -1 to 1
|
||||||
|
* @param flap flap deflection in radians, 0 for surfaces that are not flaps
|
||||||
* @param stalled whether the surface was stalled in the previous substep
|
* @param stalled whether the surface was stalled in the previous substep
|
||||||
* @return whether the surface is stalled now
|
* @return whether the surface is stalled now
|
||||||
*/
|
*/
|
||||||
static boolean evaluate(AeroSurface s, double[] v, double[] w, double[] r, double input, double weight,
|
static boolean evaluate(AeroSurface s, double[] v, double[] w, double[] r, double input, double flap, double weight,
|
||||||
Tuning tuning, boolean stalled, Wrench out) {
|
Tuning tuning, boolean stalled, Wrench out) {
|
||||||
// Airflow at the block: ship velocity plus w x r
|
// Airflow at the block: ship velocity plus w x r
|
||||||
double vx = v[0] + w[1] * r[2] - w[2] * r[1];
|
double vx = v[0] + w[1] * r[2] - w[2] * r[1];
|
||||||
@@ -114,6 +121,10 @@ final class ImprovedAero {
|
|||||||
// Same sign convention as Warium: positive input pushes along n while moving against the facing
|
// Same sign convention as Warium: positive input pushes along n while moving against the facing
|
||||||
alpha += FLAP_EFFECTIVENESS * MAX_DEFLECTION * input * (u <= 0.0 ? 1.0 : -1.0);
|
alpha += FLAP_EFFECTIVENESS * MAX_DEFLECTION * input * (u <= 0.0 ? 1.0 : -1.0);
|
||||||
}
|
}
|
||||||
|
// A flap bends the airfoil's camber whichever way the air comes, so it always adds lift, and it moves the stall
|
||||||
|
// up with it: a flap raises the highest lift a wing can make
|
||||||
|
double flapShift = FLAP_ALPHA_FACTOR * flap;
|
||||||
|
alpha += flapShift;
|
||||||
alpha = Math.max(-Math.PI / 2.0, Math.min(Math.PI / 2.0, alpha));
|
alpha = Math.max(-Math.PI / 2.0, Math.min(Math.PI / 2.0, alpha));
|
||||||
|
|
||||||
// Low aspect ratio wings keep their flow attached longer thanks to leading-edge vortices. Control surfaces
|
// Low aspect ratio wings keep their flow attached longer thanks to leading-edge vortices. Control surfaces
|
||||||
@@ -124,6 +135,9 @@ final class ImprovedAero {
|
|||||||
: s.wingGroup() && s.type().controllable ? SurfaceType.WING.stallAngle : s.type().stallAngle;
|
: s.wingGroup() && s.type().controllable ? SurfaceType.WING.stallAngle : s.type().stallAngle;
|
||||||
double critical = Math.toRadians(baseStall) * (1.0 + tuning.vortexStallBonus() * vortexFactor) * tuning.stallAngleScale();
|
double critical = Math.toRadians(baseStall) * (1.0 + tuning.vortexStallBonus() * vortexFactor) * tuning.stallAngleScale();
|
||||||
double absAlpha = Math.abs(alpha);
|
double absAlpha = Math.abs(alpha);
|
||||||
|
if (alpha >= 0.0) {
|
||||||
|
critical += FLAP_STALL_GAIN * flapShift;
|
||||||
|
}
|
||||||
// Flips only where both blends agree, so the hysteresis never makes the force jump
|
// Flips only where both blends agree, so the hysteresis never makes the force jump
|
||||||
if (!stalled && absAlpha > critical + STALL_BLEND) {
|
if (!stalled && absAlpha > critical + STALL_BLEND) {
|
||||||
stalled = true;
|
stalled = true;
|
||||||
@@ -156,7 +170,7 @@ final class ImprovedAero {
|
|||||||
double dy = -(vn * ny) / planeSpeed;
|
double dy = -(vn * ny) / planeSpeed;
|
||||||
double dz = -(u * cz + vn * nz) / planeSpeed;
|
double dz = -(u * cz + vn * nz) / planeSpeed;
|
||||||
double liftForce = q * lift * tuning.liftScale();
|
double liftForce = q * lift * tuning.liftScale();
|
||||||
double dragForce = q * drag / m;
|
double dragForce = q * (drag + FLAP_DRAG * flap * flap) / m;
|
||||||
out.addForce(r, lx * liftForce + dx * dragForce, ly * liftForce + dy * dragForce, lz * liftForce + dz * dragForce);
|
out.addForce(r, lx * liftForce + dx * dragForce, ly * liftForce + dy * dragForce, lz * liftForce + dz * dragForce);
|
||||||
return stalled;
|
return stalled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package dev.lumareth.improvedAerodynamics.flight;
|
|||||||
* trim state.
|
* trim state.
|
||||||
*
|
*
|
||||||
* @param storedTrim trim saved in the node, used when the physics thread first sees the node
|
* @param storedTrim trim saved in the node, used when the physics thread first sees the node
|
||||||
|
* @param flapSetting the node's flap lever, see {@link FlapSetting#ordinal()}
|
||||||
*/
|
*/
|
||||||
public record NodeData(long key, double pitch, double yaw, double roll, double throttle, boolean autoTrim, double storedTrim) {
|
public record NodeData(long key, double pitch, double yaw, double roll, double throttle, boolean autoTrim, double storedTrim, int flapSetting) {
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,9 @@ import org.valkyrienskies.mod.common.VSGameUtilsKt;
|
|||||||
public final class WariumCapture {
|
public final class WariumCapture {
|
||||||
public static final String AUTO_TRIM_TAG = "improved_aerodynamics_autotrim";
|
public static final String AUTO_TRIM_TAG = "improved_aerodynamics_autotrim";
|
||||||
public static final String TRIM_TAG = "improved_aerodynamics_trim";
|
public static final String TRIM_TAG = "improved_aerodynamics_trim";
|
||||||
|
/** Block entity flag of a surface that is a flap, and the flap lever of a control node. */
|
||||||
|
public static final String FLAP_TAG = "improved_aerodynamics_flap";
|
||||||
|
public static final String FLAP_SETTING_TAG = "improved_aerodynamics_flap_setting";
|
||||||
private static final int TRIM_SAVE_INTERVAL = 20;
|
private static final int TRIM_SAVE_INTERVAL = 20;
|
||||||
|
|
||||||
// The procedure currently running, procedures run one at a time on the server thread
|
// The procedure currently running, procedures run one at a time on the server thread
|
||||||
@@ -77,10 +80,12 @@ public final class WariumCapture {
|
|||||||
String key = "Null";
|
String key = "Null";
|
||||||
String key2 = "Null";
|
String key2 = "Null";
|
||||||
double brakeInput = 0.0;
|
double brakeInput = 0.0;
|
||||||
|
boolean flap = false;
|
||||||
if (type.controllable || type == SurfaceType.AIR_BRAKE) {
|
if (type.controllable || type == SurfaceType.AIR_BRAKE) {
|
||||||
BlockEntity blockEntity = level.getBlockEntity(pos);
|
BlockEntity blockEntity = level.getBlockEntity(pos);
|
||||||
if (blockEntity != null) {
|
if (blockEntity != null) {
|
||||||
CompoundTag tag = blockEntity.getPersistentData();
|
CompoundTag tag = blockEntity.getPersistentData();
|
||||||
|
flap = type == SurfaceType.CONTROL_SURFACE && tag.getBoolean(FLAP_TAG);
|
||||||
key = tag.getString("Key");
|
key = tag.getString("Key");
|
||||||
key2 = type == SurfaceType.AIR_BRAKE ? "Null" : tag.getString("Key2");
|
key2 = type == SurfaceType.AIR_BRAKE ? "Null" : tag.getString("Key2");
|
||||||
BlockPos nodePos = BlockPos.containing(tag.getDouble("ControlX"), tag.getDouble("ControlY"), tag.getDouble("ControlZ"));
|
BlockPos nodePos = BlockPos.containing(tag.getDouble("ControlX"), tag.getDouble("ControlY"), tag.getDouble("ControlZ"));
|
||||||
@@ -97,7 +102,7 @@ public final class WariumCapture {
|
|||||||
return new AeroSurface(pos.asLong(), type, slab, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
|
return new AeroSurface(pos.asLong(), type, slab, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
|
||||||
facing.getStepX(), facing.getStepZ(), pressure, fluidFactor(level), speedMultiplier,
|
facing.getStepX(), facing.getStepZ(), pressure, fluidFactor(level), speedMultiplier,
|
||||||
node, ControlAxis.of(key), ControlAxis.multiplier(key), ControlAxis.of(key2), ControlAxis.multiplier(key2),
|
node, ControlAxis.of(key), ControlAxis.multiplier(key), ControlAxis.of(key2), ControlAxis.multiplier(key2),
|
||||||
brakeInput, 0.0, false);
|
brakeInput, 0.0, false, flap);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Registers a node's inputs once per tick, and saves its trim to it now and then so it survives reloading. */
|
/** Registers a node's inputs once per tick, and saves its trim to it now and then so it survives reloading. */
|
||||||
@@ -119,7 +124,8 @@ public final class WariumCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
aero.addNode(new NodeData(nodeKey, tag.getDouble("Pitch"), tag.getDouble("Yaw"), tag.getDouble("Roll"),
|
aero.addNode(new NodeData(nodeKey, tag.getDouble("Pitch"), tag.getDouble("Yaw"), tag.getDouble("Roll"),
|
||||||
tag.getDouble("Throttle"), autoTrim, storedTrim));
|
tag.getDouble("Throttle"), autoTrim, storedTrim,
|
||||||
|
FlapSetting.of(tag.getInt(FLAP_SETTING_TAG)).ordinal()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Raw node value for a key like "Pitch+", as Warium's air brakes read it. */
|
/** Raw node value for a key like "Pitch+", as Warium's air brakes read it. */
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package dev.lumareth.improvedAerodynamics.network;
|
||||||
|
|
||||||
|
import dev.lumareth.improvedAerodynamics.flight.FlapControl;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraftforge.fml.ModList;
|
||||||
|
import net.minecraftforge.network.NetworkEvent;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/** Sent when a pilot moves the flap lever: more flaps (true) or fewer. */
|
||||||
|
public final class FlapPacket {
|
||||||
|
private final boolean extend;
|
||||||
|
|
||||||
|
public FlapPacket(boolean extend) {
|
||||||
|
this.extend = extend;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void encode(FriendlyByteBuf buffer) {
|
||||||
|
buffer.writeBoolean(extend);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FlapPacket decode(FriendlyByteBuf buffer) {
|
||||||
|
return new FlapPacket(buffer.readBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handle(Supplier<NetworkEvent.Context> context) {
|
||||||
|
ServerPlayer player = context.get().getSender();
|
||||||
|
if (player != null && ModList.get().isLoaded("valkyrien_warium")) {
|
||||||
|
FlapControl.step(player, extend ? 1 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,5 +33,11 @@ public final class Network {
|
|||||||
.decoder(ExhaustPacket::decode)
|
.decoder(ExhaustPacket::decode)
|
||||||
.consumerMainThread(ExhaustPacket::handle)
|
.consumerMainThread(ExhaustPacket::handle)
|
||||||
.add();
|
.add();
|
||||||
|
// Higher id than the neighbours so it does not collide with packets added on other branches
|
||||||
|
CHANNEL.messageBuilder(FlapPacket.class, 10, NetworkDirection.PLAY_TO_SERVER)
|
||||||
|
.encoder(FlapPacket::encode)
|
||||||
|
.decoder(FlapPacket::decode)
|
||||||
|
.consumerMainThread(FlapPacket::handle)
|
||||||
|
.add();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,5 +22,16 @@
|
|||||||
"message.improved_aerodynamics.afterburner.off": "Nachbrenner aus",
|
"message.improved_aerodynamics.afterburner.off": "Nachbrenner aus",
|
||||||
"message.improved_aerodynamics.afterburner.throttle": "Der Nachbrenner braucht mindestens Schub %s",
|
"message.improved_aerodynamics.afterburner.throttle": "Der Nachbrenner braucht mindestens Schub %s",
|
||||||
"message.improved_aerodynamics.afterburner.no_seat": "Setz dich in einen Warium-Steuersitz, um den Nachbrenner zu nutzen",
|
"message.improved_aerodynamics.afterburner.no_seat": "Setz dich in einen Warium-Steuersitz, um den Nachbrenner zu nutzen",
|
||||||
"message.improved_aerodynamics.afterburner.no_node": "Dieser Sitz ist mit keinem Vehicle Control Node verbunden"
|
"message.improved_aerodynamics.afterburner.no_node": "Dieser Sitz ist mit keinem Vehicle Control Node verbunden",
|
||||||
|
"key.improved_aerodynamics.flaps_down": "Klappen runter (eine Stufe)",
|
||||||
|
"key.improved_aerodynamics.flaps_up": "Klappen rauf (eine Stufe)",
|
||||||
|
"message.improved_aerodynamics.flaps": "Klappen: %s",
|
||||||
|
"message.improved_aerodynamics.flaps.up": "eingefahren",
|
||||||
|
"message.improved_aerodynamics.flaps.takeoff": "Start",
|
||||||
|
"message.improved_aerodynamics.flaps.landing": "Landung",
|
||||||
|
"message.improved_aerodynamics.flaps.no_seat": "Setz dich in einen Warium-Steuersitz, um die Klappen zu nutzen",
|
||||||
|
"message.improved_aerodynamics.flaps.no_node": "Dieser Sitz ist mit keinem Vehicle Control Node verbunden",
|
||||||
|
"message.improved_aerodynamics.flap.on": "Das Ruder ist jetzt eine Klappe",
|
||||||
|
"message.improved_aerodynamics.flap.off": "Das Ruder ist wieder ein normales Ruder",
|
||||||
|
"message.improved_aerodynamics.flap.unlinked": "Das Ruder ist jetzt eine Klappe, aber noch mit keinem Vehicle Control Node verbunden"
|
||||||
}
|
}
|
||||||
@@ -22,5 +22,16 @@
|
|||||||
"message.improved_aerodynamics.afterburner.off": "Afterburner off",
|
"message.improved_aerodynamics.afterburner.off": "Afterburner off",
|
||||||
"message.improved_aerodynamics.afterburner.throttle": "The afterburner needs a throttle of at least %s",
|
"message.improved_aerodynamics.afterburner.throttle": "The afterburner needs a throttle of at least %s",
|
||||||
"message.improved_aerodynamics.afterburner.no_seat": "Sit in a Warium control seat to use the afterburner",
|
"message.improved_aerodynamics.afterburner.no_seat": "Sit in a Warium control seat to use the afterburner",
|
||||||
"message.improved_aerodynamics.afterburner.no_node": "This seat is not linked to a vehicle control node"
|
"message.improved_aerodynamics.afterburner.no_node": "This seat is not linked to a vehicle control node",
|
||||||
|
"key.improved_aerodynamics.flaps_down": "Flaps down (one step)",
|
||||||
|
"key.improved_aerodynamics.flaps_up": "Flaps up (one step)",
|
||||||
|
"message.improved_aerodynamics.flaps": "Flaps: %s",
|
||||||
|
"message.improved_aerodynamics.flaps.up": "up",
|
||||||
|
"message.improved_aerodynamics.flaps.takeoff": "takeoff",
|
||||||
|
"message.improved_aerodynamics.flaps.landing": "landing",
|
||||||
|
"message.improved_aerodynamics.flaps.no_seat": "Sit in a Warium control seat to use the flaps",
|
||||||
|
"message.improved_aerodynamics.flaps.no_node": "This seat is not linked to a vehicle control node",
|
||||||
|
"message.improved_aerodynamics.flap.on": "Control surface is now a flap",
|
||||||
|
"message.improved_aerodynamics.flap.off": "Control surface is a normal control surface again",
|
||||||
|
"message.improved_aerodynamics.flap.unlinked": "Control surface is now a flap, but it is not linked to a vehicle control node yet"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
{
|
||||||
|
"replace": false,
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:control_surface_dark_gray",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:control_surface_offset_top_dark_gray",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:control_surface_offset_bottom_dark_gray",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:wooden_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:wooden_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:wooden_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:red_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:red_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:red_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:blue_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:blue_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:blue_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:brown_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:brown_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:brown_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:green_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:green_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:cyan_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:cyan_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:cyan_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:orange_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:orange_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:orange_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:yellow_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:yellow_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:yellow_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:gray_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:gray_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:gray_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:light_gray_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:light_gray_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:light_gray_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:light_blue_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:light_blue_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:light_blue_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:pink_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:pink_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:pink_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:magenta_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:magenta_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:magenta_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:purple_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:purple_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:purple_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:white_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:white_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:white_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:black_control_surface",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:black_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:black_control_surface_offset_bottom",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valkyrien_warium:green_control_surface_offset_top",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in new issue
Block a user