diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..b8f4081 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,14 @@ +name: Release + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install mkdocs-material + - run: mkdocs gh-deploy --force diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07a7edb..2749628 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,3 +41,9 @@ jobs: with: files: 'build/libs/*.jar;!build/libs/*-sources.jar;!build/libs/*-dev.jar' repo-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/setup-python@v2 + with: + python-version: 3.x + - run: pip install mkdocs-material + - run: mkdocs gh-deploy --force diff --git a/README.md b/README.md index 70be58f..7ac0ac9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -# Fabric Placeholder API +# About Placeholder API It's a small, jij-able API that allows creation and parsing placeholders within strings and Minecraft Text Components. Placeholders use simple format of `%modid:type%` or `%modid:type/data%`. +It also includes simple, general usage text format indented for simplifying user input in configs/chats/etc. -For list of currently available placeholders, check [wiki](https://github.com/Patbox/FabricPlaceholderAPI/wiki). +For list of currently available placeholders, check [wiki](https://github.com/Patbox/TextPlaceholderAPI/wiki). ## Usage: Add it to your dependencies like this: @@ -26,3 +27,5 @@ PlaceholderAPI.register(new Identifier("server", "name"), (ctx) -> PlaceholderRe If you want to parse placeholders, you need to use `PlaceholderAPI.parseString(String, MinecraftServer or ServerPlayerEntity)` for strings or `PlaceholderAPI.parseText(Text, MinecraftServer or ServerPlayerEntity)` for Vanilla Text Components. These function don't modify original one, instead they just return new String/Text. + + diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 0000000..66e1fca Binary files /dev/null and b/docs/assets/logo.png differ diff --git a/docs/assets/style.css b/docs/assets/style.css new file mode 100644 index 0000000..aed3481 --- /dev/null +++ b/docs/assets/style.css @@ -0,0 +1,3 @@ +.md-header__button.md-logo img, .md-header__button.md-logo svg { + width: auto; +} \ No newline at end of file diff --git a/docs/dev/adding-placeholders.md b/docs/dev/adding-placeholders.md new file mode 100644 index 0000000..945488b --- /dev/null +++ b/docs/dev/adding-placeholders.md @@ -0,0 +1,54 @@ +# Adding placeholders +Creation of new placeholders is simple. You just need to import `eu.pb4.placeholders.PlaceholderAPI` +and call static `register` method. You only need to provide 2 arguments: + +- Identifier with your mod id as namespace and path as argument name (with one additional limitation being not allowed to use `/` in it). +- A function (in form of lambda for example) that takes PlaceholderContext and returns PlaceholderResult, + +Example +``` +PlaceholderAPI.register( + new Identifier("example", "placeholder"), + (ctx) -> PlaceholderResult.value(new LiteralText("Hello World!")) +); +``` + +## Using the context +PlaceholderContext object passed to placeholder contains allows getting player (if exist), server and argument value. +It also includes few methods for checking if they are present. + +Here is example for player only placeholder +``` +PlaceholderAPI.register(new Identifier("player", "displayname"), (ctx) -> { + if (ctx.hasPlayer()) { + return PlaceholderResult.value(ctx.getPlayer().getDisplayName()); + } else { + return PlaceholderResult.invalid("No player!"); + } +}); +``` + +And one for usage of argument +``` +PlaceholderAPI.register(new Identifier("server", "name_from_uuid"), (ctx) -> { + if (ctx.hasArgument()) { + return PlaceholderResult.value(ctx.getServer().getUserCache().getByUuid(UUID.fromString(ctx.getArgument())).get().getName())); + } else { + return PlaceholderResult.invalid("No argument!"); + } +}); +``` + +## Returning correct value +Placeholders need to return instance of PlaceholderResult. It can be created by usage of provided static methods on this class. + +If it was successful: + +* `PlaceholderResult.value(Text text)` - Creates a value with text +* `PlaceholderResult.value(String text)` - Creates a value from string, by parsing it with TextParser + +If it was invalid (no player or argument for example): + +* `PlaceholderResult.invalid()` - Creates simple invalid result +* `PlaceholderResult.invalid(String reason)` - Creates invalid result with a reason, + which is currently unused, but can be implemented by other parsers \ No newline at end of file diff --git a/docs/dev/getting-started.md b/docs/dev/getting-started.md new file mode 100644 index 0000000..105600b --- /dev/null +++ b/docs/dev/getting-started.md @@ -0,0 +1,23 @@ +# Getting Started + +To begin, you need to add Nucleoid's maven to your build `build.gradle`. + +```groovy +repositories { + // There might be other repos there too, just add it at the end + maven { url "https://maven.nucleoid.xyz/" } +} +``` + +Then you just declare it as dependency! +```groovy +dependencies { + // You will have other dependencies here too + + modImplementation include("eu.pb4:placeholder-api:[VERSION]") +} +``` +This will also include it in yours mods, so users won't need to download it separately. + +You just need to replace `[VERSION]` with version you want to use (which should be usually the latest available). +For list of version names, you can check [maven](https://maven.nucleoid.xyz/eu/pb4/placeholder-api/) \ No newline at end of file diff --git a/docs/dev/parsing-placeholders.md b/docs/dev/parsing-placeholders.md new file mode 100644 index 0000000..a691662 --- /dev/null +++ b/docs/dev/parsing-placeholders.md @@ -0,0 +1,82 @@ +# Parsing placeholders +There are few ways (and types) of placeholders you can parse with PlaceholderAPI. +So depending on your use case some of these will be more useful than others. + +## Parsing global placeholders +Parsing global placeholders is really simple, as long as you have access to ServerPlayerEntity +or MinecraftServer object. You just need to simply import `eu.pb4.placeholders.PlaceholderAPI` and call +`parseText`. This method will return fully parsed Text, which can be displayed to the user. + +Example +``` +// for ServerPlayerEntity +Text message = PlaceholderAPI.parseText(textInput, player); + +// for Server +Text message2 = PlaceholderAPI.parseText(textInput, server); +``` + +Placeholders itself will use default formatting of `%category:placeholder%`. +If you want to use other formatting for them (which is recommended), you can use +`parseTextAlt(Text, ServerPlayerEntity or MinecraftServer)` for `{category:placeholder}`. + +## Parsing own/custom/predefined placeholders +If you want to parse your own placeholders, you can do this in 2 ways. + +### Static placeholders +To parse static placeholders you need to create a Map with `String` as a key and `Text` as a value. +You also need a Pattern object (which can be taken from predefined ones). Then it's as simple as calling +a `parsePredefinedText` static method on `PlaceholderAPI` class. + +Example +``` +ServerPlayerEntity player = something.getPlayer(); // MinecraftServer server = something.getServer() + +Text inputText = new LiteralText("Hello! ${player}"); +Map placeholders = Map.of("player", new LiteralText("You are a player!")); +Pattern pattern = PlaceholderAPI.PREDEFINED_PLACEHOLDER_PATTERN; + +Text output = PlaceholderAPI.parsePredefinedText(inputText, pattern, placeholders); +``` + +### Dynamic placeholders +In case where you want to parse placeholder with a context similar to global one, you need to +create a Map with `Identifier` (with `/` being disallowed) as a key and `PlaceholderHandler` as a value (same as adding global ones). +You also will need a pattern object, which is the same as with static ones. + +As opposite to global ones, you don't need to define namespace/category as it can default to minecraft one (for simpler user input). +Then you just parse it with `parseTextCustom(Text, ServerPlayerEntity or MinecraftServer, Map, Pattern)`. + +Example +``` +ServerPlayerEntity player = something.getPlayer(); // MinecraftServer server = something.getServer() + +Text inputText = new LiteralText("Hello! ${player/blue}"); +Map placeholders = Map.of(new Identifier("player"), + (ctx) -> { + if (ctx.hasPlayer()) { + return PlaceholderResult.value(new LiteralText("You are a player!") + .setStyle(Style.EMPTY.withColor(TextColor.parse(ctx.getArgument())))); + } else { + return PlaceholderResult.value(new LiteralText("You are a server!") + .setStyle(Style.EMPTY.withColor(TextColor.parse(ctx.getArgument())))); + } + }); +Pattern pattern = PlaceholderAPI.PREDEFINED_PLACEHOLDER_PATTERN; + +Text output = PlaceholderAPI.parseTextCustom(inputText, player, pattern, placeholders); +// Text output = PlaceholderAPI.parseTextCustom(inputText, server, pattern, placeholders); +``` + +### Preferred Patterns for static +PlaceholderAPI has few Patterns you can use, which are accessible as static objects on `PlaceholderAPI` class. + +* `PREDEFINED_PLACEHOLDER_PATTERN` (`${placeholder}`) - works the best in most cases, doesn't collide with other ones. +* `ALT_PLACEHOLDER_PATTERN_CUSTOM` (`{placeholder}`) - second best, but have more chance of colliding with user formatting. + +There are other ones, which usage is allowed, but they might work worse. + +* `PLACEHOLDER_PATTERN_CUSTOM` (`%placeholder%`) - is the same as default one, but doesn't require `:`. +* `PLACEHOLDER_PATTERN` (`%category:placeholder%`) - used by default global placeholders (requires category). +* `PLACEHOLDER_PATTERN_ALT` (`{category:placeholder}`) - used as alternative formatting for global ones (requires category). + diff --git a/docs/dev/text-format.md b/docs/dev/text-format.md new file mode 100644 index 0000000..f867f50 --- /dev/null +++ b/docs/dev/text-format.md @@ -0,0 +1,30 @@ +# Using Simplified Text Format/TextParser +[*You can read about format here!*](/user/text-format) + +Usage of TextParser is simple and really customisable. You just need to import `eu.pb4.placeholders.TextParser` +and call static `parse` method for admin provided configs or `parseSafe` for player provided ones. +They both take only one String argument and output a Text object. + +Example +``` +String inputString = "Hello World!" + +Text output = TextParser.parse(inputString); // Text output = TextParser.parseSafe(inputString); +``` + +## Parsing with only selected ones +If you want to only use selected tags, you can simply get map of all with `TextParser.getRegisteredTags()` +or `TextParser.getRegisteredSafeTags()`. Then you can copy these to your own Map with String keys +and `TextParser.TextFormatterHandler` values. +Then you just use them with `TextParser.parse(String, Map)`. + +Example +``` +String inputString = "Hello World!" + +Map tags = Map.of("red", TextParser.getRegisteredTags().get("red"), + "blue", TextParser.getRegisteredTags().get("yellow") // renames works too! + ); + +Text output = TextParser.parse(inputString, tags); +``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..287f0d0 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,27 @@ +# About Placeholder API +It's a small, jij-able API that allows creation and parsing placeholders within strings and Minecraft Text Components. +Placeholders use simple format of `%modid:type%` or `%modid:type/data%`. +It also includes simple, general usage text format indented for simplifying user input in configs/chats/etc. + +## For users +It allows users to configure multiple mods in similar way without losing compatibility between mods. +Placeholders allow changing what and where any information is present within compatible mods. + +Additionally, Simplified Text Format allows to style them in readable way without requirement of writing JSON manually or using +generators. + +* [Using placeholders](user/general) +* [Default placeholder list](user/default-placeholders) +* [Mod placeholder list](user/mod-placeholders) +* [Simplified Text Format](user/text-format) + +## For developers +Usage of Placeholder API is a simple way to achieve good mod compatibility without having to implement +multiple mod specific apis. Additionally, the placeholder parsing system can be used for replacing +own static (or dynamic placeholders) in Text created by player or read from config. This with combination +of Simplified Text Format allows creating great user/admin experience. + +* [Getting Started](dev/getting-started) +* [Adding placeholders](dev/adding-placeholders) +* [Parsing placeholders](dev/parsing-placeholders) +* [Using Simplified Text Format/TextParser](dev/text-format) diff --git a/docs/user/default-placeholders.md b/docs/user/default-placeholders.md new file mode 100644 index 0000000..102aaf6 --- /dev/null +++ b/docs/user/default-placeholders.md @@ -0,0 +1,52 @@ +# Default placeholder list +These placeholders are provided by default and are available for every mod using Placeholder API. + +## List of placeholders +### Server +- `%server:tps%` - server's tps +- `%server:tps_colored%` - colored server's tps +- `%server:mspt%` - server's mspt +- `%server:mspt_colored%` - colored server's mspt +- `%server:time%`/`%server:time/[formatting]%` - server's time +- `%server:version%` - server's version +- `%server:name%` - server's name +- `%server:used_ram%`/`%server:used_ram/gb%` - amount of ram used by server +- `%server:max_ram%`/`%server:max_ram/gb%` - maximal amount of ram, that can be used by server +- `%server:online%` - number of online players +- `%server:max_players%` - maximal player count + +### Server +- `%world:time%` - world's time +- `%world:time_alt%` - world's time (alternative formatting) +- `%world:day%` - world's day +- `%world:player_count%` - world's player count +- `%world:mob_count%`/`%world:mob_count/[group]%` - Shows amount of spawned mobs +- `%world:mob_cap%`/`%world:mob_cap/[group]%` - Shows maximum amount of mobs that can spawn is player's world +- `%world:id%` - world's id +- `%world:name%` - world's name + +### Player +- `%player:name%` - player's name +- `%player:name_unformatted%` - player's name (without formatting) +- `%player:displayname%` - player's displayname (used on chat) +- `%player:displayname_unformatted%` - player's displayname (without formatting) +- `%player:ping%` - player's ping +- `%player:ping_colored%` - colored player's ping +- `%player:pos_x%` - player's x coordinate +- `%player:pos_y%` - player's y coordinate +- `%player:pos_z%` - player's z coordinate +- `%player:health%` - player's health +- `%player:max_health%` - player's max health +- `%player:hunger%` - player's health +- `%player:saturation%` - player's health +- `%player:playtime%`/`%player:playtime/[formatting]%` - player's playtime +- `%player:statistic/[statistic]%` - value of player's statistic + +Vanilla statistics: +``` +leave_game, play_one_minute, time_since_death, time_since_rest, sneak_time, walk_one_cm, crouch_one_cm, sprint_one_cm, walk_on_water_one_cm, fall_one_cm, climb_one_cm, fly_one_cm, walk_under_water_one_cm, minecart_one_cm, boat_one_cm, pig_one_cm, horse_one_cm, aviate_one_cm, swim_one_cm, strider_one_cm, jump, drop, damage_dealt, damage_dealt_absorbed, damage_dealt_resisted, damage_taken, damage_blocked_by_shield, damage_absorbed, damage_resisted, deaths, mob_kills, animals_bred, player_kills, fish_caught, talked_to_villager, traded_with_villager, eat_cake_slice, fill_cauldron, use_cauldron, clean_armor, clean_banner, clean_shulker_box, interact_with_brewingstand, interact_with_beacon, inspect_dropper, inspect_hopper, inspect_dispenser, play_noteblock, tune_noteblock, pot_flower, trigger_trapped_chest, open_enderchest, enchant_item, play_record, interact_with_furnace, interact_with_crafting_table, open_chest, sleep_in_bed, open_shulker_box, open_barrel, interact_with_blast_furnace, interact_with_smoker, interact_with_lectern, interact_with_campfire, interact_with_cartography_table, interact_with_loom, interact_with_stonecutter, bell_ring, raid_trigger, raid_win, interact_with_anvil, interact_with_grindstone, target_hit, interact_with_smithing_table +``` + + + + diff --git a/docs/user/general.md b/docs/user/general.md new file mode 100644 index 0000000..b909d5c --- /dev/null +++ b/docs/user/general.md @@ -0,0 +1,37 @@ +# Using placeholders + +Usage of placeholder mostly depends on implementation of mod itself. If mod uses simple, one/multiline text +(for example with Simple Text Format) you just need to add it by just writing `%placeholder%` +(or in some cases`{placeholder}`, `${placeholder}` or other format which should be provided on mods page). + +Inner part of placeholder can take shape of either `category:placeholder` or `category:placeholder/argument`, +where `category` is replaced by type (`player`, `world`, etc) or ID of the mod and `placeholder` is the placeholder itself. +Additionally, some placeholders might have additional or required argument provided after first `/` symbol. It's format +fully depend on mod providing it. + +You can check list of [build in placeholders here](/users/default-placeholders) +and [placeholders from mods here](/users/mod-placeholders). + +### List of mods supporting displaying Placeholder API's placeholders: + +- Styled Player List - + [CurseForge](https://www.curseforge.com/minecraft/mc-mods/styled-player-list), + [Modrinth](https://modrinth.com/mod/styledplayerlist), + [Github](https://github.com/Patbox/StyledPlayerList) + +- Styled Chat - + [CurseForge](https://www.curseforge.com/minecraft/mc-mods/styled-chat), + [Modrinth](https://modrinth.com/mod/styled-chat), + [Github](https://github.com/Patbox/StyledChat) + +- Holograms - + [CurseForge](https://www.curseforge.com/minecraft/mc-mods/server-holograms), + [Modrinth](https://modrinth.com/mod/holograms), + [Github](https://github.com/Patbox/Holograms) + +- Player Events - + [CurseForge](https://www.curseforge.com/minecraft/mc-mods/player-events), + [Github](https://github.com/ByMartrixx/player-events) + + +*Are you a mod dev, and your mod is missing? Feel free to create an issue!* \ No newline at end of file diff --git a/docs/user/mod-placeholders.md b/docs/user/mod-placeholders.md new file mode 100644 index 0000000..a062fef --- /dev/null +++ b/docs/user/mod-placeholders.md @@ -0,0 +1,44 @@ +# Mod placeholders list +These placeholders are provided by other mods. Some are build in directly, while others require an addon. + +Box Of Placeholders is a mod that adds placeholder for other mods. +You can download it from https://github.com/Patbox/BoxOfPlaceholders/releases + +## List of placeholders +### [Box of Placeholders](https://www.curseforge.com/minecraft/mc-mods/box-of-placeholders) +- `%bop:ram_max%` / `%bop:ram_max/gb%` - Shows maximal amount of ram +- `%bop:ram_used%` / `%bop:ram_used/gb%` - Shows amount of used ram +- `%bop:ram_free%` / `%bop:ram_free/gb%` - Shows amount of free ram +- `%bop:ram_used_percent%` - Shows amount of used ram (as percent) +- `%bop:ram_free_percent%` - Shows amount of free ram (as percent) +- `%bop:animation/[id]%` - Shows animation based on id +- `%bop:mob_count%`/`%bop:mob_count/[group]%` - Shows amount of spawned mobs +- `%bop:mob_cap%`/`%bop:mob_cap/[group]%` - Shows maximum amount of mobs that can spawn is player's world + +### [Get Off My Lawn](https://www.curseforge.com/minecraft/mc-mods/get-off-my-lawn) +Requires [Box of Placeholders addon](https://www.curseforge.com/minecraft/mc-mods/box-of-placeholders) + +- `%goml:claim_owners%`/`%goml:claim_owners/[No owners text]%` - Returns list of claim owners +- `%goml:claim_owners_uuid%`/`%goml:claim_owners_uuid/[No owners text]%` - Returns list of claim owners (as uuids) +- `%goml:claim_trusted%`/`%goml:claim_trusted/[No trusted text]%` - Returns list of trusted players +- `%goml:claim_trusted_uuid%`/`%goml:claim_trusted_uuid/[No trusted text]%` - Returns list of trusted players (as uuids) +- `%goml:claim_info%`/`%goml:claim_info/[no claim text]/[player can build]/[player can't build]%` - Returns list of trusted players (variables: `${owners}`, `${owners_uuid}`, `${trusted}`, `${trusted_uuid}`, `${anchor}`) + +### [Luckperms](https://luckperms.net/) +Requires [Box of Placeholders addon](https://www.curseforge.com/minecraft/mc-mods/box-of-placeholders) + +- `%luckperms:prefix%` / `%luckperms:prefix/[number]%` - One or more prefixes of player +- `%luckperms:suffix%` / `%luckperms:suffix/[number]%` - One or more suffixes of player +- `%luckperms:prefix_if_in_group/[group]%` - Returns player's prefix, if they are in selected group (otherwise empty) +- `%luckperms:suffix_if_in_group/[group]%` - Returns player's suffix, if they are in selected group (otherwise empty) +- `%luckperms:primary_group%` - Returns player's primary group +- `%luckperms:group_expiry_time/[group]%` - Time after which player's group is removed +- `%luckperms:permission_expiry_time/[permission]%` - Time after which player's permission is removed + +### [Player Pronouns](https://modrinth.com/mod/player-pronouns) +- `%playerpronouns:pronouns%` - Displays players pronouns + +### [Vanish](https://www.curseforge.com/minecraft/mc-mods/vanish) +Requires [Box of Placeholders addon](https://www.curseforge.com/minecraft/mc-mods/box-of-placeholders) +- `%vanish:safe_online%` - Returns (safe) number of player's online +- `%vanish:invisible_player_count%` - Number of player's using vanish diff --git a/docs/user/text-format.md b/docs/user/text-format.md new file mode 100644 index 0000000..9c17df9 --- /dev/null +++ b/docs/user/text-format.md @@ -0,0 +1,131 @@ +# Simplified Text Format + +It's a simple, string format inspired by formats like HTML or MiniMessage. +It was created to allow quick and readable way of formatting Minecraft Text Components +while still providing all of its functionality as opposed for legacy &/§ formatting +used by bukkit and bukkit-based plugins. + +## Structure + +Formatting is build on concept of tags. + +Most of them come in pairs of a starting (``) and closing one (``). +While closing ones are technically optional, without them formatting will continue until end of +an input text or special `` tag. Some tags support arguments, which can be passed by adding `:` +after tag name in starting one (for example ` `). Arguments containing symbols like +`:`, `<`, `>`, `%` and spaces should be wrapped in a `'` symbols (for example `Hello!'>...`). +In case you want to type `` as plain text, you need to prefix it with `\ ` symbol . + +Few examples: + +* `%player:displayname% » ${message}` +* `Hello world!` +* `Some colors for you` + +There are also few self-contained tags, that don't require closing ones. They can also take arguments +in the same way to previous ones. + +Few examples: + +* `` +* `` + + +## List of available tags +Here is list of all default tags available. Other mods can add new or limit usage +of existing ones, so not every might work in yours case. + +### Colors +By default, there are multiple tags representing colors. They use their vanilla name or (additional aliases). +This tag should be closed. + +The current list includes: ``, ``, ``, ``, ``, ``, +``, ``, ``, ``, ``, ``, ``, ``, +``, `` + +There is also a universal `` and `` tags, in which you can replace `[value]` with vanilla color name or +a rgb color starting with `#` (for example ``) + +### Decorations +These tags allow decorating text, they are quite simple. + +This tag should be closed. + +* ``/`` - Makes text strikethrough, +* `` - Underlines text, +* ``/`` - Makes text italic, +* ``/`` - Obfuscates text (matrix effect), +* ``/`` - Makes text bold, + +### Click events +Click events allow making text more interactive. They should be however limited to admin usage only, +as they can do harm if accessible by normal players. + +This tag should be closed. + +There are few available actions: + +* ``/``/`` - Opens provided url +* ``/`` - Runs command as player +* ``/``/`` - Suggests command to player +* ``/``/`` - Copies text to clipboard +* ``/``/`` - Changes page in a book + +`[value]` needs to be replaced with targeted value, for example `'gamemode creative'` + +### Hover +Hover tag allows adding simple hover on text. It can be used to display additional information. +This tag should be closed. + +* ``/`` - Adds simple text hover (`[value]` uses the same formatting as rest) +* `` - Adds simple ItemStack hover (`[value]` is item in sNBT format) +* `` - Adds entity hover (`:` in entity type needs to be replaced with `\:`) + +### Fonts +This tag allows you to change font to any build in one or one provided by resource pack. + +You can use it by simply adding ``, where `[value]` is just a font name. +Minecraft has 3 build-in fonts: `default`, `uniform` and `alt`. + +This tag should be closed. + + +### Inserting +This tag inserts text into players chat message after being clicked. + +You can use it by writing ``, where `[value]` inserted text (should be wrapped in `'`). + +This tag should be closed. + + +### Translations +Translations tag allows you to insert a text from a lang file (including ones parsed on servers by some mods). + +You use it with ``, where `[key]` is a translation key +and `[optional arg X]` are optional, fully formatted arguments you can pass. + +This tag is self containing, so it doesn't contain a closing tag. + +### Control keys +This tag allows you to add information about player control keys, with respecting of theirs configuration. + +You can use it with ``, where `[value]` is a control key used, which you can hind [here](https://minecraft.fandom.com/wiki/Controls#Configurable_controls). + +This tag is self containing, so it doesn't contain a closing tag. + +### Gradients +This tag allows you to add gradients to the text. However, it has multiple limitation that can +block its usage. Currently, you can't use dynamic values (translations, control keys, placeholders, etc) +within them, as they require static text. + +There 2 types of gradients: + +* ``/`` - I can take multiple colors to move between them smoothly. +* ``/`` - It's simple rainbow gradient. All arguments are optional (`` is still valid) and they take numbers between 0 and 1 (`0.3` for example) + + +### Reset +`` and `` are special, self-contained tags which close all previous formatting. They are +useful in cases, where you want to close multiple formatting tags quickly + + diff --git a/gradle.properties b/gradle.properties index 4063194..3ffb50e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,13 +3,13 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://fabricmc.net/use - minecraft_version=1.17 - yarn_mappings=1.17+build.13 + minecraft_version=1.17.1 + yarn_mappings=1.17.1+build.1 loader_version=0.11.3 # Mod Properties - mod_version = 1.0.1+1.17 + mod_version = 1.1.0+1.17.1 maven_group = eu.pb4 archives_base_name = placeholder-api - fabric_version=0.34.8+1.17 + fabric_version=0.36.1+1.17 diff --git a/icon.png b/icon.png index 027f66a..f9667ae 100644 Binary files a/icon.png and b/icon.png differ diff --git a/icon.xcf b/icon.xcf new file mode 100644 index 0000000..75949ea Binary files /dev/null and b/icon.xcf differ diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..729b306 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,51 @@ +site_name: Placeholder API + +repo_url: https://github.com/Patbox/TextPlaceholderAPI +repo_name: Patbox/TextPlaceholderAPI + +nav: + - index.md + - For Users: + - user/general.md + - user/default-placeholders.md + - user/mod-placeholders.md + - user/text-format.md + - For Developers: + - dev/getting-started.md + - dev/adding-placeholders.md + - dev/parsing-placeholders.md + - dev/text-format.md + + +theme: + name: material + logo: assets/logo.png + favicon: images/favicon.png + + palette: + - scheme: slate + primary: teal + accent: teal + features: + - navigation.expand + toggle: + icon: material/weather-sunny + name: Switch to light mode + - scheme: default + primary: teal + accent: teal + features: + - navigation.expand + toggle: + icon: material/weather-night + name: Switch to dark mode + +extra_css: + - assets/style.css + +plugins: + - search +extra: + version: + provider: mike + default: latest \ No newline at end of file diff --git a/src/main/java/eu/pb4/placeholders/PlaceholderAPI.java b/src/main/java/eu/pb4/placeholders/PlaceholderAPI.java index 0b0886a..6bc0ee7 100644 --- a/src/main/java/eu/pb4/placeholders/PlaceholderAPI.java +++ b/src/main/java/eu/pb4/placeholders/PlaceholderAPI.java @@ -1,10 +1,12 @@ package eu.pb4.placeholders; +import com.google.common.collect.ImmutableMap; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import eu.pb4.placeholders.util.PlaceholderUtils; +import net.minecraft.util.InvalidIdentifierException; import java.util.HashMap; import java.util.Map; @@ -109,9 +111,17 @@ public static Text parsePredefinedText(Text text, Pattern pattern, Map getPlaceholders() { + return ImmutableMap.copyOf(PLACEHOLDERS); + } + @Deprecated public static String parseStringAlt(String text, ServerPlayerEntity player) { return PlaceholderUtils.parseString(text, player, ALT_PLACEHOLDER_PATTERN, PLACEHOLDERS); diff --git a/src/main/java/eu/pb4/placeholders/PlaceholderAPIMod.java b/src/main/java/eu/pb4/placeholders/PlaceholderAPIMod.java index 14cc026..05bef00 100644 --- a/src/main/java/eu/pb4/placeholders/PlaceholderAPIMod.java +++ b/src/main/java/eu/pb4/placeholders/PlaceholderAPIMod.java @@ -5,7 +5,9 @@ import eu.pb4.placeholders.builtin.WorldPlaceholders; import eu.pb4.placeholders.util.TextParserUtils; import net.fabricmc.api.ModInitializer; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Internal public class PlaceholderAPIMod implements ModInitializer { @Override public void onInitialize() { diff --git a/src/main/java/eu/pb4/placeholders/PlaceholderHandler.java b/src/main/java/eu/pb4/placeholders/PlaceholderHandler.java index 7c6fb69..6bbae8a 100644 --- a/src/main/java/eu/pb4/placeholders/PlaceholderHandler.java +++ b/src/main/java/eu/pb4/placeholders/PlaceholderHandler.java @@ -1,5 +1,6 @@ package eu.pb4.placeholders; +@FunctionalInterface public interface PlaceholderHandler { PlaceholderResult PlaceholderHandler(PlaceholderContext context); } diff --git a/src/main/java/eu/pb4/placeholders/TextParser.java b/src/main/java/eu/pb4/placeholders/TextParser.java index d7d85fe..48a38a4 100644 --- a/src/main/java/eu/pb4/placeholders/TextParser.java +++ b/src/main/java/eu/pb4/placeholders/TextParser.java @@ -1,16 +1,27 @@ package eu.pb4.placeholders; import com.google.common.collect.ImmutableMap; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; import eu.pb4.placeholders.util.GeneralUtils; import eu.pb4.placeholders.util.TextParserUtils; import net.minecraft.text.MutableText; import net.minecraft.text.Text; +import org.jetbrains.annotations.ApiStatus; import java.util.HashMap; import java.util.Map; public final class TextParser { private static final HashMap TAGS = new HashMap<>(); + private static final HashMap SAFE_TAGS = new HashMap<>(); + + /** + * You can use this codec while reading from files + * It still has limited support for writing, but it won't work correctly + * If you need to write text somewhere, it will be better to use it directly + */ + public static final Codec CODEC = Codec.STRING.comapFlatMap((s) -> DataResult.success(TextParser.parse(s)), TextParserUtils::convertToString); /** * Parses input string and outputs corresponding Text @@ -22,6 +33,17 @@ public static Text parse(String input) { return TextParserUtils.parse(input, TAGS); } + /** + * Parses input string and outputs corresponding Text + * Uses only player input safe tags + * + * @param input with formatting + * @return Text + */ + public static Text parseSafe(String input) { + return TextParserUtils.parse(input, SAFE_TAGS); + } + /** * Parses input string with it's own handler map (ideal for limiting formatting) * @@ -36,7 +58,19 @@ public static Text parse(String input, Map handler /** * Registers new text tag handler */ + @ApiStatus.Internal public static void register(String identifier, TextFormatterHandler handler) { + register(identifier, handler, true); + } + + /** + * Registers new text tag handler + */ + @ApiStatus.Internal + public static void register(String identifier, TextFormatterHandler handler, boolean safe) { + if (safe) { + SAFE_TAGS.put(identifier, handler); + } TAGS.put(identifier, handler); } @@ -47,6 +81,13 @@ public static ImmutableMap getRegisteredTags() { return ImmutableMap.copyOf(TAGS); } + /** + * Returns map of registered safe tags, that can be used for custom player input + */ + public static ImmutableMap getRegisteredSafeTags() { + return ImmutableMap.copyOf(SAFE_TAGS); + } + @FunctionalInterface public interface TextFormatterHandler { GeneralUtils.TextLengthPair parse(String tag, String data, String input, Map handlers, String endAt); diff --git a/src/main/java/eu/pb4/placeholders/util/GeneralUtils.java b/src/main/java/eu/pb4/placeholders/util/GeneralUtils.java index 1c03344..8e92e6d 100644 --- a/src/main/java/eu/pb4/placeholders/util/GeneralUtils.java +++ b/src/main/java/eu/pb4/placeholders/util/GeneralUtils.java @@ -2,9 +2,11 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; import net.minecraft.text.*; +import org.jetbrains.annotations.ApiStatus; import java.util.List; +@ApiStatus.Internal public class GeneralUtils { public static String textToString(Text text) { StringBuffer string = new StringBuffer(text.asString()); @@ -48,7 +50,7 @@ public static MutableText toGradient(Text base, Int2ObjectFunction po private static TextLengthPair recursiveGradient(Text base, Int2ObjectFunction posToColor, int pos) { MutableText out = new LiteralText("").setStyle(base.getStyle()); - for (String letter : base.asString().split("")) { + for (String letter : base.asString().replaceAll("\\p{So}|.", "$0\0").split("\0+")) { if (!letter.isEmpty()) { out.append(new LiteralText(letter).setStyle(Style.EMPTY.withColor(posToColor.apply(pos)))); pos++; diff --git a/src/main/java/eu/pb4/placeholders/util/PlaceholderUtils.java b/src/main/java/eu/pb4/placeholders/util/PlaceholderUtils.java index 0a3d6a8..9d9ed0e 100644 --- a/src/main/java/eu/pb4/placeholders/util/PlaceholderUtils.java +++ b/src/main/java/eu/pb4/placeholders/util/PlaceholderUtils.java @@ -7,6 +7,7 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.*; import net.minecraft.util.Identifier; +import org.jetbrains.annotations.ApiStatus; import java.util.ArrayList; import java.util.List; @@ -14,6 +15,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +@ApiStatus.Internal public class PlaceholderUtils { public static Text recursivePlaceholderParsing(Text text, Object object, Pattern pattern, Map placeholders) { @@ -22,8 +24,7 @@ public static Text recursivePlaceholderParsing(Text text, Object object, Pattern ServerPlayerEntity player = object instanceof ServerPlayerEntity ? (ServerPlayerEntity) object : null; MinecraftServer server = !(object instanceof ServerPlayerEntity) ? (MinecraftServer) object : player.server; - if (text instanceof TranslatableText) { - TranslatableText translatableText = (TranslatableText) text; + if (text instanceof TranslatableText translatableText) { ArrayList list = new ArrayList<>(); for(Object arg : translatableText.getArgs()) { @@ -104,8 +105,7 @@ private static Style parsePlaceholdersInStyle(Style style, Object object, Patter public static Text recursivePredefinedPlaceholderParsing(Text text, Pattern pattern, Map placeholders) { MutableText out = null; - if (text instanceof TranslatableText) { - TranslatableText translatableText = (TranslatableText) text; + if (text instanceof TranslatableText translatableText) { ArrayList list = new ArrayList<>(); for(Object arg : translatableText.getArgs()) { @@ -183,7 +183,7 @@ private static Style parsePredefinedPlaceholdersInStyle(Style style, Pattern pat public static String parseString(String text, Object object, Pattern pattern, Map placeholders) { Matcher matcher = pattern.matcher(text); - StringBuffer out = new StringBuffer(text.length()); + StringBuilder out = new StringBuilder(text.length()); ServerPlayerEntity player = object instanceof ServerPlayerEntity ? (ServerPlayerEntity) object : null; MinecraftServer server = !(object instanceof ServerPlayerEntity) ? (MinecraftServer) object : player.server; @@ -206,7 +206,7 @@ public static String parseString(String text, Object object, Pattern pattern, Ma public static String parseStringPredefined(String text, Pattern pattern, Map placeholders) { Matcher matcher = pattern.matcher(text); - StringBuffer out = new StringBuffer(text.length()); + StringBuilder out = new StringBuilder(text.length()); while (matcher.find()) { String placeholder = matcher.group(1); diff --git a/src/main/java/eu/pb4/placeholders/util/TextParserUtils.java b/src/main/java/eu/pb4/placeholders/util/TextParserUtils.java index a26d87f..684eee8 100644 --- a/src/main/java/eu/pb4/placeholders/util/TextParserUtils.java +++ b/src/main/java/eu/pb4/placeholders/util/TextParserUtils.java @@ -1,7 +1,10 @@ package eu.pb4.placeholders.util; import com.google.common.util.concurrent.AtomicDouble; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import eu.pb4.placeholders.TextParser; +import io.netty.util.internal.UnstableApi; import net.minecraft.entity.EntityType; import net.minecraft.item.ItemStack; import net.minecraft.nbt.StringNbtReader; @@ -9,19 +12,22 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.ApiStatus; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static eu.pb4.placeholders.util.GeneralUtils.TextLengthPair; import static eu.pb4.placeholders.util.GeneralUtils.Pair; +import static eu.pb4.placeholders.util.GeneralUtils.TextLengthPair; +@ApiStatus.Internal public class TextParserUtils { // Based on minimessage's regex, modified to fit more parsers needs public static final Pattern STARTING_PATTERN = Pattern.compile("<(?[^<>/]+)(?(:([']?([^'](\\\\\\\\['])?)+[']?))*)>"); public static final List> ESCAPED_CHARS = new ArrayList<>(); public static final List> UNESCAPED_CHARS = new ArrayList<>(); + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().registerTypeHierarchyAdapter(Style.class, new Style.Serializer()).create(); private static boolean IS_REGISTERED = false; public static Text parse(String string, Map handlers) { @@ -43,6 +49,7 @@ public static String removeEscaping(String string) { try { string = string.replaceAll(entry.right(), entry.left()); } catch (Exception e) { + // Silence! } } return string; @@ -83,7 +90,7 @@ public static TextLengthPair recursiveParsing(String input, Map tag - if (tag.equals("reset")) { + if (tag.equals("reset") || tag.equals("r")) { if (endAt != null) { currentEnd = matcher.start(); if (currentPos < currentEnd) { @@ -111,6 +118,12 @@ public static TextLengthPair recursiveParsing(String input, Map"; TextParser.TextFormatterHandler handler = handlers.get(tag); @@ -183,32 +196,47 @@ public static void register() { IS_REGISTERED = true; } - for (Formatting formatting : Formatting.values()) { - TextParser.register(formatting.getName(), (String tag, String data, String input, Map handlers, String endAt) -> { - TextLengthPair out = recursiveParsing(input, handlers, endAt); - out.text().formatted(formatting); - return out; - }); + { + for (Formatting formatting : Formatting.values()) { + TextParser.register(formatting.getName(), (tag, data, input, handlers, endAt) -> { + TextLengthPair out = recursiveParsing(input, handlers, endAt); + out.text().formatted(formatting); + return out; + }); + } + + var reg = TextParser.getRegisteredTags(); + + TextParser.register("orange", reg.get("gold")); + TextParser.register("grey", reg.get("gray")); + TextParser.register("dark_grey", reg.get("dark_gray")); + TextParser.register("st", reg.get("strikethrough")); + TextParser.register("obf", reg.get("obfuscated")); + TextParser.register("em", reg.get("italic")); + TextParser.register("i", reg.get("italic")); + TextParser.register("b", reg.get("bold")); + TextParser.register("underlined", reg.get("underline")); } { - TextParser.TextFormatterHandler color = (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.TextFormatterHandler color = (tag, data, input, handlers, endAt) -> { TextLengthPair out = recursiveParsing(input, handlers, endAt); out.text().fillStyle(Style.EMPTY.withColor(TextColor.parse(cleanArgument(data)))); return out; }; TextParser.register("color", color); + TextParser.register("colour", color); TextParser.register("c", color); } - TextParser.register("font", (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.register("font", (tag, data, input, handlers, endAt) -> { TextLengthPair out = recursiveParsing(input, handlers, endAt); out.text().fillStyle(Style.EMPTY.withFont(Identifier.tryParse(cleanArgument(data)))); return out; }); - TextParser.register("lang", (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.register("lang", (tag, data, input, handlers, endAt) -> { String[] lines = data.split(":"); if (lines.length > 0) { List textList = new ArrayList<>(); @@ -227,7 +255,7 @@ public static void register() { return TextLengthPair.EMPTY; }); - TextParser.register("key", (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.register("key", (tag, data, input, handlers, endAt) -> { if (!data.isEmpty()) { MutableText out = new KeybindText(cleanArgument(data)); return new TextLengthPair(out, 0); @@ -235,7 +263,7 @@ public static void register() { return TextLengthPair.EMPTY; }); - TextParser.register("click", (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.register("click", (tag, data, input, handlers, endAt) -> { String[] lines = data.split(":", 2); TextLengthPair out = recursiveParsing(input, handlers, endAt); if (lines.length > 1) { @@ -245,15 +273,81 @@ public static void register() { } } return out; - }); + }, false); + + { + TextParser.TextFormatterHandler formatter = (tag, data, input, handlers, endAt) -> { + TextLengthPair out = recursiveParsing(input, handlers, endAt); + if (!data.isEmpty()) { + out.text().setStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, removeEscaping(cleanArgument(data))))); + } + return out; + }; + + TextParser.register("run_command", formatter, false); + TextParser.register("run_cmd", formatter, false); + } + + { + TextParser.TextFormatterHandler formatter = (tag, data, input, handlers, endAt) -> { + TextLengthPair out = recursiveParsing(input, handlers, endAt); + if (!data.isEmpty()) { + out.text().setStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, removeEscaping(cleanArgument(data))))); + } + return out; + }; + + TextParser.register("suggest_command", formatter, false); + TextParser.register("cmd", formatter, false); + } + + { + TextParser.TextFormatterHandler formatter = (tag, data, input, handlers, endAt) -> { + TextLengthPair out = recursiveParsing(input, handlers, endAt); + if (!data.isEmpty()) { + out.text().setStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, removeEscaping(cleanArgument(data))))); + } + return out; + }; + + TextParser.register("open_url", formatter, false); + TextParser.register("url", formatter, false); + } + + { + TextParser.TextFormatterHandler formatter = (tag, data, input, handlers, endAt) -> { + TextLengthPair out = recursiveParsing(input, handlers, endAt); + if (!data.isEmpty()) { + out.text().setStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, removeEscaping(cleanArgument(data))))); + } + return out; + }; - TextParser.register("hover", (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.register("copy_to_clipboard", formatter, false); + TextParser.register("copy", formatter, false); + } + + { + TextParser.TextFormatterHandler formatter = (tag, data, input, handlers, endAt) -> { + TextLengthPair out = recursiveParsing(input, handlers, endAt); + if (!data.isEmpty()) { + out.text().setStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.CHANGE_PAGE, removeEscaping(cleanArgument(data))))); + } + return out; + }; + + TextParser.register("change_page", formatter); + TextParser.register("page", formatter); + } + + TextParser.register("hover", (tag, data, input, handlers, endAt) -> { String[] lines = data.split(":", 2); TextLengthPair out = recursiveParsing(input, handlers, endAt); try { if (lines.length > 1) { HoverEvent.Action action = HoverEvent.Action.byName(cleanArgument(lines[0].toLowerCase(Locale.ROOT))); + if (action == HoverEvent.Action.SHOW_TEXT) { out.text().setStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, parse(removeEscaping(cleanArgument(lines[1])), handlers)))); } else if (action == HoverEvent.Action.SHOW_ENTITY) { @@ -270,21 +364,26 @@ public static void register() { out.text().setStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackContent(ItemStack.fromNbt(StringNbtReader.parse(removeEscaping(cleanArgument(lines[1]))))) ))); + } else { + out.text().setStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, parse(removeEscaping(cleanArgument(data)), handlers)))); } + } else { + out.text().setStyle(Style.EMPTY.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, parse(removeEscaping(cleanArgument(data)), handlers)))); } } catch (Exception e) { + // Shut } return out; }); - TextParser.register("insert", (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.register("insert", (tag, data, input, handlers, endAt) -> { TextLengthPair out = recursiveParsing(input, handlers, endAt); out.text().setStyle(Style.EMPTY.withInsertion(removeEscaping(cleanArgument(data)))); return out; - }); + }, false); { - TextParser.TextFormatterHandler rainbow = (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.TextFormatterHandler rainbow = (tag, data, input, handlers, endAt) -> { String[] val = data.split(":"); float freq = 1; float saturation = 1; @@ -294,18 +393,21 @@ public static void register() { try { freq = Float.parseFloat(val[0]); } catch (Exception e) { + // No u } } if (val.length >= 2) { try { saturation = Float.parseFloat(val[1]); } catch (Exception e) { + // Idc } } if (val.length >= 3) { try { offset = Float.parseFloat(val[2]); } catch (Exception e) { + // Ok float } } @@ -316,8 +418,10 @@ public static void register() { final float finalOffset = offset; final float finalSaturation = saturation; + final int length = flatString.replaceAll("\\p{So}|.", "$0\0").split("\0+").length; + return new TextLengthPair( - GeneralUtils.toGradient(out.text(), (pos) -> TextColor.fromRgb(GeneralUtils.hvsToRgb(((pos * finalFreq) / (flatString.length() + 1) + finalOffset) % 1, finalSaturation, 1))), + GeneralUtils.toGradient(out.text(), (pos) -> TextColor.fromRgb(GeneralUtils.hvsToRgb(((pos * finalFreq) / (length + 1) + finalOffset) % 1, finalSaturation, 1))), out.length()); }; @@ -326,7 +430,7 @@ public static void register() { } { - TextParser.TextFormatterHandler gradient = (String tag, String data, String input, Map handlers, String endAt) -> { + TextParser.TextFormatterHandler gradient = (tag, data, input, handlers, endAt) -> { String[] val = data.split(":"); TextLengthPair out = recursiveParsing(input, handlers, endAt); @@ -345,14 +449,17 @@ public static void register() { textColors.add(textColors.get(0)); } - final double step = ((double) textColors.size() - 1) / flatString.length(); - final float sectionSize = ((float) textColors.size() - 1) / (flatString.length() + 1); + final int length = flatString.replaceAll("\\p{So}|.", "$0\0").split("\0+").length; + + final double step = ((double) textColors.size() - 1) / length; + final float sectionSize = ((float) textColors.size() - 1) / (length + 1); GeneralUtils.HSV hsv = GeneralUtils.rgbToHsv(textColors.get(0).getRgb()); AtomicDouble hue = new AtomicDouble(hsv.h()); AtomicDouble saturation = new AtomicDouble(hsv.s()); AtomicDouble value = new AtomicDouble(hsv.v()); + return new TextLengthPair(GeneralUtils.toGradient(out.text(), (pos) -> { GeneralUtils.HSV colorA = GeneralUtils.rgbToHsv(textColors.get((int) (pos * sectionSize)).getRgb()); GeneralUtils.HSV colorB = GeneralUtils.rgbToHsv(textColors.get((int) (pos * sectionSize) + 1).getRgb()); @@ -396,6 +503,23 @@ public static void register() { TextParser.register("gr", gradient); } + TextParser.register("style", (tag, data, input, handlers, endAt) -> { + TextLengthPair out = recursiveParsing(input, handlers, endAt); + out.text().setStyle(GSON.fromJson(removeEscaping(cleanArgument(data)), Style.class)); + return out; + }, false); + + TextParser.register("raw", (tag, data, input, handlers, endAt) -> new TextLengthPair(Text.Serializer.fromLenientJson(removeEscaping(cleanArgument(data))), 0), false); + + TextParser.register("score", (tag, data, input, handlers, endAt) -> { + String[] lines = data.split(":"); + if (lines.length == 2) { + MutableText out = new ScoreText(removeEscaping(cleanArgument(lines[0])), removeEscaping(cleanArgument(lines[1]))); + return new TextLengthPair(out, 0); + } + return TextLengthPair.EMPTY; + }, false); + ESCAPED_CHARS.add(new Pair<>("\\\\", "&slsh;")); ESCAPED_CHARS.add(new Pair<>("\\<", "<")); ESCAPED_CHARS.add(new Pair<>("\\>", ">")); @@ -411,4 +535,49 @@ public static void register() { UNESCAPED_CHARS.add(new Pair<>(":", ":")); } + // Cursed don't touch this + @ApiStatus.Experimental + @UnstableApi + public static String convertToString(Text text) { + StringBuilder builder = new StringBuilder(); + String style = GSON.toJson(text.getStyle()); + if (style != null && !style.equals("null")) { + builder.append(""); + } + if (text instanceof LiteralText literalText) { + builder.append(escapeCharacters(literalText.asString())); + } else if (text instanceof TranslatableText translatableText) { + List stringList = new ArrayList<>(); + + for (Object arg : translatableText.getArgs()) { + if (arg instanceof Text text1) { + stringList.add("'" + escapeCharacters(convertToString(text1)) + "'"); + } else { + stringList.add("'" + escapeCharacters(arg.toString()) + "'"); + } + } + + if (stringList.size() > 0) { + stringList.add(0, ""); + } + + String additional = String.join(":", stringList); + + builder.append(""); + } else if (text instanceof KeybindText keybindText) { + builder.append(""); + } else { + builder.append(""); + } + + for (Text text1 : text.getSiblings()) { + builder.append(convertToString(text1)); + } + + if (style != null && !style.equals("null")) { + builder.append(""); + } + return builder.toString(); + } + } \ No newline at end of file diff --git a/src/main/resources/assets/icon.png b/src/main/resources/assets/icon.png new file mode 100644 index 0000000..f9667ae Binary files /dev/null and b/src/main/resources/assets/icon.png differ diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e0f044a..ee0d9ad 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -3,16 +3,17 @@ "id": "placeholder-api", "version": "${version}", - "name": "Fabric Placeholder API", - "description": "Simple api that allows mods to create and use universal placeholders!", + "name": "Placeholder API", + "description": "Simple api that allows mods to create and use universal text placeholders!", "authors": [ "Patbox" ], "contact": { "homepage": "https://pb4.eu", - "sources": "https://github.com/Patbox/FabricPlaceholderAPI" + "sources": "https://github.com/Patbox/TextPlaceholderAPI" }, "license": "LGPLv3", + "icon": "assets/icon.png", "environment": "*", "entrypoints": { "main": [ @@ -22,5 +23,10 @@ "depends": { "fabricloader": ">=0.7.4", "minecraft": ">=1.17-beta.1" + }, + "custom": { + "modmenu": { + "badges": [ "library" ] + } } } diff --git a/src/testmod/java/eu/pb4/placeholderstest/TestMod.java b/src/testmod/java/eu/pb4/placeholderstest/TestMod.java index 879d879..c4c41fc 100644 --- a/src/testmod/java/eu/pb4/placeholderstest/TestMod.java +++ b/src/testmod/java/eu/pb4/placeholderstest/TestMod.java @@ -2,6 +2,7 @@ import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; +import eu.pb4.placeholders.util.TextParserUtils; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; import net.minecraft.command.argument.TextArgumentType; @@ -9,7 +10,10 @@ import net.minecraft.server.network.ServerPlayerEntity; import eu.pb4.placeholders.*; import net.minecraft.text.LiteralText; +import net.minecraft.text.Style; import net.minecraft.text.Text; +import net.minecraft.text.TextColor; +import net.minecraft.util.Identifier; import java.util.Map; @@ -69,6 +73,42 @@ private static int test4(CommandContext context) { return 0; } + private static int test5(CommandContext context) { + try { + ServerPlayerEntity player = context.getSource().getPlayer(); + Text text = PlaceholderAPI.parseTextCustom( + TextParser.parse(context.getArgument("text", String.class)), + player, + Map.of(new Identifier("player"), (ctx) -> PlaceholderResult.value(new LiteralText("").append(player.getName()).setStyle(Style.EMPTY.withColor(TextColor.parse(ctx.getArgument()))))), PlaceholderAPI.ALT_PLACEHOLDER_PATTERN_CUSTOM); + + player.sendMessage(new LiteralText(Text.Serializer.toJson(text)), false); + player.sendMessage(text, false); + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + + private static int test6x(CommandContext context) { + try { + ServerPlayerEntity player = context.getSource().getPlayer(); + Text text = PlaceholderAPI.parseTextCustom( + TextParser.parse(context.getArgument("text", String.class)), + player, + Map.of(new Identifier("player"), (ctx) -> PlaceholderResult.value(new LiteralText("").append(player.getName()).setStyle(Style.EMPTY.withColor(TextColor.parse(ctx.getArgument()))))), PlaceholderAPI.ALT_PLACEHOLDER_PATTERN_CUSTOM); + + player.sendMessage(new LiteralText(Text.Serializer.toJson(text)), false); + + // Never use it, pls + player.sendMessage(new LiteralText(TextParserUtils.convertToString(text)), false); + + player.sendMessage(text, false); + } catch (Exception e) { + e.printStackTrace(); + } + return 0; + } + public void onInitialize() { CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { dispatcher.register( @@ -85,6 +125,14 @@ public void onInitialize() { dispatcher.register( literal("test4").then(argument("text", StringArgumentType.greedyString()).executes(TestMod::test4)) ); + + dispatcher.register( + literal("test5").then(argument("text", StringArgumentType.greedyString()).executes(TestMod::test5)) + ); + + dispatcher.register( + literal("test6ohno").then(argument("text", StringArgumentType.greedyString()).executes(TestMod::test6x)) + ); }); }