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"
|
||||
version = "1.7.0-beta.2+1.20"
|
||||
version = "1.7.0-beta.3+1.20.2"
|
||||
val isAlpha = "alpha" in version.toString()
|
||||
val isBeta = "beta" in version.toString()
|
||||
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 dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.compatibility.ControlifyCompat;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
@ -63,10 +64,12 @@ public class Controlify implements ControlifyApi {
|
||||
private boolean probeMode = false;
|
||||
|
||||
private Controller<?, ?> currentController = null;
|
||||
private InputMode currentInputMode = InputMode.KEYBOARD_MOUSE;
|
||||
|
||||
private InGameInputHandler inGameInputHandler;
|
||||
public InGameButtonGuide inGameButtonGuide;
|
||||
private VirtualMouseHandler virtualMouseHandler;
|
||||
private InputMode currentInputMode = InputMode.KEYBOARD_MOUSE;
|
||||
|
||||
private ControllerHIDService controllerHIDService;
|
||||
|
||||
private CompletableFuture<Boolean> nativeOnboardingFuture = null;
|
||||
@ -81,10 +84,6 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
private int showMouseTicks = 0;
|
||||
|
||||
private @Nullable Controller<?, ?> switchableController = null;
|
||||
private double askSwitchTime = 0;
|
||||
private ToastUtils.ControlifyToast askSwitchToast = null;
|
||||
|
||||
/**
|
||||
* Called at usual fabric client entrypoint.
|
||||
* Always runs, even with no controllers detected.
|
||||
@ -178,10 +177,6 @@ public class Controlify implements ControlifyApi {
|
||||
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
|
||||
ClientLifecycleEvents.CLIENT_STOPPING.register(minecraft -> {
|
||||
controllerHIDService().stop();
|
||||
@ -211,19 +206,28 @@ public class Controlify implements ControlifyApi {
|
||||
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()) {
|
||||
Controller<?, ?> controller = controllerManager.getConnectedControllers().stream().findFirst().orElse(null);
|
||||
if (controller != null && (controller.config().delayedCalibration || !controller.config().deadzonesCalibrated)) {
|
||||
controller = null;
|
||||
}
|
||||
Optional<Controller<?, ?>> lastUsedController = controllerManager.getConnectedControllers()
|
||||
.stream()
|
||||
.filter(c -> c.uid().equals(config().currentControllerUid()))
|
||||
.findAny();
|
||||
|
||||
this.setCurrentController(controller, false);
|
||||
} else {
|
||||
// setCurrentController saves config so there is no need to set dirty to save
|
||||
config().saveIfDirty();
|
||||
if (lastUsedController.isPresent()) {
|
||||
this.setCurrentController(lastUsedController.get(), false);
|
||||
} else {
|
||||
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 -> {
|
||||
try {
|
||||
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() {
|
||||
if (finishedInit) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
probeMode = false;
|
||||
finishedInit = true;
|
||||
|
||||
askNatives().whenComplete((loaded, th) -> {
|
||||
@ -248,12 +258,33 @@ public class Controlify implements ControlifyApi {
|
||||
ConnectServerEvent.EVENT.register((minecraft, address, 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();
|
||||
});
|
||||
|
||||
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) {
|
||||
if (SubmitUnknownControllerScreen.canSubmit(controller)) {
|
||||
minecraft.setScreen(new SubmitUnknownControllerScreen(controller, minecraft.screen));
|
||||
@ -264,12 +295,10 @@ public class Controlify implements ControlifyApi {
|
||||
config().setDirty();
|
||||
}
|
||||
|
||||
if (hotplugged) {
|
||||
if (controller.config().deadzonesCalibrated) {
|
||||
setCurrentController(controller, hotplugged);
|
||||
} else {
|
||||
calibrationQueue.add(controller);
|
||||
}
|
||||
if (!controller.config().deadzonesCalibrated) {
|
||||
calibrationQueue.add(controller);
|
||||
} else if (hotplugged) {
|
||||
setCurrentController(controller, true);
|
||||
}
|
||||
|
||||
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()),
|
||||
true
|
||||
);
|
||||
} else {
|
||||
} else if (hotplugged) {
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.controller_connected.title"),
|
||||
Component.translatable("controlify.toast.controller_connected.description", controller.name()),
|
||||
@ -289,8 +318,17 @@ public class Controlify implements ControlifyApi {
|
||||
if (minecraft.screen instanceof ControllerCarouselScreen controllerListScreen) {
|
||||
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) {
|
||||
this.setCurrentController(
|
||||
controllerManager.getConnectedControllers()
|
||||
@ -325,6 +363,14 @@ public class Controlify implements ControlifyApi {
|
||||
if (nativeOnboardingFuture != null)
|
||||
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
|
||||
// and return a completed future
|
||||
if (config().globalSettings().vibrationOnboarded) {
|
||||
@ -373,19 +419,10 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
boolean outOfFocus = !config().globalSettings().outOfFocusInput && !client.isWindowActive();
|
||||
|
||||
// handles updating state of all controllers
|
||||
controllerManager.tick(outOfFocus);
|
||||
|
||||
if (switchableController != null && Blaze3D.getTime() - askSwitchTime <= 10000) {
|
||||
if (switchableController.state().hasAnyInput()) {
|
||||
switchableController.clearState();
|
||||
this.setCurrentController(switchableController, true); // setCurrentController sets switchableController to null
|
||||
if (askSwitchToast != null) {
|
||||
askSwitchToast.remove();
|
||||
askSwitchToast = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle showing/hiding mouse whilst in mixed input mode
|
||||
if (minecraft.mouseHandler.isMouseGrabbed())
|
||||
showMouseTicks = 0;
|
||||
if (currentInputMode() == InputMode.MIXED && showMouseTicks > 0) {
|
||||
@ -400,6 +437,7 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
LowBatteryNotifier.tick();
|
||||
|
||||
// if splitscreen ever happens this can tick over every controller
|
||||
getCurrentController().ifPresent(currentController -> {
|
||||
wrapControllerError(
|
||||
() -> 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) {
|
||||
ControllerState state = controller.state();
|
||||
|
||||
@ -479,10 +523,6 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
this.currentController = controller;
|
||||
|
||||
if (switchableController == controller) {
|
||||
switchableController = null;
|
||||
}
|
||||
|
||||
if (controller == null) {
|
||||
this.setInputMode(InputMode.KEYBOARD_MOUSE);
|
||||
this.inGameInputHandler = null;
|
||||
@ -495,7 +535,7 @@ public class Controlify implements ControlifyApi {
|
||||
DebugLog.log("Updated current controller to {}({})", controller.name(), controller.uid());
|
||||
|
||||
if (!controller.uid().equals(config().currentControllerUid())) {
|
||||
config().save();
|
||||
config().setDirty();
|
||||
}
|
||||
|
||||
this.inGameInputHandler = new InGameInputHandler(controller);
|
||||
@ -504,6 +544,8 @@ public class Controlify implements ControlifyApi {
|
||||
setInputMode(InputMode.MIXED);
|
||||
else if (changeInputMode)
|
||||
setInputMode(InputMode.CONTROLLER);
|
||||
|
||||
config().saveIfDirty();
|
||||
}
|
||||
|
||||
public Optional<ControllerManager> getControllerManager() {
|
||||
|
@ -3,7 +3,6 @@ package dev.isxander.controlify.api.bind;
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.isxander.controlify.bindings.BindContext;
|
||||
import dev.isxander.controlify.bindings.IBind;
|
||||
import dev.isxander.controlify.bindings.RadialIcons;
|
||||
import dev.isxander.yacl3.api.Option;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
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.yacl3.api.Option;
|
||||
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.gui.GuiGraphics;
|
||||
import net.minecraft.locale.Language;
|
||||
@ -39,9 +41,9 @@ public class ControllerBindingImpl<T extends ControllerState> implements Control
|
||||
private final ResourceLocation radialIcon;
|
||||
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) {
|
||||
this.controller = controller;
|
||||
@ -219,7 +221,7 @@ public class ControllerBindingImpl<T extends ControllerState> implements Control
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -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.ToggleKeyMappingAccessor;
|
||||
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.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
@ -23,6 +24,7 @@ import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.item.Items;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BooleanSupplier;
|
||||
@ -32,6 +34,7 @@ import java.util.function.UnaryOperator;
|
||||
public class ControllerBindings<T extends ControllerState> {
|
||||
private static final Map<ResourceLocation, Function<ControllerBindings<?>, ControllerBinding>> CUSTOM_BINDS = new LinkedHashMap<>();
|
||||
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 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,
|
||||
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;
|
||||
|
||||
@ -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 Api INSTANCE = new Api();
|
||||
|
||||
private boolean lockedRegistry = false;
|
||||
|
||||
@Override
|
||||
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)));
|
||||
return controller -> controller.bindings().get(id);
|
||||
}
|
||||
@ -655,6 +665,7 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
@Deprecated
|
||||
@Override
|
||||
public dev.isxander.controlify.api.bind.BindingSupplier registerBind(GamepadBinds bind, ResourceLocation id) {
|
||||
checkLocked();
|
||||
CUSTOM_BINDS.put(id, bindings -> bindings.create(bind, id));
|
||||
return controller -> controller.bindings().get(id);
|
||||
}
|
||||
@ -662,18 +673,29 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
@Deprecated
|
||||
@Override
|
||||
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));
|
||||
return controller -> controller.bindings().get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void excludeVanillaBind(KeyMapping... keyMappings) {
|
||||
checkLocked();
|
||||
EXCLUDED_VANILLA_BINDS.addAll(Arrays.asList(keyMappings));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRadialIcon(ResourceLocation id, RadialIcon icon) {
|
||||
checkLocked();
|
||||
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;
|
||||
|
||||
import dev.isxander.controlify.api.bind.RadialIcon;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
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.ItemStack;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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");
|
||||
|
||||
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(FABRIC_ICON, (graphics, x, y, tickDelta) -> {
|
||||
|
@ -63,8 +63,8 @@ public class ControlifyConfig {
|
||||
Log.LOGGER.info("Loading Controlify config...");
|
||||
|
||||
if (!Files.exists(CONFIG_PATH)) {
|
||||
firstLaunch = true;
|
||||
if (lastSeenVersion == null) {
|
||||
firstLaunch = true;
|
||||
try {
|
||||
lastSeenVersion = Version.parse("0.0.0");
|
||||
} catch (VersionParsingException e) {
|
||||
|
@ -29,11 +29,6 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
|
||||
protected C config, defaultConfig;
|
||||
|
||||
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.joystickId = joystickId;
|
||||
|
@ -2,13 +2,15 @@ package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import dev.isxander.controlify.controller.ControllerConfig;
|
||||
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 java.util.*;
|
||||
|
||||
public class JoystickConfig extends ControllerConfig {
|
||||
private Map<String, Float> deadzones;
|
||||
private Set<Integer> triggerAxes = new HashSet<>();
|
||||
private Set<Integer> triggerAxes = new IntOpenHashSet();
|
||||
|
||||
private transient JoystickController<?> controller;
|
||||
|
||||
@ -55,7 +57,7 @@ public class JoystickConfig extends ControllerConfig {
|
||||
this.controller = controller;
|
||||
|
||||
if (this.deadzones == null) {
|
||||
deadzones = new HashMap<>();
|
||||
deadzones = new Object2FloatOpenHashMap<>();
|
||||
for (int i = 0; i < controller.mapping().axes().length; 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.DebugLog;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.CrashReport;
|
||||
import net.minecraft.CrashReportCategory;
|
||||
import net.minecraft.ReportedException;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -29,7 +29,8 @@ import static dev.isxander.controlify.utils.ControllerUtils.wrapControllerError;
|
||||
public abstract class AbstractControllerManager implements ControllerManager {
|
||||
protected final Controlify controlify;
|
||||
protected final Minecraft minecraft;
|
||||
private final Map<String, Controller<?, ?>> CONTROLLERS = new HashMap<>();
|
||||
|
||||
protected final Map<String, Controller<?, ?>> controllersByUid = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
public AbstractControllerManager() {
|
||||
this.controlify = Controlify.instance();
|
||||
@ -40,37 +41,35 @@ public abstract class AbstractControllerManager implements ControllerManager {
|
||||
.ifPresent(this::loadGamepadMappings);
|
||||
}
|
||||
|
||||
public Optional<Controller<?, ?>> createOrGet(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
public Optional<Controller<?, ?>> createOrGet(int joystickIndex, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
try {
|
||||
Optional<String> uid = hidInfo.createControllerUID();
|
||||
if (uid.isPresent() && CONTROLLERS.containsKey(uid.get())) {
|
||||
return Optional.of(CONTROLLERS.get(uid.get()));
|
||||
if (uid.isPresent() && controllersByUid.containsKey(uid.get())) {
|
||||
return Optional.of(controllersByUid.get(uid.get()));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (this.isControllerGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK && !hidInfo.type().forceJoystick()) {
|
||||
GamepadController controller = new GamepadController(joystickId, hidInfo);
|
||||
CONTROLLERS.put(controller.uid(), controller);
|
||||
checkCompoundJoysticks();
|
||||
if (this.isControllerGamepad(joystickIndex) && !DebugProperties.FORCE_JOYSTICK && !hidInfo.type().forceJoystick()) {
|
||||
GamepadController controller = new GamepadController(joystickIndex, hidInfo);
|
||||
this.addController(joystickIndex, controller);
|
||||
return Optional.of(controller);
|
||||
}
|
||||
|
||||
SingleJoystickController controller = new SingleJoystickController(joystickId, hidInfo);
|
||||
CONTROLLERS.put(controller.uid(), controller);
|
||||
checkCompoundJoysticks();
|
||||
SingleJoystickController controller = new SingleJoystickController(joystickIndex, hidInfo);
|
||||
this.addController(joystickIndex, controller);
|
||||
return Optional.of(controller);
|
||||
} 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");
|
||||
category.setDetail("Joystick ID", joystickId);
|
||||
category.setDetail("Joystick ID", joystickIndex);
|
||||
category.setDetail("Controller identification", hidInfo.type());
|
||||
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("GLFW name", Optional.ofNullable(getControllerSystemName(joystickId)).orElse("N/A"));
|
||||
category.setDetail("GLFW name", Optional.ofNullable(getControllerSystemName(joystickIndex)).orElse("N/A"));
|
||||
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) {
|
||||
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));
|
||||
|
||||
controller.hidInfo().ifPresent(controlify.controllerHIDService()::unconsumeController);
|
||||
removeController(controller);
|
||||
removeController(controller.uid());
|
||||
checkCompoundJoysticks();
|
||||
|
||||
ControlifyEvents.CONTROLLER_DISCONNECTED.invoker().onControllerDisconnected(controller);
|
||||
}
|
||||
|
||||
protected void removeController(Controller<?, ?> controller) {
|
||||
controller.close();
|
||||
CONTROLLERS.remove(controller.uid(), controller);
|
||||
|
||||
protected void addController(int id, Controller<?, ?> controller) {
|
||||
controllersByUid.put(controller.uid(), controller);
|
||||
checkCompoundJoysticks();
|
||||
}
|
||||
|
||||
protected void removeController(String uid) {
|
||||
Controller<?, ?> prev = CONTROLLERS.remove(uid);
|
||||
Controller<?, ?> prev = controllersByUid.remove(uid);
|
||||
if (prev != null) {
|
||||
prev.close();
|
||||
}
|
||||
@ -126,17 +119,17 @@ public abstract class AbstractControllerManager implements ControllerManager {
|
||||
|
||||
@Override
|
||||
public List<Controller<?, ?>> getConnectedControllers() {
|
||||
return ImmutableList.copyOf(CONTROLLERS.values());
|
||||
return ImmutableList.copyOf(controllersByUid.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isControllerConnected(String uid) {
|
||||
return CONTROLLERS.containsKey(uid);
|
||||
return controllersByUid.containsKey(uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
CONTROLLERS.values().forEach(Controller::close);
|
||||
controllersByUid.values().forEach(Controller::close);
|
||||
}
|
||||
|
||||
protected abstract void loadGamepadMappings(Resource resource);
|
||||
@ -154,7 +147,7 @@ public abstract class AbstractControllerManager implements ControllerManager {
|
||||
if (!info.isLoaded() && info.canBeUsed()) {
|
||||
Log.LOGGER.info("Loading compound joystick " + info.type().mappingId() + ".");
|
||||
CompoundJoystickController controller = info.attemptCreate().orElseThrow();
|
||||
CONTROLLERS.put(info.type().mappingId(), controller);
|
||||
controllersByUid.put(info.type().mappingId(), controller);
|
||||
Controlify.instance().config().loadOrCreateControllerData(controller);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -14,8 +14,6 @@ public interface ControllerManager {
|
||||
|
||||
List<Controller<?, ?>> getConnectedControllers();
|
||||
|
||||
Optional<Controller<?, ?>> getController(int jid);
|
||||
|
||||
boolean isControllerConnected(String uid);
|
||||
|
||||
boolean isControllerGamepad(int jid);
|
||||
|
@ -58,11 +58,6 @@ public class GLFWControllerManager extends AbstractControllerManager {
|
||||
return areControllersConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(boolean outOfFocus) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadGamepadMappings(Resource resource) {
|
||||
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
|
||||
public boolean isControllerGamepad(int jid) {
|
||||
return GLFW.glfwJoystickIsGamepad(jid);
|
||||
|
@ -5,11 +5,15 @@ import com.sun.jna.Memory;
|
||||
import com.sun.jna.Pointer;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.driver.SDL2NativesManager;
|
||||
import dev.isxander.controlify.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import io.github.libsdl4j.api.event.SDL_Event;
|
||||
import io.github.libsdl4j.api.event.SDL_EventFilter;
|
||||
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.server.packs.resources.Resource;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
@ -17,17 +21,17 @@ import org.apache.commons.lang3.Validate;
|
||||
import java.io.InputStream;
|
||||
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.SdlEvents.*;
|
||||
import static io.github.libsdl4j.api.gamecontroller.SdlGamecontroller.*;
|
||||
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 {
|
||||
private final Controlify controlify;
|
||||
private final Minecraft minecraft;
|
||||
|
||||
private final Int2ObjectMap<Controller<?, ?>> controllersByJid = new Int2ObjectArrayMap<>();
|
||||
private final SDL_Event event = new SDL_Event();
|
||||
|
||||
// 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");
|
||||
|
||||
this.controlify = Controlify.instance();
|
||||
this.minecraft = Minecraft.getInstance();
|
||||
|
||||
SDL_SetEventFilter(eventFilter = new EventFilter(), Pointer.NULL);
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
switch (event.type) {
|
||||
// On added, `which` refers to the device index
|
||||
case SDL_JOYDEVICEADDED -> {
|
||||
int jid = event.jdevice.which;
|
||||
Optional<Controller<?, ?>> controllerOpt = createOrGet(jid, controlify.controllerHIDService().fetchType(jid));
|
||||
controllerOpt.ifPresent(controller -> onControllerConnected(controller, true));
|
||||
}
|
||||
case SDL_CONTROLLERDEVICEADDED -> {
|
||||
int jid = event.cdevice.which;
|
||||
Optional<Controller<?, ?>> controllerOpt = createOrGet(jid, controlify.controllerHIDService().fetchType(jid));
|
||||
int deviceIndex = event.jdevice.which;
|
||||
Optional<Controller<?, ?>> controllerOpt = createOrGet(
|
||||
deviceIndex,
|
||||
ControllerHIDService.fetchTypeFromSDL(deviceIndex)
|
||||
.orElse(new ControllerHIDService.ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty()))
|
||||
);
|
||||
controllerOpt.ifPresent(controller -> onControllerConnected(controller, true));
|
||||
}
|
||||
|
||||
// On removed, `which` refers to the device instance ID
|
||||
case SDL_JOYDEVICEREMOVED -> {
|
||||
int jid = event.jdevice.which;
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
protected void loadGamepadMappings(Resource resource) {
|
||||
Log.LOGGER.debug("Loading gamepad mappings...");
|
||||
@ -119,8 +136,6 @@ public class SDLControllerManager extends AbstractControllerManager {
|
||||
switch (event.type) {
|
||||
case SDL_JOYDEVICEADDED:
|
||||
case SDL_JOYDEVICEREMOVED:
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
|
@ -23,7 +23,7 @@ public class DebugProperties {
|
||||
/** Print what drivers are being used */
|
||||
public static final boolean PRINT_DRIVER = boolProp("controlify.debug.print_driver", true, true);
|
||||
/** 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 */
|
||||
public static final boolean USE_SNAPBACK = boolProp("controlify.debug.use_snapback", false, false);
|
||||
|
||||
|
@ -54,6 +54,11 @@ public class SDL2NativesManager {
|
||||
|
||||
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());
|
||||
|
||||
if (Files.exists(localLibraryPath)) {
|
||||
@ -178,6 +183,10 @@ public class SDL2NativesManager {
|
||||
return attemptedLoad;
|
||||
}
|
||||
|
||||
public static boolean isSupportedOnThisPlatform() {
|
||||
return Target.CURRENT.hasNativeLibrary();
|
||||
}
|
||||
|
||||
private static Path getNativesFolderPath() {
|
||||
Path nativesFolderPath = FabricLoader.getInstance().getGameDir();
|
||||
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!)
|
||||
// https://wiki.libsdl.org/SDL2/SDL_GameControllerGetAxis
|
||||
GamepadState.AxesState axes = new GamepadState.AxesState(
|
||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_LEFTX), Short.MIN_VALUE, Short.MAX_VALUE) * 2f - 1f,
|
||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_LEFTY), Short.MIN_VALUE, Short.MAX_VALUE) * 2f - 1f,
|
||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_RIGHTX), Short.MIN_VALUE, Short.MAX_VALUE) * 2f - 1f,
|
||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_RIGHTY), Short.MIN_VALUE, Short.MAX_VALUE) * 2f - 1f,
|
||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_TRIGGERLEFT), 0, Short.MAX_VALUE),
|
||||
Mth.inverseLerp(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_TRIGGERRIGHT), 0, Short.MAX_VALUE)
|
||||
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_LEFTX)),
|
||||
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_LEFTY)),
|
||||
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_RIGHTX)),
|
||||
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_RIGHTY)),
|
||||
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_TRIGGERLEFT)),
|
||||
mapShortToFloat(SDL_GameControllerGetAxis(ptrGamepad, SDL_CONTROLLER_AXIS_TRIGGERRIGHT))
|
||||
);
|
||||
// Button values return 1 if pressed, 0 if not
|
||||
// https://wiki.libsdl.org/SDL2/SDL_GameControllerGetButton
|
||||
@ -190,4 +190,9 @@ public class SDL2GamepadDriver implements BasicGamepadInputDriver, GyroDriver, R
|
||||
public String getBasicGamepadDetails() {
|
||||
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) -> {
|
||||
var hitResult = ctx.hitResult();
|
||||
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)
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.break"));
|
||||
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.utils.Log;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import io.github.libsdl4j.api.joystick.SDL_JoystickGUID;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.hid4java.*;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.*;
|
||||
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()) {
|
||||
int vid = SDL_JoystickGetDeviceVendor(jid);
|
||||
int pid = SDL_JoystickGetDeviceProduct(jid);
|
||||
String path = GLFW.glfwGetJoystickGUID(jid);
|
||||
SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(jid);
|
||||
|
||||
if (vid != 0 && pid != 0) {
|
||||
Log.LOGGER.info("Using SDL to identify controller type.");
|
||||
return Optional.of(new ControllerHIDInfo(
|
||||
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 dev.isxander.controlify.utils.Log;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
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
|
||||
MASTER = register("master"),
|
||||
|
@ -1,8 +1,8 @@
|
||||
package dev.isxander.controlify.screenop;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
@ -13,8 +13,8 @@ public class Registry<T, U> {
|
||||
private final Map<T, U> cache;
|
||||
|
||||
public Registry() {
|
||||
this.registry = new HashMap<>();
|
||||
this.cache = new HashMap<>();
|
||||
this.registry = new Object2ObjectOpenHashMap<>();
|
||||
this.cache = new Object2ObjectOpenHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.isxander.controlify.screenop;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -8,7 +9,7 @@ import java.util.function.Function;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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() {
|
||||
}
|
||||
|
@ -147,6 +147,9 @@
|
||||
|
||||
"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.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_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.ask_to_switch.title": "Switch Controller?",
|
||||
"controlify.toast.ask_to_switch.description": "A new controller named '%s' has just been connected. Press any button to switch to it.",
|
||||
"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_connected.title": "Controller Connected",
|
||||
"controlify.toast.controller_connected.description": "A controller named `%s` has been connected. It has automatically been switched to.",
|
||||
"controlify.toast.controller_disconnected.title": "Controller Disconnected",
|
||||
"controlify.toast.controller_disconnected.description": "'%s' was disconnected.",
|
||||
"controlify.toast.faulty_input.title": "Controller disabled",
|
||||
|
Reference in New Issue
Block a user