forked from Clones/Controlify
Finish & fix abstract controller manager + optimize hashmaps in codebase + no SDL screen
This commit is contained in:
@ -11,7 +11,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "dev.isxander"
|
group = "dev.isxander"
|
||||||
version = "1.7.0-beta.2+1.20"
|
version = "1.7.0-beta.3+1.20.2"
|
||||||
val isAlpha = "alpha" in version.toString()
|
val isAlpha = "alpha" in version.toString()
|
||||||
val isBeta = "beta" in version.toString()
|
val isBeta = "beta" in version.toString()
|
||||||
if (isAlpha) println("Controlify alpha version detected.")
|
if (isAlpha) println("Controlify alpha version detected.")
|
||||||
|
29
changelogs/1.7.0-beta.3+1.20.2.md
Normal file
29
changelogs/1.7.0-beta.3+1.20.2.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Controlify 1.7.0 (Beta 3) for 1.20.2
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
- Added D-Pad snapping in container screens
|
||||||
|
- Keyboard-like movement whitelist and warning toast when joining new servers
|
||||||
|
- Added bind to open F3 debug screen
|
||||||
|
- More snap points on recipe book
|
||||||
|
- Allow users to define a custom SDL natives path (so you can put them in a common dir if you want)
|
||||||
|
- Add a reset all binds button to controls tab
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Internal changes to the way controllers are discovered, loaded and managed. (this could introduce new bugs)
|
||||||
|
- `delegate_setup` config option has been renamed to `quiet_mode`.
|
||||||
|
- Pause screen's disconnect shortcut now focuses the button instead of clicking it.
|
||||||
|
- Add a donate button to the controller carousel screen.
|
||||||
|
- Modify how analogue inputs are processed whilst ingame or using the virtual mouse to make it feel more "circular"
|
||||||
|
- Marginally improve performance of Controlify by using optimized hashmaps.
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
- Fix hotplugging when using natives.
|
||||||
|
- Fix SDL download screen progress bar being a missing texture.
|
||||||
|
- Fix people being unable to write newlines and spaces in signs when using mixed input mode.
|
||||||
|
- Fix some modded GUIs crashing when attempting to open when Controlify is loaded.
|
||||||
|
- Fix tridents not causing a vibration.
|
||||||
|
- Fix rumble not working on joysticks.
|
||||||
|
- Fix fabric mod json requirement allowing any 1.20 version not 1.20.2 and above.
|
@ -3,6 +3,7 @@ package dev.isxander.controlify;
|
|||||||
import com.mojang.blaze3d.Blaze3D;
|
import com.mojang.blaze3d.Blaze3D;
|
||||||
import dev.isxander.controlify.api.ControlifyApi;
|
import dev.isxander.controlify.api.ControlifyApi;
|
||||||
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint;
|
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint;
|
||||||
|
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||||
import dev.isxander.controlify.compatibility.ControlifyCompat;
|
import dev.isxander.controlify.compatibility.ControlifyCompat;
|
||||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||||
@ -63,10 +64,12 @@ public class Controlify implements ControlifyApi {
|
|||||||
private boolean probeMode = false;
|
private boolean probeMode = false;
|
||||||
|
|
||||||
private Controller<?, ?> currentController = null;
|
private Controller<?, ?> currentController = null;
|
||||||
|
private InputMode currentInputMode = InputMode.KEYBOARD_MOUSE;
|
||||||
|
|
||||||
private InGameInputHandler inGameInputHandler;
|
private InGameInputHandler inGameInputHandler;
|
||||||
public InGameButtonGuide inGameButtonGuide;
|
public InGameButtonGuide inGameButtonGuide;
|
||||||
private VirtualMouseHandler virtualMouseHandler;
|
private VirtualMouseHandler virtualMouseHandler;
|
||||||
private InputMode currentInputMode = InputMode.KEYBOARD_MOUSE;
|
|
||||||
private ControllerHIDService controllerHIDService;
|
private ControllerHIDService controllerHIDService;
|
||||||
|
|
||||||
private CompletableFuture<Boolean> nativeOnboardingFuture = null;
|
private CompletableFuture<Boolean> nativeOnboardingFuture = null;
|
||||||
@ -81,10 +84,6 @@ public class Controlify implements ControlifyApi {
|
|||||||
|
|
||||||
private int showMouseTicks = 0;
|
private int showMouseTicks = 0;
|
||||||
|
|
||||||
private @Nullable Controller<?, ?> switchableController = null;
|
|
||||||
private double askSwitchTime = 0;
|
|
||||||
private ToastUtils.ControlifyToast askSwitchToast = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called at usual fabric client entrypoint.
|
* Called at usual fabric client entrypoint.
|
||||||
* Always runs, even with no controllers detected.
|
* Always runs, even with no controllers detected.
|
||||||
@ -178,10 +177,6 @@ public class Controlify implements ControlifyApi {
|
|||||||
ClientTickEvents.END_CLIENT_TICK.register(client -> this.probeTick());
|
ClientTickEvents.END_CLIENT_TICK.register(client -> this.probeTick());
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialise and compatability modules that controlify implements itself
|
|
||||||
// this does NOT invoke any entrypoints. this is done in the pre-initialisation phase
|
|
||||||
ControlifyCompat.init();
|
|
||||||
|
|
||||||
// register events
|
// register events
|
||||||
ClientLifecycleEvents.CLIENT_STOPPING.register(minecraft -> {
|
ClientLifecycleEvents.CLIENT_STOPPING.register(minecraft -> {
|
||||||
controllerHIDService().stop();
|
controllerHIDService().stop();
|
||||||
@ -211,19 +206,28 @@ public class Controlify implements ControlifyApi {
|
|||||||
Log.LOGGER.info("No controllers found.");
|
Log.LOGGER.info("No controllers found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no controller is currently selected, select the first one
|
// if no controller is currently selected, pick one
|
||||||
if (getCurrentController().isEmpty()) {
|
if (getCurrentController().isEmpty()) {
|
||||||
Controller<?, ?> controller = controllerManager.getConnectedControllers().stream().findFirst().orElse(null);
|
Optional<Controller<?, ?>> lastUsedController = controllerManager.getConnectedControllers()
|
||||||
if (controller != null && (controller.config().delayedCalibration || !controller.config().deadzonesCalibrated)) {
|
.stream()
|
||||||
controller = null;
|
.filter(c -> c.uid().equals(config().currentControllerUid()))
|
||||||
}
|
.findAny();
|
||||||
|
|
||||||
this.setCurrentController(controller, false);
|
if (lastUsedController.isPresent()) {
|
||||||
} else {
|
this.setCurrentController(lastUsedController.get(), false);
|
||||||
// setCurrentController saves config so there is no need to set dirty to save
|
} else {
|
||||||
config().saveIfDirty();
|
Controller<?, ?> anyController = controllerManager.getConnectedControllers()
|
||||||
|
.stream()
|
||||||
|
.filter(c -> !c.config().delayedCalibration && c.config().deadzonesCalibrated)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
this.setCurrentController(anyController, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config().saveIfDirty();
|
||||||
|
|
||||||
FabricLoader.getInstance().getEntrypoints("controlify", ControlifyEntrypoint.class).forEach(entrypoint -> {
|
FabricLoader.getInstance().getEntrypoints("controlify", ControlifyEntrypoint.class).forEach(entrypoint -> {
|
||||||
try {
|
try {
|
||||||
entrypoint.onControllersDiscovered(this);
|
entrypoint.onControllersDiscovered(this);
|
||||||
@ -233,10 +237,16 @@ public class Controlify implements ControlifyApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely finishes controlify initialization.
|
||||||
|
* This can be run at any point during the game's lifecycle.
|
||||||
|
* @return the future that completes when controlify has finished initializing
|
||||||
|
*/
|
||||||
public CompletableFuture<Void> finishControlifyInit() {
|
public CompletableFuture<Void> finishControlifyInit() {
|
||||||
if (finishedInit) {
|
if (finishedInit) {
|
||||||
return CompletableFuture.completedFuture(null);
|
return CompletableFuture.completedFuture(null);
|
||||||
}
|
}
|
||||||
|
probeMode = false;
|
||||||
finishedInit = true;
|
finishedInit = true;
|
||||||
|
|
||||||
askNatives().whenComplete((loaded, th) -> {
|
askNatives().whenComplete((loaded, th) -> {
|
||||||
@ -248,12 +258,33 @@ public class Controlify implements ControlifyApi {
|
|||||||
ConnectServerEvent.EVENT.register((minecraft, address, data) -> {
|
ConnectServerEvent.EVENT.register((minecraft, address, data) -> {
|
||||||
notifyNewServer(data);
|
notifyNewServer(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// initialise and compatability modules that controlify implements itself
|
||||||
|
// this does NOT invoke any entrypoints. this is done in the pre-initialisation phase
|
||||||
|
ControlifyCompat.init();
|
||||||
|
|
||||||
|
// make sure people don't someone add binds after controllers could have been created
|
||||||
|
ControllerBindings.lockRegistry();
|
||||||
|
|
||||||
|
if (config().globalSettings().quietMode) {
|
||||||
|
config().globalSettings().quietMode = false;
|
||||||
|
config().setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
discoverControllers();
|
discoverControllers();
|
||||||
});
|
});
|
||||||
|
|
||||||
return askNatives().thenApply(loaded -> null);
|
return askNatives().thenApply(loaded -> null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a controller is connected. Either from controller
|
||||||
|
* discovery or hotplugging.
|
||||||
|
*
|
||||||
|
* @param controller the new controller
|
||||||
|
* @param hotplugged if this was a result of hotplugging
|
||||||
|
* @param newController if this controller has never been seen before
|
||||||
|
*/
|
||||||
private void onControllerAdded(Controller<?, ?> controller, boolean hotplugged, boolean newController) {
|
private void onControllerAdded(Controller<?, ?> controller, boolean hotplugged, boolean newController) {
|
||||||
if (SubmitUnknownControllerScreen.canSubmit(controller)) {
|
if (SubmitUnknownControllerScreen.canSubmit(controller)) {
|
||||||
minecraft.setScreen(new SubmitUnknownControllerScreen(controller, minecraft.screen));
|
minecraft.setScreen(new SubmitUnknownControllerScreen(controller, minecraft.screen));
|
||||||
@ -264,12 +295,10 @@ public class Controlify implements ControlifyApi {
|
|||||||
config().setDirty();
|
config().setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hotplugged) {
|
if (!controller.config().deadzonesCalibrated) {
|
||||||
if (controller.config().deadzonesCalibrated) {
|
calibrationQueue.add(controller);
|
||||||
setCurrentController(controller, hotplugged);
|
} else if (hotplugged) {
|
||||||
} else {
|
setCurrentController(controller, true);
|
||||||
calibrationQueue.add(controller);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller instanceof JoystickController<?> joystick && joystick.mapping() instanceof UnmappedJoystickMapping) {
|
if (controller instanceof JoystickController<?> joystick && joystick.mapping() instanceof UnmappedJoystickMapping) {
|
||||||
@ -278,7 +307,7 @@ public class Controlify implements ControlifyApi {
|
|||||||
Component.translatable("controlify.toast.unmapped_joystick.description", controller.name()),
|
Component.translatable("controlify.toast.unmapped_joystick.description", controller.name()),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
} else {
|
} else if (hotplugged) {
|
||||||
ToastUtils.sendToast(
|
ToastUtils.sendToast(
|
||||||
Component.translatable("controlify.toast.controller_connected.title"),
|
Component.translatable("controlify.toast.controller_connected.title"),
|
||||||
Component.translatable("controlify.toast.controller_connected.description", controller.name()),
|
Component.translatable("controlify.toast.controller_connected.description", controller.name()),
|
||||||
@ -289,8 +318,17 @@ public class Controlify implements ControlifyApi {
|
|||||||
if (minecraft.screen instanceof ControllerCarouselScreen controllerListScreen) {
|
if (minecraft.screen instanceof ControllerCarouselScreen controllerListScreen) {
|
||||||
controllerListScreen.refreshControllers();
|
controllerListScreen.refreshControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saved after discovery
|
||||||
|
if (hotplugged) {
|
||||||
|
config().saveIfDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a controller is disconnected.
|
||||||
|
* @param controller controller that has been disconnected
|
||||||
|
*/
|
||||||
private void onControllerRemoved(Controller<?, ?> controller) {
|
private void onControllerRemoved(Controller<?, ?> controller) {
|
||||||
this.setCurrentController(
|
this.setCurrentController(
|
||||||
controllerManager.getConnectedControllers()
|
controllerManager.getConnectedControllers()
|
||||||
@ -325,6 +363,14 @@ public class Controlify implements ControlifyApi {
|
|||||||
if (nativeOnboardingFuture != null)
|
if (nativeOnboardingFuture != null)
|
||||||
return nativeOnboardingFuture;
|
return nativeOnboardingFuture;
|
||||||
|
|
||||||
|
// just say no if the platform doesn't support it
|
||||||
|
if (!SDL2NativesManager.isSupportedOnThisPlatform()) {
|
||||||
|
Log.LOGGER.warn("SDL is not supported on this platform. Platform: {}", SDL2NativesManager.Target.CURRENT);
|
||||||
|
nativeOnboardingFuture = new CompletableFuture<>();
|
||||||
|
minecraft.setScreen(new NoSDLScreen(() -> nativeOnboardingFuture.complete(false), minecraft.screen));
|
||||||
|
return nativeOnboardingFuture;
|
||||||
|
}
|
||||||
|
|
||||||
// the user has already been asked, initialise SDL if necessary
|
// the user has already been asked, initialise SDL if necessary
|
||||||
// and return a completed future
|
// and return a completed future
|
||||||
if (config().globalSettings().vibrationOnboarded) {
|
if (config().globalSettings().vibrationOnboarded) {
|
||||||
@ -373,19 +419,10 @@ public class Controlify implements ControlifyApi {
|
|||||||
|
|
||||||
boolean outOfFocus = !config().globalSettings().outOfFocusInput && !client.isWindowActive();
|
boolean outOfFocus = !config().globalSettings().outOfFocusInput && !client.isWindowActive();
|
||||||
|
|
||||||
|
// handles updating state of all controllers
|
||||||
controllerManager.tick(outOfFocus);
|
controllerManager.tick(outOfFocus);
|
||||||
|
|
||||||
if (switchableController != null && Blaze3D.getTime() - askSwitchTime <= 10000) {
|
// handle showing/hiding mouse whilst in mixed input mode
|
||||||
if (switchableController.state().hasAnyInput()) {
|
|
||||||
switchableController.clearState();
|
|
||||||
this.setCurrentController(switchableController, true); // setCurrentController sets switchableController to null
|
|
||||||
if (askSwitchToast != null) {
|
|
||||||
askSwitchToast.remove();
|
|
||||||
askSwitchToast = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minecraft.mouseHandler.isMouseGrabbed())
|
if (minecraft.mouseHandler.isMouseGrabbed())
|
||||||
showMouseTicks = 0;
|
showMouseTicks = 0;
|
||||||
if (currentInputMode() == InputMode.MIXED && showMouseTicks > 0) {
|
if (currentInputMode() == InputMode.MIXED && showMouseTicks > 0) {
|
||||||
@ -400,6 +437,7 @@ public class Controlify implements ControlifyApi {
|
|||||||
|
|
||||||
LowBatteryNotifier.tick();
|
LowBatteryNotifier.tick();
|
||||||
|
|
||||||
|
// if splitscreen ever happens this can tick over every controller
|
||||||
getCurrentController().ifPresent(currentController -> {
|
getCurrentController().ifPresent(currentController -> {
|
||||||
wrapControllerError(
|
wrapControllerError(
|
||||||
() -> tickController(currentController, outOfFocus),
|
() -> tickController(currentController, outOfFocus),
|
||||||
@ -409,6 +447,12 @@ public class Controlify implements ControlifyApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ticks a specific controller.
|
||||||
|
*
|
||||||
|
* @param controller controller to tick
|
||||||
|
* @param outOfFocus if the window is out of focus
|
||||||
|
*/
|
||||||
private void tickController(Controller<?, ?> controller, boolean outOfFocus) {
|
private void tickController(Controller<?, ?> controller, boolean outOfFocus) {
|
||||||
ControllerState state = controller.state();
|
ControllerState state = controller.state();
|
||||||
|
|
||||||
@ -479,10 +523,6 @@ public class Controlify implements ControlifyApi {
|
|||||||
|
|
||||||
this.currentController = controller;
|
this.currentController = controller;
|
||||||
|
|
||||||
if (switchableController == controller) {
|
|
||||||
switchableController = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller == null) {
|
if (controller == null) {
|
||||||
this.setInputMode(InputMode.KEYBOARD_MOUSE);
|
this.setInputMode(InputMode.KEYBOARD_MOUSE);
|
||||||
this.inGameInputHandler = null;
|
this.inGameInputHandler = null;
|
||||||
@ -495,7 +535,7 @@ public class Controlify implements ControlifyApi {
|
|||||||
DebugLog.log("Updated current controller to {}({})", controller.name(), controller.uid());
|
DebugLog.log("Updated current controller to {}({})", controller.name(), controller.uid());
|
||||||
|
|
||||||
if (!controller.uid().equals(config().currentControllerUid())) {
|
if (!controller.uid().equals(config().currentControllerUid())) {
|
||||||
config().save();
|
config().setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inGameInputHandler = new InGameInputHandler(controller);
|
this.inGameInputHandler = new InGameInputHandler(controller);
|
||||||
@ -504,6 +544,8 @@ public class Controlify implements ControlifyApi {
|
|||||||
setInputMode(InputMode.MIXED);
|
setInputMode(InputMode.MIXED);
|
||||||
else if (changeInputMode)
|
else if (changeInputMode)
|
||||||
setInputMode(InputMode.CONTROLLER);
|
setInputMode(InputMode.CONTROLLER);
|
||||||
|
|
||||||
|
config().saveIfDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ControllerManager> getControllerManager() {
|
public Optional<ControllerManager> getControllerManager() {
|
||||||
|
@ -3,7 +3,6 @@ package dev.isxander.controlify.api.bind;
|
|||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import dev.isxander.controlify.bindings.BindContext;
|
import dev.isxander.controlify.bindings.BindContext;
|
||||||
import dev.isxander.controlify.bindings.IBind;
|
import dev.isxander.controlify.bindings.IBind;
|
||||||
import dev.isxander.controlify.bindings.RadialIcons;
|
|
||||||
import dev.isxander.yacl3.api.Option;
|
import dev.isxander.yacl3.api.Option;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
@ -17,6 +17,8 @@ import dev.isxander.controlify.controller.joystick.JoystickState;
|
|||||||
import dev.isxander.controlify.gui.DrawSize;
|
import dev.isxander.controlify.gui.DrawSize;
|
||||||
import dev.isxander.yacl3.api.Option;
|
import dev.isxander.yacl3.api.Option;
|
||||||
import dev.isxander.yacl3.api.OptionDescription;
|
import dev.isxander.yacl3.api.OptionDescription;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.locale.Language;
|
import net.minecraft.locale.Language;
|
||||||
@ -39,9 +41,9 @@ public class ControllerBindingImpl<T extends ControllerState> implements Control
|
|||||||
private final ResourceLocation radialIcon;
|
private final ResourceLocation radialIcon;
|
||||||
private final KeyMappingOverride override;
|
private final KeyMappingOverride override;
|
||||||
|
|
||||||
private static final Map<Controller<?, ?>, Set<IBind<?>>> pressedBinds = new HashMap<>();
|
private static final Map<Controller<?, ?>, Set<IBind<?>>> pressedBinds = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
private int fakePressState = 0;
|
private byte fakePressState = 0;
|
||||||
|
|
||||||
private ControllerBindingImpl(Controller<T, ?> controller, IBind<T> defaultBind, ResourceLocation id, KeyMappingOverride vanillaOverride, Component name, Component description, Component category, Set<BindContext> contexts, ResourceLocation icon) {
|
private ControllerBindingImpl(Controller<T, ?> controller, IBind<T> defaultBind, ResourceLocation id, KeyMappingOverride vanillaOverride, Component name, Component description, Component category, Set<BindContext> contexts, ResourceLocation icon) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
@ -219,7 +221,7 @@ public class ControllerBindingImpl<T extends ControllerState> implements Control
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void addPressedBind(ControllerBindingImpl<?> binding) {
|
private static void addPressedBind(ControllerBindingImpl<?> binding) {
|
||||||
pressedBinds.computeIfAbsent(binding.controller, c -> new HashSet<>()).addAll(getBinds(binding.bind));
|
pressedBinds.computeIfAbsent(binding.controller, c -> new ObjectOpenHashSet<>()).addAll(getBinds(binding.bind));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<IBind<?>> getBinds(IBind<?> bind) {
|
private static Set<IBind<?>> getBinds(IBind<?> bind) {
|
||||||
|
@ -14,6 +14,7 @@ import dev.isxander.controlify.mixins.compat.fapi.KeyBindingRegistryImplAccessor
|
|||||||
import dev.isxander.controlify.mixins.feature.bind.KeyMappingAccessor;
|
import dev.isxander.controlify.mixins.feature.bind.KeyMappingAccessor;
|
||||||
import dev.isxander.controlify.mixins.feature.bind.ToggleKeyMappingAccessor;
|
import dev.isxander.controlify.mixins.feature.bind.ToggleKeyMappingAccessor;
|
||||||
import dev.isxander.controlify.utils.Log;
|
import dev.isxander.controlify.utils.Log;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
@ -23,6 +24,7 @@ import net.minecraft.network.chat.Component;
|
|||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.effect.MobEffects;
|
import net.minecraft.world.effect.MobEffects;
|
||||||
import net.minecraft.world.item.Items;
|
import net.minecraft.world.item.Items;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
@ -32,6 +34,7 @@ import java.util.function.UnaryOperator;
|
|||||||
public class ControllerBindings<T extends ControllerState> {
|
public class ControllerBindings<T extends ControllerState> {
|
||||||
private static final Map<ResourceLocation, Function<ControllerBindings<?>, ControllerBinding>> CUSTOM_BINDS = new LinkedHashMap<>();
|
private static final Map<ResourceLocation, Function<ControllerBindings<?>, ControllerBinding>> CUSTOM_BINDS = new LinkedHashMap<>();
|
||||||
private static final Set<KeyMapping> EXCLUDED_VANILLA_BINDS = new HashSet<>();
|
private static final Set<KeyMapping> EXCLUDED_VANILLA_BINDS = new HashSet<>();
|
||||||
|
private static boolean lockRegistry = false;
|
||||||
|
|
||||||
public static final Component MOVEMENT_CATEGORY = Component.translatable("key.categories.movement");
|
public static final Component MOVEMENT_CATEGORY = Component.translatable("key.categories.movement");
|
||||||
public static final Component GAMEPLAY_CATEGORY = Component.translatable("key.categories.gameplay");
|
public static final Component GAMEPLAY_CATEGORY = Component.translatable("key.categories.gameplay");
|
||||||
@ -75,7 +78,7 @@ public class ControllerBindings<T extends ControllerState> {
|
|||||||
GUI_NAVI_UP, GUI_NAVI_DOWN, GUI_NAVI_LEFT, GUI_NAVI_RIGHT,
|
GUI_NAVI_UP, GUI_NAVI_DOWN, GUI_NAVI_LEFT, GUI_NAVI_RIGHT,
|
||||||
CYCLE_OPT_FORWARD, CYCLE_OPT_BACKWARD;
|
CYCLE_OPT_FORWARD, CYCLE_OPT_BACKWARD;
|
||||||
|
|
||||||
private final Map<ResourceLocation, ControllerBinding> registry = new LinkedHashMap<>();
|
private final Map<ResourceLocation, ControllerBinding> registry = new Object2ObjectLinkedOpenHashMap<>();
|
||||||
|
|
||||||
private final Controller<T, ?> controller;
|
private final Controller<T, ?> controller;
|
||||||
|
|
||||||
@ -643,11 +646,18 @@ public class ControllerBindings<T extends ControllerState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void lockRegistry() {
|
||||||
|
Api.INSTANCE.lockRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
public static final class Api implements ControlifyBindingsApi {
|
public static final class Api implements ControlifyBindingsApi {
|
||||||
public static final Api INSTANCE = new Api();
|
public static final Api INSTANCE = new Api();
|
||||||
|
|
||||||
|
private boolean lockedRegistry = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public dev.isxander.controlify.api.bind.BindingSupplier registerBind(ResourceLocation id, UnaryOperator<ControllerBindingBuilder<?>> builder) {
|
public dev.isxander.controlify.api.bind.BindingSupplier registerBind(ResourceLocation id, UnaryOperator<ControllerBindingBuilder<?>> builder) {
|
||||||
|
checkLocked();
|
||||||
CUSTOM_BINDS.put(id, bindings -> bindings.create(b -> builder.apply(b).identifier(id)));
|
CUSTOM_BINDS.put(id, bindings -> bindings.create(b -> builder.apply(b).identifier(id)));
|
||||||
return controller -> controller.bindings().get(id);
|
return controller -> controller.bindings().get(id);
|
||||||
}
|
}
|
||||||
@ -655,6 +665,7 @@ public class ControllerBindings<T extends ControllerState> {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public dev.isxander.controlify.api.bind.BindingSupplier registerBind(GamepadBinds bind, ResourceLocation id) {
|
public dev.isxander.controlify.api.bind.BindingSupplier registerBind(GamepadBinds bind, ResourceLocation id) {
|
||||||
|
checkLocked();
|
||||||
CUSTOM_BINDS.put(id, bindings -> bindings.create(bind, id));
|
CUSTOM_BINDS.put(id, bindings -> bindings.create(bind, id));
|
||||||
return controller -> controller.bindings().get(id);
|
return controller -> controller.bindings().get(id);
|
||||||
}
|
}
|
||||||
@ -662,18 +673,29 @@ public class ControllerBindings<T extends ControllerState> {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public dev.isxander.controlify.api.bind.BindingSupplier registerBind(GamepadBinds bind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride) {
|
public dev.isxander.controlify.api.bind.BindingSupplier registerBind(GamepadBinds bind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride) {
|
||||||
|
checkLocked();
|
||||||
CUSTOM_BINDS.put(id, bindings -> bindings.create(bind, id, override, toggleOverride));
|
CUSTOM_BINDS.put(id, bindings -> bindings.create(bind, id, override, toggleOverride));
|
||||||
return controller -> controller.bindings().get(id);
|
return controller -> controller.bindings().get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void excludeVanillaBind(KeyMapping... keyMappings) {
|
public void excludeVanillaBind(KeyMapping... keyMappings) {
|
||||||
|
checkLocked();
|
||||||
EXCLUDED_VANILLA_BINDS.addAll(Arrays.asList(keyMappings));
|
EXCLUDED_VANILLA_BINDS.addAll(Arrays.asList(keyMappings));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerRadialIcon(ResourceLocation id, RadialIcon icon) {
|
public void registerRadialIcon(ResourceLocation id, RadialIcon icon) {
|
||||||
|
checkLocked();
|
||||||
RadialIcons.registerIcon(id, icon);
|
RadialIcons.registerIcon(id, icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void lockRegistry() {
|
||||||
|
this.lockedRegistry = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLocked() {
|
||||||
|
Validate.isTrue(!lockedRegistry, "Cannot register new binds after registry is locked! You most likely tried to register a binding too late in Controlify's lifecycle.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
import dev.isxander.controlify.api.bind.RadialIcon;
|
import dev.isxander.controlify.api.bind.RadialIcon;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import net.minecraft.Util;
|
import net.minecraft.Util;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||||
@ -12,7 +13,6 @@ import net.minecraft.world.effect.MobEffect;
|
|||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class RadialIcons {
|
public final class RadialIcons {
|
||||||
@ -22,7 +22,7 @@ public final class RadialIcons {
|
|||||||
public static final ResourceLocation FABRIC_ICON = new ResourceLocation("fabric-resource-loader-v0", "icon.png");
|
public static final ResourceLocation FABRIC_ICON = new ResourceLocation("fabric-resource-loader-v0", "icon.png");
|
||||||
|
|
||||||
private static final Map<ResourceLocation, RadialIcon> icons = Util.make(() -> {
|
private static final Map<ResourceLocation, RadialIcon> icons = Util.make(() -> {
|
||||||
Map<ResourceLocation, RadialIcon> map = new HashMap<>();
|
Map<ResourceLocation, RadialIcon> map = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
map.put(EMPTY, (graphics, x, y, tickDelta) -> {});
|
map.put(EMPTY, (graphics, x, y, tickDelta) -> {});
|
||||||
map.put(FABRIC_ICON, (graphics, x, y, tickDelta) -> {
|
map.put(FABRIC_ICON, (graphics, x, y, tickDelta) -> {
|
||||||
|
@ -63,8 +63,8 @@ public class ControlifyConfig {
|
|||||||
Log.LOGGER.info("Loading Controlify config...");
|
Log.LOGGER.info("Loading Controlify config...");
|
||||||
|
|
||||||
if (!Files.exists(CONFIG_PATH)) {
|
if (!Files.exists(CONFIG_PATH)) {
|
||||||
firstLaunch = true;
|
|
||||||
if (lastSeenVersion == null) {
|
if (lastSeenVersion == null) {
|
||||||
|
firstLaunch = true;
|
||||||
try {
|
try {
|
||||||
lastSeenVersion = Version.parse("0.0.0");
|
lastSeenVersion = Version.parse("0.0.0");
|
||||||
} catch (VersionParsingException e) {
|
} catch (VersionParsingException e) {
|
||||||
|
@ -29,11 +29,6 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
|
|||||||
protected C config, defaultConfig;
|
protected C config, defaultConfig;
|
||||||
|
|
||||||
public AbstractController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
public AbstractController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||||
if (joystickId > GLFW.GLFW_JOYSTICK_LAST || joystickId < 0)
|
|
||||||
throw new IllegalArgumentException("Joystick ID " + joystickId + " is out of range!");
|
|
||||||
if (!GLFW.glfwJoystickPresent(joystickId))
|
|
||||||
throw new IllegalArgumentException("Joystick " + joystickId + " is not present and cannot be initialised!");
|
|
||||||
|
|
||||||
this.hidInfo = hidInfo;
|
this.hidInfo = hidInfo;
|
||||||
|
|
||||||
this.joystickId = joystickId;
|
this.joystickId = joystickId;
|
||||||
|
@ -2,13 +2,15 @@ package dev.isxander.controlify.controller.joystick;
|
|||||||
|
|
||||||
import dev.isxander.controlify.controller.ControllerConfig;
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class JoystickConfig extends ControllerConfig {
|
public class JoystickConfig extends ControllerConfig {
|
||||||
private Map<String, Float> deadzones;
|
private Map<String, Float> deadzones;
|
||||||
private Set<Integer> triggerAxes = new HashSet<>();
|
private Set<Integer> triggerAxes = new IntOpenHashSet();
|
||||||
|
|
||||||
private transient JoystickController<?> controller;
|
private transient JoystickController<?> controller;
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ public class JoystickConfig extends ControllerConfig {
|
|||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
|
|
||||||
if (this.deadzones == null) {
|
if (this.deadzones == null) {
|
||||||
deadzones = new HashMap<>();
|
deadzones = new Object2FloatOpenHashMap<>();
|
||||||
for (int i = 0; i < controller.mapping().axes().length; i++) {
|
for (int i = 0; i < controller.mapping().axes().length; i++) {
|
||||||
JoystickMapping.Axis axis = controller.mapping().axes()[i];
|
JoystickMapping.Axis axis = controller.mapping().axes()[i];
|
||||||
|
|
||||||
|
@ -13,13 +13,13 @@ import dev.isxander.controlify.hid.HIDDevice;
|
|||||||
import dev.isxander.controlify.utils.ControllerUtils;
|
import dev.isxander.controlify.utils.ControllerUtils;
|
||||||
import dev.isxander.controlify.utils.DebugLog;
|
import dev.isxander.controlify.utils.DebugLog;
|
||||||
import dev.isxander.controlify.utils.Log;
|
import dev.isxander.controlify.utils.Log;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import net.minecraft.CrashReport;
|
import net.minecraft.CrashReport;
|
||||||
import net.minecraft.CrashReportCategory;
|
import net.minecraft.CrashReportCategory;
|
||||||
import net.minecraft.ReportedException;
|
import net.minecraft.ReportedException;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.server.packs.resources.Resource;
|
import net.minecraft.server.packs.resources.Resource;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -29,7 +29,8 @@ import static dev.isxander.controlify.utils.ControllerUtils.wrapControllerError;
|
|||||||
public abstract class AbstractControllerManager implements ControllerManager {
|
public abstract class AbstractControllerManager implements ControllerManager {
|
||||||
protected final Controlify controlify;
|
protected final Controlify controlify;
|
||||||
protected final Minecraft minecraft;
|
protected final Minecraft minecraft;
|
||||||
private final Map<String, Controller<?, ?>> CONTROLLERS = new HashMap<>();
|
|
||||||
|
protected final Map<String, Controller<?, ?>> controllersByUid = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
public AbstractControllerManager() {
|
public AbstractControllerManager() {
|
||||||
this.controlify = Controlify.instance();
|
this.controlify = Controlify.instance();
|
||||||
@ -40,37 +41,35 @@ public abstract class AbstractControllerManager implements ControllerManager {
|
|||||||
.ifPresent(this::loadGamepadMappings);
|
.ifPresent(this::loadGamepadMappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Controller<?, ?>> createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
public Optional<Controller<?, ?>> createOrGet(int joystickIndex, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||||
try {
|
try {
|
||||||
Optional<String> uid = hidInfo.createControllerUID();
|
Optional<String> uid = hidInfo.createControllerUID();
|
||||||
if (uid.isPresent() && CONTROLLERS.containsKey(uid.get())) {
|
if (uid.isPresent() && controllersByUid.containsKey(uid.get())) {
|
||||||
return Optional.of(CONTROLLERS.get(uid.get()));
|
return Optional.of(controllersByUid.get(uid.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hidInfo.type().dontLoad()) {
|
if (hidInfo.type().dontLoad()) {
|
||||||
DebugLog.log("Preventing load of controller #" + joystickId + " because its type prevents loading.");
|
DebugLog.log("Preventing load of controller #" + joystickIndex + " because its type prevents loading.");
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isControllerGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK && !hidInfo.type().forceJoystick()) {
|
if (this.isControllerGamepad(joystickIndex) && !DebugProperties.FORCE_JOYSTICK && !hidInfo.type().forceJoystick()) {
|
||||||
GamepadController controller = new GamepadController(joystickId, hidInfo);
|
GamepadController controller = new GamepadController(joystickIndex, hidInfo);
|
||||||
CONTROLLERS.put(controller.uid(), controller);
|
this.addController(joystickIndex, controller);
|
||||||
checkCompoundJoysticks();
|
|
||||||
return Optional.of(controller);
|
return Optional.of(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleJoystickController controller = new SingleJoystickController(joystickId, hidInfo);
|
SingleJoystickController controller = new SingleJoystickController(joystickIndex, hidInfo);
|
||||||
CONTROLLERS.put(controller.uid(), controller);
|
this.addController(joystickIndex, controller);
|
||||||
checkCompoundJoysticks();
|
|
||||||
return Optional.of(controller);
|
return Optional.of(controller);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
CrashReport crashReport = CrashReport.forThrowable(e, "Creating controller #" + joystickId);
|
CrashReport crashReport = CrashReport.forThrowable(e, "Creating controller #" + joystickIndex);
|
||||||
CrashReportCategory category = crashReport.addCategory("Controller Info");
|
CrashReportCategory category = crashReport.addCategory("Controller Info");
|
||||||
category.setDetail("Joystick ID", joystickId);
|
category.setDetail("Joystick ID", joystickIndex);
|
||||||
category.setDetail("Controller identification", hidInfo.type());
|
category.setDetail("Controller identification", hidInfo.type());
|
||||||
category.setDetail("HID path", hidInfo.hidDevice().map(HIDDevice::path).orElse("N/A"));
|
category.setDetail("HID path", hidInfo.hidDevice().map(HIDDevice::path).orElse("N/A"));
|
||||||
category.setDetail("HID service status", Controlify.instance().controllerHIDService().isDisabled() ? "Disabled" : "Enabled");
|
category.setDetail("HID service status", Controlify.instance().controllerHIDService().isDisabled() ? "Disabled" : "Enabled");
|
||||||
category.setDetail("GLFW name", Optional.ofNullable(getControllerSystemName(joystickId)).orElse("N/A"));
|
category.setDetail("GLFW name", Optional.ofNullable(getControllerSystemName(joystickIndex)).orElse("N/A"));
|
||||||
throw new ReportedException(crashReport);
|
throw new ReportedException(crashReport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,11 +85,6 @@ public abstract class AbstractControllerManager implements ControllerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Controller<?, ?>> getController(int joystickId) {
|
|
||||||
return CONTROLLERS.values().stream().filter(controller -> controller.joystickId() == joystickId).findAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onControllerConnected(Controller<?, ?> controller, boolean hotplug) {
|
protected void onControllerConnected(Controller<?, ?> controller, boolean hotplug) {
|
||||||
Log.LOGGER.info("Controller connected: {}", ControllerUtils.createControllerString(controller));
|
Log.LOGGER.info("Controller connected: {}", ControllerUtils.createControllerString(controller));
|
||||||
|
|
||||||
@ -103,20 +97,19 @@ public abstract class AbstractControllerManager implements ControllerManager {
|
|||||||
Log.LOGGER.info("Controller disconnected: {}", ControllerUtils.createControllerString(controller));
|
Log.LOGGER.info("Controller disconnected: {}", ControllerUtils.createControllerString(controller));
|
||||||
|
|
||||||
controller.hidInfo().ifPresent(controlify.controllerHIDService()::unconsumeController);
|
controller.hidInfo().ifPresent(controlify.controllerHIDService()::unconsumeController);
|
||||||
removeController(controller);
|
removeController(controller.uid());
|
||||||
|
checkCompoundJoysticks();
|
||||||
|
|
||||||
ControlifyEvents.CONTROLLER_DISCONNECTED.invoker().onControllerDisconnected(controller);
|
ControlifyEvents.CONTROLLER_DISCONNECTED.invoker().onControllerDisconnected(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeController(Controller<?, ?> controller) {
|
protected void addController(int id, Controller<?, ?> controller) {
|
||||||
controller.close();
|
controllersByUid.put(controller.uid(), controller);
|
||||||
CONTROLLERS.remove(controller.uid(), controller);
|
|
||||||
|
|
||||||
checkCompoundJoysticks();
|
checkCompoundJoysticks();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeController(String uid) {
|
protected void removeController(String uid) {
|
||||||
Controller<?, ?> prev = CONTROLLERS.remove(uid);
|
Controller<?, ?> prev = controllersByUid.remove(uid);
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
prev.close();
|
prev.close();
|
||||||
}
|
}
|
||||||
@ -126,17 +119,17 @@ public abstract class AbstractControllerManager implements ControllerManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Controller<?, ?>> getConnectedControllers() {
|
public List<Controller<?, ?>> getConnectedControllers() {
|
||||||
return ImmutableList.copyOf(CONTROLLERS.values());
|
return ImmutableList.copyOf(controllersByUid.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isControllerConnected(String uid) {
|
public boolean isControllerConnected(String uid) {
|
||||||
return CONTROLLERS.containsKey(uid);
|
return controllersByUid.containsKey(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
CONTROLLERS.values().forEach(Controller::close);
|
controllersByUid.values().forEach(Controller::close);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void loadGamepadMappings(Resource resource);
|
protected abstract void loadGamepadMappings(Resource resource);
|
||||||
@ -154,7 +147,7 @@ public abstract class AbstractControllerManager implements ControllerManager {
|
|||||||
if (!info.isLoaded() && info.canBeUsed()) {
|
if (!info.isLoaded() && info.canBeUsed()) {
|
||||||
Log.LOGGER.info("Loading compound joystick " + info.type().mappingId() + ".");
|
Log.LOGGER.info("Loading compound joystick " + info.type().mappingId() + ".");
|
||||||
CompoundJoystickController controller = info.attemptCreate().orElseThrow();
|
CompoundJoystickController controller = info.attemptCreate().orElseThrow();
|
||||||
CONTROLLERS.put(info.type().mappingId(), controller);
|
controllersByUid.put(info.type().mappingId(), controller);
|
||||||
Controlify.instance().config().loadOrCreateControllerData(controller);
|
Controlify.instance().config().loadOrCreateControllerData(controller);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -14,8 +14,6 @@ public interface ControllerManager {
|
|||||||
|
|
||||||
List<Controller<?, ?>> getConnectedControllers();
|
List<Controller<?, ?>> getConnectedControllers();
|
||||||
|
|
||||||
Optional<Controller<?, ?>> getController(int jid);
|
|
||||||
|
|
||||||
boolean isControllerConnected(String uid);
|
boolean isControllerConnected(String uid);
|
||||||
|
|
||||||
boolean isControllerGamepad(int jid);
|
boolean isControllerGamepad(int jid);
|
||||||
|
@ -58,11 +58,6 @@ public class GLFWControllerManager extends AbstractControllerManager {
|
|||||||
return areControllersConnected();
|
return areControllersConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void tick(boolean outOfFocus) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void loadGamepadMappings(Resource resource) {
|
protected void loadGamepadMappings(Resource resource) {
|
||||||
Log.LOGGER.debug("Loading gamepad mappings...");
|
Log.LOGGER.debug("Loading gamepad mappings...");
|
||||||
@ -79,6 +74,10 @@ public class GLFWControllerManager extends AbstractControllerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<Controller<?, ?>> getController(int joystickId) {
|
||||||
|
return controllersByUid.values().stream().filter(controller -> controller.joystickId() == joystickId).findAny();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isControllerGamepad(int jid) {
|
public boolean isControllerGamepad(int jid) {
|
||||||
return GLFW.glfwJoystickIsGamepad(jid);
|
return GLFW.glfwJoystickIsGamepad(jid);
|
||||||
|
@ -5,11 +5,15 @@ import com.sun.jna.Memory;
|
|||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerType;
|
||||||
import dev.isxander.controlify.driver.SDL2NativesManager;
|
import dev.isxander.controlify.driver.SDL2NativesManager;
|
||||||
|
import dev.isxander.controlify.hid.ControllerHIDService;
|
||||||
import dev.isxander.controlify.utils.Log;
|
import dev.isxander.controlify.utils.Log;
|
||||||
import io.github.libsdl4j.api.event.SDL_Event;
|
import io.github.libsdl4j.api.event.SDL_Event;
|
||||||
import io.github.libsdl4j.api.event.SDL_EventFilter;
|
import io.github.libsdl4j.api.event.SDL_EventFilter;
|
||||||
import io.github.libsdl4j.api.rwops.SDL_RWops;
|
import io.github.libsdl4j.api.rwops.SDL_RWops;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.server.packs.resources.Resource;
|
import net.minecraft.server.packs.resources.Resource;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
@ -17,17 +21,17 @@ import org.apache.commons.lang3.Validate;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static io.github.libsdl4j.api.error.SdlError.SDL_GetError;
|
import static io.github.libsdl4j.api.error.SdlError.*;
|
||||||
import static io.github.libsdl4j.api.event.SDL_EventType.*;
|
import static io.github.libsdl4j.api.event.SDL_EventType.*;
|
||||||
import static io.github.libsdl4j.api.event.SdlEvents.*;
|
import static io.github.libsdl4j.api.event.SdlEvents.*;
|
||||||
import static io.github.libsdl4j.api.gamecontroller.SdlGamecontroller.*;
|
import static io.github.libsdl4j.api.gamecontroller.SdlGamecontroller.*;
|
||||||
import static io.github.libsdl4j.api.joystick.SdlJoystick.*;
|
import static io.github.libsdl4j.api.joystick.SdlJoystick.*;
|
||||||
import static io.github.libsdl4j.api.rwops.SdlRWops.SDL_RWFromConstMem;
|
import static io.github.libsdl4j.api.rwops.SdlRWops.*;
|
||||||
|
|
||||||
public class SDLControllerManager extends AbstractControllerManager {
|
public class SDLControllerManager extends AbstractControllerManager {
|
||||||
private final Controlify controlify;
|
private final Controlify controlify;
|
||||||
private final Minecraft minecraft;
|
|
||||||
|
|
||||||
|
private final Int2ObjectMap<Controller<?, ?>> controllersByJid = new Int2ObjectArrayMap<>();
|
||||||
private final SDL_Event event = new SDL_Event();
|
private final SDL_Event event = new SDL_Event();
|
||||||
|
|
||||||
// must keep a reference to prevent GC from collecting it and the callback failing
|
// must keep a reference to prevent GC from collecting it and the callback failing
|
||||||
@ -38,34 +42,34 @@ public class SDLControllerManager extends AbstractControllerManager {
|
|||||||
Validate.isTrue(SDL2NativesManager.isLoaded(), "SDL2 natives must be loaded before creating SDLControllerManager");
|
Validate.isTrue(SDL2NativesManager.isLoaded(), "SDL2 natives must be loaded before creating SDLControllerManager");
|
||||||
|
|
||||||
this.controlify = Controlify.instance();
|
this.controlify = Controlify.instance();
|
||||||
this.minecraft = Minecraft.getInstance();
|
|
||||||
|
|
||||||
SDL_SetEventFilter(eventFilter = new EventFilter(), Pointer.NULL);
|
SDL_SetEventFilter(eventFilter = new EventFilter(), Pointer.NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick(boolean outOfFocus) {
|
public void tick(boolean outOfFocus) {
|
||||||
|
super.tick(outOfFocus);
|
||||||
|
|
||||||
|
// SDL identifiers controllers in two different ways:
|
||||||
|
// device index, and device instance ID.
|
||||||
while (SDL_PollEvent(event) == 1) {
|
while (SDL_PollEvent(event) == 1) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
|
// On added, `which` refers to the device index
|
||||||
case SDL_JOYDEVICEADDED -> {
|
case SDL_JOYDEVICEADDED -> {
|
||||||
int jid = event.jdevice.which;
|
int deviceIndex = event.jdevice.which;
|
||||||
Optional<Controller<?, ?>> controllerOpt = createOrGet(jid, controlify.controllerHIDService().fetchType(jid));
|
Optional<Controller<?, ?>> controllerOpt = createOrGet(
|
||||||
controllerOpt.ifPresent(controller -> onControllerConnected(controller, true));
|
deviceIndex,
|
||||||
}
|
ControllerHIDService.fetchTypeFromSDL(deviceIndex)
|
||||||
case SDL_CONTROLLERDEVICEADDED -> {
|
.orElse(new ControllerHIDService.ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty()))
|
||||||
int jid = event.cdevice.which;
|
);
|
||||||
Optional<Controller<?, ?>> controllerOpt = createOrGet(jid, controlify.controllerHIDService().fetchType(jid));
|
|
||||||
controllerOpt.ifPresent(controller -> onControllerConnected(controller, true));
|
controllerOpt.ifPresent(controller -> onControllerConnected(controller, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On removed, `which` refers to the device instance ID
|
||||||
case SDL_JOYDEVICEREMOVED -> {
|
case SDL_JOYDEVICEREMOVED -> {
|
||||||
int jid = event.jdevice.which;
|
int jid = event.jdevice.which;
|
||||||
getController(jid).ifPresent(this::onControllerRemoved);
|
getController(jid).ifPresent(this::onControllerRemoved);
|
||||||
}
|
}
|
||||||
case SDL_CONTROLLERDEVICEREMOVED -> {
|
|
||||||
int jid = event.cdevice.which;
|
|
||||||
getController(jid).ifPresent(this::onControllerRemoved);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,6 +97,19 @@ public class SDLControllerManager extends AbstractControllerManager {
|
|||||||
return isControllerGamepad(joystickId) ? SDL_GameControllerNameForIndex(joystickId) : SDL_JoystickNameForIndex(joystickId);
|
return isControllerGamepad(joystickId) ? SDL_GameControllerNameForIndex(joystickId) : SDL_JoystickNameForIndex(joystickId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addController(int index, Controller<?, ?> controller) {
|
||||||
|
super.addController(index, controller);
|
||||||
|
|
||||||
|
// the instance id is technically a long, but it's usually only like 0, 1, 2, 3, etc.
|
||||||
|
int joystickId = SDL_JoystickGetDeviceInstanceID(index).intValue();
|
||||||
|
controllersByJid.put(joystickId, controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Controller<?, ?>> getController(int joystickId) {
|
||||||
|
return Optional.ofNullable(controllersByJid.get(joystickId));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void loadGamepadMappings(Resource resource) {
|
protected void loadGamepadMappings(Resource resource) {
|
||||||
Log.LOGGER.debug("Loading gamepad mappings...");
|
Log.LOGGER.debug("Loading gamepad mappings...");
|
||||||
@ -119,8 +136,6 @@ public class SDLControllerManager extends AbstractControllerManager {
|
|||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SDL_JOYDEVICEADDED:
|
case SDL_JOYDEVICEADDED:
|
||||||
case SDL_JOYDEVICEREMOVED:
|
case SDL_JOYDEVICEREMOVED:
|
||||||
case SDL_CONTROLLERDEVICEADDED:
|
|
||||||
case SDL_CONTROLLERDEVICEREMOVED:
|
|
||||||
return 1;
|
return 1;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -23,7 +23,7 @@ public class DebugProperties {
|
|||||||
/** Print what drivers are being used */
|
/** Print what drivers are being used */
|
||||||
public static final boolean PRINT_DRIVER = boolProp("controlify.debug.print_driver", true, true);
|
public static final boolean PRINT_DRIVER = boolProp("controlify.debug.print_driver", true, true);
|
||||||
/** Print the state of the left and right triggers on gamepads */
|
/** Print the state of the left and right triggers on gamepads */
|
||||||
public static final boolean PRINT_GAMEPAD_STATE = boolProp("controlify.debug.print_gamepad_state", false, true);
|
public static final boolean PRINT_GAMEPAD_STATE = boolProp("controlify.debug.print_gamepad_state", false, false);
|
||||||
/** Use experimental anti-snapback */
|
/** Use experimental anti-snapback */
|
||||||
public static final boolean USE_SNAPBACK = boolProp("controlify.debug.use_snapback", false, false);
|
public static final boolean USE_SNAPBACK = boolProp("controlify.debug.use_snapback", false, false);
|
||||||
|
|
||||||
|
@ -54,6 +54,11 @@ public class SDL2NativesManager {
|
|||||||
|
|
||||||
attemptedLoad = true;
|
attemptedLoad = true;
|
||||||
|
|
||||||
|
if (!isSupportedOnThisPlatform()) {
|
||||||
|
Log.LOGGER.warn("No native library for current platform, skipping SDL2 load");
|
||||||
|
return initFuture = CompletableFuture.completedFuture(false);
|
||||||
|
}
|
||||||
|
|
||||||
Path localLibraryPath = getNativesFolderPath().resolve(Target.CURRENT.getArtifactName());
|
Path localLibraryPath = getNativesFolderPath().resolve(Target.CURRENT.getArtifactName());
|
||||||
|
|
||||||
if (Files.exists(localLibraryPath)) {
|
if (Files.exists(localLibraryPath)) {
|
||||||
@ -178,6 +183,10 @@ public class SDL2NativesManager {
|
|||||||
return attemptedLoad;
|
return attemptedLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isSupportedOnThisPlatform() {
|
||||||
|
return Target.CURRENT.hasNativeLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
private static Path getNativesFolderPath() {
|
private static Path getNativesFolderPath() {
|
||||||
Path nativesFolderPath = FabricLoader.getInstance().getGameDir();
|
Path nativesFolderPath = FabricLoader.getInstance().getGameDir();
|
||||||
ControlifyConfig config = Controlify.instance().config();
|
ControlifyConfig config = Controlify.instance().config();
|
||||||
|
@ -63,12 +63,12 @@ public class SDL2GamepadDriver implements BasicGamepadInputDriver, GyroDriver, R
|
|||||||
// Triggers are in the range [0, 32767] (thanks SDL!)
|
// Triggers are in the range [0, 32767] (thanks SDL!)
|
||||||
// https://wiki.libsdl.org/SDL2/SDL_GameControllerGetAxis
|
// https://wiki.libsdl.org/SDL2/SDL_GameControllerGetAxis
|
||||||
GamepadState.AxesState axes = new GamepadState.AxesState(
|
GamepadState.AxesState axes = new GamepadState.AxesState(
|
||||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_LEFTX), Short.MIN_VALUE, Short.MAX_VALUE) * 2f - 1f,
|
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_LEFTX)),
|
||||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_LEFTY), Short.MIN_VALUE, Short.MAX_VALUE) * 2f - 1f,
|
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_LEFTY)),
|
||||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_RIGHTX), Short.MIN_VALUE, Short.MAX_VALUE) * 2f - 1f,
|
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_RIGHTX)),
|
||||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_RIGHTY), Short.MIN_VALUE, Short.MAX_VALUE) * 2f - 1f,
|
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_RIGHTY)),
|
||||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_TRIGGERLEFT), 0, Short.MAX_VALUE),
|
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_TRIGGERLEFT)),
|
||||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_TRIGGERRIGHT), 0, Short.MAX_VALUE)
|
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_TRIGGERRIGHT))
|
||||||
);
|
);
|
||||||
// Button values return 1 if pressed, 0 if not
|
// Button values return 1 if pressed, 0 if not
|
||||||
// https://wiki.libsdl.org/SDL2/SDL_GameControllerGetButton
|
// https://wiki.libsdl.org/SDL2/SDL_GameControllerGetButton
|
||||||
@ -190,4 +190,9 @@ public class SDL2GamepadDriver implements BasicGamepadInputDriver, GyroDriver, R
|
|||||||
public String getBasicGamepadDetails() {
|
public String getBasicGamepadDetails() {
|
||||||
return "SDL2gp";
|
return "SDL2gp";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float mapShortToFloat(short value) {
|
||||||
|
return Mth.clampedMap(value, Short.MIN_VALUE, 0, -1f, 0f)
|
||||||
|
+ Mth.clampedMap(value, 0, Short.MAX_VALUE, 0f, 1f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,10 @@ public class InGameButtonGuide implements IngameGuideRegistry {
|
|||||||
registerGuideAction(controller.bindings().ATTACK, ActionLocation.RIGHT, (ctx) -> {
|
registerGuideAction(controller.bindings().ATTACK, ActionLocation.RIGHT, (ctx) -> {
|
||||||
var hitResult = ctx.hitResult();
|
var hitResult = ctx.hitResult();
|
||||||
if (hitResult.getType() == HitResult.Type.ENTITY)
|
if (hitResult.getType() == HitResult.Type.ENTITY)
|
||||||
return Optional.of(Component.translatable("controlify.guide.ingame.attack"));
|
if (player.isSpectator())
|
||||||
|
return Optional.of(Component.translatable("controlify.guide.ingame.spectate"));
|
||||||
|
else
|
||||||
|
return Optional.of(Component.translatable("controlify.guide.ingame.attack"));
|
||||||
if (hitResult.getType() == HitResult.Type.BLOCK)
|
if (hitResult.getType() == HitResult.Type.BLOCK)
|
||||||
return Optional.of(Component.translatable("controlify.guide.ingame.break"));
|
return Optional.of(Component.translatable("controlify.guide.ingame.break"));
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package dev.isxander.controlify.gui.screen;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.AlertScreen;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.network.chat.CommonComponents;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
public class NoSDLScreen extends AlertScreen {
|
||||||
|
public NoSDLScreen(Runnable actionHandler, Screen parent) {
|
||||||
|
super(
|
||||||
|
() -> {
|
||||||
|
actionHandler.run();
|
||||||
|
Minecraft.getInstance().setScreen(parent);
|
||||||
|
},
|
||||||
|
Component.translatable("controlify.gui.no_sdl.title"),
|
||||||
|
Component.translatable("controlify.gui.no_sdl.message"),
|
||||||
|
CommonComponents.GUI_OK,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,9 +7,9 @@ import dev.isxander.controlify.driver.SDL2NativesManager;
|
|||||||
import dev.isxander.controlify.debug.DebugProperties;
|
import dev.isxander.controlify.debug.DebugProperties;
|
||||||
import dev.isxander.controlify.utils.Log;
|
import dev.isxander.controlify.utils.Log;
|
||||||
import dev.isxander.controlify.utils.ToastUtils;
|
import dev.isxander.controlify.utils.ToastUtils;
|
||||||
|
import io.github.libsdl4j.api.joystick.SDL_JoystickGUID;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import org.hid4java.*;
|
import org.hid4java.*;
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
@ -154,17 +154,17 @@ public class ControllerHIDService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ControllerHIDInfo> fetchTypeFromSDL(int jid) {
|
public static Optional<ControllerHIDInfo> fetchTypeFromSDL(int jid) {
|
||||||
if (SDL2NativesManager.isLoaded()) {
|
if (SDL2NativesManager.isLoaded()) {
|
||||||
int vid = SDL_JoystickGetDeviceVendor(jid);
|
int vid = SDL_JoystickGetDeviceVendor(jid);
|
||||||
int pid = SDL_JoystickGetDeviceProduct(jid);
|
int pid = SDL_JoystickGetDeviceProduct(jid);
|
||||||
String path = GLFW.glfwGetJoystickGUID(jid);
|
SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(jid);
|
||||||
|
|
||||||
if (vid != 0 && pid != 0) {
|
if (vid != 0 && pid != 0) {
|
||||||
Log.LOGGER.info("Using SDL to identify controller type.");
|
Log.LOGGER.info("Using SDL to identify controller type.");
|
||||||
return Optional.of(new ControllerHIDInfo(
|
return Optional.of(new ControllerHIDInfo(
|
||||||
ControllerType.getTypeForHID(new HIDIdentifier(vid, pid)),
|
ControllerType.getTypeForHID(new HIDIdentifier(vid, pid)),
|
||||||
Optional.of(new HIDDevice.SDLHidApi(vid, pid, path))
|
Optional.of(new HIDDevice.SDLHidApi(vid, pid, guid.toString()))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,13 @@ package dev.isxander.controlify.rumble;
|
|||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import dev.isxander.controlify.utils.Log;
|
import dev.isxander.controlify.utils.Log;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public record RumbleSource(ResourceLocation id) {
|
public record RumbleSource(ResourceLocation id) {
|
||||||
private static final Map<ResourceLocation, RumbleSource> SOURCES = new LinkedHashMap<>();
|
private static final Map<ResourceLocation, RumbleSource> SOURCES = new Object2ObjectLinkedOpenHashMap<>();
|
||||||
|
|
||||||
public static final RumbleSource
|
public static final RumbleSource
|
||||||
MASTER = register("master"),
|
MASTER = register("master"),
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package dev.isxander.controlify.screenop;
|
package dev.isxander.controlify.screenop;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -13,8 +13,8 @@ public class Registry<T, U> {
|
|||||||
private final Map<T, U> cache;
|
private final Map<T, U> cache;
|
||||||
|
|
||||||
public Registry() {
|
public Registry() {
|
||||||
this.registry = new HashMap<>();
|
this.registry = new Object2ObjectOpenHashMap<>();
|
||||||
this.cache = new HashMap<>();
|
this.cache = new Object2ObjectOpenHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package dev.isxander.controlify.screenop;
|
package dev.isxander.controlify.screenop;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -8,7 +9,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public final class ScreenProcessorFactory {
|
public final class ScreenProcessorFactory {
|
||||||
private static final Map<Class<? extends Screen>, Factory<?>> factories = new HashMap<>();
|
private static final Map<Class<? extends Screen>, Factory<?>> factories = new Object2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
private ScreenProcessorFactory() {
|
private ScreenProcessorFactory() {
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,9 @@
|
|||||||
|
|
||||||
"controlify.gui.controller_unavailable": "Controller unavailable and cannot be edited.",
|
"controlify.gui.controller_unavailable": "Controller unavailable and cannot be edited.",
|
||||||
|
|
||||||
|
"controlify.gui.no_sdl.title": "Controlify Native Library Incompatible",
|
||||||
|
"controlify.gui.no_sdl.message": "Controlify uses an extra native library to add more features for controller support. Your system does not support this library. You will be missing out on features such as:\n - controller vibration\n - enhanced controller identification\n - gyroscope controls",
|
||||||
|
|
||||||
"controlify.new_features.title": "Controlify updated to %s!",
|
"controlify.new_features.title": "Controlify updated to %s!",
|
||||||
"controlify.new_features.1.5.0": "Added a radial menu that can be configured to any action you want. You can find it in your controller settings.",
|
"controlify.new_features.1.5.0": "Added a radial menu that can be configured to any action you want. You can find it in your controller settings.",
|
||||||
|
|
||||||
@ -175,10 +178,8 @@
|
|||||||
"controlify.toast.vmouse_disabled.description": "Controlify virtual mouse is now disabled for this screen.",
|
"controlify.toast.vmouse_disabled.description": "Controlify virtual mouse is now disabled for this screen.",
|
||||||
"controlify.toast.vmouse_unavailable.title": "Virtual Mouse Unavailable",
|
"controlify.toast.vmouse_unavailable.title": "Virtual Mouse Unavailable",
|
||||||
"controlify.toast.vmouse_unavailable.description": "This screen is forcing a specific virtual mouse mode and you cannot change it.",
|
"controlify.toast.vmouse_unavailable.description": "This screen is forcing a specific virtual mouse mode and you cannot change it.",
|
||||||
"controlify.toast.ask_to_switch.title": "Switch Controller?",
|
"controlify.toast.controller_connected.title": "Controller Connected",
|
||||||
"controlify.toast.ask_to_switch.description": "A new controller named '%s' has just been connected. Press any button to switch to it.",
|
"controlify.toast.controller_connected.description": "A controller named `%s` has been connected. It has automatically been switched to.",
|
||||||
"controlify.toast.default_controller_connected.title": "Controller Connected",
|
|
||||||
"controlify.toast.default_controller_connected.description": "Your primary controller has been connected and automatically switched to.",
|
|
||||||
"controlify.toast.controller_disconnected.title": "Controller Disconnected",
|
"controlify.toast.controller_disconnected.title": "Controller Disconnected",
|
||||||
"controlify.toast.controller_disconnected.description": "'%s' was disconnected.",
|
"controlify.toast.controller_disconnected.description": "'%s' was disconnected.",
|
||||||
"controlify.toast.faulty_input.title": "Controller disabled",
|
"controlify.toast.faulty_input.title": "Controller disabled",
|
||||||
|
Reference in New Issue
Block a user