Add overwriting screen handler factory (#2373)

This adds `FabricScreenHandlerFactory` (interface-injected to `NamedScreenHandlerFactory`).

This also fixes a crash when passing `SimpleNamedScreenHandlerFactory` that wraps a `ExtendedScreenHandlerFactory`. The mixin now un-wraps the factory.
This commit is contained in:
apple502j 2022-07-02 20:36:20 +09:00 committed by modmuss50
parent 056269f0d8
commit 1cc24b1b0e
7 changed files with 106 additions and 5 deletions

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.screenhandler.v1;
/**
* An extension to {@link net.minecraft.screen.NamedScreenHandlerFactory}.
* Unlike {@link ExtendedScreenHandlerFactory}, this can be used by any screen
* handlers, and is implemented via interface injection.
*/
public interface FabricScreenHandlerFactory {
/**
* {@return whether the server should send {@link
* net.minecraft.network.packet.s2c.play.CloseScreenS2CPacket} when opening the screen}
*
* <p>In vanilla, opening a new screen will always send the close screen packet.
* This, among other things, causes the mouse cursor to move to the center of the screen,
* which might not be expected in some cases. If this returns {@code false}, the packet
* is not sent to the client, stopping the behavior.
*/
default boolean shouldCloseCurrentScreen() {
return true;
}
}

View file

@ -38,6 +38,16 @@
* Screen handlers can be opened using
* {@link net.minecraft.entity.player.PlayerEntity#openHandledScreen(net.minecraft.screen.NamedScreenHandlerFactory)}.
* Note that calling it on the logical client does nothing. To open an extended screen handler, the factory passed in
* should be an {@link net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory}.
* should be an {@link net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory}, or a
* {@link net.minecraft.screen.SimpleNamedScreenHandlerFactory} that wraps such factory.
*
* <h2>Overwriting screen handlers</h2>
* You might have noticed that calling {@link net.minecraft.entity.player.PlayerEntity#openHandledScreen(net.minecraft.screen.NamedScreenHandlerFactory) openHandledScreen} while on another screen will move
* the cursor to the center of the screen. This is because the current screen gets closed before
* opening the screen, resetting the cursor position. Since this behavior can be problematic,
* this API provides a way to disable this. By overriding {@link
* net.fabricmc.fabric.api.screenhandler.v1.FabricScreenHandlerFactory#shouldCloseCurrentScreen()}
* on the screen handler factory to return {@code false} and passing that to the {@code
* openHandledScreen} method, it will stop closing the screen and instead "overwrites" it.
*/
package net.fabricmc.fabric.api.screenhandler.v1;

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.screenhandler;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.fabricmc.fabric.api.screenhandler.v1.FabricScreenHandlerFactory;
@Mixin(NamedScreenHandlerFactory.class)
public interface NamedScreenHandlerFactoryMixin extends FabricScreenHandlerFactory {
}

View file

@ -30,8 +30,9 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.network.Packet;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.screen.SimpleNamedScreenHandlerFactory;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
@ -40,16 +41,32 @@ import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerType;
import net.fabricmc.fabric.impl.screenhandler.Networking;
@Mixin(ServerPlayerEntity.class)
public class ServerPlayerEntityMixin {
public abstract class ServerPlayerEntityMixin {
@Shadow
private int screenHandlerSyncId;
@Shadow
public abstract void closeHandledScreen();
@Shadow
public abstract void closeScreenHandler();
@Unique
private final ThreadLocal<ScreenHandler> fabric_openedScreenHandler = new ThreadLocal<>();
@Redirect(method = "openHandledScreen(Lnet/minecraft/screen/NamedScreenHandlerFactory;)Ljava/util/OptionalInt;", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;closeHandledScreen()V"))
private void fabric_closeHandledScreenIfAllowed(ServerPlayerEntity player, NamedScreenHandlerFactory factory) {
if (factory.shouldCloseCurrentScreen()) {
this.closeHandledScreen();
} else {
// Called by closeHandledScreen in vanilla
this.closeScreenHandler();
}
}
@Inject(method = "openHandledScreen(Lnet/minecraft/screen/NamedScreenHandlerFactory;)Ljava/util/OptionalInt;", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;sendPacket(Lnet/minecraft/network/Packet;)V"), locals = LocalCapture.CAPTURE_FAILHARD)
private void fabric_storeOpenedScreenHandler(NamedScreenHandlerFactory factory, CallbackInfoReturnable<OptionalInt> info, ScreenHandler handler) {
if (factory instanceof ExtendedScreenHandlerFactory) {
if (factory instanceof ExtendedScreenHandlerFactory || (factory instanceof SimpleNamedScreenHandlerFactory simpleFactory && simpleFactory.baseFactory instanceof ExtendedScreenHandlerFactory)) {
fabric_openedScreenHandler.set(handler);
} else if (handler.getType() instanceof ExtendedScreenHandlerType<?>) {
Identifier id = Registry.SCREEN_HANDLER.getId(handler.getType());
@ -59,6 +76,10 @@ public class ServerPlayerEntityMixin {
@Redirect(method = "openHandledScreen(Lnet/minecraft/screen/NamedScreenHandlerFactory;)Ljava/util/OptionalInt;", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;sendPacket(Lnet/minecraft/network/Packet;)V"))
private void fabric_replaceVanillaScreenPacket(ServerPlayNetworkHandler networkHandler, Packet<?> packet, NamedScreenHandlerFactory factory) {
if (factory instanceof SimpleNamedScreenHandlerFactory simpleFactory && simpleFactory.baseFactory instanceof ExtendedScreenHandlerFactory extendedFactory) {
factory = extendedFactory;
}
if (factory instanceof ExtendedScreenHandlerFactory) {
ScreenHandler handler = fabric_openedScreenHandler.get();

View file

@ -6,3 +6,5 @@ extendable method net/minecraft/screen/ScreenHandlerType <init> (Lnet/minecraft/
accessible class net/minecraft/client/gui/screen/ingame/HandledScreens$Provider
accessible method net/minecraft/client/gui/screen/ingame/HandledScreens register (Lnet/minecraft/screen/ScreenHandlerType;Lnet/minecraft/client/gui/screen/ingame/HandledScreens$Provider;)V
accessible method net/minecraft/client/gui/screen/ingame/HandledScreens getProvider (Lnet/minecraft/screen/ScreenHandlerType;)Lnet/minecraft/client/gui/screen/ingame/HandledScreens$Provider;
accessible field net/minecraft/screen/SimpleNamedScreenHandlerFactory baseFactory Lnet/minecraft/screen/ScreenHandlerFactory;

View file

@ -3,6 +3,7 @@
"package": "net.fabricmc.fabric.mixin.screenhandler",
"compatibilityLevel": "JAVA_16",
"mixins": [
"NamedScreenHandlerFactoryMixin",
"ServerPlayerEntityMixin"
],
"client": [

View file

@ -29,6 +29,9 @@
],
"accessWidener": "fabric-screen-handler-api-v1.accesswidener",
"custom": {
"fabric-api:module-lifecycle": "stable"
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": {
"net/minecraft/class_3908": ["net/fabricmc/fabric/api/screenhandler/v1/FabricScreenHandlerFactory"]
}
}
}