forked from Clones/Controlify
server support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
run
|
||||
runserver
|
||||
build
|
||||
/.idea
|
||||
.gradle
|
||||
|
@ -74,9 +74,12 @@ dependencies {
|
||||
"fabric-key-binding-api-v1",
|
||||
"fabric-registry-sync-v0",
|
||||
"fabric-screen-api-v1",
|
||||
"fabric-command-api-v2",
|
||||
"fabric-networking-api-v1",
|
||||
).forEach {
|
||||
modImplementation(fabricApi.module(it, libs.versions.fabric.api.get()))
|
||||
}
|
||||
modRuntimeOnly(libs.fabric.api)
|
||||
|
||||
listOf(
|
||||
// sodium requirements
|
||||
|
@ -8,13 +8,13 @@ machete = "2.+"
|
||||
grgit = "5.0.+"
|
||||
blossom = "1.3.+"
|
||||
|
||||
minecraft = "1.20-pre6"
|
||||
minecraft = "1.20"
|
||||
quilt_mappings = "1"
|
||||
fabric_loader = "0.14.19"
|
||||
fabric_api = "0.81.2+1.20"
|
||||
fabric_loader = "0.14.21"
|
||||
fabric_api = "0.83.0+1.20"
|
||||
mixin_extras = "0.2.0-beta.8"
|
||||
yet_another_config_lib = "3.0.0-beta.7+1.20"
|
||||
mod_menu = "7.0.0-beta.2"
|
||||
yet_another_config_lib = "3.0.1+1.20"
|
||||
mod_menu = "7.0.0"
|
||||
hid4java = "0.7.0"
|
||||
quilt_json5 = "1.0.3"
|
||||
sodium = "f041f7ccba"
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.isxander.controlify;
|
||||
|
||||
import com.mojang.blaze3d.Blaze3D;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.api.entrypoint.ControlifyEntrypoint;
|
||||
import dev.isxander.controlify.gui.controllers.ControllerBindHandler;
|
||||
@ -12,6 +11,7 @@ import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
||||
import dev.isxander.controlify.gui.screen.SDLOnboardingScreen;
|
||||
import dev.isxander.controlify.reacharound.ReachAroundHandler;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.config.ControlifyConfig;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
@ -19,12 +19,19 @@ import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.gui.guide.InGameButtonGuide;
|
||||
import dev.isxander.controlify.ingame.InGameInputHandler;
|
||||
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||
import dev.isxander.controlify.server.EntityVibrationPacket;
|
||||
import dev.isxander.controlify.server.OriginVibrationPacket;
|
||||
import dev.isxander.controlify.server.ReachAroundPolicyPacket;
|
||||
import dev.isxander.controlify.server.VibrationPacket;
|
||||
import dev.isxander.controlify.sound.ControlifySounds;
|
||||
import dev.isxander.controlify.utils.DebugLog;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import dev.isxander.controlify.wireless.LowBatteryNotifier;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.CrashReport;
|
||||
import net.minecraft.CrashReportCategory;
|
||||
@ -36,7 +43,6 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Optional;
|
||||
@ -45,7 +51,6 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class Controlify implements ControlifyApi {
|
||||
public static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static Controlify instance = null;
|
||||
|
||||
private final Minecraft minecraft = Minecraft.getInstance();
|
||||
@ -72,7 +77,7 @@ public class Controlify implements ControlifyApi {
|
||||
private ToastUtils.ControlifyToast askSwitchToast = null;
|
||||
|
||||
public void initializeControlify() {
|
||||
LOGGER.info("Initializing Controlify...");
|
||||
Log.LOGGER.info("Initializing Controlify...");
|
||||
|
||||
config().load();
|
||||
|
||||
@ -150,7 +155,7 @@ public class Controlify implements ControlifyApi {
|
||||
if (controllerOpt.isEmpty()) continue;
|
||||
var controller = controllerOpt.get();
|
||||
|
||||
LOGGER.info("Controller found: " + controller.name());
|
||||
Log.LOGGER.info("Controller found: " + controller.name());
|
||||
|
||||
config().loadOrCreateControllerData(controller);
|
||||
|
||||
@ -165,7 +170,7 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
|
||||
if (ControllerManager.getConnectedControllers().isEmpty()) {
|
||||
LOGGER.info("No controllers found.");
|
||||
Log.LOGGER.info("No controllers found.");
|
||||
}
|
||||
|
||||
if (getCurrentController().isEmpty()) {
|
||||
@ -181,7 +186,7 @@ public class Controlify implements ControlifyApi {
|
||||
try {
|
||||
entrypoint.onControllersDiscovered(this);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to run `onControllersDiscovered` on Controlify entrypoint: " + entrypoint.getClass().getName(), e);
|
||||
Log.LOGGER.error("Failed to run `onControllersDiscovered` on Controlify entrypoint: " + entrypoint.getClass().getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -189,7 +194,7 @@ public class Controlify implements ControlifyApi {
|
||||
public void preInitialiseControlify() {
|
||||
DebugProperties.printProperties();
|
||||
|
||||
LOGGER.info("Pre-initializing Controlify...");
|
||||
Log.LOGGER.info("Pre-initializing Controlify...");
|
||||
|
||||
ControlifySounds.init();
|
||||
|
||||
@ -201,11 +206,38 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
ControllerBindHandler.setup();
|
||||
|
||||
ClientPlayNetworking.registerGlobalReceiver(VibrationPacket.TYPE, (packet, player, sender) -> {
|
||||
if (config().globalSettings().allowServerRumble) {
|
||||
getCurrentController().ifPresent(controller ->
|
||||
controller.rumbleManager().play(packet.source(), packet.createEffect()));
|
||||
}
|
||||
});
|
||||
ClientPlayNetworking.registerGlobalReceiver(OriginVibrationPacket.TYPE, (packet, player, sender) -> {
|
||||
if (config().globalSettings().allowServerRumble) {
|
||||
getCurrentController().ifPresent(controller ->
|
||||
controller.rumbleManager().play(packet.source(), packet.createEffect()));
|
||||
}
|
||||
});
|
||||
ClientPlayNetworking.registerGlobalReceiver(EntityVibrationPacket.TYPE, (packet, player, sender) -> {
|
||||
if (config().globalSettings().allowServerRumble) {
|
||||
getCurrentController().ifPresent(controller ->
|
||||
controller.rumbleManager().play(packet.source(), packet.createEffect()));
|
||||
}
|
||||
});
|
||||
ClientPlayNetworking.registerGlobalReceiver(ReachAroundPolicyPacket.TYPE, (packet, player, sender) -> {
|
||||
Log.LOGGER.info("Connected server specified reach around policy is {}.", packet.allowed() ? "ALLOWED" : "DISALLOWED");
|
||||
ReachAroundHandler.reachAroundPolicy = packet.allowed();
|
||||
});
|
||||
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> {
|
||||
DebugLog.log("Disconnected from server, resetting reach around policy");
|
||||
ReachAroundHandler.reachAroundPolicy = true;
|
||||
});
|
||||
|
||||
FabricLoader.getInstance().getEntrypoints("controlify", ControlifyEntrypoint.class).forEach(entrypoint -> {
|
||||
try {
|
||||
entrypoint.onControlifyPreInit(this);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to run `onControlifyPreInit` on Controlify entrypoint: " + entrypoint.getClass().getName(), e);
|
||||
Log.LOGGER.error("Failed to run `onControlifyPreInit` on Controlify entrypoint: " + entrypoint.getClass().getName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -268,7 +300,7 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
|
||||
if (consecutiveInputSwitches > 100) {
|
||||
LOGGER.warn("Controlify detected current controller to be constantly giving input and has been disabled.");
|
||||
Log.LOGGER.warn("Controlify detected current controller to be constantly giving input and has been disabled.");
|
||||
ToastUtils.sendToast(
|
||||
Component.translatable("controlify.toast.faulty_input.title"),
|
||||
Component.translatable("controlify.toast.faulty_input.description"),
|
||||
@ -312,7 +344,7 @@ public class Controlify implements ControlifyApi {
|
||||
if (controllerOpt.isEmpty()) return;
|
||||
var controller = controllerOpt.get();
|
||||
|
||||
LOGGER.info("Controller connected: " + controller.name());
|
||||
Log.LOGGER.info("Controller connected: " + controller.name());
|
||||
|
||||
config().loadOrCreateControllerData(controller);
|
||||
|
||||
@ -348,7 +380,7 @@ public class Controlify implements ControlifyApi {
|
||||
controller.hidInfo().ifPresent(controllerHIDService::unconsumeController);
|
||||
|
||||
setCurrentController(ControllerManager.getConnectedControllers().stream().findFirst().orElse(null));
|
||||
LOGGER.info("Controller disconnected: " + controller.name());
|
||||
Log.LOGGER.info("Controller disconnected: " + controller.name());
|
||||
this.setInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
||||
|
||||
ToastUtils.sendToast(
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.isxander.controlify;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
@ -9,6 +8,7 @@ import dev.isxander.controlify.controller.joystick.CompoundJoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.SingleJoystickController;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.utils.DebugLog;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.minecraft.CrashReport;
|
||||
import net.minecraft.CrashReportCategory;
|
||||
import net.minecraft.ReportedException;
|
||||
@ -89,12 +89,12 @@ public final class ControllerManager {
|
||||
Controlify.instance().config().getCompoundJoysticks().values().forEach(info -> {
|
||||
try {
|
||||
if (info.isLoaded() && !info.canBeUsed()) {
|
||||
Controlify.LOGGER.warn("Unloading compound joystick " + info.friendlyName() + " due to missing controllers.");
|
||||
Log.LOGGER.warn("Unloading compound joystick " + info.friendlyName() + " due to missing controllers.");
|
||||
disconnect(info.type().mappingId());
|
||||
}
|
||||
|
||||
if (!info.isLoaded() && info.canBeUsed()) {
|
||||
Controlify.LOGGER.info("Loading compound joystick " + info.type().mappingId() + ".");
|
||||
Log.LOGGER.info("Loading compound joystick " + info.type().mappingId() + ".");
|
||||
CompoundJoystickController controller = info.attemptCreate().orElseThrow();
|
||||
CONTROLLERS.put(info.type().mappingId(), controller);
|
||||
Controlify.instance().config().loadOrCreateControllerData(controller);
|
||||
|
@ -13,6 +13,7 @@ import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
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 net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@ -459,14 +460,14 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
boolean clean = true;
|
||||
for (var binding : registry().values()) {
|
||||
if (!json.has(binding.id().toString())) {
|
||||
Controlify.LOGGER.warn("Missing binding: " + binding.id() + " in config file. Skipping!");
|
||||
Log.LOGGER.warn("Missing binding: " + binding.id() + " in config file. Skipping!");
|
||||
clean = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var bind = json.get(binding.id().toString()).getAsJsonObject();
|
||||
if (bind == null) {
|
||||
Controlify.LOGGER.warn("Unknown binding: " + binding.id() + " in config file. Skipping!");
|
||||
Log.LOGGER.warn("Unknown binding: " + binding.id() + " in config file. Skipping!");
|
||||
clean = false;
|
||||
continue;
|
||||
}
|
||||
@ -510,7 +511,7 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
|
||||
register(binding);
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to automatically register modded keybind: " + keyMapping.getName(), e);
|
||||
Log.LOGGER.error("Failed to automatically register modded keybind: " + keyMapping.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import dev.isxander.controlify.ControllerManager;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.joystick.CompoundJoystickInfo;
|
||||
import dev.isxander.controlify.utils.DebugLog;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -13,7 +14,6 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@ -43,7 +43,7 @@ public class ControlifyConfig {
|
||||
}
|
||||
|
||||
public void save() {
|
||||
Controlify.LOGGER.info("Saving Controlify config...");
|
||||
Log.LOGGER.info("Saving Controlify config...");
|
||||
|
||||
try {
|
||||
Files.deleteIfExists(CONFIG_PATH);
|
||||
@ -55,7 +55,7 @@ public class ControlifyConfig {
|
||||
}
|
||||
|
||||
public void load() {
|
||||
Controlify.LOGGER.info("Loading Controlify config...");
|
||||
Log.LOGGER.info("Loading Controlify config...");
|
||||
|
||||
if (!Files.exists(CONFIG_PATH)) {
|
||||
firstLaunch = true;
|
||||
@ -66,7 +66,7 @@ public class ControlifyConfig {
|
||||
try {
|
||||
applyConfig(GSON.fromJson(Files.readString(CONFIG_PATH), JsonObject.class));
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to load Controlify config!", e);
|
||||
Log.LOGGER.error("Failed to load Controlify config!", e);
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
@ -156,7 +156,7 @@ public class ControlifyConfig {
|
||||
controller.setConfig(GSON, object.getAsJsonObject("config"));
|
||||
dirty |= !controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to load controller data for " + controller.uid() + ". Resetting to default!", e);
|
||||
Log.LOGGER.error("Failed to load controller data for " + controller.uid() + ". Resetting to default!", e);
|
||||
controller.resetConfig();
|
||||
save();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ public class GlobalSettings {
|
||||
public boolean loadVibrationNatives = false;
|
||||
public boolean vibrationOnboarded = false;
|
||||
public ReachAroundMode reachAround = ReachAroundMode.OFF;
|
||||
public boolean allowServerRumble = true;
|
||||
public boolean uiSounds = false;
|
||||
public boolean notifyLowBattery = true;
|
||||
public boolean delegateSetup = false;
|
||||
|
@ -8,6 +8,7 @@ import dev.isxander.controlify.ControllerManager;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
import dev.isxander.controlify.rumble.RumbleCapable;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Objects;
|
||||
@ -106,7 +107,7 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
|
||||
try {
|
||||
newConfig = gson.fromJson(json, new TypeToken<C>(getClass()){}.getType());
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead. Printing json: " + json.toString(), e);
|
||||
Log.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead. Printing json: " + json.toString(), e);
|
||||
Controlify.instance().config().setDirty();
|
||||
return;
|
||||
}
|
||||
@ -114,7 +115,7 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
|
||||
if (newConfig != null) {
|
||||
this.config = newConfig;
|
||||
} else {
|
||||
Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead.");
|
||||
Log.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead.");
|
||||
this.config = defaultConfig();
|
||||
Controlify.instance().config().setDirty();
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package dev.isxander.controlify.controller;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.hid.HIDIdentifier;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
@ -38,7 +38,7 @@ public record ControllerType(String friendlyName, String mappingId, String theme
|
||||
readControllerIdFiles(reader);
|
||||
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to load HID DB from source", e);
|
||||
Log.LOGGER.error("Failed to load HID DB from source", e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -78,7 +78,7 @@ public record ControllerType(String friendlyName, String mappingId, String theme
|
||||
} else if (productId == -1) {
|
||||
productId = reader.nextInt();
|
||||
} else {
|
||||
Controlify.LOGGER.warn("Too many values in HID array. Skipping...");
|
||||
Log.LOGGER.warn("Too many values in HID array. Skipping...");
|
||||
reader.skipValue();
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ public record ControllerType(String friendlyName, String mappingId, String theme
|
||||
case "force_joystick" -> forceJoystick = reader.nextBoolean();
|
||||
case "dont_load" -> dontLoad = reader.nextBoolean();
|
||||
default -> {
|
||||
Controlify.LOGGER.warn("Unknown key in HID DB: " + name + ". Skipping...");
|
||||
Log.LOGGER.warn("Unknown key in HID DB: " + name + ". Skipping...");
|
||||
reader.skipValue();
|
||||
}
|
||||
}
|
||||
@ -98,13 +98,13 @@ public record ControllerType(String friendlyName, String mappingId, String theme
|
||||
reader.endObject();
|
||||
|
||||
if (legacyIdentifier != null) {
|
||||
Controlify.LOGGER.warn("Legacy identifier found in HID DB. Please replace with `theme` and `mapping` (if needed).");
|
||||
Log.LOGGER.warn("Legacy identifier found in HID DB. Please replace with `theme` and `mapping` (if needed).");
|
||||
themeId = legacyIdentifier;
|
||||
mappingId = legacyIdentifier;
|
||||
}
|
||||
|
||||
if (friendlyName == null || themeId == null || hids.isEmpty()) {
|
||||
Controlify.LOGGER.warn("Invalid entry in HID DB. Skipping...");
|
||||
Log.LOGGER.warn("Invalid entry in HID DB. Skipping...");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import com.mojang.datafixers.util.Pair;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import dev.isxander.controlify.utils.ToastUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.hid4java.*;
|
||||
@ -38,7 +39,7 @@ public class ControllerHIDService {
|
||||
services = HidManager.getHidServices(specification);
|
||||
services.start();
|
||||
} catch (HidException e) {
|
||||
Controlify.LOGGER.error("Failed to start controller HID service! If you are on Linux using flatpak or snap, this is likely because your launcher has not added libusb to their package.", e);
|
||||
Log.LOGGER.error("Failed to start controller HID service! If you are on Linux using flatpak or snap, this is likely because your launcher has not added libusb to their package.", e);
|
||||
disabled = true;
|
||||
}
|
||||
}
|
||||
@ -74,13 +75,13 @@ public class ControllerHIDService {
|
||||
|
||||
Pair<HidDevice, HIDIdentifier> hid = unconsumedControllerHIDs.poll();
|
||||
if (hid == null) {
|
||||
Controlify.LOGGER.warn("No controller found via USB hardware scan! This prevents identifying controller type.");
|
||||
Log.LOGGER.warn("No controller found via USB hardware scan! This prevents identifying controller type.");
|
||||
return new ControllerHIDInfo(ControllerType.UNKNOWN, Optional.empty());
|
||||
}
|
||||
|
||||
ControllerType type = ControllerType.getTypeForHID(hid.getSecond());
|
||||
if (type == ControllerType.UNKNOWN)
|
||||
Controlify.LOGGER.warn("Controller found via USB hardware scan, but it was not found in the controller identification database! (HID: {})", hid.getSecond());
|
||||
Log.LOGGER.warn("Controller found via USB hardware scan, but it was not found in the controller identification database! (HID: {})", hid.getSecond());
|
||||
|
||||
unconsumedControllerHIDs.removeIf(h -> hid.getFirst().getPath().equals(h.getFirst().getPath()));
|
||||
|
||||
|
@ -3,7 +3,6 @@ package dev.isxander.controlify.controller.joystick;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
@ -12,6 +11,7 @@ import dev.isxander.controlify.controller.joystick.mapping.RPJoystickMapping;
|
||||
import dev.isxander.controlify.rumble.RumbleCapable;
|
||||
import dev.isxander.controlify.rumble.RumbleManager;
|
||||
import dev.isxander.controlify.rumble.RumbleSource;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.List;
|
||||
@ -106,7 +106,7 @@ public class CompoundJoystickController implements JoystickController<JoystickCo
|
||||
if (newConfig != null) {
|
||||
this.config = newConfig;
|
||||
} else {
|
||||
Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead.");
|
||||
Log.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead.");
|
||||
this.config = defaultConfig();
|
||||
}
|
||||
this.config.setup(this);
|
||||
|
@ -1,11 +1,11 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.utils.ControllerUtils;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import dev.isxander.yacl3.api.NameableEnum;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
@ -131,10 +131,10 @@ public class JoystickState implements ControllerState {
|
||||
List<HatState> hats = Arrays.stream(mapping.hats()).map(hat -> hat.getHatState(data)).toList();
|
||||
|
||||
if (DebugProperties.PRINT_JOY_STATE) {
|
||||
Controlify.LOGGER.info("Printing joystick state for controller {}", joystick);
|
||||
Controlify.LOGGER.info(Arrays.stream(axes).map(axis -> axis.name().getString() + ": " + axis.getAxis(data)).toList().toString());
|
||||
Controlify.LOGGER.info(Arrays.stream(mapping.buttons()).map(button -> button.name().getString() + ": " + button.isPressed(data)).toList().toString());
|
||||
Controlify.LOGGER.info(Arrays.stream(mapping.hats()).map(hat -> hat.name().getString() + ": " + hat.getHatState(data)).toList().toString());
|
||||
Log.LOGGER.info("Printing joystick state for controller {}", joystick);
|
||||
Log.LOGGER.info(Arrays.stream(axes).map(axis -> axis.name().getString() + ": " + axis.getAxis(data)).toList().toString());
|
||||
Log.LOGGER.info(Arrays.stream(mapping.buttons()).map(button -> button.name().getString() + ": " + button.isPressed(data)).toList().toString());
|
||||
Log.LOGGER.info(Arrays.stream(mapping.hats()).map(hat -> hat.name().getString() + ": " + hat.getHatState(data)).toList().toString());
|
||||
}
|
||||
|
||||
return new JoystickState(joystick.mapping(), deadzoneAxes, rawAxes, buttons, hats);
|
||||
|
@ -2,9 +2,6 @@ package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.controller.AbstractController;
|
||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||
@ -13,6 +10,7 @@ import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.rumble.RumbleManager;
|
||||
import dev.isxander.controlify.rumble.RumbleSource;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import org.libsdl.SDL;
|
||||
|
||||
import java.util.Objects;
|
||||
@ -106,7 +104,7 @@ public class SingleJoystickController extends AbstractController<JoystickState,
|
||||
// the duration doesn't matter because we are not updating the joystick state,
|
||||
// so there is never any SDL check to stop the rumble after the desired time.
|
||||
if (!SDL.SDL_JoystickRumbleTriggers(ptrJoystick, (int)(strongMagnitude * 65535.0F), (int)(weakMagnitude * 65535.0F), 1)) {
|
||||
Controlify.LOGGER.error("Could not rumble controller " + name() + ": " + SDL.SDL_GetError());
|
||||
Log.LOGGER.error("Could not rumble controller " + name() + ": " + SDL.SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1,11 +1,11 @@
|
||||
package dev.isxander.controlify.controller.joystick.mapping;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import dev.isxander.controlify.controller.joystick.render.JoystickRenderer;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@ -48,7 +48,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
hats = readHats(reader, type);
|
||||
}
|
||||
default -> {
|
||||
Controlify.LOGGER.warn("Unknown field in joystick mapping: " + name + ". Expected values: ['axes', 'buttons', 'hats']");
|
||||
Log.LOGGER.warn("Unknown field in joystick mapping: " + name + ". Expected values: ['axes', 'buttons', 'hats']");
|
||||
reader.skipValue();
|
||||
}
|
||||
}
|
||||
@ -111,7 +111,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
}
|
||||
default -> {
|
||||
reader.skipValue();
|
||||
Controlify.LOGGER.info("Unknown axis range property: " + rangeName + ". Expected are ['in', 'out']");
|
||||
Log.LOGGER.info("Unknown axis range property: " + rangeName + ". Expected are ['in', 'out']");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,7 +135,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
}
|
||||
default -> {
|
||||
reader.skipValue();
|
||||
Controlify.LOGGER.info("Unknown axis property: " + name + ". Expected are ['identifier', 'axis_names', 'ids', 'range', 'rest', 'deadzone']");
|
||||
Log.LOGGER.info("Unknown axis property: " + name + ". Expected are ['identifier', 'axis_names', 'ids', 'range', 'rest', 'deadzone']");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,7 +166,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
case "name" -> btnName = reader.nextString();
|
||||
default -> {
|
||||
reader.skipValue();
|
||||
Controlify.LOGGER.info("Unknown button property: " + name + ". Expected are ['button', 'name']");
|
||||
Log.LOGGER.info("Unknown button property: " + name + ". Expected are ['button', 'name']");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -214,11 +214,11 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
reader.endObject();
|
||||
|
||||
if (axisId == -1) {
|
||||
Controlify.LOGGER.error("No axis id defined for emulated hat " + hatName + "! Skipping.");
|
||||
Log.LOGGER.error("No axis id defined for emulated hat " + hatName + "! Skipping.");
|
||||
continue;
|
||||
}
|
||||
if (states.size() != JoystickState.HatState.values().length) {
|
||||
Controlify.LOGGER.error("Not all hat states are defined for emulated hat " + hatName + "! Skipping.");
|
||||
Log.LOGGER.error("Not all hat states are defined for emulated hat " + hatName + "! Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -226,7 +226,7 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
}
|
||||
default -> {
|
||||
reader.skipValue();
|
||||
Controlify.LOGGER.info("Unknown hat property: " + name + ". Expected are ['hat', 'name']");
|
||||
Log.LOGGER.info("Unknown hat property: " + name + ". Expected are ['hat', 'name']");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -257,14 +257,14 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
public static JoystickMapping fromType(JoystickController<?> joystick) {
|
||||
var resource = Minecraft.getInstance().getResourceManager().getResource(new ResourceLocation("controlify", "mappings/" + joystick.type().mappingId() + ".json"));
|
||||
if (resource.isEmpty()) {
|
||||
Controlify.LOGGER.warn("No joystick mapping found for controller: '" + joystick.type().mappingId() + "'");
|
||||
Log.LOGGER.warn("No joystick mapping found for controller: '" + joystick.type().mappingId() + "'");
|
||||
return new UnmappedJoystickMapping(joystick.joystickId());
|
||||
}
|
||||
|
||||
try (var reader = JsonReader.json5(resource.get().openAsReader())) {
|
||||
return new RPJoystickMapping(reader, joystick.type());
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to load joystick mapping for controller: '" + joystick.type().mappingId() + "'", e);
|
||||
Log.LOGGER.error("Failed to load joystick mapping for controller: '" + joystick.type().mappingId() + "'", e);
|
||||
return new UnmappedJoystickMapping(joystick.joystickId());
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.controller.sdl2;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.utils.DebugLog;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.Util;
|
||||
import org.libsdl.SDL;
|
||||
@ -38,7 +38,7 @@ public class SDL2NativesManager {
|
||||
DebugLog.log("Initialising SDL2 native library");
|
||||
|
||||
if (!Target.CURRENT.hasNativeLibrary()) {
|
||||
Controlify.LOGGER.warn("SDL2 native library not available for OS: " + Target.CURRENT);
|
||||
Log.LOGGER.warn("SDL2 native library not available for OS: " + Target.CURRENT);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -50,11 +50,11 @@ public class SDL2NativesManager {
|
||||
.map(Path::toFile)
|
||||
.forEachOrdered(File::delete);
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to delete old SDL2 native library", e);
|
||||
Log.LOGGER.error("Failed to delete old SDL2 native library", e);
|
||||
}
|
||||
}
|
||||
|
||||
Controlify.LOGGER.info("Downloading SDL2 native library: " + Target.CURRENT.getArtifactName());
|
||||
Log.LOGGER.info("Downloading SDL2 native library: " + Target.CURRENT.getArtifactName());
|
||||
downloadLibrary(localLibraryPath);
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ public class SDL2NativesManager {
|
||||
|
||||
loaded = true;
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to load SDL2 native library", e);
|
||||
Log.LOGGER.error("Failed to load SDL2 native library", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ public class SDL2NativesManager {
|
||||
int joystickSubsystem = 0x00000200; // implies event subsystem
|
||||
int gameControllerSubsystem = 0x00002000; // implies event subsystem
|
||||
if (SDL.SDL_Init(joystickSubsystem | gameControllerSubsystem) != 0) {
|
||||
Controlify.LOGGER.error("Failed to initialise SDL2: " + SDL.SDL_GetError());
|
||||
Log.LOGGER.error("Failed to initialise SDL2: " + SDL.SDL_GetError());
|
||||
throw new RuntimeException("Failed to initialise SDL2: " + SDL.SDL_GetError());
|
||||
}
|
||||
|
||||
@ -111,7 +111,7 @@ public class SDL2NativesManager {
|
||||
ReadableByteChannel readableByteChannel = Channels.newChannel(downloadUrl.openStream());
|
||||
FileChannel fileChannel = fileOutputStream.getChannel();
|
||||
fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
|
||||
Controlify.LOGGER.info("Downloaded SDL2 native library from " + downloadUrl);
|
||||
Log.LOGGER.info("Downloaded SDL2 native library from " + downloadUrl);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.isxander.controlify.debug;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -28,17 +28,17 @@ public class DebugProperties {
|
||||
return;
|
||||
|
||||
String header = "*----------------- Controlify Debug Properties -----------------*";
|
||||
Controlify.LOGGER.error(header);
|
||||
Log.LOGGER.error(header);
|
||||
|
||||
int maxWidth = properties.stream().mapToInt(prop -> prop.name().length()).max().orElse(0);
|
||||
for (var prop : properties) {
|
||||
String line = "| %s%s = %s".formatted(prop.name(), " ".repeat(maxWidth - prop.name().length()), prop.enabled());
|
||||
line += " ".repeat(header.length() - line.length() - 1) + "|";
|
||||
|
||||
Controlify.LOGGER.error(line);
|
||||
Log.LOGGER.error(line);
|
||||
}
|
||||
|
||||
Controlify.LOGGER.error("*---------------------------------------------------------------*");
|
||||
Log.LOGGER.error("*---------------------------------------------------------------*");
|
||||
}
|
||||
|
||||
private static boolean boolProp(String name, boolean defProd, boolean defDev) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package dev.isxander.controlify.driver;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import org.hid4java.HidDevice;
|
||||
|
||||
import java.util.*;
|
||||
@ -17,7 +17,7 @@ public record GamepadDrivers(BasicGamepadInputDriver basicGamepadInputDriver, Gy
|
||||
|
||||
public void printDrivers() {
|
||||
if (DebugProperties.PRINT_DRIVER) {
|
||||
Controlify.LOGGER.info("Drivers in use: Basic Input = '{}', Gyro = '{}', Rumble = '{}', Battery = '{}', Name = '{}', GUID = '{}'",
|
||||
Log.LOGGER.info("Drivers in use: Basic Input = '{}', Gyro = '{}', Rumble = '{}', Battery = '{}', Name = '{}', GUID = '{}'",
|
||||
basicGamepadInputDriver.getBasicGamepadDetails(),
|
||||
gyroDriver.getGyroDetails(),
|
||||
rumbleDriver.getRumbleDetails(),
|
||||
|
@ -1,9 +1,9 @@
|
||||
package dev.isxander.controlify.driver;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.BatteryLevel;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import org.libsdl.SDL;
|
||||
|
||||
public class SDL2GamepadDriver implements GyroDriver, RumbleDriver, BatteryDriver, GUIDProvider {
|
||||
@ -29,9 +29,9 @@ public class SDL2GamepadDriver implements GyroDriver, RumbleDriver, BatteryDrive
|
||||
float[] gyro = new float[3];
|
||||
if (SDL.SDL_GameControllerGetSensorData(ptrGamepad, SDL.SDL_SENSOR_GYRO, gyro, 3) == 0) {
|
||||
gyroDelta = new GamepadState.GyroState(gyro[0], gyro[1], gyro[2]);
|
||||
if (DebugProperties.PRINT_GYRO) Controlify.LOGGER.info("Gyro delta: " + gyroDelta);
|
||||
if (DebugProperties.PRINT_GYRO) Log.LOGGER.info("Gyro delta: " + gyroDelta);
|
||||
} else {
|
||||
Controlify.LOGGER.error("Could not get gyro data: " + SDL.SDL_GetError());
|
||||
Log.LOGGER.error("Could not get gyro data: " + SDL.SDL_GetError());
|
||||
}
|
||||
|
||||
}
|
||||
@ -42,7 +42,7 @@ public class SDL2GamepadDriver implements GyroDriver, RumbleDriver, BatteryDrive
|
||||
public boolean rumble(float strongMagnitude, float weakMagnitude) {
|
||||
// duration of 0 is infinite
|
||||
if (!SDL.SDL_GameControllerRumble(ptrGamepad, (int)(strongMagnitude * 65535.0F), (int)(weakMagnitude * 65535.0F), 0)) {
|
||||
Controlify.LOGGER.error("Could not rumble controller: " + SDL.SDL_GetError());
|
||||
Log.LOGGER.error("Could not rumble controller: " + SDL.SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.isxander.controlify.driver;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import org.libsdl.SDL;
|
||||
|
||||
public class SDL2JoystickDriver implements RumbleDriver {
|
||||
@ -21,7 +21,7 @@ public class SDL2JoystickDriver implements RumbleDriver {
|
||||
public boolean rumble(float strongMagnitude, float weakMagnitude) {
|
||||
// duration of 0 is infinite
|
||||
if (!SDL.SDL_JoystickRumble(ptrJoystick, (int)(strongMagnitude * 65535.0F), (int)(weakMagnitude * 65535.0F), 0)) {
|
||||
Controlify.LOGGER.error("Could not rumble controller: " + SDL.SDL_GetError());
|
||||
Log.LOGGER.error("Could not rumble controller: " + SDL.SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.driver;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import org.hid4java.HidDevice;
|
||||
|
||||
import java.util.Arrays;
|
||||
@ -33,10 +33,10 @@ public class SteamDeckDriver implements GyroDriver, BasicGamepadInputDriver {
|
||||
int readCnt = hidDevice.read(data);
|
||||
|
||||
if (readCnt == 0) {
|
||||
Controlify.LOGGER.warn("No data available.");
|
||||
Log.LOGGER.warn("No data available.");
|
||||
}
|
||||
if (readCnt == -1) {
|
||||
Controlify.LOGGER.warn("Error reading data.");
|
||||
Log.LOGGER.warn("Error reading data.");
|
||||
}
|
||||
|
||||
System.out.println(Arrays.toString(data));
|
||||
|
@ -96,7 +96,7 @@ public class ControllerCarouselScreen extends Screen implements ScreenController
|
||||
|
||||
public void refreshControllers() {
|
||||
Controller<?, ?> prevSelectedController;
|
||||
if (carouselEntries != null) {
|
||||
if (carouselEntries != null && !carouselEntries.isEmpty()) {
|
||||
carouselEntries.forEach(this::removeWidget);
|
||||
prevSelectedController = carouselEntries.get(carouselIndex).controller;
|
||||
} else {
|
||||
|
@ -2,6 +2,7 @@ package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
@ -124,7 +125,7 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
|
||||
var deadzone = (float)Mth.clamp(0.05 * Math.ceil(minDeadzone / 0.05), 0, 0.95);
|
||||
|
||||
if (Float.isNaN(deadzone)) {
|
||||
Controlify.LOGGER.warn("Deadzone for axis {} is NaN, using default deadzone.", i);
|
||||
Log.LOGGER.warn("Deadzone for axis {} is NaN, using default deadzone.", i);
|
||||
deadzone = controller.defaultConfig().getDeadzone(i);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.api.ControlifyApi;
|
||||
import dev.isxander.controlify.config.GlobalSettings;
|
||||
import dev.isxander.controlify.reacharound.ReachAroundHandler;
|
||||
import dev.isxander.controlify.reacharound.ReachAroundMode;
|
||||
import dev.isxander.yacl3.api.*;
|
||||
import dev.isxander.yacl3.api.controller.BooleanControllerBuilder;
|
||||
@ -37,9 +39,11 @@ public class GlobalSettingsScreenFactory {
|
||||
.text(Component.translatable("controlify.gui.reach_around.tooltip"))
|
||||
.text(Component.translatable("controlify.gui.reach_around.tooltip.parity").withStyle(ChatFormatting.GRAY))
|
||||
.text(state == ReachAroundMode.EVERYWHERE ? Component.translatable("controlify.gui.reach_around.tooltip.warning").withStyle(ChatFormatting.RED) : Component.empty())
|
||||
.text(!ReachAroundHandler.reachAroundPolicy ? Component.translatable("controlify.gui.reach_around.tooltip.server_disabled").withStyle(ChatFormatting.GOLD) : Component.empty())
|
||||
.build())
|
||||
.binding(GlobalSettings.DEFAULT.reachAround, () -> globalSettings.reachAround, v -> globalSettings.reachAround = v)
|
||||
.binding(GlobalSettings.DEFAULT.reachAround, () -> ReachAroundHandler.reachAroundPolicy ? globalSettings.reachAround : ReachAroundMode.OFF, v -> globalSettings.reachAround = v)
|
||||
.controller(opt -> EnumControllerBuilder.create(opt).enumClass(ReachAroundMode.class))
|
||||
.available(ReachAroundHandler.reachAroundPolicy)
|
||||
.build())
|
||||
.option(Option.<Boolean>createBuilder()
|
||||
.name(Component.translatable("controlify.gui.ui_sounds"))
|
||||
@ -49,6 +53,17 @@ public class GlobalSettingsScreenFactory {
|
||||
.binding(GlobalSettings.DEFAULT.uiSounds, () -> globalSettings.uiSounds, v -> globalSettings.uiSounds = v)
|
||||
.controller(TickBoxControllerBuilder::create)
|
||||
.build())
|
||||
.option(Option.<Boolean>createBuilder()
|
||||
.name(Component.translatable("controlify.gui.allow_server_rumble"))
|
||||
.description(OptionDescription.createBuilder()
|
||||
.text(Component.translatable("controlify.gui.allow_server_rumble.tooltip"))
|
||||
.build())
|
||||
.binding(GlobalSettings.DEFAULT.allowServerRumble, () -> globalSettings.allowServerRumble, v -> globalSettings.allowServerRumble = v)
|
||||
.controller(TickBoxControllerBuilder::create)
|
||||
.listener((opt, val) -> {
|
||||
if (!val) ControlifyApi.get().getCurrentController().ifPresent(c -> c.rumbleManager().clearEffects());
|
||||
})
|
||||
.build())
|
||||
.option(Option.<Boolean>createBuilder()
|
||||
.name(Component.translatable("controlify.gui.notify_low_battery"))
|
||||
.description(OptionDescription.createBuilder()
|
||||
|
@ -8,8 +8,10 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
|
||||
public class ReachAroundHandler {
|
||||
public static boolean reachAroundPolicy = true;
|
||||
|
||||
public static HitResult getReachAroundHitResult(Entity entity, HitResult hitResult) {
|
||||
// if there is already a valid hit, we don't want to override it
|
||||
// if there is already a valid hit, we don't want to override it
|
||||
if (hitResult.getType() != HitResult.Type.MISS)
|
||||
return hitResult;
|
||||
|
||||
@ -29,8 +31,9 @@ public class ReachAroundHandler {
|
||||
}
|
||||
|
||||
private static boolean canReachAround(Entity cameraEntity) {
|
||||
return // don't want to place blocks while riding an entity
|
||||
cameraEntity.getVehicle() == null
|
||||
return reachAroundPolicy
|
||||
// don't want to place blocks while riding an entity
|
||||
&& cameraEntity.getVehicle() == null
|
||||
// straight ahead = 0deg, up = -90deg, down = 90deg
|
||||
// 45deg and above is half between straight ahead and down, must be lower or equal to this threshold
|
||||
&& cameraEntity.getXRot() >= 45
|
||||
|
@ -5,7 +5,9 @@ import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ContinuousRumbleEffect implements RumbleEffect {
|
||||
private final Function<Integer, RumbleState> stateFunction;
|
||||
@ -14,17 +16,21 @@ public class ContinuousRumbleEffect implements RumbleEffect {
|
||||
private final int minTime;
|
||||
private int tick;
|
||||
private boolean stopped;
|
||||
private BooleanSupplier stopCondition;
|
||||
|
||||
public ContinuousRumbleEffect(Function<Integer, RumbleState> stateFunction, int priority, int timeout, int minTime) {
|
||||
public ContinuousRumbleEffect(Function<Integer, RumbleState> stateFunction, int priority, int timeout, int minTime, BooleanSupplier stopCondition) {
|
||||
this.stateFunction = stateFunction;
|
||||
this.priority = priority;
|
||||
this.timeout = timeout;
|
||||
this.minTime = minTime;
|
||||
this.stopCondition = stopCondition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
tick++;
|
||||
if (stopCondition.getAsBoolean())
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,6 +70,7 @@ public class ContinuousRumbleEffect implements RumbleEffect {
|
||||
private int timeout = -1;
|
||||
private int minTime;
|
||||
private InWorldProperties inWorldProperties;
|
||||
private BooleanSupplier stopCondition = () -> false;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
@ -101,8 +108,15 @@ public class ContinuousRumbleEffect implements RumbleEffect {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder inWorld(Vec3 sourceLocation, float minMagnitude, float maxMagnitude, float minDistance, float maxDistance, Function<Float, Float> fallofFunction) {
|
||||
this.inWorldProperties = new InWorldProperties(sourceLocation, minMagnitude, maxMagnitude, minDistance, maxDistance, fallofFunction);
|
||||
public Builder inWorld(Supplier<Vec3> sourceLocation, float min, float max, float effectRange, Function<Float, Float> fallofFunction) {
|
||||
this.inWorldProperties = new InWorldProperties(sourceLocation, min, max, effectRange, fallofFunction);
|
||||
stopCondition(() -> Minecraft.getInstance().cameraEntity == null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder stopCondition(BooleanSupplier stopCondition) {
|
||||
BooleanSupplier oldStopCondition = this.stopCondition;
|
||||
this.stopCondition = () -> stopCondition.getAsBoolean() || oldStopCondition.getAsBoolean();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -114,15 +128,20 @@ public class ContinuousRumbleEffect implements RumbleEffect {
|
||||
if (inWorldProperties != null)
|
||||
stateFunction = inWorldProperties.modify(stateFunction);
|
||||
|
||||
return new ContinuousRumbleEffect(stateFunction, priority, timeout, minTime);
|
||||
return new ContinuousRumbleEffect(stateFunction, priority, timeout, minTime, stopCondition);
|
||||
}
|
||||
|
||||
private record InWorldProperties(Vec3 sourceLocation, float minMagnitude, float maxMagnitude, float minDistance, float maxDistance, Function<Float, Float> fallofFunction) {
|
||||
private record InWorldProperties(Supplier<Vec3> sourceLocation, float minMagnitude, float maxMagnitude, float effectRange, Function<Float, Float> fallofFunction) {
|
||||
private Function<Integer, RumbleState> modify(Function<Integer, RumbleState> stateFunction) {
|
||||
return tick -> {
|
||||
float distanceSqr = (float) Mth.clamp(Minecraft.getInstance().player.distanceToSqr(sourceLocation), minDistance, maxDistance);
|
||||
float magnitude = Mth.lerp(1f - fallofFunction.apply(distanceSqr / (maxDistance * maxDistance)), minMagnitude, maxMagnitude);
|
||||
return stateFunction.apply(tick).mul(magnitude);
|
||||
if (Minecraft.getInstance().cameraEntity == null)
|
||||
return RumbleState.NONE;
|
||||
|
||||
float distanceSqr = (float) Minecraft.getInstance().cameraEntity.distanceToSqr(sourceLocation.get());
|
||||
float normalizedDistance = Mth.clamp(distanceSqr / (effectRange * effectRange), 0, 1);
|
||||
float multiplier = Mth.lerp(fallofFunction.apply(1f - normalizedDistance), minMagnitude, maxMagnitude);
|
||||
|
||||
return stateFunction.apply(tick).mul(multiplier);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
package dev.isxander.controlify.rumble;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class RumbleSource {
|
||||
public record RumbleSource(ResourceLocation id) {
|
||||
private static final Map<ResourceLocation, RumbleSource> SOURCES = new LinkedHashMap<>();
|
||||
|
||||
public static final RumbleSource
|
||||
@ -19,14 +20,13 @@ public class RumbleSource {
|
||||
MISC = register("misc"),
|
||||
GLOBAL_EVENT = register("global_event");
|
||||
|
||||
private final ResourceLocation id;
|
||||
|
||||
private RumbleSource(ResourceLocation id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ResourceLocation id() {
|
||||
return id;
|
||||
public static RumbleSource get(ResourceLocation id) {
|
||||
RumbleSource source = SOURCES.get(id);
|
||||
if (source == null) {
|
||||
Log.LOGGER.warn("Unknown rumble source: {}. Using master.", id);
|
||||
return MASTER;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
public static Collection<RumbleSource> values() {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package dev.isxander.controlify.rumble;
|
||||
|
||||
public record RumbleState(float strong, float weak) {
|
||||
public static final RumbleState NONE = new RumbleState(0.0F, 0.0F);
|
||||
|
||||
public RumbleState mul(float multiplier) {
|
||||
return new RumbleState(strong * multiplier, weak * multiplier);
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package dev.isxander.controlify.server;
|
||||
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.fabricmc.api.DedicatedServerModInitializer;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
||||
public class ControlifyServer implements ModInitializer, DedicatedServerModInitializer {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registry, env) -> {
|
||||
VibrateCommand.register(dispatcher);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeServer() {
|
||||
ControlifyServerConfig.INSTANCE.load();
|
||||
Log.LOGGER.info("Reach-around policy: " + ControlifyServerConfig.INSTANCE.getConfig().reachAroundPolicy);
|
||||
|
||||
ServerPlayConnectionEvents.INIT.register((handler, server) -> {
|
||||
ServerPlayNetworking.send(handler.getPlayer(), new ReachAroundPolicyPacket(ControlifyServerConfig.INSTANCE.getConfig().reachAroundPolicy));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package dev.isxander.controlify.server;
|
||||
|
||||
import dev.isxander.yacl3.config.ConfigInstance;
|
||||
import dev.isxander.yacl3.config.GsonConfigInstance;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
public class ControlifyServerConfig {
|
||||
public static final ConfigInstance<ControlifyServerConfig> INSTANCE = GsonConfigInstance.createBuilder(ControlifyServerConfig.class)
|
||||
.setPath(FabricLoader.getInstance().getConfigDir().resolve("controlify.json"))
|
||||
.build();
|
||||
|
||||
public boolean reachAroundPolicy = false;
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package dev.isxander.controlify.server;
|
||||
|
||||
import dev.isxander.controlify.rumble.ContinuousRumbleEffect;
|
||||
import dev.isxander.controlify.rumble.RumbleEffect;
|
||||
import dev.isxander.controlify.rumble.RumbleSource;
|
||||
import dev.isxander.controlify.rumble.RumbleState;
|
||||
import dev.isxander.controlify.utils.Easings;
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
public record EntityVibrationPacket(int entityId, float range, int duration, RumbleState state, RumbleSource source) implements FabricPacket {
|
||||
public static final PacketType<EntityVibrationPacket> TYPE = PacketType.create(new ResourceLocation("controlify", "vibrate_from_entity"), EntityVibrationPacket::new);
|
||||
|
||||
public EntityVibrationPacket(FriendlyByteBuf buf) {
|
||||
this(buf.readInt(), buf.readFloat(), buf.readInt(), OriginVibrationPacket.readState(buf), RumbleSource.get(buf.readResourceLocation()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeInt(entityId);
|
||||
buf.writeFloat(range);
|
||||
buf.writeInt(duration);
|
||||
|
||||
int high = (int)(state.strong() * 32767.0F);
|
||||
int low = (int)(state.weak() * 32767.0F);
|
||||
buf.writeInt((high << 16) | (low & 0xFFFF));
|
||||
|
||||
buf.writeResourceLocation(source.id());
|
||||
}
|
||||
|
||||
public RumbleEffect createEffect() {
|
||||
Entity entity = Minecraft.getInstance().level.getEntity(entityId);
|
||||
return ContinuousRumbleEffect.builder()
|
||||
.constant(state)
|
||||
.inWorld(() -> entity.position(), 0, 1, range, Easings::easeInSine)
|
||||
.timeout(duration)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType<?> getType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package dev.isxander.controlify.server;
|
||||
|
||||
import dev.isxander.controlify.rumble.*;
|
||||
import dev.isxander.controlify.utils.Easings;
|
||||
import dev.isxander.controlify.utils.Log;
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketType;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public record OriginVibrationPacket(Vector3f origin, float effectRange, int duration, RumbleState state, RumbleSource source) implements FabricPacket {
|
||||
public static final PacketType<OriginVibrationPacket> TYPE = PacketType.create(new ResourceLocation("controlify", "vibrate_from_origin"), OriginVibrationPacket::new);
|
||||
|
||||
public OriginVibrationPacket(FriendlyByteBuf buf) {
|
||||
this(buf.readVector3f(), buf.readFloat(), buf.readVarInt(), readState(buf), RumbleSource.get(buf.readResourceLocation()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeVector3f(origin);
|
||||
buf.writeFloat(effectRange);
|
||||
buf.writeVarInt(duration);
|
||||
|
||||
int high = (int)(state.strong() * 32767.0F);
|
||||
int low = (int)(state.weak() * 32767.0F);
|
||||
buf.writeInt((high << 16) | (low & 0xFFFF));
|
||||
|
||||
buf.writeResourceLocation(source.id());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType<?> getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public RumbleEffect createEffect() {
|
||||
var originVec3 = new Vec3(origin);
|
||||
return ContinuousRumbleEffect.builder()
|
||||
.constant(state)
|
||||
.inWorld(() -> originVec3, 0, 1, effectRange, Easings::easeInSine)
|
||||
.timeout(duration)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static RumbleState readState(FriendlyByteBuf buf) {
|
||||
int packed = buf.readInt();
|
||||
float strong = (short)(packed >> 16) / 32767.0F;
|
||||
float weak = (short)packed / 32767.0F;
|
||||
return new RumbleState(strong, weak);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package dev.isxander.controlify.server;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketType;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public record ReachAroundPolicyPacket(boolean allowed) implements FabricPacket {
|
||||
public static final PacketType<ReachAroundPolicyPacket> TYPE = PacketType.create(new ResourceLocation("controlify", "reach_around_policy"), ReachAroundPolicyPacket::new);
|
||||
|
||||
public ReachAroundPolicyPacket(FriendlyByteBuf buf) {
|
||||
this(buf.readBoolean());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeBoolean(allowed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType<?> getType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
159
src/main/java/dev/isxander/controlify/server/VibrateCommand.java
Normal file
159
src/main/java/dev/isxander/controlify/server/VibrateCommand.java
Normal file
@ -0,0 +1,159 @@
|
||||
package dev.isxander.controlify.server;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.FloatArgumentType;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import dev.isxander.controlify.rumble.RumbleSource;
|
||||
import dev.isxander.controlify.rumble.RumbleState;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.commands.arguments.ResourceLocationArgument;
|
||||
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
|
||||
import net.minecraft.commands.synchronization.SuggestionProviders;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
public class VibrateCommand {
|
||||
private static final SuggestionProvider<CommandSourceStack> SOURCES_SUGGESTION = SuggestionProviders.register(
|
||||
new ResourceLocation("controlify", "vibration_sources"),
|
||||
(context, builder) -> SharedSuggestionProvider.suggestResource(
|
||||
RumbleSource.values().stream()
|
||||
.map(RumbleSource::id)
|
||||
.toList(),
|
||||
builder
|
||||
)
|
||||
);
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
dispatcher.register(
|
||||
Commands.literal("vibratecontroller")
|
||||
.requires(source -> source.hasPermission(2))
|
||||
.then(
|
||||
Commands.argument("receivers", EntityArgument.players())
|
||||
.then(
|
||||
Commands.argument("low_freq_vibration", FloatArgumentType.floatArg(0, 1))
|
||||
.then(
|
||||
Commands.argument("high_freq_vibration", FloatArgumentType.floatArg(0, 1))
|
||||
.then(
|
||||
Commands.argument("duration", IntegerArgumentType.integer(1))
|
||||
.then(
|
||||
Commands.literal("static")
|
||||
.executes(context -> vibrateStatic(
|
||||
context.getSource(),
|
||||
EntityArgument.getPlayers(context, "receivers"),
|
||||
FloatArgumentType.getFloat(context, "low_freq_vibration"),
|
||||
FloatArgumentType.getFloat(context, "high_freq_vibration"),
|
||||
IntegerArgumentType.getInteger(context, "duration"),
|
||||
RumbleSource.MASTER
|
||||
))
|
||||
)
|
||||
.then(
|
||||
Commands.literal("positioned")
|
||||
.then(
|
||||
Commands.argument("range", FloatArgumentType.floatArg(0))
|
||||
.then(
|
||||
Commands.argument("position", Vec3Argument.vec3(true))
|
||||
.executes(context -> vibrateFromOrigin(
|
||||
context.getSource(),
|
||||
EntityArgument.getPlayers(context, "receivers"),
|
||||
Vec3Argument.getVec3(context, "position"),
|
||||
FloatArgumentType.getFloat(context, "range"),
|
||||
IntegerArgumentType.getInteger(context, "duration"),
|
||||
FloatArgumentType.getFloat(context, "low_freq_vibration"),
|
||||
FloatArgumentType.getFloat(context, "high_freq_vibration"),
|
||||
RumbleSource.MASTER
|
||||
))
|
||||
)
|
||||
.then(
|
||||
Commands.argument("entity", EntityArgument.entity())
|
||||
.executes(context -> vibrateFromEntity(
|
||||
context.getSource(),
|
||||
EntityArgument.getPlayers(context, "receivers"),
|
||||
EntityArgument.getEntity(context, "entity"),
|
||||
FloatArgumentType.getFloat(context, "range"),
|
||||
IntegerArgumentType.getInteger(context, "duration"),
|
||||
FloatArgumentType.getFloat(context, "low_freq_vibration"),
|
||||
FloatArgumentType.getFloat(context, "high_freq_vibration"),
|
||||
RumbleSource.MASTER
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static int vibrateStatic(CommandSourceStack source, Collection<ServerPlayer> targets, float lowFreqMagnitude, float highFreqMagnitude, int durationTicks, RumbleSource rumbleSource) {
|
||||
RumbleState[] frames = new RumbleState[durationTicks];
|
||||
Arrays.fill(frames, new RumbleState(lowFreqMagnitude, highFreqMagnitude));
|
||||
|
||||
VibrationPacket packet = new VibrationPacket(rumbleSource, frames);
|
||||
for (var player : targets) {
|
||||
ServerPlayNetworking.send(player, packet);
|
||||
}
|
||||
|
||||
source.sendSuccess(
|
||||
() -> targets.size() == 1
|
||||
? Component.translatable("controlify.command.vibratecontroller.static.single")
|
||||
: Component.translatable("controlify.command.vibratecontroller.static.multiple", targets.size()),
|
||||
true
|
||||
);
|
||||
|
||||
return targets.size();
|
||||
}
|
||||
|
||||
private static int vibrateFromOrigin(CommandSourceStack source, Collection<ServerPlayer> targets, Vec3 origin, float effectRange, int duration, float lowFreqMagnitude, float highFreqMagnitude, RumbleSource rumbleSource) {
|
||||
RumbleState state = new RumbleState(lowFreqMagnitude, highFreqMagnitude);
|
||||
|
||||
OriginVibrationPacket packet = new OriginVibrationPacket(origin.toVector3f(), effectRange, duration, state, rumbleSource);
|
||||
for (var player : targets) {
|
||||
ServerPlayNetworking.send(player, packet);
|
||||
}
|
||||
|
||||
source.sendSuccess(
|
||||
() -> targets.size() == 1
|
||||
? Component.translatable("controlify.command.vibratecontroller.pos.single", formatDouble(origin.x), formatDouble(origin.y), formatDouble(origin.z))
|
||||
: Component.translatable("controlify.command.vibratecontroller.pos.multiple", targets.size(), formatDouble(origin.x), formatDouble(origin.y), formatDouble(origin.z)),
|
||||
true
|
||||
);
|
||||
|
||||
return targets.size();
|
||||
}
|
||||
|
||||
private static int vibrateFromEntity(CommandSourceStack source, Collection<ServerPlayer> targets, Entity origin, float effectRange, int duration, float lowFreqMagnitude, float highFreqMagnitude, RumbleSource rumbleSource) {
|
||||
RumbleState state = new RumbleState(lowFreqMagnitude, highFreqMagnitude);
|
||||
|
||||
EntityVibrationPacket packet = new EntityVibrationPacket(origin.getId(), effectRange, duration, state, rumbleSource);
|
||||
for (var player : targets) {
|
||||
ServerPlayNetworking.send(player, packet);
|
||||
}
|
||||
|
||||
source.sendSuccess(
|
||||
() -> targets.size() == 1
|
||||
? Component.translatable("controlify.command.vibratecontroller.entity.single", origin.getDisplayName())
|
||||
: Component.translatable("controlify.command.vibratecontroller.entity.multiple", targets.size(), origin.getDisplayName()),
|
||||
true
|
||||
);
|
||||
|
||||
return targets.size();
|
||||
}
|
||||
|
||||
private static String formatDouble(double d) {
|
||||
return String.format(Locale.ROOT, "%f", d);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package dev.isxander.controlify.server;
|
||||
|
||||
import dev.isxander.controlify.rumble.BasicRumbleEffect;
|
||||
import dev.isxander.controlify.rumble.RumbleEffect;
|
||||
import dev.isxander.controlify.rumble.RumbleSource;
|
||||
import dev.isxander.controlify.rumble.RumbleState;
|
||||
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketType;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public record VibrationPacket(RumbleSource source, RumbleState[] frames) implements FabricPacket {
|
||||
public static final PacketType<VibrationPacket> TYPE = PacketType.create(new ResourceLocation("controlify", "vibration"), VibrationPacket::new);
|
||||
|
||||
public VibrationPacket(FriendlyByteBuf buf) {
|
||||
this(RumbleSource.get(buf.readResourceLocation()), readFrames(buf));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
buf.writeResourceLocation(source.id());
|
||||
|
||||
int[] packedFrames = new int[frames.length];
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
RumbleState frame = frames[i];
|
||||
int high = (int)(frame.strong() * 32767.0F);
|
||||
int low = (int)(frame.weak() * 32767.0F);
|
||||
packedFrames[i] = (high << 16) | (low & 0xFFFF);
|
||||
}
|
||||
buf.writeVarIntArray(packedFrames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketType<?> getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public RumbleEffect createEffect() {
|
||||
return new BasicRumbleEffect(frames).earlyFinish(() -> Minecraft.getInstance().level == null);
|
||||
}
|
||||
|
||||
private static RumbleState[] readFrames(FriendlyByteBuf buf) {
|
||||
int[] packedFrames = buf.readVarIntArray();
|
||||
RumbleState[] frames = new RumbleState[packedFrames.length];
|
||||
for (int i = 0; i < packedFrames.length; i++) {
|
||||
int packed = packedFrames[i];
|
||||
float strong = (short)(packed >> 16) / 32767.0F;
|
||||
float weak = (short)packed / 32767.0F;
|
||||
frames[i] = new RumbleState(strong, weak);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
package dev.isxander.controlify.utils;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
|
||||
public class DebugLog {
|
||||
public static void log(String message, Object... args) {
|
||||
if (DebugProperties.DEBUG_LOGGING) {
|
||||
Controlify.LOGGER.info(message, args);
|
||||
Log.LOGGER.info(message, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
package dev.isxander.controlify.utils;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class Easings {
|
||||
public static float easeInSine(float t) {
|
||||
return 1 - Mth.cos((float) ((t * Math.PI) / 2));
|
||||
}
|
||||
|
||||
public static float easeInQuad(float t) {
|
||||
return t * t;
|
||||
}
|
||||
|
8
src/main/java/dev/isxander/controlify/utils/Log.java
Normal file
8
src/main/java/dev/isxander/controlify/utils/Log.java
Normal file
@ -0,0 +1,8 @@
|
||||
package dev.isxander.controlify.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Log {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger("Controlify");
|
||||
}
|
@ -13,12 +13,15 @@
|
||||
"controlify.gui.reach_around.tooltip": "If enabled, you can interact with the block you are standing on in the direction you are looking.",
|
||||
"controlify.gui.reach_around.tooltip.parity": "This is parity with bedrock edition where you can also do this.",
|
||||
"controlify.gui.reach_around.tooltip.warning": "WARNING: This is an unfair advantage over other players without Controlify, and you will likely be flagged by many anti-cheats. This should only be used in situations where everyone playing recognises that you have this ability and are okay with it.",
|
||||
"controlify.gui.reach_around.tooltip.server_disabled": "The server you are playing on has does not allow you to use this feature.",
|
||||
"controlify.reach_around.off": "Off",
|
||||
"controlify.reach_around.singleplayer_only": "Singleplayer Only",
|
||||
"controlify.reach_around.singleplayer_and_lan": "Singleplayer and LAN",
|
||||
"controlify.reach_around.everywhere": "Everywhere",
|
||||
"controlify.gui.ui_sounds": "UI Sounds",
|
||||
"controlify.gui.ui_sounds.tooltip": "If enabled, Controlify will play UI sounds when you interact with the UI, like in legacy console editions of Minecraft.",
|
||||
"controlify.gui.allow_server_rumble": "Allow Server Vibration",
|
||||
"controlify.gui.allow_server_rumble.tooltip": "Accepts vibration packets from servers and vibrates your controller. If a server is doing this maliciously you can turn it off here.",
|
||||
"controlify.gui.notify_low_battery": "Notify Low Battery",
|
||||
"controlify.gui.notify_low_battery.tooltip": "A toast will appear when your wireless controller's battery becomes low. (EXPERIMENTAL)",
|
||||
"controlify.gui.out_of_focus_input": "Out of Focus Input",
|
||||
@ -270,6 +273,13 @@
|
||||
"controlify.battery_level.high": "High",
|
||||
"controlify.battery_level.full": "Full",
|
||||
|
||||
"controlify.command.vibratecontroller.static.single": "Vibrated controller of 1 player.",
|
||||
"controlify.command.vibratecontroller.static.multiple": "Vibrated controller of %s players.",
|
||||
"controlify.command.vibratecontroller.pos.single": "Vibrated controller of 1 player at %s, %s, %s.",
|
||||
"controlify.command.vibratecontroller.pos.multiple": "Vibrated controller of %s players at %s, %s, %s.",
|
||||
"controlify.command.vibratecontroller.entity.single": "Vibrated controller of 1 player from %s's position.",
|
||||
"controlify.command.vibratecontroller.entity.multiple": "Vibrated controller of %s players from %s's position.",
|
||||
|
||||
"controlify.hat_state.up": "Up",
|
||||
"controlify.hat_state.down": "Down",
|
||||
"controlify.hat_state.left": "Left",
|
||||
|
@ -13,7 +13,7 @@
|
||||
"sources": "https://github.com/${github}"
|
||||
},
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"environment": "client",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"preLaunch": [
|
||||
"com.llamalad7.mixinextras.MixinExtrasBootstrap::init"
|
||||
@ -21,8 +21,14 @@
|
||||
"modmenu": [
|
||||
"dev.isxander.controlify.config.ModMenuIntegration"
|
||||
],
|
||||
"main": [
|
||||
"dev.isxander.controlify.server.ControlifyServer"
|
||||
],
|
||||
"client": [
|
||||
"dev.isxander.controlify.ControlifyEntrypoint"
|
||||
],
|
||||
"server": [
|
||||
"dev.isxander.controlify.server.ControlifyServer"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
@ -31,12 +37,14 @@
|
||||
"icon": "icon.png",
|
||||
"accessWidener": "controlify.accesswidener",
|
||||
"depends": {
|
||||
"fabricloader": ">=0.14.0",
|
||||
"minecraft": ">1.20-",
|
||||
"fabricloader": ">=0.14.21",
|
||||
"minecraft": "1.20.x",
|
||||
"java": ">=17",
|
||||
"yet_another_config_lib_v3": ">=3.0.0-"
|
||||
"yet_another_config_lib_v3": ">=3.0.0-",
|
||||
"fabric-api": "*"
|
||||
},
|
||||
"breaks": {
|
||||
"midnightcontrols": "*"
|
||||
"midnightcontrols": "*",
|
||||
"controllable": "*"
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user