Skip to content
Open
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
36 changes: 22 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
# HeadIndex
Head Index is an easy to user server-side head database mod for the Fabric Loader.
Head Index provides easy access to over 36,000 heads provided by minecraft-heads.com.
Head Index is an easy-to-use server-side head database mod for the Fabric Loader.
Head Index provides easy access to thousands of heads provided by minecraft-heads.com.
Unlike a plugin, Head Index can also be used in singleplayer without the need for a server.
New heads are added all the time and will be automatically added whenever you restart the server without needing to update.
Head Index also supports setting an item or economy (Common Economy API) cost for survival servers.

Head index now also supports using level points or whole levels as a currency.
Head Index also supports setting an item, xp, or economy (Common Economy API) cost for survival servers.

Head Index is powered by [Minecraft Heads](https://minecraft-heads.com).
To use the mod, you must enter a free [license key](https://minecraft-heads.com/wiki/minecraft-heads/api-v2-for-users) into the config file.

# Usage
### Commands

`/head` - Opens head GUI
- `/head` - Opens head GUI

- `/head menu` - Also opens head GUI

`/head menu` - Also opens head GUI
- `/head search <search>` - Opens search menu

`/head search` <search> - Opens search menu
- `/head player <playername>` - Get a specific player head

### Config
`config/head-index.json`
```json5
```json
{
"permissionLevel": 2, // The default permission level for the commands. Set to 0 to allow all players access
"economyType": "FREE", // The type of economy to use. Set to FREE to disable economy, ITEM to use an item, TAG to use a tag, ECONOMY to use an economy currency, LEVEL to use minecraft levels, or LEVELPOINTS to use level points
"costType": "minecraft:diamond", // The identifier for the item, tag or currency to use for the cost, only needed if economyType is set to ITEM, TAG, or ECONOMY
"costAmount": 1 // The amount of the item, currency or level to use for the cost
"_licenseComment": "Enter your license key below. Register here: https://minecraft-heads.com/wiki/minecraft-heads/api-v2-for-users",
"license": "LICENSE_HERE",
"_permissionComment": "The default permission level for the commands. Set to 0 to allow all players access",
"permissionLevel": 2,
"_economyComment": "The type of economy to use. Set to FREE to disable economy, ITEM to use an item, TAG to use a tag, ECONOMY to use an economy currency, LEVEL to use minecraft levels, or LEVELPOINTS to use level points",
"economyType": "FREE",
"_costComment": "The identifier for the item, tag or currency to use for the cost, only needed if economyType is set to ITEM, TAG, or ECONOMY",
"costType": "minecraft:diamond",
"_costAmountComment": "The amount of the item, currency or level to use for the cost",
"costAmount": 1
}
```

### Permissions - Compatible with LuckPerms, PlayerRoles or any other farbic permission manager
### Permissions - Compatible with LuckPerms, PlayerRoles, or any other farbic permission manager
You can adjust the default permission level in the config file to allow all players access without a permission manager.

`headindex.menu` - Grants /head and /head menu - Defaults to level 2 OP
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ minecraft_version=1.21.10
yarn_mappings=1.21.10+build.2
loader_version=0.17.3
# Mod Properties
mod_version=1.1.13
mod_version=1.2.0
maven_group=us.potatoboy
archives_base_name=headindex
# Dependencies
Expand Down
30 changes: 22 additions & 8 deletions src/main/java/us/potatoboy/headindex/HeadIndex.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package us.potatoboy.headindex;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import eu.pb4.common.economy.api.CommonEconomy;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
Expand All @@ -11,32 +9,38 @@
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import us.potatoboy.headindex.api.Category;
import us.potatoboy.headindex.api.Head;
import us.potatoboy.headindex.api.HeadDatabaseAPI;
import us.potatoboy.headindex.commands.HeadCommand;
import us.potatoboy.headindex.config.HeadIndexConfig;

import java.io.File;
import java.util.Comparator;
import java.util.HashSet;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.CompletableFuture;

public class HeadIndex implements ModInitializer {
public static final String MOD_ID = "headindex";
public static final Logger LOGGER = LogManager.getLogger();
public static HeadIndexConfig config;
public static final HeadDatabaseAPI HEAD_DATABASE = new HeadDatabaseAPI();
public static Multimap<Head.Category, Head> heads = HashMultimap.create();
public static Map<Category, List<Head>> heads = new HashMap<>();

@Override
public void onInitialize() {
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> new HeadCommand(dispatcher));

CompletableFuture.runAsync(() -> heads = HEAD_DATABASE.getHeads());


config = HeadIndexConfig.loadConfig(new File(FabricLoader.getInstance().getConfigDir() + "/head-index.json"));

CompletableFuture.runAsync(() -> heads = HEAD_DATABASE.getHeads());
}

public static void tryPurchase(ServerPlayerEntity player, int amount, Runnable onPurchase) {
Expand Down Expand Up @@ -101,4 +105,14 @@ public static void tryPurchase(ServerPlayerEntity player, int amount, Runnable o
}
}
}

public static Text licenseWarn() {
try {
return Text.translatable("text.headindex.config.license", Text.literal("https://minecraft-heads.com/wiki/minecraft-heads/api-v2-for-users").setStyle(
Style.EMPTY.withColor(Formatting.BLUE).withClickEvent(new ClickEvent.OpenUrl(new URI("https://minecraft-heads.com/wiki/minecraft-heads/api-v2-for-users")))
));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/us/potatoboy/headindex/api/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package us.potatoboy.headindex.api;

import com.google.gson.annotations.SerializedName;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;

public class Category implements Comparable<Category> {
@SerializedName("id")
public int id;

@SerializedName("n")
public String name;

public String getFileName() {
return name.toLowerCase().replaceAll("[^a-z0-9]", "_");
}

public MutableText getDisplayName() {
return Text.literal(name);
}

@Override
public int compareTo(@NotNull Category o) {
return this.id - o.id;
}
}
127 changes: 30 additions & 97 deletions src/main/java/us/potatoboy/headindex/api/Head.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package us.potatoboy.headindex.api;

import com.google.common.collect.ImmutableMultimap;
import com.google.gson.annotations.SerializedName;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
Expand All @@ -9,37 +10,43 @@
import net.minecraft.component.type.ProfileComponent;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.text.MutableText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import us.potatoboy.headindex.HeadIndex;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

public class Head {
public class Head implements Comparable<Head> {
@SerializedName("n")
public final String name;
public final UUID uuid;

@SerializedName("v")
public final String value;
@Nullable
public final String tags;

public Head(@Nullable String name, UUID uuid, String value, @Nullable String tags) {
this.name = name;
this.uuid = uuid;
this.value = value;
this.tags = tags;
}
@SerializedName("i")
private final String uuid;

public Head(UUID uuid, String value) {
@SerializedName("t")
private final List<Integer> tags;

public Head(String uuid, String value) {
this.name = "";
this.uuid = uuid;
this.value = value;
this.tags = null;
this.tags = new ArrayList<>();
}

public String getTagsOrEmpty() {
return tags == null ? "" : tags;
public UUID getUuid() {
return UUID.fromString(uuid);
}

public List<String> getTags() {
return tags.stream().map(HeadIndex.HEAD_DATABASE::getTagName).filter(Objects::nonNull).collect(Collectors.toList());
}

public ItemStack createStack(Text displayName) {
Expand All @@ -48,97 +55,23 @@ public ItemStack createStack(Text displayName) {
stack.set(DataComponentTypes.CUSTOM_NAME, displayName);
}

if (tags != null) {
stack.set(DataComponentTypes.LORE, new LoreComponent(List.of(Text.literal(tags))));
if (tags != null && !tags.isEmpty()) {
stack.set(DataComponentTypes.LORE, new LoreComponent(getTags().stream().map(Text::literal).collect(Collectors.toUnmodifiableList())));
}

var props = new PropertyMap(ImmutableMultimap.of("textures", new Property("textures", value, null)));
var profile = new GameProfile(uuid, "", props);
var profile = new GameProfile(getUuid(), "", props);
stack.set(DataComponentTypes.PROFILE, ProfileComponent.ofStatic(profile));

return stack;
}

public ItemStack createStack() {
return createStack(name != null ? Text.literal(name).setStyle(Style.EMPTY.withItalic(false)) : null);
return createStack(Text.literal(name).setStyle(Style.EMPTY.withItalic(false)));
}

public enum Category {
ALPHABET("alphabet",
new Head(
UUID.fromString("1f961930-4e97-47b7-a5a1-2cc5150f3764"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmMzNWU3MjAyMmUyMjQ5YzlhMTNlNWVkOGE0NTgzNzE3YTYyNjAyNjc3M2Y1NDE2NDQwZDU3M2E5MzhjOTMifX19").createStack()
),
ANIMALS("animals",
new Head(
UUID.fromString("6554e785-2a74-481a-9aac-06fc18620a57"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWQxN2U0OGM5MjUzZTY3NDczM2NlYjdiYzNkYTdmNTIxNTFlNTI4OWQwMjEyYzhmMmRkNzFlNDE2ZTRlZTY1In19fQ=="
).createStack()
),
BLOCKS("blocks",
new Head(
UUID.fromString("795e1ad8-de6d-4edc-a1b5-4e6aad038403"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODQ0OWI5MzE4ZTMzMTU4ZTY0YTQ2YWIwZGUxMjFjM2Q0MDAwMGUzMzMyYzE1NzQ5MzJiM2M4NDlkOGZhMGRjMiJ9fX0="
).createStack()
),
DECORATION("decoration",
new Head(
UUID.fromString("f3244903-0c01-4f8d-bbc2-4b13338c6a10"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmQyYWNiZTVkMmM2MmU0NTViMGQ4ZTY5YzdmNmIwMWJiNjg5NzVmYmZjZmQ5NWMyNzViM2Y5MTYzMTU4NTE5YyJ9fX0="
).createStack()
),
FOOD_DRINKS("food-drinks",
new Head(
UUID.fromString("187ab05d-1d27-450b-bea8-a723fd1d3b4a"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWZiNDhlMmI5NjljNGMxYjg2YzVmODJhMmUyMzc5OWY0YTZmMzFjZTAwOWE1ZjkyYjM5ZjViMjUwNTdiMmRkMCJ9fX0="
).createStack()
),
HUMANS("humans",
new Head(
UUID.fromString("68cd5f2e-01d3-4ac8-882e-2f7ce487b33b"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGNiN2ExNWVkYTFjYmU0N2E4ZDVkN2Y3ODBlODliYmMzNWUwYzE3N2ZjYjljNjQ4MGExMWIwMmNjODE2NWMxYyJ9fX0="
).createStack()
),
HUMANOID("humanoid",
new Head(
UUID.fromString("0d8391c2-1748-4869-8631-935ff2d55e07"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGNhOGVmMjQ1OGEyYjEwMjYwYjg3NTY1NThmNzY3OWJjYjdlZjY5MWQ0MWY1MzRlZmVhMmJhNzUxMDczMTVjYyJ9fX0="
).createStack()
),
MISC("miscellaneous",
new Head(
UUID.fromString("13affe21-698a-4a5e-aff1-ad5183d5f810"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTFlNDJiOGY1MGZlZDgyOGQ0Yjk4MWMyN2NhMTNkMDcxY2U4NjNmNjE1NDBiMjc2MzgyNjZmNzcyZDQxZCJ9fX0="
).createStack()
),
MONSTERS("monsters",
new Head(
UUID.fromString("a1d05a1e-5937-48ad-973f-70b922d025be"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjI2MDJkNWIzNjJhYTE2MzZkMzVhZjIwZmM3MGQyZTc5NDEzMmVhNjRkNjJkMjNmNTVkYjg1MTVhMGM2MTljNyJ9fX0="
).createStack()
),
PLANTS("plants", new Head(
UUID.fromString("6b063c51-34b4-4fcb-be0d-a6aff0783328"),
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTAxOWVjZTEyNWVjNzlmOTUxNzhlMWFmNGRhMmE4Yjk4MmRlNzFlZDQyYzMxY2FjNGIxZDJmNjY1MzU1ZGY1YSJ9fX0="
).createStack());

public final String name;
public final ItemStack icon;

Category(String name, ItemStack icon) {
this.name = name;
this.icon = icon;
}

public ItemStack createStack() {
icon.set(DataComponentTypes.CUSTOM_NAME, getDisplayName()
.setStyle(Style.EMPTY.withItalic(false))
);
return icon;
}

public MutableText getDisplayName() {
return Text.translatable("text.headindex.category." + name);
}
@Override
public int compareTo(@NotNull Head o) {
return this.name.compareTo(o.name);
}
}
Loading