Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API and command to open guides (also server-side) #19

Merged
merged 2 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ jobs:
- name: Build PR with Gradle
run: ./gradlew build
if: github.event_name != 'pull_request'
- name: Run Game Tests
run: ./gradlew runGametest

# Always upload test results
- name: Merge Test Reports
Expand Down
8 changes: 3 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ neoForge {
client {
client()
}
server {
server()
}
gametestWorld {
client()
programArguments = [
Expand All @@ -158,11 +161,6 @@ neoForge {
'--existing', file('src/main/resources').absolutePath
]
}
// Use to run the tests
gametest {
type = "gameTestServer"
gameDirectory = project.file("build/gametest")
}
}

unitTest {
Expand Down
18 changes: 18 additions & 0 deletions docs/docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

# Changelog

## 2.1.0

- Adds API to open guides for players from both server- and client-side
- `GuidesCommon.openGuide(Player player, ResourceLocation guideId)` to open the last opened (or start-page if none) page of a guide for the given player.
- `GuidesCommon.openGuide(Player player, ResourceLocation guideId, PageAnchor anchor)` to open a specific page of a guide for the given player.
- Moves the existing client-only command to `/guidemec`
- Adds a new server-side `/guideme` [command](./commands.md) that allows opening guides for target entities similar to `/tellraw`.
This can be used to open guides using command blocks and other mechanisms.
<p><video controls><source src={require('./command-block-guide.mp4').default}/></video></p>
Example: `/guideme open @s testmod:guide` to open the start page
or `/guideme open @s testmod:guide page.md#anchor` to open a specific page at an anchor.

## 2.0.1

- Removes superflous log spam when opening the creative menu.
Binary file added docs/docs/command-block-guide.mp4
Binary file not shown.
29 changes: 29 additions & 0 deletions docs/docs/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Commands

## Open Guides

This command opens guides for any player (using an [entity target selector](https://minecraft.wiki/w/Target_selectors)).

It requires operator permissions since it can open guides for other players.

`/guideme open <target> <guide-id>`

`/guideme open <target> <guide-id> <page-id>`

`/guideme open <target> <guide-id> <page-id>#<anchor>`

This can be used to open guides using command blocks:

<video controls>
<source src={require('./command-block-guide.mp4').default}/>
</video>

## Open Guides (client only)

This command on your own client only and opens any guide for yourself.

`/guidemec open <guide-id>`

`/guidemec open <guide-id> <page-id>`

`/guidemec open <guide-id> <page-id>#<anchor>`
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ org.gradle.warning.mode=all
minecraft_version=1.21.1
minecraft_version_range=[1.21.1]
# https://projects.neoforged.net/neoforged/neoforge
neoforge_version=21.1.36
neoforge_version=21.1.113
neoforge_version_range=[21.1.1,)

#########################################################
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/guideme/GuidesCommon.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package guideme;

import guideme.internal.GuideMEProxy;
import java.util.Objects;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;

/**
* Functionality for guides that can be used on both the server and client.
*/
public final class GuidesCommon {
private GuidesCommon() {
}

/**
* Opens the last opened page (or start page) of the guide for the player.
*/
public static void openGuide(Player player, ResourceLocation guideId) {
GuideMEProxy.instance().openGuide(player, guideId, null);
}

/**
* Opens the given guide for the player and navigates to the given page position.
*/
public static void openGuide(Player player, ResourceLocation guideId, PageAnchor anchor) {
GuideMEProxy.instance().openGuide(player, guideId, Objects.requireNonNull(anchor, "anchor"));
}
}
42 changes: 40 additions & 2 deletions src/main/java/guideme/internal/GuideME.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
package guideme.internal;

import guideme.internal.command.GuideCommand;
import guideme.internal.command.GuideIdArgument;
import guideme.internal.command.PageAnchorArgument;
import guideme.internal.item.GuideItem;
import guideme.internal.network.OpenGuideRequest;
import java.util.function.Supplier;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import net.minecraft.commands.synchronization.SingletonArgumentInfo;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.RegisterCommandsEvent;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.registries.DeferredRegister;

@Mod(value = GuideME.MOD_ID)
public class GuideME {
public static GuideMEProxy PROXY = new GuideMEProxy() {
};
static GuideMEProxy PROXY = new GuideMEServerProxy();

public static final String MOD_ID = "guideme";

private static final DeferredRegister.Items DR_ITEMS = DeferredRegister.createItems(MOD_ID);
private static final DeferredRegister<ArgumentTypeInfo<?, ?>> DR_ARGUMENT_TYPE_INFOS = DeferredRegister
.create(Registries.COMMAND_ARGUMENT_TYPE, MOD_ID);

public static final Supplier<GuideItem> GUIDE_ITEM = DR_ITEMS.registerItem("guide", GuideItem::new,
GuideItem.PROPERTIES);
Expand All @@ -30,11 +42,37 @@ public class GuideME {
.build();

public GuideME(IEventBus modBus) {
DR_ARGUMENT_TYPE_INFOS.register("guide_id", () -> ArgumentTypeInfos.registerByClass(GuideIdArgument.class,
SingletonArgumentInfo.contextFree(GuideIdArgument::argument)));
DR_ARGUMENT_TYPE_INFOS.register("page_anchor", () -> ArgumentTypeInfos.registerByClass(PageAnchorArgument.class,
SingletonArgumentInfo.contextFree(PageAnchorArgument::argument)));

var drDataComponents = DeferredRegister.createDataComponents(MOD_ID);
drDataComponents.register("guide_id", () -> GUIDE_ID_COMPONENT);

DR_ARGUMENT_TYPE_INFOS.register(modBus);
DR_ITEMS.register(modBus);
drDataComponents.register(modBus);

modBus.addListener(this::registerNetworking);

NeoForge.EVENT_BUS.addListener(this::registerCommands);
}

private void registerCommands(RegisterCommandsEvent event) {
GuideCommand.register(event.getDispatcher());
}

private void registerNetworking(RegisterPayloadHandlersEvent event) {
var registrar = event.registrar("1.0");
registrar.playToClient(OpenGuideRequest.TYPE, OpenGuideRequest.STREAM_CODEC, (payload, context) -> {
var anchor = payload.pageAnchor().orElse(null);
if (anchor != null) {
GuideMEProxy.instance().openGuide(context.player(), payload.guideId(), anchor);
} else {
GuideMEProxy.instance().openGuide(context.player(), payload.guideId());
}
});
}

public static ResourceLocation makeId(String path) {
Expand Down
53 changes: 7 additions & 46 deletions src/main/java/guideme/internal/GuideMEClient.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package guideme.internal;

import guideme.Guide;
import guideme.Guides;
import guideme.PageAnchor;
import guideme.color.LightDarkMode;
import guideme.internal.command.GuideCommand;
import guideme.internal.command.GuideClientCommand;
import guideme.internal.data.GuideMELanguageProvider;
import guideme.internal.data.GuideMEModelProvider;
import guideme.internal.hotkey.OpenGuideHotkey;
Expand All @@ -13,22 +12,16 @@
import guideme.internal.screen.GlobalInMemoryHistory;
import guideme.internal.screen.GuideScreen;
import guideme.render.GuiAssets;
import java.util.List;
import java.util.Objects;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.DataGenerator;
import net.minecraft.data.PackOutput;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.TooltipFlag;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer;
Expand All @@ -44,12 +37,11 @@
import net.neoforged.neoforge.data.event.GatherDataEvent;
import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent;
import net.neoforged.neoforge.registries.RegisterEvent;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Mod(value = GuideME.MOD_ID, dist = Dist.CLIENT)
public class GuideMEClient implements GuideMEProxy {
public class GuideMEClient {
private static final Logger LOG = LoggerFactory.getLogger(GuideMEClient.class);

private static GuideMEClient INSTANCE;
Expand All @@ -59,7 +51,7 @@ public class GuideMEClient implements GuideMEProxy {

public GuideMEClient(ModContainer modContainer, IEventBus modBus) {
INSTANCE = this;
GuideME.PROXY = this;
GuideME.PROXY = new GuideMEClientProxy();

modContainer.registerConfig(ModConfig.Type.CLIENT, clientConfig.spec, "guideme.toml");

Expand Down Expand Up @@ -115,7 +107,7 @@ public static GuideMEClient instance() {

private static void registerClientCommands(RegisterClientCommandsEvent evt) {
var dispatcher = evt.getDispatcher();
GuideCommand.register(dispatcher);
GuideClientCommand.register(dispatcher);
}

private void gatherData(GatherDataEvent event) {
Expand All @@ -142,13 +134,15 @@ public static boolean openGuideAtPreviousPage(Guide guide, ResourceLocation init
}
}

public static void openGuideAtAnchor(Guide guide, PageAnchor anchor) {
public static boolean openGuideAtAnchor(Guide guide, PageAnchor anchor) {
try {
var screen = GuideScreen.openNew(guide, anchor, GlobalInMemoryHistory.INSTANCE);

openGuideScreen(screen);
return true;
} catch (Exception e) {
LOG.error("Failed to open guide at {}.", anchor, e);
return false;
}
}

Expand All @@ -175,37 +169,4 @@ public ClientConfig() {
spec = builder.build();
}
}

@Override
public void addGuideTooltip(ResourceLocation guideId, Item.TooltipContext context, List<Component> lines,
TooltipFlag tooltipFlag) {
var guide = GuideRegistry.getById(guideId);
if (guide == null) {
lines.add(GuidebookText.ItemInvalidGuideId.text().withStyle(ChatFormatting.RED));
return;
}

lines.addAll(guide.getItemSettings().tooltipLines());
}

@Override
public @Nullable Component getGuideDisplayName(ResourceLocation guideId) {
var guide = GuideRegistry.getById(guideId);
if (guide != null) {
return guide.getItemSettings().displayName().orElse(null);
}

return null;
}

@Override
public boolean openGuide(Player player, ResourceLocation id) {
var guide = Guides.getById(id);
if (guide == null) {
player.sendSystemMessage(GuidebookText.ItemInvalidGuideId.text(id.toString()));
return false;
} else {
return openGuideAtPreviousPage(guide, guide.getStartPage());
}
}
}
85 changes: 85 additions & 0 deletions src/main/java/guideme/internal/GuideMEClientProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package guideme.internal;

import guideme.Guide;
import guideme.Guides;
import guideme.PageAnchor;
import guideme.compiler.ParsedGuidePage;
import java.util.List;
import java.util.stream.Stream;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.TooltipFlag;
import org.jetbrains.annotations.Nullable;

class GuideMEClientProxy extends GuideMEServerProxy {
@Override
public void addGuideTooltip(ResourceLocation guideId, Item.TooltipContext context, List<Component> lines,
TooltipFlag tooltipFlag) {
var guide = GuideRegistry.getById(guideId);
if (guide == null) {
lines.add(GuidebookText.ItemInvalidGuideId.text().withStyle(ChatFormatting.RED));
return;
}

lines.addAll(guide.getItemSettings().tooltipLines());
}

@Override
public @Nullable Component getGuideDisplayName(ResourceLocation guideId) {
var guide = GuideRegistry.getById(guideId);
if (guide != null) {
return guide.getItemSettings().displayName().orElse(null);
}

return null;
}

@Override
public boolean openGuide(Player player, ResourceLocation id) {
if (player == Minecraft.getInstance().player) {
var guide = Guides.getById(id);
if (guide == null) {
player.sendSystemMessage(GuidebookText.ItemInvalidGuideId.text(id.toString()));
return false;
} else {
return GuideMEClient.openGuideAtPreviousPage(guide, guide.getStartPage());
}
}

return super.openGuide(player, id);
}

@Override
public boolean openGuide(Player player, ResourceLocation id, PageAnchor anchor) {
if (player == Minecraft.getInstance().player) {
var guide = Guides.getById(id);
if (guide == null) {
player.sendSystemMessage(GuidebookText.ItemInvalidGuideId.text(id.toString()));
return false;
} else {
return GuideMEClient.openGuideAtAnchor(guide, anchor);
}
}

return super.openGuide(player, id, anchor);
}

@Override
public Stream<ResourceLocation> getAvailableGuides() {
return Guides.getAll().stream().map(Guide::getId);
}

@Override
public Stream<ResourceLocation> getAvailablePages(ResourceLocation guideId) {
var guide = Guides.getById(guideId);
if (guide == null) {
return Stream.empty();
}

return guide.getPages().stream().map(ParsedGuidePage::getId);
}
}
Loading
Loading