Add configurable M shortcut for Atlas from game and Pause
This commit is contained in:
+35
@@ -10,6 +10,7 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.gui.components.*;
|
||||
import net.minecraft.client.gui.screens.*;
|
||||
import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
|
||||
@@ -42,6 +43,7 @@ public final class Story234ClientChecks implements FabricClientGameTest {
|
||||
});
|
||||
c.waitFor(m->ProgressionClient.progress.atlas()==unlocked,200);
|
||||
String before=server.computeOnServer(s->s.getPlayerList().getPlayers().getFirst().getAttached(ProgressionService.RECORD));
|
||||
shortcut(c,unlocked);
|
||||
for(String language:List.of("fr_fr","en_us"))for(int scale:List.of(2,3,4)) {
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1280,960);m.options.guiScale().set(scale);m.resizeGui();m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.gui.setScreen(new PauseScreen(true));});
|
||||
c.waitTicks(3);
|
||||
@@ -62,6 +64,10 @@ public final class Story234ClientChecks implements FabricClientGameTest {
|
||||
check(pause.mouseClicked(new net.minecraft.client.input.MouseButtonEvent(story.getX()+story.getWidth()/2.,story.getY()+story.getHeight()/2.,new net.minecraft.client.input.MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0)),false),"Native Story click handled above the map");
|
||||
check(m.gui.screen() instanceof StoryScreen,"Story opens from Pause without Atlas gating");check(m.gui.screen().isPauseScreen(),"Story retains solo pause");check(m.gui.screen().getTitle().getString().equals("Story"),"Story has its own accessible screen title");check(edit(m.gui.screen()).getValue().isEmpty()&&cycle(m.gui.screen()).getValue().toString().equals("ALL"),"Empty search and All on opening");assertEntries(m.gui.screen(),100);layout(m.gui.screen());});
|
||||
shot(c,"story-"+language+"-"+scale);
|
||||
c.runOnClient(m->{var s=m.gui.screen();var search=edit(s);s.setFocused(search);
|
||||
s.keyPressed(new KeyEvent(mapKey().getDefaultKey().getValue(),InputConstants.KEYCODE_M,0));
|
||||
check(s.charTyped(new CharacterEvent('m'))&&m.gui.screen()==s&&search.getValue().equals("m")&&search.isFocused(),"Typing M in Story keeps its screen, text and focus");
|
||||
check(net.minecraft.network.chat.Component.translatable("sanctuary.atlas.key").getString().equals(language.equals("fr_fr")?"Ouvrir la carte":"Open world map"),"Map shortcut has a localized Controls label");search.setValue("");});
|
||||
c.runOnClient(m->{var s=m.gui.screen();var search=edit(s);s.setFocused(search);for(int codepoint:"100".codePoints().toArray()){check(s.charTyped(new CharacterEvent(codepoint)),"Native typing handled");check(s.getFocused()==search&&search.isFocused(),"List refresh retains search focus");}check(search.getValue().equals("100")&&search.getCursorPosition()==3,"Continuous typing retains cursor");assertEntries(s,1);check(entries(s).getFirst().getMessage().getString().equals(StoryScreen.label("unknown_entry",100).getString()),"Search selects story 100");search.setValue(" 100 ");assertEntries(s,1);cycle(s).onPress(enter());check(has(s,"sanctuary.story.no_discoveries")&&!has(s,"sanctuary.story.no_results")&&entries(s).isEmpty(),"Discovered has a distinct empty state");cycle(s).onPress(enter());check(cycle(s).getValue().toString().equals("UNKNOWN"),"Third filter is Unknown");assertEntries(s,1);search.setValue("absent-story");check(has(s,"sanctuary.story.no_results")&&!has(s,"sanctuary.story.no_discoveries"),"Search empty state is distinct");search.setValue(language.equals("fr_fr")?"INCONNUE":"UNKNOWN");assertEntries(s,100);var area=area(s);check(s.mouseScrolled(area.getX()+5,area.getY()+5,0,-5)&&area.scrollAmount()>0,"Native wheel scrolls Story");area.setScrollAmount(area.maxScrollAmount());check(entries(s).getLast().getBottom()<=area.getBottom(),"Story 100 can be reached at the end");area.setScrollAmount(100);});
|
||||
c.runOnClient(m->{m.getWindow().setWindowed(1344,1008);m.resizeGui();});c.waitTicks(3);
|
||||
c.runOnClient(m->{var s=m.gui.screen();check(edit(s).getValue().equals(language.equals("fr_fr")?"INCONNUE":"UNKNOWN")&&cycle(s).getValue().toString().equals("UNKNOWN"),"Resize retains query and filter");check(area(s).scrollAmount()==100,"Resize retains scroll position");layout(s);edit(s).setValue("100");check(area(s).scrollAmount()==0,"Search resets scroll");edit(s).setValue("");area(s).setScrollAmount(100);cycle(s).onPress(enter());check(cycle(s).getValue().toString().equals("ALL")&&area(s).scrollAmount()==0,"Filter resets scroll and cycles back to All");assertEntries(s,100);});
|
||||
@@ -80,6 +86,35 @@ public final class Story234ClientChecks implements FabricClientGameTest {
|
||||
}finally{intro.set(false);}
|
||||
}
|
||||
|
||||
private static KeyMapping mapKey(){return KeyMapping.get("sanctuary.atlas.key");}
|
||||
private static void shortcut(ClientGameTestContext c,boolean unlocked) {
|
||||
var saved=c.computeOnClient(m->InputConstants.getKey(mapKey().saveString()));
|
||||
int scan=c.computeOnClient(m->{var key=mapKey();check(key!=null,"Map is registered in native Controls");
|
||||
int nativeScan=org.lwjgl.sdl.SDLKeyboard.SDL_GetScancodeFromKey(InputConstants.KEYCODE_M,(java.nio.ShortBuffer)null);
|
||||
check(nativeScan>0&&key.getDefaultKey().getValue()==nativeScan,"Default follows the keyboard layout's M letter");
|
||||
key.setKey(key.getDefaultKey());KeyMapping.resetMapping();m.gui.setScreen(null);return nativeScan;});
|
||||
try {
|
||||
c.getInput().pressKey(scan);c.waitTicks(5);
|
||||
c.runOnClient(m->{check(unlocked?m.gui.screen() instanceof SanctuaryAtlasScreen:m.gui.screen()==null,"Game M respects Atlas unlock");});
|
||||
if(unlocked){c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen()==null,"Atlas opened from game returns to game"));}
|
||||
var pause=c.computeOnClient(m->{var screen=new PauseScreen(true);m.gui.setScreen(screen);return screen;});
|
||||
c.getInput().pressKey(scan);c.waitTicks(3);
|
||||
c.runOnClient(m->{check(unlocked?m.gui.screen() instanceof SanctuaryAtlasScreen:m.gui.screen()==pause,"Pause M respects Atlas unlock");
|
||||
if(unlocked){press(m.gui.screen(),"gui.done");check(m.gui.screen()==pause,"M from Pause keeps original parent");}m.gui.setScreen(null);});
|
||||
if(unlocked) {
|
||||
c.runOnClient(m->{mapKey().setKey(InputConstants.Type.KEYBOARD.getOrCreate(InputConstants.KEY_J));KeyMapping.resetMapping();});
|
||||
c.getInput().pressKey(scan);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen()==null,"Old M binding stops opening the map"));
|
||||
c.getInput().pressKey(InputConstants.KEY_J);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen() instanceof SanctuaryAtlasScreen,"Remapped J opens map from game"));
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);
|
||||
c.runOnClient(m->m.gui.setScreen(pause));c.getInput().pressKey(scan);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen()==pause,"Pause respects removed M binding"));
|
||||
c.getInput().pressKey(InputConstants.KEY_J);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen() instanceof SanctuaryAtlasScreen,"Pause respects remapped J"));
|
||||
c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);c.runOnClient(m->{check(m.gui.screen()==pause,"Remapped shortcut retains Pause parent");mapKey().setKey(InputConstants.UNKNOWN);KeyMapping.resetMapping();});
|
||||
c.getInput().pressKey(scan);c.getInput().pressKey(InputConstants.KEY_J);c.waitTicks(3);c.runOnClient(m->check(m.gui.screen()==pause,"Unbound disables map shortcut"));
|
||||
}
|
||||
System.out.println("ATLAS236_SHORTCUT_PASS: native layout-aware M, game/Pause returns, unlock="+unlocked+", configurable key and Unbound, binding restored");
|
||||
}finally{c.getInput().releaseKey(scan);c.getInput().releaseKey(InputConstants.KEY_J);c.runOnClient(m->{m.gui.setScreen(null);mapKey().setKey(saved);KeyMapping.resetMapping();});}
|
||||
}
|
||||
|
||||
private static List<Button> entries(Screen s){return widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b).equals("sanctuary.story.unknown_entry")).toList();}
|
||||
private static String key(AbstractWidget w){return w.getMessage().getContents() instanceof TranslatableContents t?t.getKey():"";}
|
||||
private static CycleButton<?> cycle(Screen s){return widgets(s).filter(CycleButton.class::isInstance).map(CycleButton.class::cast).findFirst().orElseThrow();}
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
package fr.koka.sanctuary.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import fr.koka.sanctuary.atlas.AtlasPayloads;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
|
||||
/** No cross-server atlas cache: responses belong only to the currently open screen/token. */
|
||||
public final class AtlasClient {
|
||||
private static int sequence;
|
||||
private static KeyMapping mapKey;
|
||||
private AtlasClient() { }
|
||||
public static void register() {
|
||||
ClientPlayNetworking.registerGlobalReceiver(fr.koka.sanctuary.atlas.AtlasMarkerPayload.TYPE,(p,c) -> c.client().execute(() -> {
|
||||
@@ -19,6 +28,49 @@ public final class AtlasClient {
|
||||
else if (c.client().gui.screen() instanceof PauseAtlasAccess pause && pause.sanctuary$pauseMap()!=null) pause.sanctuary$pauseMap().accept(p);
|
||||
}));
|
||||
}
|
||||
public static void registerShortcut(KeyMapping.Category category) {
|
||||
var shortcut=new MapKey(category);
|
||||
mapKey=KeyMappingHelper.registerKeyMapping(shortcut);
|
||||
ClientLifecycleEvents.CLIENT_STARTED.register(client->shortcut.resolveLayout());
|
||||
ClientTickEvents.END_CLIENT_TICK.register(client->{
|
||||
while(mapKey.consumeClick())if(client.gui.screen()==null)openFromShortcut(null);
|
||||
});
|
||||
}
|
||||
public static boolean openFromKey(Screen parent,KeyEvent event) {
|
||||
return mapKey!=null && mapKey.matches(event) && openFromShortcut(parent);
|
||||
}
|
||||
private static boolean openFromShortcut(Screen parent) {
|
||||
var client=Minecraft.getInstance();
|
||||
if(client.player==null || client.level==null || !client.player.isAlive() || !ProgressionClient.active())return false;
|
||||
if(!ProgressionClient.progress.atlas())client.player.sendOverlayMessage(SanctuaryAtlasScreen.label("locked"));
|
||||
else ProgressionClient.open(parent,"history");
|
||||
return true;
|
||||
}
|
||||
private static final class MapKey extends KeyMapping {
|
||||
private InputConstants.Key layoutDefault;
|
||||
private final boolean savedBinding;
|
||||
MapKey(Category category) {
|
||||
super("sanctuary.atlas.key",InputConstants.Type.KEYBOARD,InputConstants.KEY_M,category);
|
||||
layoutDefault=super.getDefaultKey();
|
||||
// An explicit saved binding, including Unbound, takes precedence over the layout default.
|
||||
var options=net.fabricmc.loader.api.FabricLoader.getInstance().getGameDir().resolve("options.txt");
|
||||
boolean saved=false;
|
||||
if(java.nio.file.Files.exists(options))try(var lines=java.nio.file.Files.lines(options)) {
|
||||
saved=lines.anyMatch(line->line.startsWith("key_"+getName()+":"));
|
||||
}catch(java.io.IOException error){saved=true;}
|
||||
savedBinding=saved;
|
||||
}
|
||||
void resolveLayout() {
|
||||
// Resolve the letter M after native startup: KEY_M is only its QWERTY position.
|
||||
boolean useDefault=!savedBinding && isDefault();
|
||||
int scan=org.lwjgl.sdl.SDLKeyboard.SDL_GetScancodeFromKey(InputConstants.KEYCODE_M,(java.nio.ShortBuffer)null);
|
||||
if(scan>0)layoutDefault=InputConstants.Type.KEYBOARD.getOrCreate(scan);
|
||||
if(useDefault)setKey(layoutDefault);
|
||||
KeyMapping.resetMapping();
|
||||
}
|
||||
@Override public InputConstants.Key getDefaultKey(){return layoutDefault;}
|
||||
@Override public boolean isDefault(){return key.equals(layoutDefault);}
|
||||
}
|
||||
public static int nextToken() {sequence=sequence==Integer.MAX_VALUE ? 1 : sequence+1;return sequence;}
|
||||
public static void send(AtlasPayloads.Request request) {
|
||||
if (ClientPlayNetworking.canSend(AtlasPayloads.Request.TYPE)) ClientPlayNetworking.send(request);
|
||||
|
||||
@@ -53,6 +53,7 @@ public final class SanctuaryKnowledgeClient implements ClientModInitializer {
|
||||
GraveInventoryScreen.register();
|
||||
TutorialClient.register();
|
||||
var category=KeyMapping.Category.register(SanctuaryMod.id("sanctuary"));
|
||||
AtlasClient.registerShortcut(category);
|
||||
DynamicLightClient.register(category);
|
||||
ZoomClient.register(category);
|
||||
LyingClient.register(category);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fr.koka.sanctuary.mixin.client;
|
||||
|
||||
import fr.koka.sanctuary.client.ProgressionClient;
|
||||
import fr.koka.sanctuary.client.AtlasClient;
|
||||
import fr.koka.sanctuary.client.PauseAtlasAccess;
|
||||
import fr.koka.sanctuary.client.PauseAtlasWidget;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
@@ -8,6 +9,7 @@ import fr.koka.sanctuary.client.CommunityPausePanels;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.PauseScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.client.input.KeyEvent;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -30,6 +32,10 @@ public abstract class CommunityPauseMixin extends Screen implements PauseAtlasAc
|
||||
}
|
||||
@Inject(method="tick",at=@At("TAIL"))
|
||||
private void sanctuary$communityTick(CallbackInfo callback) { if(sanctuary$community!=null)sanctuary$community.tick(); }
|
||||
@Override public boolean keyPressed(KeyEvent event) {
|
||||
if(sanctuary$community!=null && AtlasClient.openFromKey(this,event))return true;
|
||||
return super.keyPressed(event);
|
||||
}
|
||||
@Override public PauseAtlasWidget sanctuary$pauseMap() {return sanctuary$community==null?null:sanctuary$community.map();}
|
||||
@Inject(method="removed",at=@At("HEAD"))
|
||||
private void sanctuary$communityRemoved(CallbackInfo callback) {if(sanctuary$community!=null)sanctuary$community.close();}
|
||||
|
||||
@@ -2452,6 +2452,7 @@
|
||||
"sanctuary.community.actions_for": "Actions · %s",
|
||||
"sanctuary.community.reply_hint": "Write a reply…",
|
||||
"sanctuary.pause.map": "World map",
|
||||
"sanctuary.atlas.key": "Open world map",
|
||||
"sanctuary.pause.map_hint": "Open the map and explore",
|
||||
"sanctuary.community.more": "Load more",
|
||||
"sanctuary.community.browse": "All publications",
|
||||
|
||||
@@ -2452,6 +2452,7 @@
|
||||
"sanctuary.community.actions_for": "Actions · %s",
|
||||
"sanctuary.community.reply_hint": "Écrire une réponse…",
|
||||
"sanctuary.pause.map": "Carte du monde",
|
||||
"sanctuary.atlas.key": "Ouvrir la carte",
|
||||
"sanctuary.pause.map_hint": "Ouvrir la carte et explorer",
|
||||
"sanctuary.community.more": "Charger la suite",
|
||||
"sanctuary.community.browse": "Toutes les publications",
|
||||
|
||||
Reference in New Issue
Block a user