Rewrite races to use a registry

This commit is contained in:
Sollace 2022-08-27 15:07:29 +02:00
parent 4699788472
commit 09e0bcd7b2
25 changed files with 283 additions and 217 deletions

View file

@ -1,42 +1,55 @@
package com.minelittlepony.unicopia;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import com.google.common.base.Strings;
import com.minelittlepony.common.client.gui.sprite.TextureSprite;
import com.minelittlepony.common.client.gui.style.Style;
import com.minelittlepony.unicopia.ability.magic.Affine;
import com.minelittlepony.unicopia.util.Registries;
import net.minecraft.command.argument.RegistryKeyArgumentType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
public final class Race implements Affine {
public static final String DEFAULT_ID = "unicopia:human";
public static final Registry<Race> REGISTRY = Registries.createDefaulted(Unicopia.id("race"), DEFAULT_ID);
public static final RegistryKey<? extends Registry<Race>> REGISTRY_KEY = REGISTRY.getKey();
public static Race register(String name, boolean magic, FlightType flight, boolean earth) {
return register(Unicopia.id(name), magic, flight, earth);
}
public static Race register(Identifier id, boolean magic, FlightType flight, boolean earth) {
return Registry.register(REGISTRY, id, new Race(magic, flight, earth));
}
public static RegistryKeyArgumentType<Race> argument() {
return RegistryKeyArgumentType.registryKey(REGISTRY_KEY);
}
public enum Race implements Affine {
/**
* The default, unset race.
* This is used if there are no other races.
*/
HUMAN(false, FlightType.NONE, false),
EARTH(false, FlightType.NONE, true),
UNICORN(true, FlightType.NONE, false),
PEGASUS(false, FlightType.AVIAN, false),
BAT(false, FlightType.AVIAN, false),
ALICORN(true, FlightType.AVIAN, true),
CHANGELING(false, FlightType.INSECTOID, false);
public static final Race HUMAN = register("human", false, FlightType.NONE, false);
public static final Race EARTH = register("earth", false, FlightType.NONE, true);
public static final Race UNICORN = register("unicorn", true, FlightType.NONE, false);
public static final Race PEGASUS = register("pegasus", false, FlightType.AVIAN, false);
public static final Race BAT = register("bat", false, FlightType.AVIAN, false);
public static final Race ALICORN = register("alicorn", true, FlightType.AVIAN, true);
public static final Race CHANGELING = register("changeling", false, FlightType.INSECTOID, false);
public static void bootstrap() {}
private final boolean magic;
private final FlightType flight;
private final boolean earth;
private final static Map<Integer, Race> REGISTRY = Arrays.stream(values()).collect(Collectors.toMap(Enum::ordinal, Function.identity()));
Race(boolean magic, FlightType flight, boolean earth) {
this.magic = magic;
this.flight = flight;
@ -93,7 +106,8 @@ public enum Race implements Affine {
}
public String getTranslationKey() {
return String.format("unicopia.race.%s", name().toLowerCase());
Identifier id = REGISTRY.getId(this);
return String.format("%s.race.%s", id.getNamespace(), id.getPath().toLowerCase());
}
public boolean isPermitted(@Nullable PlayerEntity sender) {
@ -120,45 +134,26 @@ public enum Race implements Affine {
return this;
}
public Style createStyle() {
return new Style()
.setIcon(new TextureSprite()
.setPosition(2, 2)
.setSize(16, 16)
.setTexture(new Identifier("unicopia", "textures/gui/icons.png"))
.setTextureOffset((16 * ordinal()) % 256, (ordinal() / 256) * 16)
)
.setTooltip(getTranslationKey(), 0, 10);
}
public boolean equals(String s) {
return name().equalsIgnoreCase(s)
return REGISTRY.getId(this).toString().equalsIgnoreCase(s)
|| getTranslationKey().equalsIgnoreCase(s);
}
public static Race fromName(String s, Race def) {
if (!Strings.isNullOrEmpty(s)) {
for (Race i : values()) {
if (i.equals(s)) return i;
Identifier id = Identifier.tryParse(s);
if (id != null) {
if (id.getNamespace() == Identifier.DEFAULT_NAMESPACE) {
id = new Identifier(Unicopia.DEFAULT_NAMESPACE, id.getPath());
}
return REGISTRY.getOrEmpty(id).orElse(def);
}
}
try {
return fromId(Integer.parseInt(s));
} catch (NumberFormatException e) { }
return def;
}
public static Collection<Race> all() {
return REGISTRY.values();
}
public static Race fromName(String name) {
return fromName(name, EARTH);
}
public static Race fromId(int id) {
return REGISTRY.getOrDefault(id, EARTH);
}
}

View file

@ -4,6 +4,7 @@ import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import java.util.Optional;
@ -23,7 +24,7 @@ import com.minelittlepony.unicopia.item.enchantment.UEnchantments;
import com.minelittlepony.unicopia.network.Channel;
public class Unicopia implements ModInitializer {
public static final String DEFAULT_NAMESPACE = "unicopia";
public static final Logger LOGGER = LogManager.getLogger();
public static SidedAccess SIDE = Optional::empty;
@ -37,6 +38,10 @@ public class Unicopia implements ModInitializer {
return CONFIG;
}
public static Identifier id(String name) {
return new Identifier(DEFAULT_NAMESPACE, name);
}
public Unicopia() {
getConfig();
}

View file

@ -14,7 +14,7 @@ public class WorldTribeManager extends PersistentState {
public WorldTribeManager() {}
public WorldTribeManager(NbtCompound nbt) {
defaultRace = Race.fromName(nbt.getString("defaultRace"));
defaultRace = Race.fromName(nbt.getString("defaultRace"), Race.HUMAN);
}
public Race getDefaultRace() {
@ -27,7 +27,7 @@ public class WorldTribeManager extends PersistentState {
@Override
public NbtCompound writeNbt(NbtCompound tag) {
tag.putString("defaultRace", defaultRace.name());
tag.putString("defaultRace", Race.REGISTRY.getId(defaultRace).toString());
return tag;
}

View file

@ -36,7 +36,7 @@ public class CustomEventCriterion extends AbstractCriterion<CustomEventCriterion
if (json.has("race")) {
json.get("race").getAsJsonArray().forEach(el -> {
races.add(Race.fromName(el.getAsString()));
races.add(Race.fromName(el.getAsString(), Race.EARTH));
});
}
@ -93,7 +93,7 @@ public class CustomEventCriterion extends AbstractCriterion<CustomEventCriterion
json.addProperty("event", event);
if (!races.isEmpty()) {
JsonArray arr = new JsonArray();
races.forEach(r -> arr.add(r.name().toLowerCase()));
races.forEach(r -> arr.add(Race.REGISTRY.getId(r).toString()));
json.add("race", arr);
}
if (flying != null) {

View file

@ -25,7 +25,7 @@ public class RaceChangeCriterion extends AbstractCriterion<RaceChangeCriterion.C
@Override
protected Conditions conditionsFromJson(JsonObject json, Extended playerPredicate, AdvancementEntityPredicateDeserializer deserializer) {
return new Conditions(playerPredicate, Race.fromName(JsonHelper.getString(json, "race")));
return new Conditions(playerPredicate, Race.fromName(JsonHelper.getString(json, "race"), Race.EARTH));
}
public void trigger(PlayerEntity player) {
@ -49,7 +49,7 @@ public class RaceChangeCriterion extends AbstractCriterion<RaceChangeCriterion.C
@Override
public JsonObject toJson(AdvancementEntityPredicateSerializer serializer) {
JsonObject json = super.toJson(serializer);
json.addProperty("race", race.name().toLowerCase());
json.addProperty("race", Race.REGISTRY.getId(race).toString());
return json;
}

View file

@ -11,16 +11,19 @@ import com.minelittlepony.common.client.gui.element.Label;
import com.minelittlepony.common.client.gui.element.Toggle;
import com.minelittlepony.common.client.gui.packing.GridPacker;
import com.minelittlepony.common.client.gui.sprite.TextureSprite;
import com.minelittlepony.common.client.gui.style.Style;
import com.minelittlepony.unicopia.Config;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.util.RegistryIndexer;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.Identifier;
public class LanSettingsScreen extends GameGui {
@ -82,7 +85,7 @@ public class LanSettingsScreen extends GameGui {
boolean whitelistEnabled = (forceShowWhitelist || !whitelist.isEmpty()) && !forceHideWhitelist;
if (whitelist.isEmpty() && forceShowWhitelist) {
for (Race r : Race.all()) {
for (Race r : Race.REGISTRY) {
if (!r.isDefault()) {
whitelist.add(r);
}
@ -106,7 +109,7 @@ public class LanSettingsScreen extends GameGui {
row += 20;
WHITELIST_GRID_PACKER.start();
for (Race race : Race.all()) {
for (Race race : Race.REGISTRY) {
if (!race.isDefault()) {
Bounds bound = WHITELIST_GRID_PACKER.next();
@ -120,7 +123,7 @@ public class LanSettingsScreen extends GameGui {
return v;
})
.setEnabled(canEditWhitelist)
.setStyle(race.createStyle());
.setStyle(createStyle(race));
((TextureSprite)button.getStyle().getIcon()).setPosition(-20, 0);
}
@ -143,7 +146,20 @@ public class LanSettingsScreen extends GameGui {
config.save();
}
public static Style createStyle(Race race) {
int ordinal = Race.REGISTRY.getRawId(race);
return new Style()
.setIcon(new TextureSprite()
.setPosition(2, 2)
.setSize(16, 16)
.setTexture(new Identifier("unicopia", "textures/gui/icons.png"))
.setTextureOffset((16 * ordinal) % 256, (ordinal / 256) * 16)
)
.setTooltip(race.getTranslationKey(), 0, 10);
}
public static Cycler createRaceSelector(Screen screen) {
RegistryIndexer<Race> races = RegistryIndexer.of(Race.REGISTRY);
return new Cycler(screen.width / 2 + 110, 60, 20, 20) {
@Override
protected void renderForground(MatrixStack matrices, MinecraftClient mc, int mouseX, int mouseY, int foreColor) {
@ -152,18 +168,11 @@ public class LanSettingsScreen extends GameGui {
renderToolTip(matrices, screen, mouseX, mouseY);
}
}
}.setStyles(
Race.EARTH.createStyle(),
Race.UNICORN.createStyle(),
Race.PEGASUS.createStyle(),
Race.BAT.createStyle(),
Race.ALICORN.createStyle(),
Race.CHANGELING.createStyle()
).onChange(i -> {
Unicopia.getConfig().preferredRace.set(Race.fromId(i + 1));
}.setStyles(Race.REGISTRY.stream().map(LanSettingsScreen::createStyle).toArray(Style[]::new)).onChange(i -> {
Unicopia.getConfig().preferredRace.set(races.valueOf(i));
Unicopia.getConfig().save();
return i;
}).setValue(MathHelper.clamp(Unicopia.getConfig().preferredRace.get().ordinal() - 1, 0, 5));
}).setValue(races.indexOf(Unicopia.getConfig().preferredRace.get()));
}
}

View file

@ -4,15 +4,11 @@ import org.jetbrains.annotations.Nullable;
import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.common.client.gui.ScrollContainer;
import com.minelittlepony.common.client.gui.element.Button;
import com.minelittlepony.common.client.gui.element.EnumSlider;
import com.minelittlepony.common.client.gui.element.Label;
import com.minelittlepony.common.client.gui.element.Toggle;
import com.minelittlepony.common.client.gui.element.*;
import com.minelittlepony.common.client.gui.style.Style;
import com.minelittlepony.unicopia.Config;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.WorldTribeManager;
import com.minelittlepony.unicopia.*;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPConnector;
import com.minelittlepony.unicopia.util.RegistryIndexer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
@ -73,9 +69,11 @@ public class SettingsScreen extends GameGui {
mineLpStatus = content.addButton(new Label(LEFT, row += 10)).getStyle().setText(getMineLPStatus());
content.addButton(new EnumSlider<>(LEFT, row += 25, config.preferredRace.get()))
.onChange(config.preferredRace::set)
.setTextFormat(v -> Text.translatable("unicopia.options.preferred_race", v.getValue().getDisplayName()));
RegistryIndexer<Race> races = RegistryIndexer.of(Race.REGISTRY);
content.addButton(new Slider(LEFT, row += 25, 0, races.size(), races.indexOf(config.preferredRace.get())))
.onChange(races.createSetter(config.preferredRace::set))
.setTextFormat(v -> Text.translatable("unicopia.options.preferred_race", races.valueOf(v.getValue()).getDisplayName()));
IntegratedServer server = client.getServer();
if (server != null) {
@ -84,9 +82,9 @@ public class SettingsScreen extends GameGui {
WorldTribeManager tribes = WorldTribeManager.forWorld((ServerWorld)server.getPlayerManager().getPlayer(MinecraftClient.getInstance().player.getUuid()).world);
content.addButton(new EnumSlider<>(LEFT, row += 20, tribes.getDefaultRace()))
.onChange(tribes::setDefaultRace)
.setTextFormat(v -> Text.translatable("unicopia.options.world.default_race", v.getValue().getDisplayName()))
content.addButton(new Slider(LEFT, row += 20, 0, races.size(), races.indexOf(tribes.getDefaultRace())))
.onChange(races.createSetter(tribes::setDefaultRace))
.setTextFormat(v -> Text.translatable("unicopia.options.world.default_race", races.valueOf(v.getValue()).getDisplayName()))
.setEnabled(client.isInSingleplayer());
}
}

View file

@ -10,6 +10,7 @@ import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
public class TribeButton extends Button {
@ -23,6 +24,7 @@ public class TribeButton extends Button {
this.race = race;
int size = 32;
int textureSize = 512;
int ordinal = Race.REGISTRY.getRawId(race);
getStyle()
.setIcon(new TextureSprite()
@ -30,7 +32,7 @@ public class TribeButton extends Button {
.setSize(size, size)
.setTextureSize(textureSize, textureSize)
.setTexture(TribeSelectionScreen.ICONS)
.setTextureOffset((size * race.ordinal()) % textureSize, (race.ordinal() / textureSize) * size)
.setTextureOffset((size * ordinal) % textureSize, (ordinal / textureSize) * size)
)
.setText(race.getTranslationKey());
}
@ -54,7 +56,8 @@ public class TribeButton extends Button {
drawTexture(matrices, x - 4, y - 14, 76, 0, 78, 71);
if (hovered && screenWidth > 0) {
drawCenteredText(matrices, getFont(), Text.translatable("gui.unicopia.tribe_selection.describe." + race.name().toLowerCase()), screenWidth / 2, y + height, 0xFFFFFFFF);
Identifier id = Race.REGISTRY.getId(race);
drawCenteredText(matrices, getFont(), Text.translatable("gui.unicopia.tribe_selection.describe." + id.getNamespace() + "." + id.getPath()), screenWidth / 2, y + height, 0xFFFFFFFF);
}
}

View file

@ -11,8 +11,7 @@ import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Language;
import net.minecraft.util.*;
public class TribeConfirmationScreen extends GameGui implements HidesHud {
private final Race selection;
@ -53,8 +52,10 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
int maxWidth = 280;
Identifier id = Race.REGISTRY.getId(selection);
for (int i = 0; i < 5; i++) {
String key = String.format("gui.unicopia.tribe_selection.confirm.goods.%d.%s", i, selection.name().toLowerCase());
String key = String.format("gui.unicopia.tribe_selection.confirm.goods.%d.%s.%s", i, id.getNamespace(), id.getPath());
if (Language.getInstance().hasTranslation(key)) {
TextBlock block = addDrawable(new TextBlock(left, top, maxWidth));
block.getStyle().setText(Text.translatable(key));
@ -67,7 +68,7 @@ public class TribeConfirmationScreen extends GameGui implements HidesHud {
top += 15;
for (int i = 0; i < 5; i++) {
String key = String.format("gui.unicopia.tribe_selection.confirm.bads.%d.%s", i, selection.name().toLowerCase());
String key = String.format("gui.unicopia.tribe_selection.confirm.bads.%d.%s.%s", i, id.getNamespace(), id.getPath());
if (Language.getInstance().hasTranslation(key)) {
TextBlock block = addDrawable(new TextBlock(left, top, maxWidth));
block.getStyle().setText(Text.translatable(key));

View file

@ -53,7 +53,7 @@ public class TribeSelectionScreen extends GameGui implements HidesHud {
final int itemWidth = 70;
List<Race> options = Race.all().stream().filter(race -> !race.isDefault() && !race.isOp()).toList();
List<Race> options = Race.REGISTRY.stream().filter(race -> !race.isDefault() && !race.isOp()).toList();
int totalWidth = options.size() * (itemWidth + 10) - 10;

View file

@ -1,15 +1,21 @@
package com.minelittlepony.unicopia.command;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import com.minelittlepony.unicopia.Unicopia;
import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Identifier;
public class Commands {
@SuppressWarnings({ "deprecation", "unchecked", "rawtypes" })
public static void bootstrap() {
ArgumentTypeRegistry.registerArgumentType(new Identifier("unicopia", "enumeration"), EnumArgumentType.class, new EnumArgumentType.Serializer());
ArgumentTypeRegistry.registerArgumentType(
Unicopia.id("enumeration"),
EnumArgumentType.class,
new EnumArgumentType.Serializer()
);
CommandRegistrationCallback.EVENT.register((dispatcher, access, environment) -> {
SpeciesCommand.register(dispatcher);
RacelistCommand.register(dispatcher);

View file

@ -11,7 +11,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import com.google.common.base.Strings;
import com.google.gson.JsonObject;
import com.minelittlepony.unicopia.Race;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
@ -29,12 +28,6 @@ import net.minecraft.network.PacketByteBuf;
class EnumArgumentType<T extends Enum<T>> implements ArgumentType<T>, Serializable {
private static final long serialVersionUID = 3731493854867412243L;
private static final EnumArgumentType<Race> RACE = of(Race.class, Race::isUsable, Race.EARTH);
public static EnumArgumentType<Race> race() {
return RACE;
}
public static <T extends Enum<T>> EnumArgumentType<T> of(Class<T> type, Predicate<T> filter, T def) {
return new EnumArgumentType<>(type, filter, def);
}

View file

@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.Unicopia;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.command.argument.RegistryKeyArgumentType;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
@ -18,7 +19,7 @@ class RacelistCommand {
static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
LiteralArgumentBuilder<ServerCommandSource> builder = CommandManager.literal("racelist").requires(s -> s.hasPermissionLevel(4));
EnumArgumentType<Race> raceArgument = EnumArgumentType.of(Race.class, Race::isUsable, Race.EARTH);
RegistryKeyArgumentType<Race> raceArgument = Race.argument();
builder.then(CommandManager.literal("allow")
.then(CommandManager.argument("race", raceArgument)
@ -51,7 +52,7 @@ class RacelistCommand {
translationKey += ".failed";
}
Text formattedName = Text.translatable(race.name().toLowerCase()).formatted(Formatting.GOLD);
Text formattedName = race.getDisplayName().copy().formatted(Formatting.GOLD);
source.sendFeedback(Text.translatable(translationKey, formattedName).formatted(Formatting.GREEN), false);
return 0;

View file

@ -6,6 +6,7 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.command.argument.EntityArgumentType;
import net.minecraft.command.argument.RegistryKeyArgumentType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
@ -18,7 +19,7 @@ class SpeciesCommand {
static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
LiteralArgumentBuilder<ServerCommandSource> builder = CommandManager.literal("race");
EnumArgumentType<Race> raceArgument = EnumArgumentType.of(Race.class, Race::isUsable, Race.EARTH);
RegistryKeyArgumentType<Race> raceArgument = Race.argument();
builder.then(CommandManager.literal("get")
.executes(context -> get(context.getSource(), context.getSource().getPlayer(), true))
@ -52,15 +53,13 @@ class SpeciesCommand {
pony.setSpecies(race);
pony.setDirty();
Text formattedName = Text.translatable(race.name().toLowerCase());
if (!isSelf) {
source.sendFeedback(Text.translatable("commands.race.success.other", player.getName(), formattedName), true);
source.sendFeedback(Text.translatable("commands.race.success.other", player.getName(), race.getDisplayName()), true);
} else {
if (player.getEntityWorld().getGameRules().getBoolean(GameRules.SEND_COMMAND_FEEDBACK)) {
player.sendMessage(Text.translatable("commands.race.success.self"), false);
}
source.sendFeedback(Text.translatable("commands.race.success.otherself", player.getName(), formattedName), true);
source.sendFeedback(Text.translatable("commands.race.success.otherself", player.getName(), race.getDisplayName()), true);
}
} else if (player.getEntityWorld().getGameRules().getBoolean(GameRules.SEND_COMMAND_FEEDBACK)) {
player.sendMessage(Text.translatable("commands.race.permission"), false);
@ -88,9 +87,10 @@ class SpeciesCommand {
MutableText message = Text.literal("");
boolean first = true;
for (Race i : Race.values()) {
for (Race i : Race.REGISTRY) {
if (!i.isDefault() && i.isPermitted(player)) {
message.append(Text.translatable((!first ? "\n" : "") + " - " + i.name().toLowerCase()));
message.append(Text.literal((!first ? "\n" : "") + " - "));
message.append(i.getDisplayName());
first = false;
}
}
@ -101,7 +101,7 @@ class SpeciesCommand {
}
static int describe(PlayerEntity player, Race species) {
String name = species.name().toLowerCase();
String name = species.getTranslationKey();
player.sendMessage(Text.translatable(String.format("commands.race.describe.%s.1", name)).styled(s -> s.withColor(Formatting.YELLOW)), false);
player.sendMessage(Text.translatable(String.format("commands.race.describe.%s.2", name)), false);

View file

@ -21,6 +21,7 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.ActionResult;
@ -28,7 +29,7 @@ import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.random.Random;
public class ItemImpl implements Equine<ItemEntity>, Owned<ItemEntity> {
private static final TrackedData<Integer> ITEM_RACE = DataTracker.registerData(ItemEntity.class, TrackedDataHandlerRegistry.INTEGER);
private static final TrackedData<String> ITEM_RACE = DataTracker.registerData(ItemEntity.class, TrackedDataHandlerRegistry.STRING);
static final TrackedData<Float> ITEM_GRAVITY = DataTracker.registerData(ItemEntity.class, TrackedDataHandlerRegistry.FLOAT);
private final ItemEntity owner;
@ -41,7 +42,7 @@ public class ItemImpl implements Equine<ItemEntity>, Owned<ItemEntity> {
this.owner = owner;
this.physics = new ItemPhysics(owner);
owner.getDataTracker().startTracking(ITEM_GRAVITY, 1F);
owner.getDataTracker().startTracking(ITEM_RACE, Race.HUMAN.ordinal());
owner.getDataTracker().startTracking(ITEM_RACE, Race.REGISTRY.getId(Race.HUMAN).toString());
}
@Override
@ -135,23 +136,25 @@ public class ItemImpl implements Equine<ItemEntity>, Owned<ItemEntity> {
@Override
public Race getSpecies() {
return Race.fromId(owner.getDataTracker().get(ITEM_RACE));
return Race.fromName(owner.getDataTracker().get(ITEM_RACE), Race.HUMAN);
}
@Override
public void setSpecies(Race race) {
owner.getDataTracker().set(ITEM_RACE, race.ordinal());
owner.getDataTracker().set(ITEM_RACE, Race.REGISTRY.getId(race).toString());
}
@Override
public void toNBT(NbtCompound compound) {
compound.putString("owner_species", getSpecies().name());
compound.putString("owner_race", Race.REGISTRY.getId(getSpecies()).toString());
physics.toNBT(compound);
}
@Override
public void fromNBT(NbtCompound compound) {
setSpecies(Race.fromName(compound.getString("owner_species")));
if (compound.contains("owner_race", NbtElement.STRING_TYPE)) {
setSpecies(Race.fromName(compound.getString("owner_race"), Race.HUMAN));
}
physics.fromNBT(compound);
}

View file

@ -25,23 +25,29 @@ public class RaceChangeStatusEffect extends StatusEffect {
public static final int STAGE_DURATION = 50;
public static final int MAX_DURATION = Stage.VALUES.length * STAGE_DURATION + 1;
public static final RaceChangeStatusEffect CHANGE_RACE_EARTH = new RaceChangeStatusEffect(0x886F0F, Race.EARTH);
public static final RaceChangeStatusEffect CHANGE_RACE_UNICORN = new RaceChangeStatusEffect(0x88FFFF, Race.UNICORN);
public static final RaceChangeStatusEffect CHANGE_RACE_PEGASUS = new RaceChangeStatusEffect(0x00FFFF, Race.PEGASUS);
public static final RaceChangeStatusEffect CHANGE_RACE_BAT = new RaceChangeStatusEffect(0x0FFF00, Race.BAT);
public static final RaceChangeStatusEffect CHANGE_RACE_CHANGELING = new RaceChangeStatusEffect(0xFFFF00, Race.CHANGELING);
public static final StatusEffect CHANGE_RACE_EARTH = register(0x886F0F, Race.EARTH);
public static final StatusEffect CHANGE_RACE_UNICORN = register(0x88FFFF, Race.UNICORN);
public static final StatusEffect CHANGE_RACE_PEGASUS = register(0x00FFFF, Race.PEGASUS);
public static final StatusEffect CHANGE_RACE_BAT = register(0x0FFF00, Race.BAT);
public static final StatusEffect CHANGE_RACE_CHANGELING = register(0xFFFF00, Race.CHANGELING);
private final Race species;
private final Race race;
protected RaceChangeStatusEffect(int color, Race species) {
public static StatusEffect register(int color, Race race) {
Identifier id = Race.REGISTRY.getId(race);
return Registry.register(Registry.STATUS_EFFECT,
new Identifier(id.getNamespace(), "change_race_" + id.getPath().toLowerCase()),
new RaceChangeStatusEffect(color, race)
);
}
public RaceChangeStatusEffect(int color, Race race) {
super(StatusEffectCategory.NEUTRAL, color);
this.species = species;
Registry.register(Registry.STATUS_EFFECT, new Identifier("unicopia", "change_race_" + species.name().toLowerCase()), this);
this.race = race;
}
public Race getSpecies() {
return species;
return race;
}
@Override
@ -68,16 +74,16 @@ public class RaceChangeStatusEffect extends StatusEffect {
int progression = ticks % (stage.ordinal() * STAGE_DURATION);
if (eq.getSpecies() == species || !species.isPermitted(entity instanceof PlayerEntity ? (PlayerEntity)entity : null)) {
if (eq.getSpecies() == race || !race.isPermitted(entity instanceof PlayerEntity ? (PlayerEntity)entity : null)) {
if (progression == 0 && entity instanceof PlayerEntity && stage == Stage.CRAWLING) {
((PlayerEntity)entity).sendMessage(Stage.INITIAL.getMessage(species), true);
((PlayerEntity)entity).sendMessage(Stage.INITIAL.getMessage(race), true);
}
return;
}
if (progression == 0) {
if (stage != Stage.DEATH && entity instanceof PlayerEntity) {
((PlayerEntity)entity).sendMessage(stage.getMessage(species), true);
((PlayerEntity)entity).sendMessage(stage.getMessage(race), true);
}
float hitAmount = entity.getHealth() / 2;
@ -101,7 +107,7 @@ public class RaceChangeStatusEffect extends StatusEffect {
if (stage == Stage.DEATH) {
eq.setSpecies(species);
eq.setSpecies(race);
if (eq instanceof Caster) {
((Caster<?>)eq).getSpellSlot().clear();
}

View file

@ -6,17 +6,11 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
public interface UPotions {
Potion TRIBE_SWAP_EARTH_PONY = registerRacePotion(RaceChangeStatusEffect.CHANGE_RACE_EARTH);
Potion TRIBE_SWAP_UNICORN = registerRacePotion(RaceChangeStatusEffect.CHANGE_RACE_UNICORN);
Potion TRIBE_SWAP_PEGASUS = registerRacePotion(RaceChangeStatusEffect.CHANGE_RACE_PEGASUS);
Potion TRIBE_SWAP_BAT = registerRacePotion(RaceChangeStatusEffect.CHANGE_RACE_BAT);
Potion TRIBE_SWAP_CHANGELING = registerRacePotion(RaceChangeStatusEffect.CHANGE_RACE_CHANGELING);
static Potion registerRacePotion(RaceChangeStatusEffect effect) {
String name = "tribe_swap_" + effect.getSpecies().name().toLowerCase();
return register(name, new Potion("unicopia." + name,
new StatusEffectInstance(effect, RaceChangeStatusEffect.MAX_DURATION)));
}
Potion TRIBE_SWAP_EARTH_PONY = register("tribe_swap_earth", new Potion("unicopia.tribe_swap_earth", new StatusEffectInstance(RaceChangeStatusEffect.CHANGE_RACE_EARTH, RaceChangeStatusEffect.MAX_DURATION)));
Potion TRIBE_SWAP_UNICORN = register("tribe_swap_unicorn", new Potion("unicopia.tribe_swap_unicorn", new StatusEffectInstance(RaceChangeStatusEffect.CHANGE_RACE_UNICORN, RaceChangeStatusEffect.MAX_DURATION)));
Potion TRIBE_SWAP_PEGASUS = register("tribe_swap_pegasus", new Potion("unicopia.tribe_swap_pegasus", new StatusEffectInstance(RaceChangeStatusEffect.CHANGE_RACE_PEGASUS, RaceChangeStatusEffect.MAX_DURATION)));
Potion TRIBE_SWAP_BAT = register("tribe_swap_bat", new Potion("unicopia.tribe_swap_bat", new StatusEffectInstance(RaceChangeStatusEffect.CHANGE_RACE_BAT, RaceChangeStatusEffect.MAX_DURATION)));
Potion TRIBE_SWAP_CHANGELING = register("tribe_swap_changeling", new Potion("unicopia.tribe_swap_changeling", new StatusEffectInstance(RaceChangeStatusEffect.CHANGE_RACE_CHANGELING, RaceChangeStatusEffect.MAX_DURATION)));
static Potion register(String name, Potion potion) {
return Registry.register(Registry.POTION, new Identifier("unicopia", name), potion);

View file

@ -69,7 +69,7 @@ import net.minecraft.util.math.Direction;
public class Pony extends Living<PlayerEntity> implements Transmittable, Copieable<Pony>, UpdateCallback {
private static final TrackedData<Integer> RACE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER);
private static final TrackedData<String> RACE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.STRING);
static final TrackedData<Float> ENERGY = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT);
static final TrackedData<Float> EXHAUSTION = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT);
@ -121,7 +121,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
this.levels = new PlayerLevelStore(this);
this.tickers = Lists.newArrayList(gravity, mana, attributes, charms);
player.getDataTracker().startTracking(RACE, Race.HUMAN.ordinal());
player.getDataTracker().startTracking(RACE, Race.DEFAULT_ID);
}
public static void registerAttributes(DefaultAttributeContainer.Builder builder) {
@ -171,7 +171,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
return Race.HUMAN;
}
return Race.fromId(getMaster().getDataTracker().get(RACE));
return Race.fromName(getMaster().getDataTracker().get(RACE), Race.HUMAN);
}
@Override
@ -179,7 +179,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
race = race.validate(entity);
speciesSet = true;
ticksInSun = 0;
entity.getDataTracker().set(RACE, race.ordinal());
entity.getDataTracker().set(RACE, Race.REGISTRY.getId(race).toString());
gravity.updateFlightState();
entity.sendAbilitiesUpdate();
@ -522,7 +522,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
@Override
public void toNBT(NbtCompound compound) {
super.toNBT(compound);
compound.putString("playerSpecies", getSpecies().name());
compound.putString("playerSpecies", Race.REGISTRY.getId(getSpecies()).toString());
compound.putFloat("magicExhaustion", magicExhaustion);
@ -546,7 +546,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
public void fromNBT(NbtCompound compound) {
super.fromNBT(compound);
speciesPersisted = true;
setSpecies(Race.fromName(compound.getString("playerSpecies")));
setSpecies(Race.fromName(compound.getString("playerSpecies"), Race.HUMAN));
powers.fromNBT(compound.getCompound("powers"));
gravity.fromNBT(compound.getCompound("gravity"));

View file

@ -1,8 +1,6 @@
package com.minelittlepony.unicopia.item.toxin;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.UTags;
@ -74,7 +72,7 @@ public class Toxic {
public static class Builder {
private final Ailment def;
private final Map<Race, Ailment> ailments = new EnumMap<>(Race.class);
private final Map<Race, Ailment> ailments = new HashMap<>();
private UseAction action = UseAction.EAT;
private Optional<FoodComponent> component = Optional.empty();

View file

@ -32,7 +32,7 @@ public class MsgPlayerCapabilities implements Packet<PlayerEntity> {
MsgPlayerCapabilities(PacketByteBuf buffer) {
playerId = buffer.readUuid();
newRace = Race.values()[buffer.readInt()];
newRace = buffer.readRegistryValue(Race.REGISTRY);
try (InputStream in = new ByteBufInputStream(buffer)) {
compoundTag = NbtIo.readCompressed(in);
} catch (IOException e) {
@ -49,7 +49,7 @@ public class MsgPlayerCapabilities implements Packet<PlayerEntity> {
@Override
public void toBuffer(PacketByteBuf buffer) {
buffer.writeUuid(playerId);
buffer.writeInt(newRace.ordinal());
buffer.writeRegistryValue(Race.REGISTRY, newRace);
try (OutputStream out = new ByteBufOutputStream(buffer)) {
NbtIo.writeCompressed(compoundTag, out);
} catch (IOException e) {

View file

@ -21,7 +21,7 @@ public class MsgRequestSpeciesChange implements Packet<ServerPlayerEntity> {
MsgRequestSpeciesChange(PacketByteBuf buffer) {
force = buffer.readBoolean();
newRace = Race.fromId(buffer.readInt());
newRace = buffer.readRegistryValue(Race.REGISTRY);
}
public MsgRequestSpeciesChange(Race newRace) {
@ -36,7 +36,7 @@ public class MsgRequestSpeciesChange implements Packet<ServerPlayerEntity> {
@Override
public void toBuffer(PacketByteBuf buffer) {
buffer.writeBoolean(force);
buffer.writeInt(newRace.ordinal());
buffer.writeRegistryValue(Race.REGISTRY, newRace);
}
@Override

View file

@ -15,14 +15,14 @@ public class MsgTribeSelect implements Packet<PlayerEntity> {
private final Set<Race> availableRaces;
public MsgTribeSelect(PlayerEntity player) {
availableRaces = Race.all().stream().filter(r -> r.isPermitted(player)).collect(Collectors.toSet());
availableRaces = Race.REGISTRY.stream().filter(r -> r.isPermitted(player)).collect(Collectors.toSet());
}
public MsgTribeSelect(PacketByteBuf buffer) {
int len = buffer.readInt();
availableRaces = new HashSet<>();
while (len-- > 0) {
availableRaces.add(Race.fromId(buffer.readInt()));
availableRaces.add(buffer.readRegistryValue(Race.REGISTRY));
}
}
@ -33,7 +33,7 @@ public class MsgTribeSelect implements Packet<PlayerEntity> {
@Override
public void toBuffer(PacketByteBuf buffer) {
buffer.writeInt(availableRaces.size());
availableRaces.forEach(race -> buffer.writeInt(race.ordinal()));
availableRaces.forEach(race -> buffer.writeRegistryValue(Race.REGISTRY, race));
}
@Override

View file

@ -7,11 +7,7 @@ import com.mojang.serialization.Lifecycle;
import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
import net.minecraft.tag.TagKey;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryEntry;
import net.minecraft.util.registry.RegistryEntryList;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.util.registry.SimpleRegistry;
import net.minecraft.util.registry.*;
import net.minecraft.world.World;
public interface Registries {
@ -19,6 +15,10 @@ public interface Registries {
return FabricRegistryBuilder.from(new SimpleRegistry<T>(RegistryKey.ofRegistry(id), Lifecycle.stable(), null)).buildAndRegister();
}
static <T> Registry<T> createDefaulted(Identifier id, String def) {
return FabricRegistryBuilder.from(new DefaultedRegistry<T>(def, RegistryKey.ofRegistry(id), Lifecycle.stable(), null)).buildAndRegister();
}
static <T> RegistryEntryList<T> entriesForTag(World world, TagKey<T> key) {
return world.getRegistryManager().get(key.registry()).getOrCreateEntryList(key);
}

View file

@ -0,0 +1,54 @@
package com.minelittlepony.unicopia.util;
import com.minelittlepony.common.client.gui.IField;
import net.minecraft.util.registry.Registry;
public class RegistryIndexer<T> {
public static <T> RegistryIndexer<T> of(Registry<T> registry) {
return new RegistryIndexer<>(registry);
}
private final Registry<T> values;
private RegistryIndexer(Registry<T> registry) {
values = registry;
}
public int size() {
return values.size();
}
public int indexOf(T value) {
return values.getRawId(value);
}
public T valueOf(int index) {
return values.get(wrapIndex(index));
}
public T valueOf(float index) {
return valueOf((int)index);
}
public T cycle(T value, int increment) {
return valueOf(indexOf(value) + increment);
}
public IField.IChangeCallback<Float> createSetter(IField.IChangeCallback<T> setter) {
return index -> {
int i = wrapIndex(index.intValue());
setter.perform(valueOf(i));
return (float)i;
};
}
private int wrapIndex(int index) {
int sz = size();
while (index < 0) {
index += sz;
}
return index % sz;
}
}

View file

@ -243,58 +243,58 @@
"gui.unicopia.tribe_selection.options": "Available Tribes:",
"gui.unicopia.tribe_selection.options.disabled": "Option Unavailable",
"gui.unicopia.tribe_selection.describe.earth": "Join the Earth Tribe",
"gui.unicopia.tribe_selection.describe.unicorn": "Join the Unicorn Tribe, master the arcane arts",
"gui.unicopia.tribe_selection.describe.pegasus": "Join the Pegasus Tribe, soar with the Wonderbolts",
"gui.unicopia.tribe_selection.describe.bat": "Join the Bat Tribe, become the darkest night",
"gui.unicopia.tribe_selection.describe.changeling": "Join the Changeling Hive, your Queen demands it",
"gui.unicopia.tribe_selection.describe.unicopia.earth": "Join the Earth Tribe",
"gui.unicopia.tribe_selection.describe.unicopia.unicorn": "Join the Unicorn Tribe, master the arcane arts",
"gui.unicopia.tribe_selection.describe.unicopia.pegasus": "Join the Pegasus Tribe, soar with the Wonderbolts",
"gui.unicopia.tribe_selection.describe.unicopia.bat": "Join the Bat Tribe, become the darkest night",
"gui.unicopia.tribe_selection.describe.unicopia.changeling": "Join the Changeling Hive, your Queen demands it",
"gui.unicopia.tribe_selection.confirm": "You have selected %s",
"gui.unicopia.tribe_selection.confirm.goods": "%s enjoy the following perks:",
"gui.unicopia.tribe_selection.confirm.goods.1.earth": " - Stronger knockback and resistance to magic",
"gui.unicopia.tribe_selection.confirm.goods.2.earth": " - Have extra weight makes them effective against magic and brute force",
"gui.unicopia.tribe_selection.confirm.goods.3.earth": " - A special connection to the earth that makes farming 10,000% more effective!",
"gui.unicopia.tribe_selection.confirm.goods.1.unicopia.earth": " - Stronger knockback and resistance to magic",
"gui.unicopia.tribe_selection.confirm.goods.2.unicopia.earth": " - Have extra weight makes them effective against magic and brute force",
"gui.unicopia.tribe_selection.confirm.goods.3.unicopia.earth": " - A special connection to the earth that makes farming 10,000% more effective!",
"gui.unicopia.tribe_selection.confirm.goods.1.unicorn": " - Able to teleport and cast powerful spells",
"gui.unicopia.tribe_selection.confirm.goods.2.unicorn": " - Research and craft magical artefacts that enhance their abilities",
"gui.unicopia.tribe_selection.confirm.goods.3.unicorn": " - Can use magic to detect or reveal nearby changelings",
"gui.unicopia.tribe_selection.confirm.goods.4.unicorn": " - Have pointy sticks on their heads",
"gui.unicopia.tribe_selection.confirm.goods.1.unicopia.unicorn": " - Able to teleport and cast powerful spells",
"gui.unicopia.tribe_selection.confirm.goods.2.unicopia.unicorn": " - Research and craft magical artefacts that enhance their abilities",
"gui.unicopia.tribe_selection.confirm.goods.3.unicopia.unicorn": " - Can use magic to detect or reveal nearby changelings",
"gui.unicopia.tribe_selection.confirm.goods.4.unicopia.unicorn": " - Have pointy sticks on their heads",
"gui.unicopia.tribe_selection.confirm.goods.1.pegasus": " - Flight and the ability to train to build endurace",
"gui.unicopia.tribe_selection.confirm.goods.2.pegasus": " - Use stored mana to perform a powerful rainboom",
"gui.unicopia.tribe_selection.confirm.goods.3.pegasus": " - Moves faster and takes less fall damage",
"gui.unicopia.tribe_selection.confirm.goods.4.pegasus": " - Can eat vegitables and certain types of fish",
"gui.unicopia.tribe_selection.confirm.goods.1.unicopia.pegasus": " - Flight and the ability to train to build endurace",
"gui.unicopia.tribe_selection.confirm.goods.2.unicopia.pegasus": " - Use stored mana to perform a powerful rainboom",
"gui.unicopia.tribe_selection.confirm.goods.3.unicopia.pegasus": " - Moves faster and takes less fall damage",
"gui.unicopia.tribe_selection.confirm.goods.4.unicopia.pegasus": " - Can eat vegitables and certain types of fish",
"gui.unicopia.tribe_selection.confirm.goods.1.bat": " - Flight and the ability to train to build endurance",
"gui.unicopia.tribe_selection.confirm.goods.2.bat": " - Sees better in the night",
"gui.unicopia.tribe_selection.confirm.goods.3.bat": " - Able to cling to the underside of blocks",
"gui.unicopia.tribe_selection.confirm.goods.4.bat": " - Has a terrifying, yet adorable, but still slightly annoying screech",
"gui.unicopia.tribe_selection.confirm.goods.1.unicopia.bat": " - Flight and the ability to train to build endurance",
"gui.unicopia.tribe_selection.confirm.goods.2.unicopia.bat": " - Sees better in the night",
"gui.unicopia.tribe_selection.confirm.goods.3.unicopia.bat": " - Able to cling to the underside of blocks",
"gui.unicopia.tribe_selection.confirm.goods.4.unicopia.bat": " - Has a terrifying, yet adorable, but still slightly annoying screech",
"gui.unicopia.tribe_selection.confirm.goods.1.changeling": " - Able to fly and hover in place",
"gui.unicopia.tribe_selection.confirm.goods.2.changeling": " - Shapeshift and morph into nearly anyone or anyling",
"gui.unicopia.tribe_selection.confirm.goods.3.changeling": " - Is carnivorous. Can eat anything that doesn't make them sick",
"gui.unicopia.tribe_selection.confirm.goods.1.unicopia.changeling": " - Able to fly and hover in place",
"gui.unicopia.tribe_selection.confirm.goods.2.unicopia.changeling": " - Shapeshift and morph into nearly anyone or anyling",
"gui.unicopia.tribe_selection.confirm.goods.3.unicopia.changeling": " - Is carnivorous. Can eat anything that doesn't make them sick",
"gui.unicopia.tribe_selection.confirm.bads": "but they...",
"gui.unicopia.tribe_selection.confirm.bads.1.earth": " - Cannot fly",
"gui.unicopia.tribe_selection.confirm.bads.2.earth": " - Are weak to certain types of magic",
"gui.unicopia.tribe_selection.confirm.bads.3.earth": " - Can only eat plants and vegitables",
"gui.unicopia.tribe_selection.confirm.bads.1.unicopia.earth": " - Cannot fly",
"gui.unicopia.tribe_selection.confirm.bads.2.unicopia.earth": " - Are weak to certain types of magic",
"gui.unicopia.tribe_selection.confirm.bads.3.unicopia.earth": " - Can only eat plants and vegitables",
"gui.unicopia.tribe_selection.confirm.bads.1.unicorn": " - Cannot fly",
"gui.unicopia.tribe_selection.confirm.bads.2.unicorn": " - Are weak to brute force attacks",
"gui.unicopia.tribe_selection.confirm.bads.3.unicorn": " - Can only eat plants and vegitables",
"gui.unicopia.tribe_selection.confirm.bads.1.unicopia.unicorn": " - Cannot fly",
"gui.unicopia.tribe_selection.confirm.bads.2.unicopia.unicorn": " - Are weak to brute force attacks",
"gui.unicopia.tribe_selection.confirm.bads.3.unicopia.unicorn": " - Can only eat plants and vegitables",
"gui.unicopia.tribe_selection.confirm.bads.1.pegasus": " - Light weight makes them the weakest to brute force",
"gui.unicopia.tribe_selection.confirm.bads.2.pegasus": " - Must rest between flights to regain their strength",
"gui.unicopia.tribe_selection.confirm.bads.1.unicopia.pegasus": " - Light weight makes them the weakest to brute force",
"gui.unicopia.tribe_selection.confirm.bads.2.unicopia.pegasus": " - Must rest between flights to regain their strength",
"gui.unicopia.tribe_selection.confirm.bads.1.bat": " - Light weight makes them weak to brute force attacks.",
"gui.unicopia.tribe_selection.confirm.bads.2.bat": " - Must rest between flights to regain their strength",
"gui.unicopia.tribe_selection.confirm.bads.3.bat": " - Is sometimes scared of even themselves",
"gui.unicopia.tribe_selection.confirm.bads.4.bat": " - Is carnivorous. Can eat anything that doesn't make them sick",
"gui.unicopia.tribe_selection.confirm.bads.1.unicopia.bat": " - Light weight makes them weak to brute force attacks.",
"gui.unicopia.tribe_selection.confirm.bads.2.unicopia.bat": " - Must rest between flights to regain their strength",
"gui.unicopia.tribe_selection.confirm.bads.3.unicopia.bat": " - Is sometimes scared of even themselves",
"gui.unicopia.tribe_selection.confirm.bads.4.unicopia.bat": " - Is carnivorous. Can eat anything that doesn't make them sick",
"gui.unicopia.tribe_selection.confirm.bads.1.changeling": " - Are always starving",
"gui.unicopia.tribe_selection.confirm.bads.2.changeling": " - Require love, collected from ponies or other hostile mobs to subsidise their diet",
"gui.unicopia.tribe_selection.confirm.bads.3.changeling": " - Becomes sick from eating regular food and must eat love hasten a cure",
"gui.unicopia.tribe_selection.confirm.bads.1.unicopia.changeling": " - Are always starving",
"gui.unicopia.tribe_selection.confirm.bads.2.unicopia.changeling": " - Require love, collected from ponies or other hostile mobs to subsidise their diet",
"gui.unicopia.tribe_selection.confirm.bads.3.unicopia.changeling": " - Becomes sick from eating regular food and must eat love hasten a cure",
"gui.unicopia.spellbook.page.inventory": "Inventory",
"gui.unicopia.spellbook.page.discoveries": "Discoveries",
@ -338,33 +338,33 @@
"commands.race.tell.other.alt": "%s is an ",
"commands.race.describe.title": "%s",
"commands.race.describe.human.1": "This is a default race with no abilities.",
"commands.race.describe.human.2": "If you have this, it means there are no other races available.",
"commands.race.describe.human.3": "It has no special abilities.",
"commands.race.describe.unicopia.human.1": "This is a default race with no abilities.",
"commands.race.describe.unicopia.human.2": "If you have this, it means there are no other races available.",
"commands.race.describe.unicopia.human.3": "It has no special abilities.",
"commands.race.describe.earth.1": "Earth Ponies can grow crops using mana and punch trees for apples (yeeeeeeehaaaaawwwwwwwwwww)",
"commands.race.describe.earth.2": "Their offensive ability is to kick down whilst jumping for a deafening ground smash.",
"commands.race.describe.earth.3": "They are strong but slow.",
"commands.race.describe.unicopia.earth.1": "Earth Ponies can grow crops using mana and punch trees for apples (yeeeeeeehaaaaawwwwwwwwwww)",
"commands.race.describe.unicopia.earth.2": "Their offensive ability is to kick down whilst jumping for a deafening ground smash.",
"commands.race.describe.unicopia.earth.3": "They are strong but slow.",
"commands.race.describe.unicorn.1": "Unicorns are the primary magic users. They can teleport and cast powerful spells.",
"commands.race.describe.unicorn.2": "Their defensive features a powerful shield powered by their mana.",
"commands.race.describe.unicorn.3": "They are prone to tiring quickly.",
"commands.race.describe.unicopia.unicorn.1": "Unicorns are the primary magic users. They can teleport and cast powerful spells.",
"commands.race.describe.unicopia.unicorn.2": "Their defensive features a powerful shield powered by their mana.",
"commands.race.describe.unicopia.unicorn.3": "They are prone to tiring quickly.",
"commands.race.describe.pegasus.1": "Pegasi are the masters of the skies.",
"commands.race.describe.pegasus.2": "They live mostly in the air and are the only species with the ability to mould and harness cloud materials for their homes.",
"commands.race.describe.pegasus.3": "They are fast and light but easy to knock down.",
"commands.race.describe.unicopia.pegasus.1": "Pegasi are the masters of the skies.",
"commands.race.describe.unicopia.pegasus.2": "They live mostly in the air and are the only species with the ability to mould and harness cloud materials for their homes.",
"commands.race.describe.unicopia.pegasus.3": "They are fast and light but easy to knock down.",
"commands.race.describe.alicorn.1": "Praise the sun!",
"commands.race.describe.alicorn.2": "Alicorns have all abilities of the other races.",
"commands.race.describe.alicorn.3": "Only available to CREATIVE mode players.",
"commands.race.describe.unicopia.alicorn.1": "Praise the sun!",
"commands.race.describe.unicopia.alicorn.2": "Alicorns have all abilities of the other races.",
"commands.race.describe.unicopia.alicorn.3": "Only available to CREATIVE mode players.",
"commands.race.describe.changeling.1": "Beware the changeling, for they can appear when least expected.",
"commands.race.describe.changeling.2": "Changelings can fly but but do not interact with clouds.",
"commands.race.describe.changeling.3": "They have to feed on mobs and other players to eat.",
"commands.race.describe.unicopia.changeling.1": "Beware the changeling, for they can appear when least expected.",
"commands.race.describe.unicopia.changeling.2": "Changelings can fly but but do not interact with clouds.",
"commands.race.describe.unicopia.changeling.3": "They have to feed on mobs and other players to eat.",
"commands.race.describe.bat.1": "I am the night! EEEEEEEEEEEEE!!!",
"commands.race.describe.bat.2": "Bat Ponies can fly but but do not interact with clouds.",
"commands.race.describe.bat.3": "They love mangoes. Give them one, and they will follow you forever.",
"commands.race.describe.unicopia.bat.1": "I am the night! EEEEEEEEEEEEE!!!",
"commands.race.describe.unicopia.bat.2": "Bat Ponies can fly but but do not interact with clouds.",
"commands.race.describe.unicopia.bat.3": "They love mangoes. Give them one, and they will follow you forever.",
"commands.racelist.usage": "/racelist <allow|disallow> <species>",
"commands.racelist.illegal": "The default race %s cannot be used with this command.",