1
0
forked from Clones/Controlify

rumble source

This commit is contained in:
isXander
2023-04-05 12:27:47 +01:00
parent f7fe7d8ec0
commit cf3e67fff4
16 changed files with 209 additions and 31 deletions

View File

@ -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)

View File

@ -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<Float, Component> percentFormatter = v -> Component.literal(String.format("%.0f%%", v*100));
Function<Float, Component> 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<Option<Float>> strengthOptions = new ArrayList<>();
Option<Boolean> 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)

View File

@ -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<S extends ControllerState, C extends Co
@Override
public void setConfig(Gson gson, JsonElement json) {
C newConfig = gson.fromJson(json, new TypeToken<C>(getClass()){}.getType());
C newConfig;
try {
newConfig = gson.fromJson(json, new TypeToken<C>(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<S extends ControllerState, C extends Co
}
@Override
public boolean setRumble(float strongMagnitude, float weakMagnitude) {
public boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source) {
if (!canRumble()) return false;
var strengthMod = config().getRumbleStrength(source);
if (source != RumbleSource.MASTER)
strengthMod *= config().getRumbleStrength(RumbleSource.MASTER);
strongMagnitude *= strengthMod;
weakMagnitude *= strengthMod;
// the duration doesn't matter because we are not updating the joystick state,
// so there is never any SDL check to stop the rumble after the desired time.
if (!SDL.SDL_JoystickRumble(ptrJoystick, (int)(strongMagnitude * 65535.0F), (int)(weakMagnitude * 65535.0F), 1)) {
@ -131,7 +148,9 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
@Override
public boolean canRumble() {
return SDL2NativesManager.isLoaded() && config().allowVibrations;
return SDL2NativesManager.isLoaded()
&& config().allowVibrations
&& ControlifyApi.get().currentInputMode() == InputMode.CONTROLLER;
}
@Override

View File

@ -7,7 +7,9 @@ import dev.isxander.controlify.controller.gamepad.GamepadController;
import dev.isxander.controlify.controller.hid.ControllerHIDService;
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.rumble.RumbleCapable;
import dev.isxander.controlify.rumble.RumbleManager;
import dev.isxander.controlify.rumble.RumbleSource;
import org.lwjgl.glfw.GLFW;
import java.util.HashMap;
@ -71,6 +73,17 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
Controller<?, ?> DUMMY = new Controller<>() {
private final ControllerBindings<ControllerState> 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<S extends ControllerState, C extends ControllerConfi
@Override
public RumbleManager rumbleManager() {
return null;
return rumbleManager;
}
};
}

View File

@ -1,5 +1,12 @@
package dev.isxander.controlify.controller;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import dev.isxander.controlify.rumble.RumbleSource;
import net.minecraft.resources.ResourceLocation;
import java.util.Map;
public abstract class ControllerConfig {
public float horizontalLookSensitivity = 1f;
public float verticalLookSensitivity = 0.9f;
@ -24,9 +31,18 @@ public abstract class ControllerConfig {
public boolean reduceAimingSensitivity = true;
public boolean allowVibrations = true;
public JsonObject vibrationStrengths = RumbleSource.getDefaultJson();
public boolean calibrated = false;
public abstract void setDeadzone(int axis, float deadzone);
public abstract float getDeadzone(int axis);
public float getRumbleStrength(RumbleSource source) {
return vibrationStrengths.asMap().getOrDefault(source.id().toString(), new JsonPrimitive(1f)).getAsFloat();
}
public void setRumbleStrength(RumbleSource source, float strength) {
vibrationStrengths.addProperty(source.id().toString(), strength);
}
}

View File

@ -10,6 +10,7 @@ import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
import dev.isxander.controlify.rumble.RumbleCapable;
import dev.isxander.controlify.rumble.RumbleManager;
import dev.isxander.controlify.rumble.RumbleSource;
import org.lwjgl.glfw.GLFW;
import java.util.List;
@ -140,7 +141,7 @@ public class CompoundJoystickController implements JoystickController<JoystickCo
}
@Override
public boolean setRumble(float strongMagnitude, float weakMagnitude) {
public boolean setRumble(float strongMagnitude, float weakMagnitude, RumbleSource source) {
return false;
}

View File

@ -3,6 +3,7 @@ package dev.isxander.controlify.mixins.feature.rumble.blockbreak;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.rumble.ContinuousRumbleEffect;
import dev.isxander.controlify.rumble.RumbleSource;
import dev.isxander.controlify.rumble.RumbleState;
import net.minecraft.client.multiplayer.MultiPlayerGameMode;
import net.minecraft.core.BlockPos;
@ -65,7 +66,7 @@ public class MultiPlayerGameModeMixin {
};
blockBreakRumble = effect;
ControlifyApi.get().currentController().rumbleManager().play(effect);
ControlifyApi.get().currentController().rumbleManager().play(RumbleSource.BLOCK_DESTROY, effect);
}
private void stopRumble() {

View File

@ -3,6 +3,7 @@ package dev.isxander.controlify.mixins.feature.rumble.damage;
import com.mojang.authlib.GameProfile;
import dev.isxander.controlify.api.ControlifyApi;
import dev.isxander.controlify.rumble.BasicRumbleEffect;
import dev.isxander.controlify.rumble.RumbleSource;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
@ -35,6 +36,7 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer {
float magnitude = (Mth.clamp(damageTaken, smallestDamage, maxDamage) - smallestDamage) / (maxDamage - smallestDamage) * (1 - minMagnitude) + minMagnitude;
System.out.println(magnitude);
ControlifyApi.get().currentController().rumbleManager().play(
RumbleSource.DAMAGE,
BasicRumbleEffect.constant(magnitude, 0f, magnitude >= 0.75f ? 8 : 5)
);
}

View File

@ -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)
);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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) {
}
}

View File

@ -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<ResourceLocation, RumbleSource> 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<RumbleSource> 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);
}
}

View File

@ -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",

View File

@ -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<JoystickConfig> {
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;
}