diff --git a/content/docs/en/guides/plugin/setting-up-env.mdx b/content/docs/en/guides/plugin/setting-up-env.mdx index 3c002a45..3b981b8b 100644 --- a/content/docs/en/guides/plugin/setting-up-env.mdx +++ b/content/docs/en/guides/plugin/setting-up-env.mdx @@ -61,7 +61,24 @@ You can now try the command `mvn -version` to verify Maven is installed correctl ## Setting Up Your Workspace -### 1. Add HytaleServer.jar Dependency +### 1. Clone the Plugin Template + +Instead of creating an empty directory, we'll use the official Hytale plugin template: + +```bash +# Clone the template repository +git clone https://github.com/HytaleModding/plugin-template.git MyFirstMod +cd MyFirstMod +``` + +### 2. Open in IntelliJ IDEA + +1. Open IntelliJ IDEA +2. Click "Open" and navigate to your `MyFirstMod` directory +3. IntelliJ will automatically detect it as a Maven project +4. Wait for the project to index and dependencies to download + +### 3. Add HytaleServer.jar Dependency Before you can start modding, you need to add the HytaleServer.jar file as a dependency: @@ -73,21 +90,19 @@ Before you can start modding, you need to add the HytaleServer.jar file as a dep - Click on the **+ icon** - Select the **HytaleServer.jar** you downloaded -### 2. Clone the Plugin Template +### 4. Add the HytaleServer to your Artifacts -Instead of creating an empty directory, we'll use the official Hytale plugin template: + +This is a temporary workaround until Hypixel provides an official Maven repository for HytaleServer.jar + + +Run this command in your console ```bash -# Clone the template repository -git clone https://github.com/HytaleModding/plugin-template.git MyFirstMod -cd MyFirstMod +mvn install:install-file -Dfile=[ROUTE TO HytaleServer.jar] -DgroupId=com.hypixel.hytale -DartifactId=HytaleServer-parent -Dversion=1.0-SNAPSHOT -Dpackaging=jar ``` -### 3. Open in IntelliJ IDEA -1. Open IntelliJ IDEA -2. Click "Open" and navigate to your `MyFirstMod` directory -3. IntelliJ will automatically detect it as a Maven project -4. Wait for the project to index and dependencies to download +Replace `[ROUTE TO HytaleServer.jar]` with the actual path to the `HytaleServer.jar` file on your system. ## Next Steps @@ -97,8 +112,8 @@ Now that you have your development environment set up with the plugin template: 2. **Explore the code structure** - Familiarize yourself with the template's organization 3. **Start modding** - Begin writing your first Hytale plugin! -The plugin template includes: -- Pre-configured Gradle build system -- Example plugin structure -- Essential dependencies -- Development tools configuration +Learn how to: + - [Create Commands](./creating-commands) + - [Create Events](./creating-events) + +and more! Explore the documentation to learn about all available features and best practices. \ No newline at end of file diff --git a/content/docs/en/guides/plugin/text-hologram.mdx b/content/docs/en/guides/plugin/text-hologram.mdx new file mode 100644 index 00000000..3ff613fd --- /dev/null +++ b/content/docs/en/guides/plugin/text-hologram.mdx @@ -0,0 +1,237 @@ +--- +title: "Title Holograms" +description: "Learn how to create title holograms." +authors: + - name: "Bird" + link: "https://discord.com/users/495709580068651009" +--- + +# Overview + +In this guide, you'll learn how to create a **title hologram** using an entity nameplate. + +## Steps + +1. Open and equip the **Entity Grabber Tool**. + +![tile-hologram-1](/assets/guides/tile-hologram-1.png) + + +This tool will be used later to **adjust the position of the nameplate**, so it’s useful to keep it equipped throughout the process. + + +--- + +2. Spawn a **small entity** (for example, rubble or a similar object), then **scale it down** to lowest possible value. + +![tile-hologram-2](/assets/guides/tile-hologram-2.png) + +--- + +3. Hover over the entity you just spawned and run the following command: + +``` +/entity nameplate "Text Here" +``` + +![tile-hologram-3](/assets/guides/tile-hologram-3.png) + +--- + +Your title hologram should now be visible in the world, floating where the entity is placed. + +![tile-hologram-4](/assets/guides/tile-hologram-4.png) + +--- + +## Bonus Section - Creating Title Holograms With Code + +This section shows how to create the same title hologram using a **server plugin command** instead of in-game tools. + +The example below creates a command called `/titlehologram` that spawns an invisible entity with a floating nameplate at the player’s location. + +```java +package org.example.plugin; + +import com.hypixel.hytale.component.Holder; +import com.hypixel.hytale.math.vector.Transform; +import com.hypixel.hytale.server.core.command.system.CommandContext; +import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase; +import com.hypixel.hytale.server.core.entity.UUIDComponent; +import com.hypixel.hytale.server.core.entity.entities.ProjectileComponent; +import com.hypixel.hytale.server.core.entity.nameplate.Nameplate; +import com.hypixel.hytale.server.core.modules.entity.component.Intangible; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.entity.tracker.NetworkId; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; + +import javax.annotation.Nonnull; +import java.util.UUID; + +public class TitleHologramCommand extends CommandBase { + + public TitleHologramCommand() { + super("TitleHologram", "Create a title hologram."); + } + + @Override + protected void executeSync(@Nonnull CommandContext ctx) { + + UUID playerUUID = ctx.sender().getUuid(); + PlayerRef playerRef = Universe.get().getPlayer(playerUUID); + World world = Universe.get().getWorld(playerRef.getWorldUuid()); + Transform playerTransform = playerRef.getTransform(); + + world.execute(() -> { + + Holder holder = EntityStore.REGISTRY.newHolder(); + ProjectileComponent projectileComponent = new ProjectileComponent("Projectile"); + holder.putComponent(ProjectileComponent.getComponentType(), projectileComponent); + holder.putComponent(TransformComponent.getComponentType(), new TransformComponent(playerTransform.getPosition().clone(), playerTransform.getRotation().clone())); + holder.ensureComponent(UUIDComponent.getComponentType()); + holder.ensureComponent(Intangible.getComponentType()); + + if (projectileComponent.getProjectile() == null) { + projectileComponent.initialize(); + if (projectileComponent.getProjectile() == null) { + return; + } + } + holder.addComponent(NetworkId.getComponentType(), new NetworkId(world.getEntityStore().getStore().getExternalData().takeNextNetworkId())); + holder.addComponent(Nameplate.getComponentType(), new Nameplate("Testing Holograms")); + + world.getEntityStore().getStore().addEntity(holder, com.hypixel.hytale.component.AddReason.SPAWN); + }); + } +} +``` + +--- + +### 1. Getting the Player and World + +```java +UUID playerUUID = ctx.sender().getUuid(); +PlayerRef playerRef = Universe.get().getPlayer(playerUUID); +World world = Universe.get().getWorld(playerRef.getWorldUuid()); +Transform playerTransform = playerRef.getTransform(); +``` + +This retrieves: + +| Value | Purpose | +| ----------------- | ------------------------------------- | +| `playerUUID` | The unique ID of the command sender | +| `playerRef` | A server-side reference to the player | +| `world` | The world the player is currently in | +| `playerTransform` | The player’s position and rotation | + +This allows the hologram to be spawned **exactly where the player is standing**. + +--- + +### 2. Spawning on the World Thread + +```java +world.execute(() -> { +``` + +All entity operations must run on the world thread. +Everything inside this block executes safely on the correct game thread. + +--- + +### 3. Creating the Entity + +```java +Holder holder = EntityStore.REGISTRY.newHolder(); +ProjectileComponent projectileComponent = new ProjectileComponent("Projectile"); +``` + +The projectile is only used as a **valid entity shell**. +It will never move or behave like a real projectile. + +--- + +### 4. Setting Position & Components + +```java +holder.putComponent(ProjectileComponent.getComponentType(), projectileComponent); +holder.putComponent(TransformComponent.getComponentType(), new TransformComponent(playerTransform.getPosition().clone(), playerTransform.getRotation().clone())); +holder.ensureComponent(UUIDComponent.getComponentType()); +holder.ensureComponent(Intangible.getComponentType()); +``` + +This builds an invisible, stationary entity at the player’s position. + +| Component | Purpose | +| --------------------- | ------------------------------------------------ | +| `ProjectileComponent` | Provides a valid entity shell | +| `TransformComponent` | Sets the entity’s position and rotation | +| `UUIDComponent` | Gives the entity a unique identity | +| `Intangible` | Makes the entity non-collidable and unselectable | + +The hologram will appear exactly at the player’s position and cannot be interacted with physically. + +--- + +### 5. Initializing the Projectile + +```java +if (projectileComponent.getProjectile() == null) { + projectileComponent.initialize(); + if (projectileComponent.getProjectile() == null) { + return; + } +} +``` + +This ensures the projectile entity is fully created. +If initialization fails, the hologram will not spawn. + +--- + +### 6. Setting Network & Nameplate Components + +```java +holder.addComponent( + NetworkId.getComponentType(), + new NetworkId( + world.getEntityStore() + .getStore() + .getExternalData() + .takeNextNetworkId() + ) +); + +holder.addComponent( + Nameplate.getComponentType(), + new Nameplate("Testing Holograms") +); +``` + +These final components make the hologram visible and give it its text. + +| Component | Purpose | +| --------------------- | ------------------------------- | +| `NetworkId` | Allows the entity to be synced | +| `Nameplate` | The actual hologram text | + +--- + +### 7. Spawning the Entity + +```java +world.getEntityStore() + .getStore() + .addEntity(holder, com.hypixel.hytale.component.AddReason.SPAWN); +``` + +This inserts the hologram entity into the world, making it active and visible. + +--- + +![tile-hologram-5](/assets/guides/tile-hologram-5.png) \ No newline at end of file diff --git a/content/docs/en/guides/plugin/ui.mdx b/content/docs/en/guides/plugin/ui.mdx new file mode 100644 index 00000000..565078f7 --- /dev/null +++ b/content/docs/en/guides/plugin/ui.mdx @@ -0,0 +1,229 @@ +--- +title: Custom UI +description: Learn how to show custom UI to the player +authors: + - name: "underscore95" + url: "https://github.com/underscore95" +--- + +## Important Information + - All .ui files must be included in your plugin resources folder, specifically they should be in resources/Common/UI/Custom + - Ensure your manifest.json contains `"IncludesAssetPack": true` + - The Hytale client has a "Diagnostic Mode" setting under General, this will give more detailed error messages. + +## Useful Resources + +Video Tutorials: + - CustomUIHud Tutorial: https://www.youtube.com/watch?v=u4pGShklEKs + - InteractiveCustomUIPage Tutorial: https://www.youtube.com/watch?v=NOFWQt9wEbk + +Examples: + - Simple InteractiveCustomUIPage Example: https://github.com/underscore95/Hytale-Sandbox-Plugin/tree/ui-pages + - Complicated InteractiveCustomUIPage Example: https://github.com/Buuz135/AdminUI/tree/main + +## .ui files + +Hytale currently uses .ui files to render UI, these are currently deprecated and Hytale is planning to move to NoesisGUI. +The transition has not happened yet and .ui files are the only way to render UI. + +UI is defined using a .ui file which functions similarly to HTML and CSS. + +### UI Elements + +A .ui file contains a tree of UI elements, declaring a UI element uses the following syntax: +``` +Group { + TextField #MyInput { + Style: $Common.@DefaultInputFieldStyle; + Background: $Common.@InputBoxBackground; + Anchor: (Top: 10, Width: 200, Height: 50); + } +} +``` + +`TextField` and `Group` are the types of UI elements. + - A text field is an input where the user can enter text. + - A group is an empty UI element, similar to a div in HTML. + +`#MyInput` is the ID of the TextField UI element, this is required for Java code to access the element later. + +### Variables + +You can define variables using the following syntax: +``` +@MyTex = PatchStyle(TexturePath: "MyBackground.png"); +``` + +### Textures + +Textures can be loaded using +``` +PatchStyle(TexturePath: "MyBackground.png"); +``` + +The path is relative to the .ui file. +Your textures must be included in your resource folder. + +If applying a texture as the background of a UI element, you do not need to match size, the texture will automatically be stretched. + +### Including other .ui files + +You can include other .ui files using `$Common = "Common.ui";`, this allows you to access variables like so: `Style: $Common.@DefaultInputFieldStyle;` + +## HUDs + +A HUD is an element of UI that stays on the screen all the time, for example the players hot bar or health bar. +It cannot be interacted with. + +### CustomUIHud + +Create a Java class that extends `CustomUIHud` and overrides the `build` function. + +The `build` function has a `UICommandBuilder` parameter, this allows you to add .ui files to the HUD. +``` +uiCommandBuilder.append("MyUI.ui"); // This file must be located at resources/Common/UI/Custom/MyUI.ui +``` + +### Showing & Hiding UI + +You can get the HudManager using `Player#getHudManager`. + - Use `HudManager#setCustomHud` to show UI. + - Use `HudManager#hideHudComponent` to hide UI. + - https://www.curseforge.com/hytale/mods/multiplehud allows showing multiple custom HUDs + +## UI Pages + +Pages are another type of UI, these prevent the player from interacting with the game and unlock the players mouse. +Some examples of pages include: Crafting menu, pause menu + +### CustomUIPage + +If you do not need user input, you can make a class extending CustomUIPage, this is very similar to CustomUIHud. + +### InteractiveCustomUIPage + +You must extend InteractiveCustomUIPage to receive events and user input. + +The following UI is used for the below code: +``` +$Common = "Common.ui"; + +@MyTex = PatchStyle(TexturePath: "MyBackground.png"); + +Group { + LayoutMode: Center; + + Group #MyPanel { + Background: @MyTex; + Anchor: (Width: 800, Height: 1000); + LayoutMode: Top; + + Label #MyLabel { + Style: (FontSize: 32, Alignment: Center); + Anchor: (Top: 50); + Text: "MyText"; + Padding: (Full: 10); + } + + TextField #MyInput { + Style: $Common.@DefaultInputFieldStyle; + Background: $Common.@InputBoxBackground; + Anchor: (Top: 10, Width: 200, Height: 50); + Padding: (Full: 10); + } + } +} +``` + +InteractiveCustomUIPage takes a generic argument, this is a class containing any UI data you want the client to send to the server. + +``` +public static class Data { + public static final BuilderCodec CODEC = BuilderCodec.builder(Data.class, Data::new) + .append(new KeyedCodec<>("@MyInput", Codec.STRING), (data, value) -> data.value = value, data -> data.value).add() + .build(); + + private String value; // Value of the TextField input +} +``` + +InteractiveCustomUIPage constructor takes a PlayerRef, CustomPageLifetime, and `BuilderCodec`. + - PlayerRef is the player you want to show UI to. + - CustomPageLifetime is an enum controlling if the player can close the UI or not + +`BuilderCodec` tells the server how to create the Data object from the JSON the client sends. +Example value in the Data class above. + +This passes the Class and a lambda function that constructs the Data object, the server uses this to deserialize the JSON.: +``` +BuilderCodec.builder(Data.class, Data::new) +``` + +This provides a setter and getter lambda function to the field that should contain whatever @MyInput is set to: +``` +.append(new KeyedCodec<>("@MyInput", Codec.STRING), (data, value) -> data.value = value, data -> data.value).add() +``` + +The following build method is used: +``` +@Override +public void build(@Nonnull Ref ref, @Nonnull UICommandBuilder uiCommandBuilder, @Nonnull UIEventBuilder uiEventBuilder, @Nonnull Store store) { + uiCommandBuilder.append("MyUI.ui"); + uiEventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#MyInput", EventData.of("@MyInput", "#MyInput.Value"), false); +} +``` + +This says we are listening for whenever the value of #MyInput changes: +``` +CustomUIEventBindingType.ValueChanged, "#MyInput" +``` + +You may be wondering where `@MyInput` in the codec previously came from, it is defined here. +This creates the codec value `@MyInput` and maps it to `#MyInput.Value`: +``` +EventData.of("@MyInput", "#MyInput.Value") +``` + +The following `handleDataEvent` will be overridden, take care to override the one that has a `Data` parameter rather than `String raw` (this is the raw json). +``` +@Override +public void handleDataEvent(@Nonnull Ref ref, @Nonnull Store store, Data data) { + super.handleDataEvent(ref, store, data); + + System.out.println("EVENT: " + data.value); + + sendUpdate(); +} +``` + +You must always either switch to a new UI or call `sendUpdate();` otherwise the Hytale client will display "Loading..." forever and prevent the user from interacting with your UI. +This will be familiar if you have ever made commands in a Discord bot, where you have to acknowledge interactions. + +### Opening UI pages + +``` +player.getPageManager().openCustomPage(ref, store, MyUI(playerRef)); +``` + +## Dynamically Updating UI + +In many cases you want to update your UI at run time. + +Here is an example of updating a Label's (with id MyLabel) text content, this method should be added to your UI Java class: + +``` +public void updateText(String newText) { + UICommandBuilder uiCommandBuilder = new UICommandBuilder(); + uiCommandBuilder.set("#MyLabel.TextSpans", Message.raw(newText)); + update(false, uiCommandBuilder); // false = don't clear existing UI +} + +## Common Issues + +### Failed to apply custom ui hud commands + +This means your .ui file has something wrong with it. + +### Could not find document XXXXX for Custom UI Append command + +This means your .ui file wasn't in the location your Java code said it was, double check that your path is correct. \ No newline at end of file diff --git a/messages/en.json b/messages/en.json index 4ac96eaa..245babd1 100644 --- a/messages/en.json +++ b/messages/en.json @@ -70,6 +70,19 @@ }, "community_projects": { "title": "Community Projects" - } + }, + "keywords": [ + "hytale modding", + "hytale", + "hytale plugins", + "hytale mods", + "how to mod hytale", + "modding tutorial", + "modding guides", + "hytale modding guides", + "hytale modding tutorial", + "how to start modding Hytale", + "how to make a mod" + ] } } diff --git a/public/assets/guides/tile-hologram-1.png b/public/assets/guides/tile-hologram-1.png new file mode 100644 index 00000000..1502e4fc Binary files /dev/null and b/public/assets/guides/tile-hologram-1.png differ diff --git a/public/assets/guides/tile-hologram-2.png b/public/assets/guides/tile-hologram-2.png new file mode 100644 index 00000000..836f48dd Binary files /dev/null and b/public/assets/guides/tile-hologram-2.png differ diff --git a/public/assets/guides/tile-hologram-3.png b/public/assets/guides/tile-hologram-3.png new file mode 100644 index 00000000..002e354a Binary files /dev/null and b/public/assets/guides/tile-hologram-3.png differ diff --git a/public/assets/guides/tile-hologram-4.png b/public/assets/guides/tile-hologram-4.png new file mode 100644 index 00000000..ec8992e5 Binary files /dev/null and b/public/assets/guides/tile-hologram-4.png differ diff --git a/public/assets/guides/tile-hologram-5.png b/public/assets/guides/tile-hologram-5.png new file mode 100644 index 00000000..28ada167 Binary files /dev/null and b/public/assets/guides/tile-hologram-5.png differ diff --git a/public/og.png b/public/og.png deleted file mode 100644 index a99d1168..00000000 Binary files a/public/og.png and /dev/null differ diff --git a/src/app/[lang]/(home)/opengraph-image.tsx b/src/app/[lang]/(home)/opengraph-image.tsx new file mode 100644 index 00000000..551d8efc --- /dev/null +++ b/src/app/[lang]/(home)/opengraph-image.tsx @@ -0,0 +1,29 @@ +import { ImageResponse } from "next/og"; + +// Image metadata +export const size = { + width: 1200, + height: 630, +}; + +export const contentType = "image/png"; + +// Image generation +export default async function Image({ params }: { params: { slug: string } }) { + return new ImageResponse( + // ImageResponse JSX element +
+ helloworld +
, + ); +} diff --git a/src/app/[lang]/docs/[[...slug]]/page.tsx b/src/app/[lang]/docs/[[...slug]]/page.tsx index d354c329..692d4577 100644 --- a/src/app/[lang]/docs/[[...slug]]/page.tsx +++ b/src/app/[lang]/docs/[[...slug]]/page.tsx @@ -8,7 +8,7 @@ import { } from "fumadocs-ui/page"; import { notFound } from "next/navigation"; import { getMDXComponents } from "@/mdx-components"; -import type { Metadata } from "next"; +import type { Metadata, ResolvingMetadata } from "next"; import { createRelativeLink } from "fumadocs-ui/mdx"; import { branch } from "@/git-info.json"; import { ViewTransition } from "react"; @@ -94,6 +94,7 @@ export async function generateStaticParams() { export async function generateMetadata( props: PageProps<"/[lang]/docs/[[...slug]]">, + parent: ResolvingMetadata, ): Promise { const params = await props.params; const page = source.getPage(params.slug, params.lang); @@ -102,31 +103,19 @@ export async function generateMetadata( const slug = params.slug || []; const imageUrl = `/api/og/docs/${params.lang}${slug.length > 0 ? "/" + slug.join("/") : ""}`; const pageKeywords = (page.data as any).keywords || []; - const globalKeywords = [ - "hytale modding", - "hytale", - "hytale plugins", - "hytale mods", - "how to mod hytale", - "modding tutorial", - "modding guides", - "hytale modding guides", - "hytale modding tutorial", - "how to start modding Hytale", - "how to make a mod", - ]; + const parentMetadata = await parent; + const parentKeywords = parentMetadata.keywords || []; - if (ogLanguageBlacklist.includes(params.lang)) - return { - title: page.data.title, - description: page.data.description, - keywords: [...globalKeywords, ...pageKeywords], - }; + const baseMetadata = { + title: page.data.title, + description: page.data.description, + keywords: [...parentKeywords, ...pageKeywords], + }; + + if (ogLanguageBlacklist.includes(params.lang)) return baseMetadata; else return { - title: page.data.title, - description: page.data.description, - keywords: [...globalKeywords, ...pageKeywords], + ...baseMetadata, openGraph: { images: [ { diff --git a/src/app/[lang]/layout.tsx b/src/app/[lang]/layout.tsx index 7d98c484..e7ec3fbc 100644 --- a/src/app/[lang]/layout.tsx +++ b/src/app/[lang]/layout.tsx @@ -4,8 +4,10 @@ import { i18n } from "@/lib/i18n"; import Script from "next/script"; import englishTranslations from "@/../messages/en.json"; import { Geist, Geist_Mono } from "next/font/google"; -import type { Metadata } from "next"; +import type { Metadata, ResolvingMetadata } from "next"; import { cn } from "@/lib/utils"; +import fallbackMessages from "@/../messages/en.json"; +import { headers } from "next/headers"; const geist = Geist({ subsets: ["latin"], @@ -16,25 +18,6 @@ const geistMono = Geist_Mono({ variable: "--font-mono", }); -export const metadata: Metadata = { - metadataBase: new URL( - process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000", - ), - keywords: [ - "hytale modding", - "hytale", - "hytale plugins", - "hytale mods", - "how to mod hytale", - "modding tutorial", - "modding guides", - "hytale modding guides", - "hytale modding tutorial", - "how to start modding Hytale", - "how to make a mod", - ], -}; - const translations = Object.fromEntries( i18n.languages.map((lang) => { const messages = require(`@/../messages/${lang}.json`); @@ -71,3 +54,41 @@ export default async function RootLayout({ ); } + +const siteUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; + +export async function generateMetadata( + { params }: { params: Promise<{ lang: string }> }, + parent: ResolvingMetadata, +): Promise { + const { lang } = await params; + const otherLangs = i18n.languages.filter((l) => l !== lang); + + const headersList = await headers(); + const pathname = headersList.get("x-pathname") || `/${lang}`; + + // Remove the language prefix from pathname to get the page path + const pagePath = pathname.replace(`/${lang}`, ""); + + const messages = require(`@/../messages/${lang}.json`); + + return { + metadataBase: new URL(siteUrl), + keywords: messages.meta?.keywords || fallbackMessages.meta.keywords, + + alternates: { + canonical: `${siteUrl}/${lang}${pagePath}`, + languages: { + ...Object.fromEntries( + otherLangs.map((l) => [l, `${siteUrl}/${l}${pagePath}`]), + ), + }, + }, + + openGraph: { + type: "website", + siteName: "Hytale Modding", + url: `${siteUrl}/${lang}${pagePath}`, + }, + }; +} diff --git a/src/app/[lang]/metadata.ts b/src/app/[lang]/metadata.ts deleted file mode 100644 index f1cd84cd..00000000 --- a/src/app/[lang]/metadata.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { Metadata } from "next"; - -const SITE_URL = "https://hytalemodding.dev"; - -export const metadata: Metadata = { - metadataBase: new URL(SITE_URL), - - alternates: { - canonical: SITE_URL, - languages: { - en: `${SITE_URL}/en`, - fr: `${SITE_URL}/fr`, - de: `${SITE_URL}/de`, - }, - }, - - openGraph: { - type: "website", - siteName: "Hytale Modding", - url: SITE_URL, - images: [ - { - url: "/og.png", - width: 1200, - height: 630, - alt: "Hytale Modding", - }, - ], - }, - - twitter: { - card: "summary_large_image", - images: ["/og.png"], - }, -}; diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 00000000..9be72a49 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,26 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +export function middleware(request: NextRequest) { + const requestHeaders = new Headers(request.headers); + requestHeaders.set("x-pathname", request.nextUrl.pathname); + + return NextResponse.next({ + request: { + headers: requestHeaders, + }, + }); +} + +export const config = { + matcher: [ + /* + * Match all request paths except for the ones starting with: + * - api (API routes) + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico (favicon file) + */ + "/((?!api|_next/static|_next/image|favicon.ico).*)", + ], +};