From cf3e67fff4d413bb5f86d228781c3a1bb8d91038 Mon Sep 17 00:00:00 2001 From: isXander Date: Wed, 5 Apr 2023 12:27:47 +0100 Subject: [PATCH] rumble source --- build.gradle.kts | 2 +- .../controlify/config/gui/YACLHelper.java | 46 ++++++++++++---- .../controller/AbstractController.java | 25 ++++++++- .../controlify/controller/Controller.java | 15 ++++- .../controller/ControllerConfig.java | 16 ++++++ .../joystick/CompoundJoystickController.java | 3 +- .../blockbreak/MultiPlayerGameModeMixin.java | 3 +- .../rumble/damage/LocalPlayerMixin.java | 2 + .../rumble/itembreak/LocalPlayerMixin.java | 2 + .../rumble/sounds/LevelRendererMixin.java | 21 ++++++- .../rumble/useitem/LocalPlayerMixin.java | 8 ++- .../controlify/rumble/RumbleCapable.java | 2 +- .../controlify/rumble/RumbleManager.java | 20 +++++-- .../controlify/rumble/RumbleSource.java | 55 +++++++++++++++++++ .../assets/controlify/lang/en_us.json | 17 ++++++ .../controlify/test/FakeController.java | 3 +- 16 files changed, 209 insertions(+), 31 deletions(-) create mode 100644 src/main/java/dev/isxander/controlify/rumble/RumbleSource.java diff --git a/build.gradle.kts b/build.gradle.kts index 1c1e891..1b2c605 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -82,7 +82,7 @@ dependencies { modImplementation(libs.yet.another.config.lib) modImplementation(libs.mod.menu) - implementation(libs.mixin.extras) + api(libs.mixin.extras) annotationProcessor(libs.mixin.extras) include(libs.mixin.extras) diff --git a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java index 8dddf9d..05dc584 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java +++ b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java @@ -16,6 +16,7 @@ import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen; import dev.isxander.controlify.reacharound.ReachAroundMode; import dev.isxander.controlify.rumble.BasicRumbleEffect; +import dev.isxander.controlify.rumble.RumbleSource; import dev.isxander.controlify.rumble.RumbleState; import dev.isxander.yacl.api.*; import dev.isxander.yacl.gui.controllers.ActionController; @@ -30,12 +31,10 @@ import net.minecraft.ChatFormatting; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -119,6 +118,7 @@ public class YACLHelper { var def = controller.defaultConfig(); Function percentFormatter = v -> Component.literal(String.format("%.0f%%", v*100)); + Function percentOrOffFormatter = v -> v == 0 ? CommonComponents.OPTION_OFF : percentFormatter.apply(v); var basicGroup = OptionGroup.createBuilder() .name(Component.translatable("controlify.gui.group.basic")) @@ -153,13 +153,6 @@ public class YACLHelper { .binding(def.autoJump, () -> config.autoJump, v -> config.autoJump = v) .controller(BooleanController::new) .build()) - .option(Option.createBuilder(boolean.class) - .name(Component.translatable("controlify.gui.allow_vibrations")) - .tooltip(Component.translatable("controlify.gui.allow_vibrations.tooltip")) - .binding(globalVibrationOption.pendingValue(), () -> config.allowVibrations && globalVibrationOption.pendingValue(), v -> config.allowVibrations = v) - .available(globalVibrationOption.pendingValue()) - .controller(TickBoxController::new) - .build()) .option(Option.createBuilder(boolean.class) .name(Component.translatable("controlify.gui.show_ingame_guide")) .tooltip(Component.translatable("controlify.gui.show_ingame_guide.tooltip")) @@ -213,6 +206,36 @@ public class YACLHelper { .build()); category.group(basicGroup.build()); + var vibrationGroup = OptionGroup.createBuilder() + .name(Component.translatable("controlify.gui.group.vibration")) + .tooltip(Component.translatable("controlify.gui.group.vibration.tooltip")); + List> strengthOptions = new ArrayList<>(); + Option allowVibrationOption; + vibrationGroup.option(allowVibrationOption = Option.createBuilder(boolean.class) + .name(Component.translatable("controlify.gui.allow_vibrations")) + .tooltip(Component.translatable("controlify.gui.allow_vibrations.tooltip")) + .binding(globalVibrationOption.pendingValue(), () -> config.allowVibrations && globalVibrationOption.pendingValue(), v -> config.allowVibrations = v) + .available(globalVibrationOption.pendingValue()) + .listener((opt, allowVibration) -> strengthOptions.forEach(so -> so.setAvailable(allowVibration))) + .controller(TickBoxController::new) + .build()); + for (RumbleSource source : RumbleSource.values()) { + var option = Option.createBuilder(float.class) + .name(Component.translatable("controlify.vibration_strength." + source.id().getNamespace() + "." + source.id().getPath())) + .tooltip(Component.translatable("controlify.vibration_strength." + source.id().getNamespace() + "." + source.id().getPath() + ".tooltip")) + .binding( + def.getRumbleStrength(source), + () -> config.getRumbleStrength(source), + v -> config.setRumbleStrength(source, v) + ) + .controller(opt -> new FloatSliderController(opt, 0f, 1f, 0.05f, percentOrOffFormatter)) + .available(allowVibrationOption.pendingValue()) + .build(); + strengthOptions.add(option); + vibrationGroup.option(option); + } + category.group(vibrationGroup.build()); + var advancedGroup = OptionGroup.createBuilder() .name(Component.translatable("controlify.gui.group.advanced")) .tooltip(Component.translatable("controlify.gui.group.advanced.tooltip")) @@ -294,6 +317,7 @@ public class YACLHelper { .controller(ActionController::new) .action((screen, btn) -> { controller.rumbleManager().play( + RumbleSource.MASTER, BasicRumbleEffect.byTime(t -> new RumbleState(0f, t), 20) .join(BasicRumbleEffect.byTime(t -> new RumbleState(0f, 1 - t), 20)) .repeat(3) diff --git a/src/main/java/dev/isxander/controlify/controller/AbstractController.java b/src/main/java/dev/isxander/controlify/controller/AbstractController.java index 3bcd868..df86470 100644 --- a/src/main/java/dev/isxander/controlify/controller/AbstractController.java +++ b/src/main/java/dev/isxander/controlify/controller/AbstractController.java @@ -4,11 +4,14 @@ import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.JsonElement; import dev.isxander.controlify.Controlify; +import dev.isxander.controlify.InputMode; +import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.controller.hid.ControllerHIDService; import dev.isxander.controlify.controller.sdl2.SDL2NativesManager; import dev.isxander.controlify.rumble.RumbleCapable; import dev.isxander.controlify.rumble.RumbleManager; +import dev.isxander.controlify.rumble.RumbleSource; import org.libsdl.SDL; import org.lwjgl.glfw.GLFW; @@ -107,7 +110,14 @@ public abstract class AbstractController(getClass()){}.getType()); + C newConfig; + try { + newConfig = gson.fromJson(json, new TypeToken(getClass()){}.getType()); + } catch (Exception e) { + Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead. Printing json: " + json.toString(), e); + return; + } + if (newConfig != null) { this.config = newConfig; } else { @@ -117,9 +127,16 @@ public abstract class AbstractController DUMMY = new Controller<>() { private final ControllerBindings bindings = new ControllerBindings<>(this); + private final RumbleManager rumbleManager = new RumbleManager(new RumbleCapable() { + @Override + public boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source) { + return false; + } + + @Override + public boolean canRumble() { + return false; + } + }); private final ControllerConfig config = new ControllerConfig() { @Override public void setDeadzone(int axis, float deadzone) { @@ -150,7 +163,7 @@ public interface Controller= 0.75f ? 8 : 5) ); } diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/rumble/itembreak/LocalPlayerMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/rumble/itembreak/LocalPlayerMixin.java index 611ef58..637ae54 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/rumble/itembreak/LocalPlayerMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/rumble/itembreak/LocalPlayerMixin.java @@ -3,6 +3,7 @@ package dev.isxander.controlify.mixins.feature.rumble.itembreak; import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.rumble.BasicRumbleEffect; import dev.isxander.controlify.rumble.RumbleEffect; +import dev.isxander.controlify.rumble.RumbleSource; import dev.isxander.controlify.rumble.RumbleState; import net.minecraft.client.player.LocalPlayer; import net.minecraft.world.item.ItemStack; @@ -14,6 +15,7 @@ public class LocalPlayerMixin extends LivingEntityMixin { @Override protected void onBreakItemParticles(ItemStack stack, CallbackInfo ci) { ControlifyApi.get().currentController().rumbleManager().play( + RumbleSource.ITEM_BREAK, BasicRumbleEffect.byTick(tick -> new RumbleState(tick <= 4 ? 1f : 0f, 1f), 10) ); } diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/rumble/sounds/LevelRendererMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/rumble/sounds/LevelRendererMixin.java index 999cde5..ace9ace 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/rumble/sounds/LevelRendererMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/rumble/sounds/LevelRendererMixin.java @@ -3,7 +3,9 @@ package dev.isxander.controlify.mixins.feature.rumble.sounds; import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.rumble.BasicRumbleEffect; import dev.isxander.controlify.rumble.RumbleEffect; +import dev.isxander.controlify.rumble.RumbleSource; import dev.isxander.controlify.rumble.RumbleState; +import dev.isxander.controlify.utils.Easings; import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.LevelEvent; @@ -18,6 +20,7 @@ public class LevelRendererMixin { private void onLevelEvent(int eventId, BlockPos pos, int data, CallbackInfo ci) { switch (eventId) { case LevelEvent.SOUND_ANVIL_USED -> rumble( + RumbleSource.GUI, BasicRumbleEffect.join( BasicRumbleEffect.constant(1f, 0.5f, 2), BasicRumbleEffect.empty(5) @@ -30,18 +33,30 @@ public class LevelRendererMixin { private void onGlobalLevelEvent(int eventId, BlockPos pos, int data, CallbackInfo ci) { switch (eventId) { case LevelEvent.SOUND_DRAGON_DEATH -> rumble( + RumbleSource.GLOBAL_EVENT, BasicRumbleEffect.join( BasicRumbleEffect.constant(1f, 1f, 194), BasicRumbleEffect.byTime(t -> { - float easeOutQuad = 1 - (1 - t) * (1 - t); + float easeOutQuad = Easings.easeOutQuad(t); return new RumbleState(1 - easeOutQuad, 1 - easeOutQuad); }, 63) ) ); + case LevelEvent.SOUND_WITHER_BOSS_SPAWN -> rumble( + RumbleSource.GLOBAL_EVENT, + BasicRumbleEffect.join( + BasicRumbleEffect.constant(1f, 1f, 9), + BasicRumbleEffect.constant(0.1f, 1f, 14), + BasicRumbleEffect.byTime(t -> { + float easeOutQuad = 1 - (1 - t) * (1 - t); + return new RumbleState(0f, 1 - easeOutQuad); + }, 56) + ) + ); } } - private void rumble(RumbleEffect effect) { - ControlifyApi.get().currentController().rumbleManager().play(effect); + private void rumble(RumbleSource source, RumbleEffect effect) { + ControlifyApi.get().currentController().rumbleManager().play(source, effect); } } diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/rumble/useitem/LocalPlayerMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/rumble/useitem/LocalPlayerMixin.java index e98a206..9e76081 100644 --- a/src/main/java/dev/isxander/controlify/mixins/feature/rumble/useitem/LocalPlayerMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/feature/rumble/useitem/LocalPlayerMixin.java @@ -3,6 +3,7 @@ package dev.isxander.controlify.mixins.feature.rumble.useitem; import dev.isxander.controlify.api.ControlifyApi; import dev.isxander.controlify.rumble.BasicRumbleEffect; import dev.isxander.controlify.rumble.ContinuousRumbleEffect; +import dev.isxander.controlify.rumble.RumbleSource; import dev.isxander.controlify.rumble.RumbleState; import net.minecraft.client.player.LocalPlayer; import net.minecraft.world.InteractionHand; @@ -27,7 +28,10 @@ public abstract class LocalPlayerMixin extends LivingEntityMixin { startRumble(new ContinuousRumbleEffect(tick -> new RumbleState(0f, tick % 4 / 4f * 0.12f + 0.05f) )); - case EAT, DRINK -> startRumble(BasicRumbleEffect.constant(0.05f, 0.1f, 1).continuous()); + case EAT, DRINK -> + startRumble(new ContinuousRumbleEffect(tick -> + new RumbleState(0.05f, 0.1f) + )); case TOOT_HORN -> startRumble(new ContinuousRumbleEffect(tick -> new RumbleState(Math.min(1f, tick / 10f), 0.25f) @@ -49,7 +53,7 @@ public abstract class LocalPlayerMixin extends LivingEntityMixin { } private void startRumble(ContinuousRumbleEffect effect) { - ControlifyApi.get().currentController().rumbleManager().play(effect); + ControlifyApi.get().currentController().rumbleManager().play(RumbleSource.USE_ITEM, effect); useItemRumble = effect; } } diff --git a/src/main/java/dev/isxander/controlify/rumble/RumbleCapable.java b/src/main/java/dev/isxander/controlify/rumble/RumbleCapable.java index 7d81dcb..50506b1 100644 --- a/src/main/java/dev/isxander/controlify/rumble/RumbleCapable.java +++ b/src/main/java/dev/isxander/controlify/rumble/RumbleCapable.java @@ -1,7 +1,7 @@ package dev.isxander.controlify.rumble; public interface RumbleCapable { - boolean setRumble(float strongMagnitude, float weakMagnitude); + boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source); boolean canRumble(); } diff --git a/src/main/java/dev/isxander/controlify/rumble/RumbleManager.java b/src/main/java/dev/isxander/controlify/rumble/RumbleManager.java index 7611002..6d5dddc 100644 --- a/src/main/java/dev/isxander/controlify/rumble/RumbleManager.java +++ b/src/main/java/dev/isxander/controlify/rumble/RumbleManager.java @@ -2,17 +2,22 @@ package dev.isxander.controlify.rumble; public class RumbleManager { private final RumbleCapable controller; - private RumbleEffect playingEffect; + private RumbleEffectInstance playingEffect; public RumbleManager(RumbleCapable controller) { this.controller = controller; } + @Deprecated public void play(RumbleEffect effect) { + play(RumbleSource.MASTER, effect); + } + + public void play(RumbleSource source, RumbleEffect effect) { if (!controller.canRumble()) return; - playingEffect = effect; + playingEffect = new RumbleEffectInstance(source, effect); } public boolean isPlaying() { @@ -23,7 +28,7 @@ public class RumbleManager { if (playingEffect == null) return; - controller.setRumble(0f, 0f); + controller.setRumble(0f, 0f, RumbleSource.MASTER); playingEffect = null; } @@ -31,12 +36,15 @@ public class RumbleManager { if (playingEffect == null) return; - if (playingEffect.isFinished()) { + if (playingEffect.effect().isFinished()) { stopCurrentEffect(); return; } - RumbleState state = playingEffect.nextState(); - controller.setRumble(state.strong(), state.weak()); + RumbleState state = playingEffect.effect().nextState(); + controller.setRumble(state.strong(), state.weak(), playingEffect.source()); + } + + private record RumbleEffectInstance(RumbleSource source, RumbleEffect effect) { } } diff --git a/src/main/java/dev/isxander/controlify/rumble/RumbleSource.java b/src/main/java/dev/isxander/controlify/rumble/RumbleSource.java new file mode 100644 index 0000000..a9036ad --- /dev/null +++ b/src/main/java/dev/isxander/controlify/rumble/RumbleSource.java @@ -0,0 +1,55 @@ +package dev.isxander.controlify.rumble; + +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; + +import java.util.*; + +public class RumbleSource { + private static final Map SOURCES = new LinkedHashMap<>(); + + public static final RumbleSource + MASTER = register("master"), + DAMAGE = register("damage"), + BLOCK_DESTROY = register("block_destroy"), + USE_ITEM = register("use_item"), + ITEM_BREAK = register("item_break"), + GUI = register("gui"), + GLOBAL_EVENT = register("global_event"); + + private final ResourceLocation id; + + private RumbleSource(ResourceLocation id) { + this.id = id; + } + + public ResourceLocation id() { + return id; + } + + public static Collection values() { + return SOURCES.values(); + } + + public static JsonObject getDefaultJson() { + JsonObject object = new JsonObject(); + for (RumbleSource source : SOURCES.values()) { + object.addProperty(source.id().toString(), 1f); + } + return object; + } + + public static RumbleSource register(ResourceLocation id) { + var source = new RumbleSource(id); + SOURCES.put(id, source); + return source; + } + + public static RumbleSource register(String identifier, String path) { + return register(new ResourceLocation(identifier, path)); + } + + private static RumbleSource register(String path) { + return register("controlify", path); + } +} diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json index 20ddacb..e8c5f12 100644 --- a/src/main/resources/assets/controlify/lang/en_us.json +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -47,6 +47,8 @@ "controlify.gui.reduce_aiming_sensitivity.tooltip": "Reduce the sensitivity when aiming.", "controlify.gui.custom_name": "Display Name", "controlify.gui.custom_name.tooltip": "Name to display for this controller throughout Minecraft.", + "controlify.gui.group.vibration": "Vibration", + "controlify.gui.group.vibration.tooltip": "Adjust how your controller vibrates.", "controlify.gui.group.advanced": "Advanced", "controlify.gui.group.advanced.tooltip": "Settings you probably shouldn't touch!.", "controlify.gui.screen_repeat_navi_delay": "Screen Repeat Navigation Delay", @@ -76,6 +78,21 @@ "controlify.gui.controller_unavailable": "Controller unavailable and cannot be edited.", + "controlify.vibration_strength.controlify.master": "Master", + "controlify.vibration_strength.controlify.master.tooltip": "The strength of all vibrations. Will also affect the strength of all other sources.", + "controlify.vibration_strength.controlify.damage": "Take Damage", + "controlify.vibration_strength.controlify.damage.tooltip": "When you take damage. This effect is scaled based on the amount of damage you take.", + "controlify.vibration_strength.controlify.block_destroy": "Block Destroy", + "controlify.vibration_strength.controlify.block_destroy.tooltip": "When you are mining a block in a survival mode.", + "controlify.vibration_strength.controlify.use_item": "Item Use", + "controlify.vibration_strength.controlify.use_item.tooltip": "When you are using an item. For example:\n - pulling a bow or trident,\n - blocking with a shield,\n - eating or drinking, etc.", + "controlify.vibration_strength.controlify.item_break": "Item Break", + "controlify.vibration_strength.controlify.item_break.tooltip": "When an item breaks.", + "controlify.vibration_strength.controlify.gui": "GUI", + "controlify.vibration_strength.controlify.gui.tooltip": "Various effects in GUIs such as anvil use.", + "controlify.vibration_strength.controlify.global_event": "Global Event", + "controlify.vibration_strength.controlify.global_event.tooltip": "When a global event occurs such as wither spawn or ender dragon death.", + "controlify.toast.vmouse_enabled.title": "Virtual Mouse Enabled", "controlify.toast.vmouse_enabled.description": "Controlify virtual mouse is now enabled for this screen.", "controlify.toast.vmouse_disabled.title": "Virtual Mouse Disabled", diff --git a/src/testmod/java/dev/isxander/controlify/test/FakeController.java b/src/testmod/java/dev/isxander/controlify/test/FakeController.java index 2e02362..5e8754e 100644 --- a/src/testmod/java/dev/isxander/controlify/test/FakeController.java +++ b/src/testmod/java/dev/isxander/controlify/test/FakeController.java @@ -13,6 +13,7 @@ import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping; import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping; import dev.isxander.controlify.rumble.RumbleCapable; import dev.isxander.controlify.rumble.RumbleManager; +import dev.isxander.controlify.rumble.RumbleSource; import java.util.List; @@ -39,7 +40,7 @@ public class FakeController implements JoystickController { this.config = new JoystickConfig(this); this.rumbleManager = new RumbleManager(new RumbleCapable() { @Override - public boolean setRumble(float strongMagnitude, float weakMagnitude) { + public boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source) { return false; }