Peach blocks and Peach forest

Elka, Glimmer, Deer
Toned down alpha mantis spawns
Increased uranium and titanium spawn by 25%
Overhauled red ant nest and pale nyxite texture
Fixed Ant Nests not spawning on Fabric
Fixed armor trims not working on antarchy armors
Fixed antarchy materials not working as armor trims
Adding missing hanging sign gui texture files
HFCS
Root beer
Elythia frog
Roseate and Lumen froglight
This commit is contained in:
craisinlord committed 2026-07-15 11:39:29 -05:00
1 parent 89d9aff0b9
commit 79cbc454e6
561 files changed
+22472 -3149

No files matched your search

+1
View File
@@ -65,6 +65,7 @@ allprojects {
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url "https://www.cursemaven.com" } maven { url "https://www.cursemaven.com" }
maven { url "https://api.modrinth.com/maven/" } maven { url "https://api.modrinth.com/maven/" }
maven { url "https://maven.blamejared.com" }
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
+4 -3
View File
@@ -17,9 +17,9 @@ dependencies {
// compileOnly("curse.maven:backpack-${project.backpack_curseforge_id}:${project.backpack_curseforge_file_id}") // compileOnly("curse.maven:backpack-${project.backpack_curseforge_id}:${project.backpack_curseforge_file_id}")
// compileOnly("curse.maven:regions_unexplored-${project.regions_unexplored_curseforge_id}:${project.regions_unexplored_curseforge_file_id}") // compileOnly("curse.maven:regions_unexplored-${project.regions_unexplored_curseforge_id}:${project.regions_unexplored_curseforge_file_id}")
// compileOnly("curse.maven:geckolib-${project.geckolib_curseforge_id}:${project.geckolib_curseforge_file_id}") // compileOnly("curse.maven:geckolib-${project.geckolib_curseforge_id}:${project.geckolib_curseforge_file_id}")
compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+1.21.1-neoforge.jar")) compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.2+1.21.1-neoforge.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+1.21.1-fabric.jar")) // compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+1.21.1-fabric.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+1.21.1-neoforge-sources.jar")) // compileOnly(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+1.21.1-neoforge-sources.jar"))
compileOnly(files("$rootDir/infinite_dimensions_jars/gravity-api-1.0.7-1.20.1.jar")) compileOnly(files("$rootDir/infinite_dimensions_jars/gravity-api-1.0.7-1.20.1.jar"))
// compileOnly(files("$rootDir/infinite_dimensions_jars/Cloro's Gravity Gun Mod (Release 1.3.1).jar")) // compileOnly(files("$rootDir/infinite_dimensions_jars/Cloro's Gravity Gun Mod (Release 1.3.1).jar"))
// compileOnly(files("$rootDir/infinite_dimensions_jars/antarchy-1.0.0+1.21.1-neoforge.jar")) // compileOnly(files("$rootDir/infinite_dimensions_jars/antarchy-1.0.0+1.21.1-neoforge.jar"))
@@ -34,6 +34,7 @@ dependencies {
// names throughout, so this is the one common can safely compile against. // names throughout, so this is the one common can safely compile against.
compileOnly(files("$rootDir/sizeable_foliage_jars/sizeable_foliage-neoforge-1.21.1-1.0.0.jar")) compileOnly(files("$rootDir/sizeable_foliage_jars/sizeable_foliage-neoforge-1.21.1-1.0.0.jar"))
compileOnly("software.bernie.geckolib:geckolib-neoforge-${minecraft_version}:${geckolib_version}") compileOnly("software.bernie.geckolib:geckolib-neoforge-${minecraft_version}:${geckolib_version}")
compileOnly("mezz.jei:jei-${minecraft_version}-common-api:${jei_version}")
// //
// compileOnly group: 'org.ow2.asm', name: 'asm-tree', version: '9.4' // compileOnly group: 'org.ow2.asm', name: 'asm-tree', version: '9.4'
// implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' // implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
@@ -0,0 +1,56 @@
package com.craisinlord.antarchy.compat.infinity;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class InfinityCompatVersion {
private static final Pattern VERSION_PARTS = Pattern.compile("(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?");
private static final int REQUIRED_MAJOR = 2;
private static final int REQUIRED_MINOR = 7;
private static final int REQUIRED_PATCH = 2;
private InfinityCompatVersion() {
}
public static boolean isSupported(String version) {
int[] parsed = parse(version);
if (parsed == null) {
return false;
}
if (parsed[0] != REQUIRED_MAJOR) {
return parsed[0] > REQUIRED_MAJOR;
}
if (parsed[1] != REQUIRED_MINOR) {
return parsed[1] > REQUIRED_MINOR;
}
return parsed[2] >= REQUIRED_PATCH;
}
public static String requiredVersion() {
return REQUIRED_MAJOR + "." + REQUIRED_MINOR + "." + REQUIRED_PATCH;
}
private static int[] parse(String version) {
if (version == null || version.isBlank()) {
return null;
}
Matcher matcher = VERSION_PARTS.matcher(version);
if (!matcher.find()) {
return null;
}
return new int[]{
parsePart(matcher.group(1)),
parsePart(matcher.group(2)),
parsePart(matcher.group(3))
};
}
private static int parsePart(String value) {
if (value == null || value.isBlank()) {
return 0;
}
return Integer.parseInt(value);
}
}
@@ -0,0 +1,64 @@
package com.craisinlord.antarchy.compat.jei;
import com.craisinlord.antarchy.Antarchy;
import java.util.List;
import mezz.jei.api.gui.builder.IRecipeLayoutBuilder;
import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.helpers.IGuiHelper;
import mezz.jei.api.recipe.IFocusGroup;
import mezz.jei.api.recipe.RecipeIngredientRole;
import mezz.jei.api.recipe.RecipeType;
import mezz.jei.api.recipe.category.IRecipeCategory;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
public final class AmericanizeJeiRecipeCategory implements IRecipeCategory<AmericanizeJeiRecipeCategory.Recipe> {
public static final RecipeType<Recipe> TYPE = RecipeType.create(Antarchy.MODID, "americanize", Recipe.class);
private final IDrawable icon;
public record Recipe(List<ItemStack> foods, ItemStack hfcs, List<ItemStack> results) {
}
public AmericanizeJeiRecipeCategory(IGuiHelper guiHelper, ItemStack hfcsIcon) {
this.icon = guiHelper.createDrawableItemStack(hfcsIcon);
}
@Override
public RecipeType<Recipe> getRecipeType() {
return TYPE;
}
@Override
public Component getTitle() {
return Component.translatable("jei.antarchy.americanize");
}
@Override
public int getWidth() {
return 78;
}
@Override
public int getHeight() {
return 18;
}
@Override
public IDrawable getIcon() {
return this.icon;
}
@Override
public void setRecipe(IRecipeLayoutBuilder builder, Recipe recipe, IFocusGroup focuses) {
builder.addSlot(RecipeIngredientRole.RENDER_ONLY, 1, 1)
.setStandardSlotBackground()
.addItemStacks(recipe.foods());
builder.addSlot(RecipeIngredientRole.INPUT, 30, 1)
.setStandardSlotBackground()
.addItemStack(recipe.hfcs());
builder.addSlot(RecipeIngredientRole.RENDER_ONLY, 59, 1)
.setStandardSlotBackground()
.addItemStacks(recipe.results());
}
}
@@ -0,0 +1,98 @@
package com.craisinlord.antarchy.compat.jei;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AmericanizeRecipe;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.CustomBrewingRecipes;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant;
import com.craisinlord.antarchy.content.item.GlimmerBottleItem;
import java.util.ArrayList;
import java.util.List;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.JeiPlugin;
import mezz.jei.api.constants.RecipeTypes;
import mezz.jei.api.recipe.vanilla.IJeiBrewingRecipe;
import mezz.jei.api.recipe.vanilla.IVanillaRecipeFactory;
import mezz.jei.api.registration.IRecipeCategoryRegistration;
import mezz.jei.api.registration.IRecipeRegistration;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Unit;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
@JeiPlugin
public class AntarchyJeiPlugin implements IModPlugin {
private static final ResourceLocation UID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "jei_plugin");
@Override
public ResourceLocation getPluginUid() {
return UID;
}
@Override
public void registerCategories(IRecipeCategoryRegistration registration) {
ItemStack hfcs = new ItemStack(CustomBrewingRecipes.highFructoseCornSyrupItem());
registration.addRecipeCategories(new AmericanizeJeiRecipeCategory(registration.getJeiHelpers().getGuiHelper(), hfcs));
}
@Override
public void registerRecipes(IRecipeRegistration registration) {
IVanillaRecipeFactory factory = registration.getVanillaRecipeFactory();
List<IJeiBrewingRecipe> recipes = new ArrayList<>();
recipes.add(factory.createBrewingRecipe(
List.of(new ItemStack(CustomBrewingRecipes.cornItem())),
CustomBrewingRecipes.waterBottleStack(),
new ItemStack(CustomBrewingRecipes.highFructoseCornSyrupItem())));
recipes.add(factory.createBrewingRecipe(
tagItems(CustomBrewingRecipes.rootsTag()),
new ItemStack(CustomBrewingRecipes.highFructoseCornSyrupItem()),
new ItemStack(CustomBrewingRecipes.rootBeerItem())));
ItemStack glimmerBottleInput = GlimmerBottleItem.create(GlimmerVariant.APPLE_COW);
recipes.add(factory.createBrewingRecipe(tagItems(AntarchyTags.Items.GLIMMER_AUGMENT_OURANWOOD_DEER),
glimmerBottleInput, GlimmerBottleItem.create(GlimmerVariant.OURANWOOD_DEER)));
recipes.add(factory.createBrewingRecipe(tagItems(AntarchyTags.Items.GLIMMER_AUGMENT_FROG),
glimmerBottleInput, GlimmerBottleItem.create(GlimmerVariant.FROG)));
recipes.add(factory.createBrewingRecipe(tagItems(AntarchyTags.Items.GLIMMER_AUGMENT_ANT),
glimmerBottleInput, GlimmerBottleItem.create(GlimmerVariant.ANT)));
recipes.add(factory.createBrewingRecipe(tagItems(AntarchyTags.Items.GLIMMER_AUGMENT_ELKA),
glimmerBottleInput, GlimmerBottleItem.create(GlimmerVariant.ELKA)));
ItemStack glimmerBottleDeer = GlimmerBottleItem.create(GlimmerVariant.OURANWOOD_DEER);
recipes.add(factory.createBrewingRecipe(tagItems(AntarchyTags.Items.GLIMMER_AUGMENT_APPLE_COW),
glimmerBottleDeer, GlimmerBottleItem.create(GlimmerVariant.APPLE_COW)));
registration.addRecipes(RecipeTypes.BREWING, recipes);
if (AntarchySettings.americanizingEnabled()) {
registration.addRecipes(AmericanizeJeiRecipeCategory.TYPE, List.of(americanizeDisplay()));
}
}
private static AmericanizeJeiRecipeCategory.Recipe americanizeDisplay() {
List<ItemStack> foods = new ArrayList<>();
List<ItemStack> results = new ArrayList<>();
for (Item item : BuiltInRegistries.ITEM) {
ItemStack stack = new ItemStack(item);
if (AmericanizeRecipe.isEligibleFood(stack)) {
foods.add(stack);
ItemStack result = stack.copyWithCount(1);
result.set(AntarchyObjects.AMERICAN_COMPONENT.get(), Unit.INSTANCE);
results.add(result);
}
}
ItemStack hfcs = new ItemStack(CustomBrewingRecipes.highFructoseCornSyrupItem());
return new AmericanizeJeiRecipeCategory.Recipe(foods, hfcs, results);
}
private static List<ItemStack> tagItems(TagKey<Item> tag) {
return List.of(Ingredient.of(tag).getItems());
}
}
@@ -127,9 +127,19 @@ public final class AntarchySettings {
private static double gravityGunBlastStrength = 1.4D; private static double gravityGunBlastStrength = 1.4D;
private static double gravityGunCooldownSeconds = 0.35D; private static double gravityGunCooldownSeconds = 0.35D;
private static double gravityGunMaxHoldDistance = 10.0D; private static double gravityGunMaxHoldDistance = 10.0D;
private static boolean minersDreamEnabled = true;
private static int minersDreamMinimumRange = 48;
private static int minersDreamMaximumRange = 96;
private static int minersDreamTorchSpacing = 10;
private static int minersDreamBlocksPerTick = 150;
private static double lucidAttackRange = 9.0D; private static double lucidAttackRange = 9.0D;
private static double lucidPearlInvertedDurationSeconds = 6.0D; private static double lucidPearlInvertedDurationSeconds = 6.0D;
private static int corneaEarNightVisionSeconds = 15; private static int corneaEarNightVisionSeconds = 15;
private static boolean americanizingEnabled = true;
private static int americanBonusNutrition = 2;
private static double americanBonusSaturation = 1.0D;
private static int americanRegenerationDurationTicks = 60;
private static int americanRegenerationAmplifier = 0;
private static int mogglesVisionRadius = 2; private static int mogglesVisionRadius = 2;
private static int mogglesVisionMaxLight = 7; private static int mogglesVisionMaxLight = 7;
private static float mogglesVisionAlpha = 0.35f; private static float mogglesVisionAlpha = 0.35f;
@@ -490,6 +500,7 @@ public final class AntarchySettings {
private static double herculesBeetleHealth = 500.0D; private static double herculesBeetleHealth = 500.0D;
private static double herculesBeetleAttackDamage = 30.0D; private static double herculesBeetleAttackDamage = 30.0D;
private static double herculesBeetleChargeDamage = 40.0D; private static double herculesBeetleChargeDamage = 40.0D;
private static double ouranwoodDeerHealth = 20.0D;
private static double glimmerHealth = 20.0D; private static double glimmerHealth = 20.0D;
private AntarchySettings() { private AntarchySettings() {
@@ -947,6 +958,26 @@ public final class AntarchySettings {
return gravityGunMaxHoldDistance; return gravityGunMaxHoldDistance;
} }
public static boolean minersDreamEnabled() {
return minersDreamEnabled;
}
public static int minersDreamMinimumRange() {
return minersDreamMinimumRange;
}
public static int minersDreamMaximumRange() {
return minersDreamMaximumRange;
}
public static int minersDreamTorchSpacing() {
return minersDreamTorchSpacing;
}
public static int minersDreamBlocksPerTick() {
return minersDreamBlocksPerTick;
}
public static void setDisableInfinityBookPortalCreation(boolean value) { public static void setDisableInfinityBookPortalCreation(boolean value) {
disableInfinityBookPortalCreation = value; disableInfinityBookPortalCreation = value;
} }
@@ -1393,6 +1424,26 @@ public final class AntarchySettings {
gravityGunMaxHoldDistance = value; gravityGunMaxHoldDistance = value;
} }
public static void setMinersDreamEnabled(boolean value) {
minersDreamEnabled = value;
}
public static void setMinersDreamMinimumRange(int value) {
minersDreamMinimumRange = value;
}
public static void setMinersDreamMaximumRange(int value) {
minersDreamMaximumRange = value;
}
public static void setMinersDreamTorchSpacing(int value) {
minersDreamTorchSpacing = value;
}
public static void setMinersDreamBlocksPerTick(int value) {
minersDreamBlocksPerTick = value;
}
public static double lucidAttackRange() { public static double lucidAttackRange() {
return lucidAttackRange; return lucidAttackRange;
} }
@@ -1417,6 +1468,46 @@ public final class AntarchySettings {
corneaEarNightVisionSeconds = value; corneaEarNightVisionSeconds = value;
} }
public static boolean americanizingEnabled() {
return americanizingEnabled;
}
public static void setAmericanizingEnabled(boolean value) {
americanizingEnabled = value;
}
public static int americanBonusNutrition() {
return americanBonusNutrition;
}
public static void setAmericanBonusNutrition(int value) {
americanBonusNutrition = value;
}
public static double americanBonusSaturation() {
return americanBonusSaturation;
}
public static void setAmericanBonusSaturation(double value) {
americanBonusSaturation = value;
}
public static int americanRegenerationDurationTicks() {
return americanRegenerationDurationTicks;
}
public static void setAmericanRegenerationDurationTicks(int value) {
americanRegenerationDurationTicks = value;
}
public static int americanRegenerationAmplifier() {
return americanRegenerationAmplifier;
}
public static void setAmericanRegenerationAmplifier(int value) {
americanRegenerationAmplifier = value;
}
public static int mogglesVisionRadius() { public static int mogglesVisionRadius() {
return mogglesVisionRadius; return mogglesVisionRadius;
} }
@@ -2548,6 +2639,8 @@ public final class AntarchySettings {
public static double herculesBeetleChargeDamage() { return herculesBeetleChargeDamage; } public static double herculesBeetleChargeDamage() { return herculesBeetleChargeDamage; }
public static void setHerculesBeetleChargeDamage(double v) { herculesBeetleChargeDamage = v; } public static void setHerculesBeetleChargeDamage(double v) { herculesBeetleChargeDamage = v; }
public static void setDorrieHealth(double v) { dorrieHealth = v; } public static void setDorrieHealth(double v) { dorrieHealth = v; }
public static double ouranwoodDeerHealth() { return ouranwoodDeerHealth; }
public static void setOuranwoodDeerHealth(double v) { ouranwoodDeerHealth = v; }
public static double glimmerHealth() { return glimmerHealth; } public static double glimmerHealth() { return glimmerHealth; }
public static void setGlimmerHealth(double v) { glimmerHealth = v; } public static void setGlimmerHealth(double v) { glimmerHealth = v; }
} }
@@ -0,0 +1,114 @@
package com.craisinlord.antarchy.content;
import com.craisinlord.antarchy.config.AntarchySettings;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.NonNullList;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Unit;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.CraftingBookCategory;
import net.minecraft.world.item.crafting.CraftingInput;
import net.minecraft.world.item.crafting.CustomRecipe;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
import net.minecraft.world.level.Level;
public class AmericanizeRecipe extends CustomRecipe {
private static final ResourceLocation HIGH_FRUCTOSE_CORN_SYRUP_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "high_fructose_corn_syrup");
public static final RecipeSerializer<AmericanizeRecipe> SERIALIZER = new SimpleCraftingRecipeSerializer<>(AmericanizeRecipe::new);
public AmericanizeRecipe(CraftingBookCategory category) {
super(category);
}
@Override
public boolean matches(CraftingInput input, Level level) {
return findFood(input) != null;
}
@Override
public ItemStack assemble(CraftingInput input, HolderLookup.Provider registries) {
ItemStack food = findFood(input);
if (food == null) {
return ItemStack.EMPTY;
}
ItemStack result = food.copyWithCount(1);
result.set(AntarchyObjects.AMERICAN_COMPONENT.get(), Unit.INSTANCE);
return result;
}
@Override
public boolean canCraftInDimensions(int width, int height) {
return width * height >= 2;
}
@Override
public NonNullList<ItemStack> getRemainingItems(CraftingInput input) {
NonNullList<ItemStack> remaining = NonNullList.withSize(input.size(), ItemStack.EMPTY);
for (int i = 0; i < input.size(); i++) {
if (isHighFructoseCornSyrup(input.getItem(i))) {
remaining.set(i, new ItemStack(Items.GLASS_BOTTLE));
}
}
return remaining;
}
@Override
public RecipeSerializer<?> getSerializer() {
return SERIALIZER;
}
private static ItemStack findFood(CraftingInput input) {
ItemStack food = ItemStack.EMPTY;
ItemStack hfcs = ItemStack.EMPTY;
for (int i = 0; i < input.size(); i++) {
ItemStack stack = input.getItem(i);
if (stack.isEmpty()) {
continue;
}
if (isHighFructoseCornSyrup(stack)) {
if (!hfcs.isEmpty()) {
return null;
}
hfcs = stack;
} else {
if (!food.isEmpty()) {
return null;
}
food = stack;
}
}
if (food.isEmpty() || hfcs.isEmpty() || !isEligibleFood(food)) {
return null;
}
return food;
}
public static boolean isEligibleFood(ItemStack stack) {
if (!AntarchySettings.americanizingEnabled()) {
return false;
}
if (stack.isEmpty() || isHighFructoseCornSyrup(stack)) {
return false;
}
if (stack.has(AntarchyObjects.AMERICAN_COMPONENT.get())) {
return false;
}
if (stack.is(AntarchyTags.Items.HFCS_CANNOT_AMERICANIZE)) {
return false;
}
return stack.get(DataComponents.FOOD) != null;
}
public static boolean isHighFructoseCornSyrup(ItemStack stack) {
return !stack.isEmpty() && stack.is(BuiltInRegistries.ITEM.get(HIGH_FRUCTOSE_CORN_SYRUP_ID));
}
}
@@ -34,12 +34,14 @@ import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.ai.attributes.Attribute; import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityType;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
public final class AntarchyObjects { public final class AntarchyObjects {
private static final Supplier<?> UNBOUND = () -> { private static final Supplier<?> UNBOUND = () -> {
@@ -99,9 +101,34 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<EntityType<HerculesBeetleEntity>> HERCULES_BEETLE = (Supplier<EntityType<HerculesBeetleEntity>>) UNBOUND; public static Supplier<EntityType<HerculesBeetleEntity>> HERCULES_BEETLE = (Supplier<EntityType<HerculesBeetleEntity>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.GlimmerEntity>> GLIMMER = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.GlimmerEntity>>) UNBOUND; public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.OuranwoodDeerEntity>> OURANWOOD_DEER = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.OuranwoodDeerEntity>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Holder<MobEffect>> GLIMMERS_GRACE = (Supplier<Holder<MobEffect>>) UNBOUND; public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.glimmer.GlimmerEntity>> GLIMMER = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.glimmer.GlimmerEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> SPIRIT_APPLE = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<com.craisinlord.antarchy.content.entity.ElkaEntity>> ELKA = (Supplier<EntityType<com.craisinlord.antarchy.content.entity.ElkaEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> PEACH = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> CORN = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> CORN_SEEDS = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> WILD_CORN = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> RAW_VENISON = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> COOKED_VENISON = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> COOKED_CORNDOG = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> GLIMMER_BOTTLE = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<net.minecraft.core.component.DataComponentType<com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant>> GLIMMER_VARIANT_COMPONENT =
(Supplier<net.minecraft.core.component.DataComponentType<com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<MenuType<com.craisinlord.antarchy.content.menu.DorrieInventoryMenu>> DORRIE_INVENTORY_MENU = (Supplier<MenuType<com.craisinlord.antarchy.content.menu.DorrieInventoryMenu>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<net.minecraft.world.item.Item> CHEEP_ITEM = (Supplier<net.minecraft.world.item.Item>) UNBOUND; public static Supplier<net.minecraft.world.item.Item> CHEEP_ITEM = (Supplier<net.minecraft.world.item.Item>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -205,6 +232,16 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Holder<MobEffect>> STINKY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND; public static Supplier<Holder<MobEffect>> STINKY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Fluid> LUMEN = (Supplier<Fluid>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Fluid> FLOWING_LUMEN = (Supplier<Fluid>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> LUMEN_BUCKET = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> LUMEN_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> LUMEN_FROGLIGHT = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> OURANWOOD_ACORN_BLOCK = (Supplier<Block>) UNBOUND; public static Supplier<Block> OURANWOOD_ACORN_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Block> MOSSY_OURANWOOD_LOG = (Supplier<Block>) UNBOUND; public static Supplier<Block> MOSSY_OURANWOOD_LOG = (Supplier<Block>) UNBOUND;
@@ -223,6 +260,14 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Block> WASP_NEST = (Supplier<Block>) UNBOUND; public static Supplier<Block> WASP_NEST = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Block> UMBRAL_MOSS_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> UMBRAL_MOSS_CARPET = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> BLUSH_MOSS_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> BLUSH_MOSS_CARPET = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> HUSHWEED = (Supplier<Block>) UNBOUND; public static Supplier<Block> HUSHWEED = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<Block> SQUIRREL_NEST_BLOCK = (Supplier<Block>) UNBOUND; public static Supplier<Block> SQUIRREL_NEST_BLOCK = (Supplier<Block>) UNBOUND;
@@ -240,6 +285,11 @@ public final class AntarchyObjects {
public static Supplier<SimpleParticleType> STINKY_GAS = (Supplier<SimpleParticleType>) UNBOUND; public static Supplier<SimpleParticleType> STINKY_GAS = (Supplier<SimpleParticleType>) UNBOUND;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Supplier<SimpleParticleType> STINKY_FLY = (Supplier<SimpleParticleType>) UNBOUND; public static Supplier<SimpleParticleType> STINKY_FLY = (Supplier<SimpleParticleType>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<SimpleParticleType> PEACH_LEAVES_PARTICLE = (Supplier<SimpleParticleType>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<net.minecraft.core.component.DataComponentType<net.minecraft.util.Unit>> AMERICAN_COMPONENT =
(Supplier<net.minecraft.core.component.DataComponentType<net.minecraft.util.Unit>>) UNBOUND;
private AntarchyObjects() { private AntarchyObjects() {
} }
@@ -319,6 +369,10 @@ public final class AntarchyObjects {
Supplier<Block> ouranwoodAcornBlock, Supplier<Block> ouranwoodAcornBlock,
Supplier<Block> mossyOuranwoodLog, Supplier<Block> mossyOuranwoodLog,
Supplier<Block> mossyOuranwoodWood, Supplier<Block> mossyOuranwoodWood,
Supplier<Block> umbralMossBlock,
Supplier<Block> umbralMossCarpet,
Supplier<Block> blushMossBlock,
Supplier<Block> blushMossCarpet,
Supplier<Block> orangeMilkweed, Supplier<Block> orangeMilkweed,
Supplier<Block> pinkMilkweed, Supplier<Block> pinkMilkweed,
Supplier<Block> bedBugEggs, Supplier<Block> bedBugEggs,
@@ -334,6 +388,7 @@ public final class AntarchyObjects {
Supplier<BlockEntityType<HushweedBlockEntity>> hushweedBlockEntity, Supplier<BlockEntityType<HushweedBlockEntity>> hushweedBlockEntity,
Supplier<SimpleParticleType> stinkyGas, Supplier<SimpleParticleType> stinkyGas,
Supplier<SimpleParticleType> stinkyFly, Supplier<SimpleParticleType> stinkyFly,
Supplier<SimpleParticleType> peachLeaves,
Supplier<Holder<Attribute>> doubleDamageChance, Supplier<Holder<Attribute>> doubleDamageChance,
Supplier<Holder<Attribute>> bloodglassMaxHearts, Supplier<Holder<Attribute>> bloodglassMaxHearts,
Supplier<Holder<MobEffect>> bloodglassWard Supplier<Holder<MobEffect>> bloodglassWard
@@ -412,6 +467,10 @@ public final class AntarchyObjects {
OURANWOOD_ACORN_BLOCK = ouranwoodAcornBlock; OURANWOOD_ACORN_BLOCK = ouranwoodAcornBlock;
MOSSY_OURANWOOD_LOG = mossyOuranwoodLog; MOSSY_OURANWOOD_LOG = mossyOuranwoodLog;
MOSSY_OURANWOOD_WOOD = mossyOuranwoodWood; MOSSY_OURANWOOD_WOOD = mossyOuranwoodWood;
UMBRAL_MOSS_BLOCK = umbralMossBlock;
UMBRAL_MOSS_CARPET = umbralMossCarpet;
BLUSH_MOSS_BLOCK = blushMossBlock;
BLUSH_MOSS_CARPET = blushMossCarpet;
ORANGE_MILKWEED = orangeMilkweed; ORANGE_MILKWEED = orangeMilkweed;
PINK_MILKWEED = pinkMilkweed; PINK_MILKWEED = pinkMilkweed;
BED_BUG_EGG = bedBugEggs; BED_BUG_EGG = bedBugEggs;
@@ -427,6 +486,7 @@ public final class AntarchyObjects {
HUSHWEED_BLOCK_ENTITY = hushweedBlockEntity; HUSHWEED_BLOCK_ENTITY = hushweedBlockEntity;
STINKY_GAS = stinkyGas; STINKY_GAS = stinkyGas;
STINKY_FLY = stinkyFly; STINKY_FLY = stinkyFly;
PEACH_LEAVES_PARTICLE = peachLeaves;
DOUBLE_DAMAGE_CHANCE = doubleDamageChance; DOUBLE_DAMAGE_CHANCE = doubleDamageChance;
BLOODGLASS_MAX_HEARTS = bloodglassMaxHearts; BLOODGLASS_MAX_HEARTS = bloodglassMaxHearts;
BLOODGLASS_WARD = bloodglassWard; BLOODGLASS_WARD = bloodglassWard;
@@ -444,12 +504,88 @@ public final class AntarchyObjects {
HERCULES_BEETLE = supplier; HERCULES_BEETLE = supplier;
} }
public static void setGlimmer(Supplier<EntityType<com.craisinlord.antarchy.content.entity.GlimmerEntity>> supplier) { public static void setOuranwoodDeer(Supplier<EntityType<com.craisinlord.antarchy.content.entity.OuranwoodDeerEntity>> supplier) {
OURANWOOD_DEER = supplier;
}
public static void setGlimmer(Supplier<EntityType<com.craisinlord.antarchy.content.entity.glimmer.GlimmerEntity>> supplier) {
GLIMMER = supplier; GLIMMER = supplier;
} }
public static void setGlimmersGrace(Supplier<Holder<MobEffect>> supplier) { public static void setLumen(Supplier<Fluid> supplier) {
GLIMMERS_GRACE = supplier; LUMEN = supplier;
}
public static void setFlowingLumen(Supplier<Fluid> supplier) {
FLOWING_LUMEN = supplier;
}
public static void setLumenBucket(Supplier<Item> supplier) {
LUMEN_BUCKET = supplier;
}
public static void setLumenBlock(Supplier<Block> supplier) {
LUMEN_BLOCK = supplier;
}
public static void setLumenFroglight(Supplier<Block> supplier) {
LUMEN_FROGLIGHT = supplier;
}
public static void setSpiritApple(Supplier<Item> supplier) {
SPIRIT_APPLE = supplier;
}
public static void setElka(Supplier<EntityType<com.craisinlord.antarchy.content.entity.ElkaEntity>> supplier) {
ELKA = supplier;
}
public static void setPeach(Supplier<Item> supplier) {
PEACH = supplier;
}
public static void setCorn(Supplier<Item> supplier) {
CORN = supplier;
}
public static void setCornSeeds(Supplier<Item> supplier) {
CORN_SEEDS = supplier;
}
public static void setWildCorn(Supplier<Block> supplier) {
WILD_CORN = supplier;
}
public static void setRawVenison(Supplier<Item> supplier) {
RAW_VENISON = supplier;
}
public static void setCookedVenison(Supplier<Item> supplier) {
COOKED_VENISON = supplier;
}
public static void setCookedCorndog(Supplier<Item> supplier) {
COOKED_CORNDOG = supplier;
}
public static void setGlimmerBottle(Supplier<Item> supplier) {
GLIMMER_BOTTLE = supplier;
}
public static void setPeachLeavesParticle(Supplier<SimpleParticleType> supplier) {
PEACH_LEAVES_PARTICLE = supplier;
}
public static void setGlimmerVariantComponent(Supplier<net.minecraft.core.component.DataComponentType<com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant>> supplier) {
GLIMMER_VARIANT_COMPONENT = supplier;
}
public static void setDorrieInventoryMenu(Supplier<MenuType<com.craisinlord.antarchy.content.menu.DorrieInventoryMenu>> supplier) {
DORRIE_INVENTORY_MENU = supplier;
}
public static void setAmericanComponent(Supplier<net.minecraft.core.component.DataComponentType<net.minecraft.util.Unit>> supplier) {
AMERICAN_COMPONENT = supplier;
} }
public static BlockBehaviour.Properties shellstoneProperties() { public static BlockBehaviour.Properties shellstoneProperties() {
@@ -10,6 +10,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.material.Fluid;
public final class AntarchyTags { public final class AntarchyTags {
private AntarchyTags() { private AntarchyTags() {
@@ -30,10 +31,12 @@ public final class AntarchyTags {
public static final TagKey<Block> MOLEVORE_SEE_THROUGH_BLOCKS = TagKey.create(Registries.BLOCK, id("molevore_see_through_blocks")); public static final TagKey<Block> MOLEVORE_SEE_THROUGH_BLOCKS = TagKey.create(Registries.BLOCK, id("molevore_see_through_blocks"));
public static final TagKey<Block> TERMITE_FOODS = TagKey.create(Registries.BLOCK, id("termite_foods")); public static final TagKey<Block> TERMITE_FOODS = TagKey.create(Registries.BLOCK, id("termite_foods"));
public static final TagKey<Block> UMBRAL_MOSS_REPLACEABLE = TagKey.create(Registries.BLOCK, id("umbral_moss_replaceable")); public static final TagKey<Block> UMBRAL_MOSS_REPLACEABLE = TagKey.create(Registries.BLOCK, id("umbral_moss_replaceable"));
public static final TagKey<Block> BLUSH_MOSS_REPLACEABLE = TagKey.create(Registries.BLOCK, id("blush_moss_replaceable"));
public static final TagKey<Block> HERCULES_BEETLE_CHARGE_IMMUNE_BLOCKS = TagKey.create(Registries.BLOCK, id("hercules_beetle_charge_immune_blocks")); public static final TagKey<Block> HERCULES_BEETLE_CHARGE_IMMUNE_BLOCKS = TagKey.create(Registries.BLOCK, id("hercules_beetle_charge_immune_blocks"));
public static final TagKey<Block> ELYTHIA_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("elythia_portal_frames")); public static final TagKey<Block> ELYTHIA_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("elythia_portal_frames"));
public static final TagKey<Block> THORAXIS_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("thoraxis_portal_frames")); public static final TagKey<Block> THORAXIS_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("thoraxis_portal_frames"));
public static final TagKey<Block> CAVARYN_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("cavaryn_portal_frames")); public static final TagKey<Block> CAVARYN_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("cavaryn_portal_frames"));
public static final TagKey<Block> MINERS_DREAM_CARVABLE = TagKey.create(Registries.BLOCK, id("miners_dream_carvable"));
private Blocks() { private Blocks() {
} }
@@ -94,16 +97,30 @@ public final class AntarchyTags {
public static final TagKey<Item> REVERIE_DUPLICATION_ITEMS = TagKey.create(Registries.ITEM, id("reverie_duplication_items")); public static final TagKey<Item> REVERIE_DUPLICATION_ITEMS = TagKey.create(Registries.ITEM, id("reverie_duplication_items"));
public static final TagKey<Item> HERCULES_BEETLE_FOOD = TagKey.create(Registries.ITEM, id("hercules_beetle_food")); public static final TagKey<Item> HERCULES_BEETLE_FOOD = TagKey.create(Registries.ITEM, id("hercules_beetle_food"));
public static final TagKey<Item> ROLLY_POLLY_FOOD = TagKey.create(Registries.ITEM, id("rolly_polly_food")); public static final TagKey<Item> ROLLY_POLLY_FOOD = TagKey.create(Registries.ITEM, id("rolly_polly_food"));
public static final TagKey<Item> GLIMMER_FOOD = TagKey.create(Registries.ITEM, id("glimmer_food")); public static final TagKey<Item> OURANWOOD_DEER_FOOD = TagKey.create(Registries.ITEM, id("ouranwood_deer_food"));
public static final TagKey<Item> GLIMMER_AUGMENT_APPLE_COW = TagKey.create(Registries.ITEM, id("glimmer_augment_apple_cow"));
public static final TagKey<Item> GLIMMER_AUGMENT_OURANWOOD_DEER = TagKey.create(Registries.ITEM, id("glimmer_augment_ouranwood_deer"));
public static final TagKey<Item> GLIMMER_AUGMENT_FROG = TagKey.create(Registries.ITEM, id("glimmer_augment_frog"));
public static final TagKey<Item> GLIMMER_AUGMENT_ANT = TagKey.create(Registries.ITEM, id("glimmer_augment_ant"));
public static final TagKey<Item> GLIMMER_AUGMENT_ELKA = TagKey.create(Registries.ITEM, id("glimmer_augment_elka"));
public static final TagKey<Item> HFCS_CANNOT_AMERICANIZE = TagKey.create(Registries.ITEM, id("hfcs_cannot_americanize"));
private Items() { private Items() {
} }
} }
public static final class Fluids {
public static final TagKey<Fluid> LUMEN = TagKey.create(Registries.FLUID, id("lumen"));
private Fluids() {
}
}
public static final class Biomes { public static final class Biomes {
public static final TagKey<Biome> MANTIS_SPAWN_BIOMES = TagKey.create(Registries.BIOME, id("mantis_spawn_biomes")); public static final TagKey<Biome> MANTIS_SPAWN_BIOMES = TagKey.create(Registries.BIOME, id("mantis_spawn_biomes"));
public static final TagKey<Biome> MANTIS_OVERWORLD_SPAWN_BIOMES = TagKey.create(Registries.BIOME, id("mantis_overworld_spawn_biomes")); public static final TagKey<Biome> MANTIS_OVERWORLD_SPAWN_BIOMES = TagKey.create(Registries.BIOME, id("mantis_overworld_spawn_biomes"));
public static final TagKey<Biome> ELYTHIA_FIREFLY_PARTICLE_BIOMES = TagKey.create(Registries.BIOME, id("elythia_firefly_particle_biomes")); public static final TagKey<Biome> ELYTHIA_FIREFLY_PARTICLE_BIOMES = TagKey.create(Registries.BIOME, id("elythia_firefly_particle_biomes"));
public static final TagKey<Biome> WILD_CORN_SPAWN_BIOMES = TagKey.create(Registries.BIOME, id("wild_corn_spawn_biomes"));
private Biomes() { private Biomes() {
} }
@@ -0,0 +1,315 @@
package com.craisinlord.antarchy.content;
import java.util.Comparator;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.Item;
public final class CreativeTabOrder {
private CreativeTabOrder() {}
public static final Comparator<Item> COMPARATOR =
Comparator.comparingInt(CreativeTabOrder::group)
.thenComparingInt(CreativeTabOrder::subOrder);
public static int group(Item item) {
String path = BuiltInRegistries.ITEM.getKey(item).getPath();
return switch (path) {
case "glimmer_bottle" -> -1;
case "ouranwood_log", "ouranwood_wood", "mossy_ouranwood_log", "mossy_ouranwood_wood",
"stripped_ouranwood_log", "stripped_ouranwood_wood", "duplicator_log",
"ouranwood_planks", "ouranwood_stairs", "ouranwood_slab",
"ouranwood_fence", "ouranwood_fence_gate",
"ouranwood_door", "ouranwood_trapdoor",
"ouranwood_pressure_plate", "ouranwood_button",
"ouranwood_sign", "ouranwood_hanging_sign",
"ouranwood_boat", "ouranwood_chest_boat",
"peach_log", "peach_wood", "stripped_peach_log", "stripped_peach_wood",
"peach_planks", "peach_stairs", "peach_slab", "peach_fence", "peach_fence_gate",
"peach_door", "peach_trapdoor", "peach_pressure_plate", "peach_button",
"peach_sign", "peach_hanging_sign", "peach_boat", "peach_chest_boat" -> 0;
case "nyxite", "polished_nyxite", "chiseled_nyxite", "nyxite_bricks",
"nyxite_stairs", "nyxite_slab", "nyxite_wall",
"polished_nyxite_stairs", "polished_nyxite_slab", "polished_nyxite_wall",
"nyxite_brick_stairs", "nyxite_brick_slab", "nyxite_brick_wall",
"pale_nyxite", "nyxite_spike", "potent_nyxite" -> 1;
case "shellstone", "polished_shellstone", "shellstone_bricks", "chiseled_shellstone",
"mossy_shellstone_bricks", "cracked_shellstone_bricks",
"shellstone_stairs", "shellstone_slab", "shellstone_wall",
"polished_shellstone_stairs", "polished_shellstone_slab", "polished_shellstone_wall",
"shellstone_brick_stairs", "shellstone_brick_slab", "shellstone_brick_wall",
"mossy_shellstone_brick_stairs", "mossy_shellstone_brick_slab", "mossy_shellstone_brick_wall" -> 2;
case "antimetal", "polished_antimetal" -> 3;
case "dream_sand", "dream_sandstone", "chiseled_dream_sandstone",
"cut_dream_sandstone", "smooth_dream_sandstone",
"dream_sandstone_stairs", "dream_sandstone_slab", "dream_sandstone_wall",
"smooth_dream_sandstone_stairs", "smooth_dream_sandstone_slab",
"cut_dream_sandstone_slab" -> 4;
case "umbral_moss_block", "umbral_moss_carpet",
"blush_moss_block", "blush_moss_carpet" -> 4;
case "uranium_ore", "deepslate_uranium_ore", "titanium_ore", "deepslate_titanium_ore",
"uranium_block", "titanium_block",
"cut_uranium", "cut_titanium", "cut_uranium_slab", "cut_titanium_slab",
"cut_uranium_stairs", "cut_titanium_stairs",
"chiseled_uranium", "chiseled_titanium",
"uranium_door", "titanium_door", "uranium_trapdoor", "titanium_trapdoor",
"uranium_bars", "titanium_bars", "uranium_bulb", "titanium_bulb" -> 6;
case "raw_uranium", "raw_titanium", "raw_uranium_scrap", "raw_titanium_scrap",
"raw_uranium_block", "raw_titanium_block" -> 7;
case "blood_crystal_block", "small_blood_crystal_bud", "medium_blood_crystal_bud",
"large_blood_crystal_bud", "budding_blood_crystal", "blood_crystal_cluster",
"myrmite", "broodstone", "chiten_block", "chiten_spike", "amber_moss_block", "amber_moss_carpet" -> 8;
case "ouranwood_leaves", "peach_leaves" -> 9;
case "duplicator_sapling", "ouranwood_acorn", "peach_sapling", "corn_seeds" -> 10;
case "orange_milkweed", "pink_milkweed", "hushweed", "amber_lichen", "creepvine",
"triffid_goo_block", "cloud_block", "wasp_nest",
"red_ant_nest", "brown_ant_nest", "rainbow_ant_nest", "termite_nest" -> 11;
case "dream_torch", "dream_lantern", "dream_campfire", "dream_fire", "dream_fire_ceiling",
"lumen_froglight", "roseate_froglight" -> 12;
case "antimetal_scaffolding" -> 13;
case "infested_rooted_dirt", "infested_coarse_dirt", "bed_bug_egg",
"creeping_horror_egg", "lurking_terror_egg" -> 14;
case "ultimate_pickaxe", "ultimate_axe", "ultimate_shovel", "ultimate_hoe",
"duct_tape", "diamond_minecart", "gravity_sculk_sensor", "miners_dream" -> 15;
case "bile_bucket", "ichor_bucket", "antiwater_bucket", "cloud_bucket", "lumen_bucket" -> 16;
case "moggles", "brutalfly_elytra", "fallen_king_crown", "jumpy_boots" -> 17;
case "battle_axe", "basilisk_dagger", "squidzooka", "shrink_ray", "growth_ray",
"gravity_gun", "water_cannon", "nightmare_sword", "lucid_pearl", "scorpion_whip", "blood_crystal_katana",
"big_bertha", "big_bertha_blade", "big_bertha_handle", "big_bertha_hilt",
"ultimate_sword", "ultimate_mace" -> 18;
case "primordial_helmet", "primordial_chestplate",
"primordial_leggings", "primordial_boots",
"blood_crystal_helmet", "blood_crystal_chestplate",
"blood_crystal_leggings", "blood_crystal_boots",
"nightmare_helmet", "nightmare_chestplate",
"nightmare_leggings", "nightmare_boots",
"ultimate_helmet", "ultimate_chestplate",
"ultimate_leggings", "ultimate_boots" -> 19;
case "ultimate_bow", "ultimate_crossbow" -> 20;
case "cloud_shark_fin_soup", "mud_pie", "rainbow_sugar",
"blood_crystal_apple", "spirit_apple", "peach", "raw_corndog", "cooked_corndog",
"raw_venison", "cooked_venison" -> 21;
case "blood_crystal_shard", "cloud_shark_fin", "basilisk_fang", "kraken_tooth",
"emperor_scorpion_stinger", "nightmare_scale", "molevore_nose",
"moleworm", "uranium_nugget", "titanium_nugget",
"uranium_ingot", "titanium_ingot", "lucid_eye",
"mantis_claw", "jumpy_bug_leg", "brutalfly_wing", "cornea_ear",
"primordial_scute", "triffid_goo", "vortex_eye",
"king_scale", "queen_scale", "chiten", "stink_bug", "corn", "high_fructose_corn_syrup" -> 22;
case "reverie_bottle", "root_beer" -> 24;
case "easter_bunny_spawn_egg", "flying_squirrel_spawn_egg", "caterpillar_spawn_egg",
"butterfly_spawn_egg", "reverie_spawn_egg", "brutalfly_spawn_egg",
"red_ant_spawn_egg", "brown_ant_spawn_egg", "rainbow_ant_spawn_egg", "termite_spawn_egg",
"moleworm_spawn_egg", "mantis_spawn_egg", "alpha_mantis_spawn_egg", "rolly_polly_spawn_egg",
"molevore_spawn_egg", "triffid_spawn_egg", "apple_cow_spawn_egg", "golden_apple_cow_spawn_egg",
"enchanted_golden_apple_cow_spawn_egg", "dr_trayaurus_spawn_egg", "wasp_spawn_egg",
"bomber_spawn_egg", "jumpy_bug_spawn_egg", "stink_bug_spawn_egg", "cloud_shark_spawn_egg",
"kraken_spawn_egg", "missile_squid_spawn_egg", "octopus_bomb_spawn_egg",
"nightmare_spawn_egg", "bed_bug_spawn_egg", "lucid_spawn_egg", "scorpion_spawn_egg",
"basilisk_spawn_egg", "emperor_scorpion_spawn_egg", "toreterror_spawn_egg",
"dorrie_spawn_egg", "glimmer_spawn_egg", "ouranwood_deer_spawn_egg", "elka_spawn_egg",
"hercules_beetle_spawn_egg", "creeping_horror_spawn_egg", "lurking_terror_spawn_egg" -> 25;
default -> 99;
};
}
public static int subOrder(Item item) {
String path = BuiltInRegistries.ITEM.getKey(item).getPath();
return switch (path) {
case "ouranwood_log" -> 0;
case "ouranwood_wood" -> 1;
case "mossy_ouranwood_log" -> 2;
case "mossy_ouranwood_wood" -> 3;
case "stripped_ouranwood_log" -> 4;
case "stripped_ouranwood_wood" -> 5;
case "duplicator_log" -> 6;
case "ouranwood_planks" -> 7;
case "ouranwood_stairs" -> 8;
case "ouranwood_slab" -> 9;
case "ouranwood_fence" -> 10;
case "ouranwood_fence_gate" -> 11;
case "ouranwood_door" -> 12;
case "ouranwood_trapdoor" -> 13;
case "ouranwood_pressure_plate" -> 14;
case "ouranwood_button" -> 15;
case "ouranwood_sign" -> 16;
case "ouranwood_hanging_sign" -> 17;
case "ouranwood_boat" -> 18;
case "ouranwood_chest_boat" -> 19;
case "peach_log" -> 20;
case "peach_wood" -> 21;
case "stripped_peach_log" -> 22;
case "stripped_peach_wood" -> 23;
case "peach_planks" -> 24;
case "peach_stairs" -> 25;
case "peach_slab" -> 26;
case "peach_fence" -> 27;
case "peach_fence_gate" -> 28;
case "peach_door" -> 29;
case "peach_trapdoor" -> 30;
case "peach_pressure_plate" -> 31;
case "peach_button" -> 32;
case "peach_sign" -> 33;
case "peach_hanging_sign" -> 34;
case "peach_boat" -> 35;
case "peach_chest_boat" -> 36;
case "ouranwood_leaves" -> 0;
case "peach_leaves" -> 1;
case "duplicator_sapling" -> 0;
case "ouranwood_acorn" -> 1;
case "peach_sapling" -> 2;
case "corn_seeds" -> 3;
case "orange_milkweed" -> 0;
case "pink_milkweed" -> 1;
case "hushweed" -> 2;
case "amber_lichen" -> 3;
case "creepvine" -> 4;
case "triffid_goo_block" -> 5;
case "cloud_block" -> 6;
case "wasp_nest" -> 7;
case "red_ant_nest" -> 8;
case "brown_ant_nest" -> 9;
case "rainbow_ant_nest" -> 10;
case "termite_nest" -> 11;
case "blood_crystal_block" -> 0;
case "small_blood_crystal_bud" -> 1;
case "medium_blood_crystal_bud" -> 2;
case "large_blood_crystal_bud" -> 3;
case "budding_blood_crystal" -> 4;
case "blood_crystal_cluster" -> 5;
case "myrmite" -> 6;
case "broodstone" -> 7;
case "chiten_block" -> 8;
case "chiten_spike" -> 9;
case "amber_moss_block" -> 10;
case "amber_moss_carpet" -> 11;
case "uranium_ore" -> 0;
case "deepslate_uranium_ore" -> 1;
case "titanium_ore" -> 2;
case "deepslate_titanium_ore" -> 3;
case "uranium_block" -> 4;
case "titanium_block" -> 5;
case "cut_uranium" -> 6;
case "cut_titanium" -> 7;
case "cut_uranium_slab" -> 8;
case "cut_titanium_slab" -> 9;
case "cut_uranium_stairs" -> 10;
case "cut_titanium_stairs" -> 11;
case "chiseled_uranium" -> 12;
case "chiseled_titanium" -> 13;
case "uranium_door" -> 14;
case "titanium_door" -> 15;
case "uranium_trapdoor" -> 16;
case "titanium_trapdoor" -> 17;
case "uranium_bars" -> 18;
case "titanium_bars" -> 19;
case "uranium_bulb" -> 20;
case "titanium_bulb" -> 21;
case "raw_uranium" -> 0;
case "raw_titanium" -> 1;
case "raw_uranium_scrap" -> 2;
case "raw_titanium_scrap" -> 3;
case "raw_uranium_block" -> 4;
case "raw_titanium_block" -> 5;
case "dream_torch" -> 0;
case "dream_lantern" -> 1;
case "dream_campfire" -> 2;
case "dream_fire" -> 3;
case "dream_fire_ceiling" -> 4;
case "lumen_froglight" -> 5;
case "roseate_froglight" -> 6;
case "infested_rooted_dirt" -> 0;
case "infested_coarse_dirt" -> 1;
case "bed_bug_egg" -> 2;
case "creeping_horror_egg" -> 3;
case "lurking_terror_egg" -> 4;
case "ultimate_pickaxe" -> 0;
case "ultimate_axe" -> 1;
case "ultimate_shovel" -> 2;
case "ultimate_hoe" -> 3;
case "duct_tape" -> 4;
case "diamond_minecart" -> 5;
case "gravity_sculk_sensor" -> 6;
case "miners_dream" -> 7;
case "bile_bucket" -> 0;
case "ichor_bucket" -> 1;
case "antiwater_bucket" -> 2;
case "cloud_bucket" -> 3;
case "lumen_bucket" -> 4;
case "moggles" -> 0;
case "brutalfly_elytra" -> 1;
case "fallen_king_crown" -> 2;
case "jumpy_boots" -> 3;
case "battle_axe" -> 0;
case "basilisk_dagger" -> 1;
case "squidzooka" -> 2;
case "shrink_ray" -> 3;
case "growth_ray" -> 4;
case "gravity_gun" -> 5;
case "water_cannon" -> 6;
case "nightmare_sword" -> 7;
case "lucid_pearl" -> 8;
case "scorpion_whip" -> 9;
case "blood_crystal_katana" -> 10;
case "big_bertha" -> 11;
case "big_bertha_blade" -> 12;
case "big_bertha_handle" -> 13;
case "big_bertha_hilt" -> 14;
case "ultimate_sword" -> 15;
case "ultimate_mace" -> 16;
case "primordial_helmet" -> 0;
case "primordial_chestplate" -> 1;
case "primordial_leggings" -> 2;
case "primordial_boots" -> 3;
case "blood_crystal_helmet" -> 4;
case "blood_crystal_chestplate" -> 5;
case "blood_crystal_leggings" -> 6;
case "blood_crystal_boots" -> 7;
case "nightmare_helmet" -> 8;
case "nightmare_chestplate" -> 9;
case "nightmare_leggings" -> 10;
case "nightmare_boots" -> 11;
case "ultimate_helmet" -> 12;
case "ultimate_chestplate" -> 13;
case "ultimate_leggings" -> 14;
case "ultimate_boots" -> 15;
case "ultimate_bow" -> 0;
case "ultimate_crossbow" -> 1;
case "blood_crystal_apple" -> 0;
case "spirit_apple" -> 1;
case "peach" -> 2;
case "raw_corndog" -> 3;
case "cooked_corndog" -> 4;
case "raw_venison" -> 5;
case "cooked_venison" -> 6;
case "reverie_bottle" -> 0;
case "root_beer" -> 1;
case "blood_crystal_shard" -> 0;
case "cloud_shark_fin" -> 1;
case "basilisk_fang" -> 2;
case "kraken_tooth" -> 3;
case "emperor_scorpion_stinger" -> 4;
case "nightmare_scale" -> 5;
case "molevore_nose" -> 6;
case "moleworm" -> 7;
case "uranium_nugget" -> 8;
case "titanium_nugget" -> 9;
case "uranium_ingot" -> 10;
case "titanium_ingot" -> 11;
case "lucid_eye" -> 12;
case "mantis_claw" -> 13;
case "jumpy_bug_leg" -> 14;
case "brutalfly_wing" -> 15;
case "cornea_ear" -> 16;
case "primordial_scute" -> 17;
case "triffid_goo" -> 18;
case "vortex_eye" -> 19;
case "king_scale" -> 20;
case "queen_scale" -> 21;
case "chiten" -> 22;
case "stink_bug" -> 23;
case "corn" -> 24;
case "high_fructose_corn_syrup" -> 25;
default -> 0;
};
}
}
@@ -0,0 +1,119 @@
package com.craisinlord.antarchy.content;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant;
import com.craisinlord.antarchy.content.item.GlimmerBottleItem;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.alchemy.PotionContents;
import net.minecraft.world.item.alchemy.Potions;
public final class CustomBrewingRecipes {
private static final ResourceLocation CORN_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "corn");
private static final ResourceLocation HIGH_FRUCTOSE_CORN_SYRUP_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "high_fructose_corn_syrup");
private static final ResourceLocation ROOT_BEER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "root_beer");
private static final TagKey<Item> ROOTS = TagKey.create(net.minecraft.core.registries.Registries.ITEM, ResourceLocation.fromNamespaceAndPath("antarchy", "roots"));
private CustomBrewingRecipes() {
}
public static boolean hasMix(ItemStack input, ItemStack ingredient) {
return !getOutput(input, ingredient).isEmpty();
}
public static ItemStack getOutput(ItemStack input, ItemStack ingredient) {
if (input.isEmpty() || ingredient.isEmpty()) {
return ItemStack.EMPTY;
}
if (matchesCornToSyrup(input, ingredient) || matchesCornToSyrup(ingredient, input)) {
return new ItemStack(highFructoseCornSyrupItem());
}
if (matchesSyrupToRootBeer(input, ingredient) || matchesSyrupToRootBeer(ingredient, input)) {
return new ItemStack(rootBeerItem());
}
ItemStack glimmerAugment = getGlimmerAugmentOutput(input, ingredient);
if (!glimmerAugment.isEmpty()) {
return glimmerAugment;
}
return ItemStack.EMPTY;
}
private static ItemStack getGlimmerAugmentOutput(ItemStack input, ItemStack ingredient) {
if (!input.is(AntarchyObjects.GLIMMER_BOTTLE.get())) {
return ItemStack.EMPTY;
}
GlimmerVariant target = glimmerAugmentTarget(ingredient);
if (target == null || target == GlimmerBottleItem.getVariant(input)) {
return ItemStack.EMPTY;
}
return GlimmerBottleItem.create(target);
}
private static GlimmerVariant glimmerAugmentTarget(ItemStack ingredient) {
if (ingredient.is(AntarchyTags.Items.GLIMMER_AUGMENT_APPLE_COW)) {
return GlimmerVariant.APPLE_COW;
}
if (ingredient.is(AntarchyTags.Items.GLIMMER_AUGMENT_OURANWOOD_DEER)) {
return GlimmerVariant.OURANWOOD_DEER;
}
if (ingredient.is(AntarchyTags.Items.GLIMMER_AUGMENT_FROG)) {
return GlimmerVariant.FROG;
}
if (ingredient.is(AntarchyTags.Items.GLIMMER_AUGMENT_ANT)) {
return GlimmerVariant.ANT;
}
if (ingredient.is(AntarchyTags.Items.GLIMMER_AUGMENT_ELKA)) {
return GlimmerVariant.ELKA;
}
return null;
}
private static boolean matchesCornToSyrup(ItemStack base, ItemStack ingredient) {
return isWaterBottle(base) && ingredient.is(cornItem());
}
private static boolean matchesSyrupToRootBeer(ItemStack base, ItemStack ingredient) {
return base.is(highFructoseCornSyrupItem()) && ingredient.is(ROOTS);
}
private static boolean isWaterBottle(ItemStack stack) {
if (!stack.is(Items.POTION)) {
return false;
}
PotionContents contents = stack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
return contents.is(Potions.WATER);
}
public static Item cornItem() {
return BuiltInRegistries.ITEM.get(CORN_ID);
}
public static Item highFructoseCornSyrupItem() {
return BuiltInRegistries.ITEM.get(HIGH_FRUCTOSE_CORN_SYRUP_ID);
}
public static Item rootBeerItem() {
return BuiltInRegistries.ITEM.get(ROOT_BEER_ID);
}
public static TagKey<Item> rootsTag() {
return ROOTS;
}
public static ItemStack waterBottleStack() {
ItemStack stack = new ItemStack(Items.POTION);
stack.set(DataComponents.POTION_CONTENTS, new PotionContents(Potions.WATER));
return stack;
}
}
@@ -1,291 +0,0 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Multiblock bush grown with bonemeal. The origin block carries the
* loot/age; the rest of the footprint is filled with invisible
* {@link BigBushPartBlock}s.
*
* age 0: single block (1x1x1)
* age 1: 2x2x2 footprint; FACING + FACING.getClockWise() pick the horizontal quadrant
* age 2: 3x3x3 footprint centered on the origin column
*
* Grown stages require a sturdy support block under every column of their
* footprint. Growing from 2x2 to 3x3 may relocate the origin to any of the four
* occupied columns to find room.
*/
public class BigBushBlock extends BushBlock implements BonemealableBlock {
public static final MapCodec<BigBushBlock> CODEC = Block.simpleCodec(BigBushBlock::new);
public static final IntegerProperty AGE = BlockStateProperties.AGE_2;
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
private static final VoxelShape SMALL_SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 13.0, 14.0);
public BigBushBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any()
.setValue(AGE, 0)
.setValue(FACING, Direction.EAST));
}
@Override
public MapCodec<BigBushBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(AGE, FACING);
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return state.getValue(AGE) == 0 ? SMALL_SHAPE : Shapes.block();
}
/** All part positions belonging to a bush with the given origin state/pos */
public static List<BlockPos> partPositions(BlockState state, BlockPos origin) {
List<BlockPos> positions = new ArrayList<>();
int age = state.getValue(AGE);
if (age == 1) {
Direction forward = state.getValue(FACING);
Direction side = forward.getClockWise();
for (int dy = 0; dy <= 1; dy++) {
for (BlockPos column : new BlockPos[]{
origin, origin.relative(forward), origin.relative(side), origin.relative(forward).relative(side)}) {
BlockPos pos = column.above(dy);
if (!pos.equals(origin)) {
positions.add(pos);
}
}
}
} else if (age == 2) {
for (int dx = -1; dx <= 1; dx++) {
for (int dy = 0; dy <= 2; dy++) {
for (int dz = -1; dz <= 1; dz++) {
if (dx == 0 && dy == 0 && dz == 0) {
continue;
}
positions.add(origin.offset(dx, dy, dz));
}
}
}
}
return positions;
}
/** Ground columns of the footprint, each needs a sturdy block below it. */
public static List<BlockPos> supportColumns(BlockState state, BlockPos origin) {
List<BlockPos> columns = new ArrayList<>();
int age = state.getValue(AGE);
if (age == 1) {
Direction forward = state.getValue(FACING);
Direction side = forward.getClockWise();
columns.add(origin);
columns.add(origin.relative(forward));
columns.add(origin.relative(side));
columns.add(origin.relative(forward).relative(side));
} else if (age == 2) {
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
columns.add(origin.offset(dx, 0, dz));
}
}
} else {
columns.add(origin);
}
return columns;
}
public static boolean hasFullSupport(BlockState state, LevelReader level, BlockPos origin) {
for (BlockPos column : supportColumns(state, origin)) {
BlockPos below = column.below();
if (!level.getBlockState(below).isFaceSturdy(level, below, Direction.UP)) {
return false;
}
}
return true;
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
if (state.getValue(AGE) == 0) {
return super.canSurvive(state, level, pos);
}
return hasFullSupport(state, level, pos);
}
private static Block partBlock() {
return BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "big_bush_part"));
}
private record GrowthTarget(BlockPos origin, BlockState state) {}
@Nullable
private GrowthTarget findGrowthTarget(LevelReader level, BlockPos pos, BlockState state, RandomSource random) {
int age = state.getValue(AGE);
if (age == 0) {
List<Direction> facings = new ArrayList<>(Direction.Plane.HORIZONTAL.stream().toList());
for (int i = facings.size() - 1; i > 0; i--) {
Direction swap = facings.set(random.nextInt(i + 1), facings.get(i));
facings.set(i, swap);
}
for (Direction facing : facings) {
BlockState grown = state.setValue(AGE, 1).setValue(FACING, facing);
if (canGrowTo(level, pos, grown, Set.of(pos))) {
return new GrowthTarget(pos, grown);
}
}
return null;
}
if (age == 1) {
Direction forward = state.getValue(FACING);
Direction side = forward.getClockWise();
Set<BlockPos> owned = new HashSet<>(partPositions(state, pos));
owned.add(pos);
BlockState grown = state.setValue(AGE, 2);
// Any of the four occupied columns can become the new center of the 3x3.
for (BlockPos candidate : new BlockPos[]{
pos, pos.relative(forward).relative(side), pos.relative(forward), pos.relative(side)}) {
if (canGrowTo(level, candidate, grown, owned)) {
return new GrowthTarget(candidate, grown);
}
}
}
return null;
}
private boolean canGrowTo(LevelReader level, BlockPos newOrigin, BlockState grownState, Set<BlockPos> owned) {
if (!hasFullSupport(grownState, level, newOrigin)) {
return false;
}
List<BlockPos> positions = new ArrayList<>(partPositions(grownState, newOrigin));
positions.add(newOrigin);
for (BlockPos pos : positions) {
if (owned.contains(pos)) {
continue;
}
if (!level.getBlockState(pos).canBeReplaced()) {
return false;
}
}
return true;
}
public static void placeBushBlocks(LevelAccessor level, BlockPos origin, BlockState grownState, int flags) {
level.setBlock(origin, grownState, flags);
Block part = partBlock();
BlockPos capPos = grownState.getValue(AGE) == 2 ? origin.above(2) : null;
for (BlockPos pos : partPositions(grownState, origin)) {
level.setBlock(pos, part.defaultBlockState()
.setValue(BigBushPartBlock.CAP, pos.equals(capPos)), flags);
}
}
private void placeStage(ServerLevel level, BlockPos origin, BlockState grownState) {
placeBushBlocks(level, origin, grownState, 3);
}
@Override
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock())) {
for (BlockPos partPos : partPositions(state, pos)) {
if (level.getBlockState(partPos).getBlock() instanceof BigBushPartBlock) {
level.setBlock(partPos, Blocks.AIR.defaultBlockState(), 35);
}
}
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
return state.getValue(AGE) < 2;
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return findGrowthTarget(level, pos, state, random) != null;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
GrowthTarget target = findGrowthTarget(level, pos, state, random);
if (target == null) {
return;
}
if (!target.origin().equals(pos)) {
level.removeBlock(pos, false);
}
placeStage(level, target.origin(), target.state());
}
/** Pops a bonus item when bone meal is used on a fully-grown (age 2) bush; shared with {@link BigBushPartBlock}. */
public static boolean grantBonemealBonus(Level level, BlockPos originPos, BlockState originState, Player player, ItemStack stack) {
if (!(originState.getBlock() instanceof BigBushBlock bigBush) || originState.getValue(AGE) != 2) {
return false;
}
if (!level.isClientSide) {
bigBush.popResource(level, originPos, new ItemStack(bigBush, 1));
if (!player.getAbilities().instabuild) {
stack.shrink(1);
}
level.levelEvent(1505, originPos, 15);
}
return true;
}
@Override
protected ItemInteractionResult useItemOn(
ItemStack stack,
BlockState state,
Level level,
BlockPos pos,
Player player,
InteractionHand hand,
BlockHitResult hitResult
) {
if (stack.is(Items.BONE_MEAL) && grantBonemealBonus(level, pos, state, player, stack)) {
return ItemInteractionResult.sidedSuccess(level.isClientSide);
}
return super.useItemOn(stack, state, level, pos, player, hand, hitResult);
}
}
@@ -1,177 +0,0 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
/**
* Invisible filler block for the grown {@link BigBushBlock} footprint. Breaking a
* part destroys the whole bush (drops come from the origin's loot table). The one
* part directly two above the origin of a stage-3 bush renders the top slice of
* the oversized cross model
*/
public class BigBushPartBlock extends Block implements BonemealableBlock {
public static final MapCodec<BigBushPartBlock> CODEC = Block.simpleCodec(BigBushPartBlock::new);
public static final BooleanProperty CAP = BooleanProperty.create("cap");
public BigBushPartBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(CAP, false));
}
@Override
public MapCodec<BigBushPartBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(CAP);
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return Shapes.block();
}
/** Finds the origin bush this part belongs to, or null if orphaned. */
@Nullable
public static BlockPos findOrigin(LevelReader level, BlockPos partPos) {
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -2; dy <= 0; dy++) {
for (int dz = -1; dz <= 1; dz++) {
if (dx == 0 && dy == 0 && dz == 0) {
continue;
}
BlockPos candidate = partPos.offset(dx, dy, dz);
BlockState state = level.getBlockState(candidate);
if (state.getBlock() instanceof BigBushBlock
&& BigBushBlock.partPositions(state, candidate).contains(partPos)) {
return candidate;
}
}
}
}
return null;
}
@Override
public BlockState playerWillDestroy(Level level, BlockPos pos, BlockState state, Player player) {
if (!level.isClientSide) {
BlockPos origin = findOrigin(level, pos);
if (origin != null) {
BlockState originState = level.getBlockState(origin);
if (!player.getAbilities().instabuild) {
Block.dropResources(originState, level, origin, null, player, player.getMainHandItem());
}
level.destroyBlock(origin, false);
}
}
return super.playerWillDestroy(level, pos, state, player);
}
@Override
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock()) && !level.isClientSide) {
BlockPos origin = findOrigin(level, pos);
if (origin != null) {
level.destroyBlock(origin, true);
}
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState,
LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
level.scheduleTick(pos, this, 1);
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
BlockPos origin = findOrigin(level, pos);
if (origin == null) {
level.removeBlock(pos, false);
return;
}
BlockState originState = level.getBlockState(origin);
if (!originState.canSurvive(level, origin)) {
level.destroyBlock(origin, true);
}
}
/** Bonemeal applied to any part of a growing bush grows the whole thing, same as clicking the origin. */
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
BlockPos origin = findOrigin(level, pos);
return origin != null && level.getBlockState(origin).getBlock() instanceof BigBushBlock bigBush
&& bigBush.isValidBonemealTarget(level, origin, level.getBlockState(origin));
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
BlockPos origin = findOrigin(level, pos);
return origin != null && level.getBlockState(origin).getBlock() instanceof BigBushBlock bigBush
&& bigBush.isBonemealSuccess(level, random, origin, level.getBlockState(origin));
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
BlockPos origin = findOrigin(level, pos);
if (origin != null && level.getBlockState(origin).getBlock() instanceof BigBushBlock bigBush) {
bigBush.performBonemeal(level, random, origin, level.getBlockState(origin));
}
}
@Override
protected ItemInteractionResult useItemOn(
ItemStack stack,
BlockState state,
Level level,
BlockPos pos,
Player player,
InteractionHand hand,
BlockHitResult hitResult
) {
if (stack.is(Items.BONE_MEAL)) {
BlockPos origin = findOrigin(level, pos);
if (origin != null && BigBushBlock.grantBonemealBonus(level, origin, level.getBlockState(origin), player, stack)) {
return ItemInteractionResult.sidedSuccess(level.isClientSide);
}
}
return super.useItemOn(stack, state, level, pos, player, hand, hitResult);
}
@Override
public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) {
BlockPos origin = findOrigin(level, pos);
if (origin != null) {
return new ItemStack(level.getBlockState(origin).getBlock());
}
return ItemStack.EMPTY;
}
}
@@ -0,0 +1,53 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
public class BlushMossBlock extends Block implements BonemealableBlock {
public static final MapCodec<BlushMossBlock> CODEC = Block.simpleCodec(BlushMossBlock::new);
private static final ResourceKey<ConfiguredFeature<?, ?>> BLUSH_MOSS_PATCH_BONEMEAL = ResourceKey.create(
Registries.CONFIGURED_FEATURE,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "blush_moss_patch_bonemeal")
);
public BlushMossBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<BlushMossBlock> codec() {
return CODEC;
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
return level.getBlockState(pos.above()).isAir();
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
Holder.Reference<ConfiguredFeature<?, ?>> feature = level.registryAccess()
.lookupOrThrow(Registries.CONFIGURED_FEATURE)
.getOrThrow(BLUSH_MOSS_PATCH_BONEMEAL);
feature.value().place(level, level.getChunkSource().getGenerator(), random, pos);
}
}
@@ -0,0 +1,136 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.WallSide;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
public class BlushMossCarpetBlock extends Block {
public static final MapCodec<BlushMossCarpetBlock> CODEC = Block.simpleCodec(BlushMossCarpetBlock::new);
public static final BooleanProperty BOTTOM = BlockStateProperties.BOTTOM;
public static final net.minecraft.world.level.block.state.properties.EnumProperty<WallSide> NORTH_WALL = BlockStateProperties.NORTH_WALL;
public static final net.minecraft.world.level.block.state.properties.EnumProperty<WallSide> EAST_WALL = BlockStateProperties.EAST_WALL;
public static final net.minecraft.world.level.block.state.properties.EnumProperty<WallSide> SOUTH_WALL = BlockStateProperties.SOUTH_WALL;
public static final net.minecraft.world.level.block.state.properties.EnumProperty<WallSide> WEST_WALL = BlockStateProperties.WEST_WALL;
private static final VoxelShape FLOOR_SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 1.0D, 16.0D);
private static final VoxelShape NORTH_LOW = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 6.0D, 1.0D);
private static final VoxelShape NORTH_TALL = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 16.0D, 1.0D);
private static final VoxelShape EAST_LOW = Block.box(15.0D, 0.0D, 0.0D, 16.0D, 6.0D, 16.0D);
private static final VoxelShape EAST_TALL = Block.box(15.0D, 0.0D, 0.0D, 16.0D, 16.0D, 16.0D);
private static final VoxelShape SOUTH_LOW = Block.box(0.0D, 0.0D, 15.0D, 16.0D, 6.0D, 16.0D);
private static final VoxelShape SOUTH_TALL = Block.box(0.0D, 0.0D, 15.0D, 16.0D, 16.0D, 16.0D);
private static final VoxelShape WEST_LOW = Block.box(0.0D, 0.0D, 0.0D, 1.0D, 6.0D, 16.0D);
private static final VoxelShape WEST_TALL = Block.box(0.0D, 0.0D, 0.0D, 1.0D, 16.0D, 16.0D);
public BlushMossCarpetBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any()
.setValue(BOTTOM, true)
.setValue(NORTH_WALL, WallSide.NONE)
.setValue(EAST_WALL, WallSide.NONE)
.setValue(SOUTH_WALL, WallSide.NONE)
.setValue(WEST_WALL, WallSide.NONE));
}
@Override
public MapCodec<BlushMossCarpetBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(BOTTOM, NORTH_WALL, EAST_WALL, SOUTH_WALL, WEST_WALL);
}
@Override
public BlockState getStateForPlacement(net.minecraft.world.item.context.BlockPlaceContext context) {
BlockPos pos = context.getClickedPos();
Direction face = context.getClickedFace();
BlockState state = this.defaultBlockState()
.setValue(BOTTOM, face == Direction.UP)
.setValue(NORTH_WALL, WallSide.NONE)
.setValue(EAST_WALL, WallSide.NONE)
.setValue(SOUTH_WALL, WallSide.NONE)
.setValue(WEST_WALL, WallSide.NONE);
if (!state.getValue(BOTTOM)) {
state = switch (face) {
case NORTH -> state.setValue(NORTH_WALL, WallSide.TALL);
case EAST -> state.setValue(EAST_WALL, WallSide.TALL);
case SOUTH -> state.setValue(SOUTH_WALL, WallSide.TALL);
case WEST -> state.setValue(WEST_WALL, WallSide.TALL);
default -> state;
};
}
return state;
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
if (state.getValue(BOTTOM)) {
return level.getBlockState(pos.below()).isFaceSturdy(level, pos.below(), Direction.UP);
}
if (supportsWall(level, pos, Direction.NORTH, state.getValue(NORTH_WALL))) {
return true;
}
if (supportsWall(level, pos, Direction.EAST, state.getValue(EAST_WALL))) {
return true;
}
if (supportsWall(level, pos, Direction.SOUTH, state.getValue(SOUTH_WALL))) {
return true;
}
if (supportsWall(level, pos, Direction.WEST, state.getValue(WEST_WALL))) {
return true;
}
return level.getBlockState(pos.above()).isFaceSturdy(level, pos.above(), Direction.DOWN);
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
return state.canSurvive(level, pos) ? state : net.minecraft.world.level.block.Blocks.AIR.defaultBlockState();
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
if (state.getValue(BOTTOM)) {
return FLOOR_SHAPE;
}
VoxelShape shape = Shapes.empty();
shape = combine(shape, state.getValue(NORTH_WALL), NORTH_LOW, NORTH_TALL);
shape = combine(shape, state.getValue(EAST_WALL), EAST_LOW, EAST_TALL);
shape = combine(shape, state.getValue(SOUTH_WALL), SOUTH_LOW, SOUTH_TALL);
shape = combine(shape, state.getValue(WEST_WALL), WEST_LOW, WEST_TALL);
return shape.isEmpty() ? FLOOR_SHAPE : shape;
}
private static boolean supportsWall(LevelReader level, BlockPos pos, Direction direction, WallSide side) {
if (side == WallSide.NONE) {
return false;
}
BlockPos supportPos = pos.relative(direction);
return level.getBlockState(supportPos).isFaceSturdy(level, supportPos, direction.getOpposite());
}
private static VoxelShape combine(VoxelShape current, WallSide side, VoxelShape lowShape, VoxelShape tallShape) {
if (side == WallSide.NONE) {
return current;
}
return Shapes.or(current, side == WallSide.TALL ? tallShape : lowShape);
}
}
@@ -0,0 +1,179 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import java.util.Optional;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class CornCropBlock extends BushBlock implements BonemealableBlock {
public static final MapCodec<CornCropBlock> CODEC = Block.simpleCodec(CornCropBlock::new);
public static final IntegerProperty AGE = BlockStateProperties.AGE_3;
public static final EnumProperty<DoubleBlockHalf> HALF = BlockStateProperties.DOUBLE_BLOCK_HALF;
private static final int MAX_AGE = 3;
private static final int TALL_AGE = 2;
private static final VoxelShape[] LOWER_SHAPES = new VoxelShape[] {
Block.box(4.0D, 0.0D, 4.0D, 12.0D, 6.0D, 12.0D),
Block.box(3.0D, 0.0D, 3.0D, 13.0D, 11.0D, 13.0D),
Block.box(2.0D, 0.0D, 2.0D, 14.0D, 14.0D, 14.0D),
Block.box(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D)
};
private static final VoxelShape[] UPPER_SHAPES = new VoxelShape[] {
Block.box(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D),
Block.box(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D),
Block.box(2.0D, 0.0D, 2.0D, 14.0D, 16.0D, 14.0D),
Block.box(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D)
};
private static final ResourceLocation CORN_SEEDS_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "corn_seeds");
public CornCropBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(AGE, 0).setValue(HALF, DoubleBlockHalf.LOWER));
}
@Override
public MapCodec<CornCropBlock> codec() {
return CODEC;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
int age = state.getValue(AGE);
return state.getValue(HALF) == DoubleBlockHalf.UPPER ? UPPER_SHAPES[age] : LOWER_SHAPES[age];
}
@Override
protected boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos) {
return state.is(Blocks.FARMLAND);
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
DoubleBlockHalf half = state.getValue(HALF);
int age = state.getValue(AGE);
if (half == DoubleBlockHalf.UPPER) {
BlockState below = level.getBlockState(pos.below());
return below.is(this) && below.getValue(HALF) == DoubleBlockHalf.LOWER && below.getValue(AGE) >= TALL_AGE;
}
if (!super.canSurvive(state, level, pos)) {
return false;
}
return age < TALL_AGE || level.getBlockState(pos.above()).is(this)
&& level.getBlockState(pos.above()).getValue(HALF) == DoubleBlockHalf.UPPER
&& level.getBlockState(pos.above()).getValue(AGE) == age;
}
@Override
protected BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
if (!state.canSurvive(level, pos)) {
return Blocks.AIR.defaultBlockState();
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
@Override
protected boolean isRandomlyTicking(BlockState state) {
return state.getValue(HALF) == DoubleBlockHalf.LOWER && state.getValue(AGE) < MAX_AGE;
}
@Override
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (level.getRawBrightness(pos, 0) < 9 || random.nextInt(5) != 0) {
return;
}
this.grow(level, pos, state);
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
BlockPos lowerPos = state.getValue(HALF) == DoubleBlockHalf.UPPER ? pos.below() : pos;
BlockState lowerState = level.getBlockState(lowerPos);
if (!lowerState.is(this) || lowerState.getValue(HALF) != DoubleBlockHalf.LOWER) {
return false;
}
int age = lowerState.getValue(AGE);
return age < MAX_AGE && (age >= TALL_AGE || level.getBlockState(lowerPos.above()).canBeReplaced());
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
BlockPos lowerPos = state.getValue(HALF) == DoubleBlockHalf.UPPER ? pos.below() : pos;
BlockState lowerState = level.getBlockState(lowerPos);
if (lowerState.is(this)) {
this.grow(level, lowerPos, lowerState);
}
}
private void grow(ServerLevel level, BlockPos lowerPos, BlockState lowerState) {
int age = lowerState.getValue(AGE);
if (age >= MAX_AGE) {
return;
}
if (age + 1 >= TALL_AGE) {
BlockPos upperPos = lowerPos.above();
if (!level.getBlockState(upperPos).canBeReplaced() && !level.getBlockState(upperPos).is(this)) {
return;
}
}
int newAge = age + 1;
level.setBlock(lowerPos, lowerState.setValue(AGE, newAge), Block.UPDATE_ALL);
if (newAge >= TALL_AGE) {
level.setBlock(lowerPos.above(), this.defaultBlockState().setValue(HALF, DoubleBlockHalf.UPPER).setValue(AGE, newAge), Block.UPDATE_ALL);
}
}
@Override
public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) {
return new ItemStack(this.getBaseSeedId());
}
protected ItemLike getBaseSeedId() {
Optional<net.minecraft.world.item.Item> item = BuiltInRegistries.ITEM.getOptional(CORN_SEEDS_ID);
return item.orElse(Blocks.AIR.asItem());
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(AGE, HALF);
}
}
@@ -0,0 +1,70 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
public class HangingPeachBlock extends BushBlock {
public static final MapCodec<HangingPeachBlock> CODEC = Block.simpleCodec(HangingPeachBlock::new);
private static final ResourceLocation PEACH_LEAVES_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "peach_leaves");
private static final ResourceLocation PEACH_ITEM_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "peach");
private static final VoxelShape SHAPE = Block.box(4.0D, 2.0D, 4.0D, 12.0D, 16.0D, 12.0D);
public HangingPeachBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<HangingPeachBlock> codec() {
return CODEC;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
protected VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return Shapes.empty();
}
@Override
public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) {
return new ItemStack(BuiltInRegistries.ITEM.getOptional(PEACH_ITEM_ID).orElse(Blocks.AIR.asItem()));
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
BlockState above = level.getBlockState(pos.above());
return above.is(BuiltInRegistries.BLOCK.getOptional(PEACH_LEAVES_ID).orElse(Blocks.AIR));
}
@Override
protected BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
return state.canSurvive(level, pos) ? super.updateShape(state, direction, neighborState, level, pos, neighborPos) : Blocks.AIR.defaultBlockState();
}
}
@@ -1,41 +0,0 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.WeepingVinesBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
@SuppressWarnings({"rawtypes", "unchecked"})
public class OuranwoodVineBlock extends WeepingVinesBlock {
public static final MapCodec<OuranwoodVineBlock> CODEC = simpleCodec(OuranwoodVineBlock::new);
private static Block bodyBlock;
public OuranwoodVineBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<WeepingVinesBlock> codec() {
return (MapCodec) CODEC;
}
@Override
protected Block getBodyBlock() {
return bodyBlock;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return Shapes.block();
}
public static void bindBodyBlock(Block block) {
bodyBlock = block;
}
}
@@ -1,42 +0,0 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.GrowingPlantHeadBlock;
import net.minecraft.world.level.block.WeepingVinesBlock;
import net.minecraft.world.level.block.WeepingVinesPlantBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
@SuppressWarnings({"rawtypes", "unchecked"})
public class OuranwoodVinePlantBlock extends WeepingVinesPlantBlock {
public static final MapCodec<OuranwoodVinePlantBlock> CODEC = simpleCodec(OuranwoodVinePlantBlock::new);
private static GrowingPlantHeadBlock headBlock;
public OuranwoodVinePlantBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<WeepingVinesPlantBlock> codec() {
return (MapCodec) CODEC;
}
@Override
protected GrowingPlantHeadBlock getHeadBlock() {
return headBlock;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return Shapes.block();
}
public static void bindHeadBlock(WeepingVinesBlock block) {
headBlock = block;
}
}
@@ -0,0 +1,88 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
public class PeachLeavesBlock extends LeavesBlock implements BonemealableBlock {
public static final MapCodec<PeachLeavesBlock> CODEC = Block.simpleCodec(PeachLeavesBlock::new);
private static final ResourceLocation HANGING_PEACH_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "hanging_peach");
public PeachLeavesBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.defaultBlockState()
.setValue(DISTANCE, DECAY_DISTANCE)
.setValue(PERSISTENT, false)
.setValue(WATERLOGGED, false));
}
@Override
public MapCodec<PeachLeavesBlock> codec() {
return CODEC;
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
FluidState fluidState = context.getLevel().getFluidState(context.getClickedPos());
return this.defaultBlockState()
.setValue(PERSISTENT, true)
.setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER);
}
@Override
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
if (random.nextInt(18) != 0) {
return;
}
level.addParticle(
AntarchyObjects.PEACH_LEAVES_PARTICLE.get(),
pos.getX() + random.nextDouble(),
pos.getY() + random.nextDouble(),
pos.getZ() + random.nextDouble(),
0.0D,
-0.02D,
0.0D
);
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
return level.getBlockState(pos.below()).isAir();
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
if (!level.getBlockState(pos.below()).isAir()) {
return;
}
level.setBlock(pos.below(), createHangingPeachState(), Block.UPDATE_ALL);
}
public static BlockState createHangingPeachState() {
return BuiltInRegistries.BLOCK.getOptional(HANGING_PEACH_ID)
.map(Block::defaultBlockState)
.orElseThrow(() -> new IllegalStateException("Missing hanging peach block: " + HANGING_PEACH_ID));
}
}
@@ -0,0 +1,134 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.worldgen.elythia.PeachTreeGrowers;
import com.mojang.serialization.MapCodec;
import java.util.LinkedHashMap;
import java.util.Map;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
public class PeachSaplingBlock extends BushBlock implements BonemealableBlock {
public static final MapCodec<PeachSaplingBlock> CODEC = Block.simpleCodec(PeachSaplingBlock::new);
public PeachSaplingBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<PeachSaplingBlock> codec() {
return CODEC;
}
@Override
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (level.getMaxLocalRawBrightness(pos.above()) >= 9 && random.nextInt(7) == 0) {
this.growConfiguredTree(level, pos, state, random);
}
}
@Override
protected BlockState updateShape(
BlockState state,
net.minecraft.core.Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
return state.canSurvive(level, pos)
? super.updateShape(state, direction, neighborState, level, pos, neighborPos)
: Blocks.AIR.defaultBlockState();
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
return true;
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return random.nextFloat() < 0.45F;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
this.growConfiguredTree(level, pos, state, random);
}
private void growConfiguredTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
Footprint largeFootprint = this.findFootprint(level, pos);
if (largeFootprint != null) {
this.placeConfiguredTree(level, largeFootprint, PeachTreeGrowers.PEACH_LARGE_TREE, random);
return;
}
this.placeConfiguredTree(level, new Footprint(pos, Map.of(pos, state)), PeachTreeGrowers.PEACH_TREE, random);
}
private Footprint findFootprint(ServerLevel level, BlockPos pos) {
for (int offsetX = 0; offsetX < 2; offsetX++) {
for (int offsetZ = 0; offsetZ < 2; offsetZ++) {
BlockPos origin = pos.offset(-offsetX, 0, -offsetZ);
Map<BlockPos, BlockState> blocks = new LinkedHashMap<>();
boolean matches = true;
for (int x = 0; x < 2 && matches; x++) {
for (int z = 0; z < 2; z++) {
BlockPos checkPos = origin.offset(x, 0, z);
BlockState checkState = level.getBlockState(checkPos);
if (!checkState.is(this)) {
matches = false;
break;
}
blocks.put(checkPos, checkState);
}
}
if (matches) {
return new Footprint(origin, blocks);
}
}
}
return null;
}
private void placeConfiguredTree(
ServerLevel level,
Footprint footprint,
net.minecraft.resources.ResourceKey<ConfiguredFeature<?, ?>> featureKey,
RandomSource random
) {
Holder.Reference<ConfiguredFeature<?, ?>> feature = level.registryAccess()
.lookupOrThrow(Registries.CONFIGURED_FEATURE)
.getOrThrow(featureKey);
for (BlockPos saplingPos : footprint.blocks().keySet()) {
level.removeBlock(saplingPos, false);
}
if (feature.value().place(level, level.getChunkSource().getGenerator(), random, footprint.origin())) {
return;
}
for (Map.Entry<BlockPos, BlockState> entry : footprint.blocks().entrySet()) {
level.setBlock(entry.getKey(), entry.getValue(), Block.UPDATE_ALL);
}
}
private record Footprint(BlockPos origin, Map<BlockPos, BlockState> blocks) {
}
}
@@ -1,18 +0,0 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
public class TorchflowerBushBlock extends MilkweedBlock {
public static final MapCodec<TorchflowerBushBlock> CODEC = Block.simpleCodec(TorchflowerBushBlock::new);
public TorchflowerBushBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<MilkweedBlock> codec() {
return (MapCodec<MilkweedBlock>) (MapCodec<?>) CODEC;
}
}
@@ -1,52 +0,0 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class VeryShortGrassBlock extends BushBlock implements BonemealableBlock {
public static final MapCodec<VeryShortGrassBlock> CODEC = Block.simpleCodec(VeryShortGrassBlock::new);
private static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 8.0, 14.0);
public VeryShortGrassBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<VeryShortGrassBlock> codec() {
return CODEC;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
return true;
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
level.setBlock(pos, Blocks.SHORT_GRASS.defaultBlockState(), 3);
}
}
@@ -1,170 +0,0 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.ItemInteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.BushBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.phys.BlockHitResult;
import org.jetbrains.annotations.Nullable;
public class VeryTallGrassBlock extends BushBlock implements BonemealableBlock {
public static final MapCodec<VeryTallGrassBlock> CODEC = Block.simpleCodec(VeryTallGrassBlock::new);
public static final EnumProperty<Part> PART = EnumProperty.create("part", Part.class);
public VeryTallGrassBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(PART, Part.LOWER));
}
@Override
public MapCodec<VeryTallGrassBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(PART);
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
return switch (state.getValue(PART)) {
case LOWER -> super.canSurvive(state, level, pos);
case MIDDLE -> level.getBlockState(pos.below()).is(this)
&& level.getBlockState(pos.below()).getValue(PART) == Part.LOWER
&& level.getBlockState(pos.above()).is(this)
&& level.getBlockState(pos.above()).getValue(PART) == Part.UPPER;
case UPPER -> level.getBlockState(pos.below()).is(this)
&& level.getBlockState(pos.below()).getValue(PART) == Part.MIDDLE;
};
}
@Override
protected BlockState updateShape(
BlockState state,
Direction direction,
BlockState neighborState,
LevelAccessor level,
BlockPos pos,
BlockPos neighborPos
) {
Part part = state.getValue(PART);
if (part == Part.LOWER && direction == Direction.UP && (!neighborState.is(this) || neighborState.getValue(PART) != Part.MIDDLE)) {
return Blocks.AIR.defaultBlockState();
}
if (part == Part.MIDDLE) {
if (direction == Direction.DOWN && (!neighborState.is(this) || neighborState.getValue(PART) != Part.LOWER)) {
return Blocks.AIR.defaultBlockState();
}
if (direction == Direction.UP && (!neighborState.is(this) || neighborState.getValue(PART) != Part.UPPER)) {
return Blocks.AIR.defaultBlockState();
}
}
if (part == Part.UPPER && direction == Direction.DOWN && (!neighborState.is(this) || neighborState.getValue(PART) != Part.MIDDLE)) {
return Blocks.AIR.defaultBlockState();
}
return state.canSurvive(level, pos) ? super.updateShape(state, direction, neighborState, level, pos, neighborPos) : Blocks.AIR.defaultBlockState();
}
@Override
protected ItemInteractionResult useItemOn(
ItemStack stack,
BlockState state,
Level level,
BlockPos pos,
Player player,
InteractionHand hand,
BlockHitResult hitResult
) {
if (player.getItemInHand(hand).is(Items.BONE_MEAL)) {
popResource(level, pos, new ItemStack(state.getBlock().asItem(), 1));
return ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
}
return super.useItemOn(stack, state, level, pos, player, hand, hitResult);
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
return false;
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return false;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
}
@Override
protected boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
return false;
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
BlockPos pos = context.getClickedPos();
Level level = context.getLevel();
if (!level.getBlockState(pos.above()).canBeReplaced(context) || !level.getBlockState(pos.above(2)).canBeReplaced(context)) {
return null;
}
return super.getStateForPlacement(context);
}
@Override
public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable net.minecraft.world.entity.LivingEntity placer, ItemStack stack) {
placeAt(level, pos, Block.UPDATE_ALL);
}
public static void placeAt(LevelAccessor level, BlockPos pos, int flags) {
level.setBlock(pos, defaultState(Part.LOWER), flags);
level.setBlock(pos.above(), defaultState(Part.MIDDLE), flags);
level.setBlock(pos.above(2), defaultState(Part.UPPER), flags);
}
private static BlockState defaultState(Part part) {
return net.minecraft.core.registries.BuiltInRegistries.BLOCK
.get(net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("antarchy", "very_tall_grass"))
.defaultBlockState()
.setValue(PART, part);
}
public enum Part implements StringRepresentable {
LOWER("lower"),
MIDDLE("middle"),
UPPER("upper");
private final String name;
Part(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return this.name;
}
}
}
@@ -0,0 +1,63 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
public final class WildCornBlock extends CornCropBlock {
public static final MapCodec<CornCropBlock> CODEC = Block.simpleCodec(WildCornBlock::new);
public WildCornBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(AGE, 3).setValue(HALF, DoubleBlockHalf.LOWER));
}
@Override
public MapCodec<CornCropBlock> codec() {
return CODEC;
}
@Override
protected boolean mayPlaceOn(BlockState state, BlockGetter level, BlockPos pos) {
return state.is(Blocks.GRASS_BLOCK);
}
@Override
protected boolean isRandomlyTicking(BlockState state) {
return false;
}
@Override
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
}
@Override
public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) {
return false;
}
@Override
public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) {
return false;
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
}
@Override
public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) {
return new ItemStack(AntarchyObjects.CORN.get());
}
}
@@ -0,0 +1,34 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.ElkaEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class ElkaModel extends GeoModel<ElkaEntity> {
private static final ResourceLocation ADULT_GEO = rl("geo/elka.geo.json");
private static final ResourceLocation BABY_GEO = rl("geo/elka_baby.geo.json");
private static final ResourceLocation ADULT_ANIM = rl("animations/elka.animation.json");
private static final ResourceLocation BABY_ANIM = rl("animations/elka_baby.animation.json");
private static final ResourceLocation ADULT_TEXTURE = rl("textures/entity/elka.png");
private static final ResourceLocation BABY_TEXTURE = rl("textures/entity/elka_baby.png");
private static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
@Override
public ResourceLocation getModelResource(ElkaEntity animatable) {
return animatable.isBaby() ? BABY_GEO : ADULT_GEO;
}
@Override
public ResourceLocation getAnimationResource(ElkaEntity animatable) {
return animatable.isBaby() ? BABY_ANIM : ADULT_ANIM;
}
@Override
public ResourceLocation getTextureResource(ElkaEntity animatable) {
return animatable.isBaby() ? BABY_TEXTURE : ADULT_TEXTURE;
}
}
@@ -1,45 +0,0 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.GlimmerEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class GlimmerModel extends GeoModel<GlimmerEntity> {
private static final ResourceLocation ADULT_GEO = rl("geo/glimmer.geo.json");
private static final ResourceLocation BABY_GEO = rl("geo/glimmer_baby.geo.json");
private static final ResourceLocation ADULT_ANIM = rl("animations/glimmer.animation.json");
private static final ResourceLocation BABY_ANIM = rl("animations/glimmer_baby.animation.json");
private static final ResourceLocation ADULT_DAY = rl("textures/entity/glimmer/glimmer_day.png");
private static final ResourceLocation ADULT_NIGHT = rl("textures/entity/glimmer/glimmer.png");
private static final ResourceLocation BABY_DAY = rl("textures/entity/glimmer/glimmer_baby_day.png");
private static final ResourceLocation BABY_NIGHT = rl("textures/entity/glimmer/glimmer_baby.png");
public static final ResourceLocation ADULT_EMISSIVE = rl("textures/entity/glimmer/glimmer_emissive.png");
public static final ResourceLocation BABY_EMISSIVE = rl("textures/entity/glimmer/glimmer_baby_emissive.png");
private static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
@Override
public ResourceLocation getModelResource(GlimmerEntity animatable) {
return animatable.isBaby() ? BABY_GEO : ADULT_GEO;
}
@Override
public ResourceLocation getAnimationResource(GlimmerEntity animatable) {
return animatable.isBaby() ? BABY_ANIM : ADULT_ANIM;
}
@Override
public ResourceLocation getTextureResource(GlimmerEntity animatable) {
return textureFor(animatable, animatable.getGlowBlend(0.0F) >= 0.5F);
}
public static ResourceLocation textureFor(GlimmerEntity animatable, boolean glowing) {
if (animatable.isBaby()) {
return glowing ? BABY_NIGHT : BABY_DAY;
}
return glowing ? ADULT_NIGHT : ADULT_DAY;
}
}
@@ -0,0 +1,41 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.entity.OuranwoodDeerEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class OuranwoodDeerModel extends GeoModel<OuranwoodDeerEntity> {
private static final ResourceLocation ADULT_GEO = rl("geo/ouranwood_deer.geo.json");
private static final ResourceLocation BABY_GEO = rl("geo/ouranwood_deer_baby.geo.json");
private static final ResourceLocation ADULT_ANIM = rl("animations/ouranwood_deer.animation.json");
private static final ResourceLocation BABY_ANIM = rl("animations/ouranwood_deer_baby.animation.json");
private static final ResourceLocation BUCK_TEXTURE = rl("textures/entity/ouranwood_deer/deer_buck.png");
private static final ResourceLocation DOE_TEXTURE = rl("textures/entity/ouranwood_deer/deer_doe.png");
private static final ResourceLocation STAG_TEXTURE = rl("textures/entity/ouranwood_deer/deer_stag.png");
private static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
@Override
public ResourceLocation getModelResource(OuranwoodDeerEntity animatable) {
return animatable.isBaby() ? BABY_GEO : ADULT_GEO;
}
@Override
public ResourceLocation getAnimationResource(OuranwoodDeerEntity animatable) {
return animatable.isBaby() ? BABY_ANIM : ADULT_ANIM;
}
@Override
public ResourceLocation getTextureResource(OuranwoodDeerEntity animatable) {
if (animatable.isBaby()) {
return STAG_TEXTURE;
}
return switch (animatable.getVariant()) {
case BUCK -> BUCK_TEXTURE;
case DOE -> DOE_TEXTURE;
};
}
}
@@ -0,0 +1,50 @@
package com.craisinlord.antarchy.content.client.model.glimmer;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.FrogModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.util.Mth;
/**
* Reuses vanilla Frog geometry ({@link FrogModel}/{@code ModelLayers.FROG}) for the Frog
* Glimmer variant instead of an ill-fitting GeckoLib placeholder mesh, with a simplified
* walk/idle animation (GlimmerEntity has no Frog-specific jump/tongue state).
*/
public class GlimmerFrogModel extends EntityModel<GlimmerEntity> {
private final ModelPart root;
private final ModelPart head;
private final ModelPart leftArm;
private final ModelPart rightArm;
private final ModelPart leftLeg;
private final ModelPart rightLeg;
public GlimmerFrogModel(ModelPart bakeRoot) {
this.root = bakeRoot.getChild("root");
ModelPart body = this.root.getChild("body");
this.head = body.getChild("head");
this.leftArm = body.getChild("left_arm");
this.rightArm = body.getChild("right_arm");
this.leftLeg = this.root.getChild("left_leg");
this.rightLeg = this.root.getChild("right_leg");
}
@Override
public void setupAnim(GlimmerEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {
this.head.yRot = netHeadYaw * ((float) Math.PI / 180F);
this.head.xRot = headPitch * ((float) Math.PI / 180F);
float legSwing = Mth.sin(limbSwing * 0.6662F) * limbSwingAmount;
this.leftLeg.xRot = legSwing;
this.rightLeg.xRot = -legSwing;
this.leftArm.xRot = -legSwing;
this.rightArm.xRot = legSwing;
}
@Override
public void renderToBuffer(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int color) {
this.root.render(poseStack, buffer, packedLight, packedOverlay, color);
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.client.model.glimmer;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class GlimmerModel extends GeoModel<GlimmerEntity> {
@Override
public ResourceLocation getModelResource(GlimmerEntity animatable) {
return animatable.getVariant().getBehavior().modelGeo();
}
@Override
public ResourceLocation getAnimationResource(GlimmerEntity animatable) {
return animatable.getVariant().getBehavior().animationFile();
}
@Override
public ResourceLocation getTextureResource(GlimmerEntity animatable) {
return animatable.getVariant().getBehavior().texture(animatable);
}
}
@@ -1,4 +1,4 @@
package com.craisinlord.antarchy.content; package com.craisinlord.antarchy.content.client.particle;
import net.minecraft.core.particles.DustParticleOptions; import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
@@ -42,4 +42,22 @@ public final class GlimmerParticles {
serverLevel.sendParticles(TRAIL, behind.x, behind.y, behind.z, 1, 0.05D, 0.05D, 0.05D, 0.0D); serverLevel.sendParticles(TRAIL, behind.x, behind.y, behind.z, 1, 0.05D, 0.05D, 0.05D, 0.0D);
} }
} }
public static void tickBurst(LivingEntity entity) {
if (!(entity.level() instanceof ServerLevel serverLevel) || !entity.isAlive()) {
return;
}
serverLevel.sendParticles(
TRAIL,
entity.getRandomX(0.4D),
entity.getY(0.1D) + entity.getBbHeight() * 0.4D,
entity.getRandomZ(0.4D),
3,
0.12D,
0.12D,
0.12D,
0.0D
);
}
} }
@@ -0,0 +1,69 @@
package com.craisinlord.antarchy.content.client.particle;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleProvider;
import net.minecraft.client.particle.ParticleRenderType;
import net.minecraft.client.particle.SpriteSet;
import net.minecraft.client.particle.TextureSheetParticle;
import net.minecraft.core.particles.SimpleParticleType;
public final class PeachLeavesParticle extends TextureSheetParticle {
private final SpriteSet sprites;
private final float gravity;
private PeachLeavesParticle(ClientLevel level, double x, double y, double z, SpriteSet sprites) {
super(level, x, y, z, 0.0D, 0.0D, 0.0D);
this.sprites = sprites;
this.gravity = 0.02F + this.random.nextFloat() * 0.01F;
this.lifetime = 28 + this.random.nextInt(26);
this.quadSize = 0.14F + this.random.nextFloat() * 0.05F;
this.xd = (this.random.nextDouble() - 0.5D) * 0.04D;
this.yd = -0.015D - this.random.nextDouble() * 0.02D;
this.zd = (this.random.nextDouble() - 0.5D) * 0.04D;
this.hasPhysics = true;
this.setSpriteFromAge(sprites);
}
@Override
public void tick() {
this.xo = this.x;
this.yo = this.y;
this.zo = this.z;
if (this.age++ >= this.lifetime) {
this.remove();
return;
}
double sway = Math.sin((this.age + this.x) * 0.22D) * 0.0035D;
this.xd += sway;
this.zd -= sway;
this.yd -= this.gravity * 0.015D;
this.xd *= 0.91D;
this.yd *= 0.92D;
this.zd *= 0.91D;
this.move(this.xd, this.yd, this.zd);
this.setSpriteFromAge(this.sprites);
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
}
public static final class Provider implements ParticleProvider<SimpleParticleType> {
private final SpriteSet sprites;
public Provider(SpriteSet sprites) {
this.sprites = sprites;
}
@Override
public Particle createParticle(SimpleParticleType type, ClientLevel level, double x, double y, double z, double xd, double yd, double zd) {
return new PeachLeavesParticle(level, x, y, z, this.sprites);
}
}
}
@@ -53,7 +53,7 @@ public class AppleCowRenderer extends GeoEntityRenderer<AppleCowEntity> {
public void render(PoseStack poseStack, AppleCowEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType, public void render(PoseStack poseStack, AppleCowEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick, MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) { int packedLight, int packedOverlay) {
if (!(animatable instanceof EnchantedGoldenAppleCow)) { if (!(animatable instanceof EnchantedGoldenAppleCow) || animatable.isBaby()) {
return; return;
} }
@@ -0,0 +1,15 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.ElkaModel;
import com.craisinlord.antarchy.content.entity.ElkaEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class ElkaRenderer extends GeoEntityRenderer<ElkaEntity> {
private static final float SHADOW_RADIUS = 0.9F;
public ElkaRenderer(EntityRendererProvider.Context context) {
super(context, new ElkaModel());
this.shadowRadius = SHADOW_RADIUS;
}
}
@@ -1,108 +0,0 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.GlimmerModel;
import com.craisinlord.antarchy.content.entity.GlimmerEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public class GlimmerRenderer extends GeoEntityRenderer<GlimmerEntity> {
private static final float SHADOW_RADIUS = 0.5F;
public GlimmerRenderer(EntityRendererProvider.Context context) {
super(context, new GlimmerModel());
this.shadowRadius = SHADOW_RADIUS;
this.addRenderLayer(new GlimmerCrossfadeLayer(this));
this.addRenderLayer(new GlimmerEmissiveLayer(this));
}
@Override
public RenderType getRenderType(GlimmerEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(texture);
}
@Override
public void preRender(PoseStack poseStack, GlimmerEntity animatable, BakedGeoModel model, @Nullable MultiBufferSource bufferSource,
@Nullable VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, int colour) {
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, colour);
this.shadowRadius = SHADOW_RADIUS;
}
private static final class GlimmerCrossfadeLayer extends GeoRenderLayer<GlimmerEntity> {
private GlimmerCrossfadeLayer(GeoEntityRenderer<GlimmerEntity> renderer) {
super(renderer);
}
@Override
public void render(PoseStack poseStack, GlimmerEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
float glowBlend = animatable.getGlowBlend(partialTick);
if (glowBlend <= 0.0F || glowBlend >= 1.0F) {
return;
}
boolean baseGlowing = glowBlend >= 0.5F;
float overlayAlpha = baseGlowing ? 1.0F - glowBlend : glowBlend;
if (overlayAlpha <= 0.0F) {
return;
}
ResourceLocation overlayTexture = GlimmerModel.textureFor(animatable, !baseGlowing);
RenderType overlayType = RenderType.entityTranslucent(overlayTexture);
VertexConsumer overlayBuffer = bufferSource.getBuffer(overlayType);
int color = ((Math.max(0, Math.min(255, Math.round(overlayAlpha * 255.0F))) & 0xFF) << 24) | 0x00FFFFFF;
this.getRenderer().reRender(
bakedModel,
poseStack,
bufferSource,
animatable,
overlayType,
overlayBuffer,
partialTick,
packedLight,
packedOverlay,
color
);
}
}
private static final class GlimmerEmissiveLayer extends GeoRenderLayer<GlimmerEntity> {
private GlimmerEmissiveLayer(GeoEntityRenderer<GlimmerEntity> renderer) {
super(renderer);
}
@Override
public void render(PoseStack poseStack, GlimmerEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
if (!animatable.isGlowingState()) {
return;
}
ResourceLocation texture = animatable.isBaby() ? GlimmerModel.BABY_EMISSIVE : GlimmerModel.ADULT_EMISSIVE;
RenderType emissiveType = RenderType.eyes(texture);
VertexConsumer emissiveBuffer = bufferSource.getBuffer(emissiveType);
this.getRenderer().reRender(
bakedModel,
poseStack,
bufferSource,
animatable,
emissiveType,
emissiveBuffer,
partialTick,
0xF000F0,
OverlayTexture.NO_OVERLAY,
0xFFFFFFFF
);
}
}
}
@@ -0,0 +1,15 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.OuranwoodDeerModel;
import com.craisinlord.antarchy.content.entity.OuranwoodDeerEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class OuranwoodDeerRenderer extends GeoEntityRenderer<OuranwoodDeerEntity> {
private static final float SHADOW_RADIUS = 0.5F;
public OuranwoodDeerRenderer(EntityRendererProvider.Context context) {
super(context, new OuranwoodDeerModel());
this.shadowRadius = SHADOW_RADIUS;
}
}
@@ -0,0 +1,78 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.model.BoatModel;
import net.minecraft.client.model.ChestBoatModel;
import net.minecraft.client.model.ListModel;
import net.minecraft.client.model.WaterPatchModel;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.vehicle.Boat;
import org.joml.Quaternionf;
public class PeachBoatRenderer<T extends Boat> extends EntityRenderer<T> {
private static final String MAIN_LAYER = "main";
private final ResourceLocation texture;
private final ListModel<Boat> model;
public PeachBoatRenderer(EntityRendererProvider.Context context, ResourceLocation texture, boolean chest) {
super(context);
this.shadowRadius = 0.8F;
this.texture = texture;
this.model = chest
? new ChestBoatModel(context.bakeLayer(chestBoatLayer()))
: new BoatModel(context.bakeLayer(boatLayer()));
}
public static ModelLayerLocation boatLayer() {
return new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath("antarchy", "boat/peach"), MAIN_LAYER);
}
public static ModelLayerLocation chestBoatLayer() {
return new ModelLayerLocation(ResourceLocation.fromNamespaceAndPath("antarchy", "chest_boat/peach"), MAIN_LAYER);
}
@Override
public void render(T boat, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight) {
poseStack.pushPose();
poseStack.translate(0.0F, 0.375F, 0.0F);
poseStack.mulPose(Axis.YP.rotationDegrees(180.0F - entityYaw));
float hurtTime = boat.getHurtTime() - partialTick;
float damage = Math.max(boat.getDamage() - partialTick, 0.0F);
if (hurtTime > 0.0F) {
poseStack.mulPose(Axis.XP.rotationDegrees(Mth.sin(hurtTime) * hurtTime * damage / 10.0F * boat.getHurtDir()));
}
float bubbleAngle = boat.getBubbleAngle(partialTick);
if (!Mth.equal(bubbleAngle, 0.0F)) {
poseStack.mulPose(new Quaternionf().setAngleAxis(boat.getBubbleAngle(partialTick) * ((float) Math.PI / 180.0F), 1.0F, 0.0F, 1.0F));
}
poseStack.scale(-1.0F, -1.0F, 1.0F);
poseStack.mulPose(Axis.YP.rotationDegrees(90.0F));
this.model.setupAnim(boat, partialTick, 0.0F, -0.1F, 0.0F, 0.0F);
VertexConsumer consumer = buffer.getBuffer(this.model.renderType(this.texture));
this.model.renderToBuffer(poseStack, consumer, packedLight, OverlayTexture.NO_OVERLAY);
if (!boat.isUnderWater() && this.model instanceof WaterPatchModel waterPatchModel) {
VertexConsumer waterConsumer = buffer.getBuffer(RenderType.waterMask());
waterPatchModel.waterPatch().render(poseStack, waterConsumer, packedLight, OverlayTexture.NO_OVERLAY);
}
poseStack.popPose();
super.render(boat, entityYaw, partialTick, poseStack, buffer, packedLight);
}
@Override
public ResourceLocation getTextureLocation(T boat) {
return this.texture;
}
}
@@ -0,0 +1,128 @@
package com.craisinlord.antarchy.content.client.renderer.glimmer;
import com.craisinlord.antarchy.content.client.model.glimmer.GlimmerFrogModel;
import com.craisinlord.antarchy.content.client.model.glimmer.GlimmerModel;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerEntity;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.geom.ModelLayers;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
public class GlimmerRenderer extends GeoEntityRenderer<GlimmerEntity> {
private static final float SHADOW_RADIUS = 0.0F;
private static final float COOLDOWN_DIM_MIN = 0.35F;
private final GlimmerFrogModel frogModel;
public GlimmerRenderer(EntityRendererProvider.Context context) {
super(context, new GlimmerModel());
this.shadowRadius = SHADOW_RADIUS;
this.addRenderLayer(new GlimmerEmissiveLayer(this));
this.frogModel = new GlimmerFrogModel(context.bakeLayer(ModelLayers.FROG));
}
@Override
public void render(GlimmerEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
if (entity.getVariant() == GlimmerVariant.FROG) {
this.renderVanillaModel(this.frogModel, entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
return;
}
super.render(entity, entityYaw, partialTick, poseStack, bufferSource, packedLight);
}
private void renderVanillaModel(EntityModel<GlimmerEntity> model, GlimmerEntity entity, float entityYaw, float partialTick,
PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
poseStack.pushPose();
poseStack.mulPose(Axis.YP.rotationDegrees(180.0F - entityYaw));
poseStack.scale(-1.0F, -1.0F, 1.0F);
poseStack.translate(0.0F, -1.501F, 0.0F);
float limbSwing = entity.walkAnimation.position(partialTick);
float limbSwingAmount = entity.walkAnimation.speed(partialTick);
float ageInTicks = entity.tickCount + partialTick;
float netHeadYaw = Mth.rotLerp(partialTick, entity.yHeadRotO, entity.yHeadRot) - entityYaw;
float headPitch = Mth.lerp(partialTick, entity.xRotO, entity.getXRot());
model.setupAnim(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch);
int color = packColor(entity.getFadeAlpha(partialTick), brightnessFor(entity, partialTick));
ResourceLocation texture = entity.getVariant().getBehavior().texture(entity);
VertexConsumer buffer = bufferSource.getBuffer(RenderType.entityCutoutNoCull(texture));
model.renderToBuffer(poseStack, buffer, packedLight, OverlayTexture.NO_OVERLAY, color);
ResourceLocation emissiveTexture = entity.getVariant().getBehavior().emissiveTexture(entity);
VertexConsumer emissiveBuffer = bufferSource.getBuffer(RenderType.eyes(emissiveTexture));
model.renderToBuffer(poseStack, emissiveBuffer, 0xF000F0, OverlayTexture.NO_OVERLAY, color);
poseStack.popPose();
}
private static int packColor(float alpha, float brightness) {
int alphaByte = Mth.clamp(Math.round(alpha * 255.0F), 0, 255);
int colorByte = Mth.clamp(Math.round(brightness * 255.0F), 0, 255);
return (alphaByte << 24) | (colorByte << 16) | (colorByte << 8) | colorByte;
}
private static float brightnessFor(GlimmerEntity entity, float partialTick) {
float cooldownFraction = entity.getAbilityCooldownFraction(partialTick);
return Mth.lerp(cooldownFraction, 1.0F, COOLDOWN_DIM_MIN);
}
@Override
public RenderType getRenderType(GlimmerEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(texture);
}
@Override
public software.bernie.geckolib.util.Color getRenderColor(GlimmerEntity animatable, float partialTick, int packedLight) {
float brightness = brightnessFor(animatable, partialTick);
return software.bernie.geckolib.util.Color.ofRGBA(brightness, brightness, brightness, animatable.getFadeAlpha(partialTick));
}
@Override
public void preRender(PoseStack poseStack, GlimmerEntity animatable, BakedGeoModel model, @Nullable MultiBufferSource bufferSource,
@Nullable VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, int colour) {
super.preRender(poseStack, animatable, model, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, colour);
this.shadowRadius = SHADOW_RADIUS;
}
private static final class GlimmerEmissiveLayer extends GeoRenderLayer<GlimmerEntity> {
private GlimmerEmissiveLayer(GeoEntityRenderer<GlimmerEntity> renderer) {
super(renderer);
}
@Override
public void render(PoseStack poseStack, GlimmerEntity animatable, BakedGeoModel bakedModel, @Nullable RenderType renderType,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
ResourceLocation texture = animatable.getVariant().getBehavior().emissiveTexture(animatable);
RenderType emissiveType = RenderType.eyes(texture);
VertexConsumer emissiveBuffer = bufferSource.getBuffer(emissiveType);
this.getRenderer().reRender(
bakedModel,
poseStack,
bufferSource,
animatable,
emissiveType,
emissiveBuffer,
partialTick,
0xF000F0,
OverlayTexture.NO_OVERLAY,
packColor(animatable.getFadeAlpha(partialTick), brightnessFor(animatable, partialTick))
);
}
}
}
@@ -0,0 +1,69 @@
package com.craisinlord.antarchy.content.client.screen;
import com.craisinlord.antarchy.content.entity.DorrieEntity;
import com.craisinlord.antarchy.content.menu.DorrieInventoryMenu;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.screens.inventory.InventoryScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Inventory;
public class DorrieInventoryScreen extends AbstractContainerScreen<DorrieInventoryMenu> {
private static final ResourceLocation HORSE_INVENTORY_TEXTURE =
ResourceLocation.withDefaultNamespace("textures/gui/container/horse.png");
private static final ResourceLocation CHEST_SLOTS_SPRITE =
ResourceLocation.withDefaultNamespace("container/horse/chest_slots");
private static final int CHEST_SLOTS_TEX_WIDTH = 90;
private static final int CHEST_SLOTS_TEX_HEIGHT = 54;
private float xMouse;
private float yMouse;
public DorrieInventoryScreen(DorrieInventoryMenu menu, Inventory playerInventory, Component title) {
super(menu, playerInventory, title);
this.imageWidth = 176;
this.imageHeight = 166;
this.inventoryLabelY = this.imageHeight - 94;
}
@Override
protected void renderBg(GuiGraphics guiGraphics, float partialTick, int mouseX, int mouseY) {
int left = this.leftPos;
int top = this.topPos;
guiGraphics.blit(HORSE_INVENTORY_TEXTURE, left, top, 0, 0, this.imageWidth, this.imageHeight);
int storageWidth = DorrieInventoryMenu.STORAGE_COLUMNS * 18;
guiGraphics.blitSprite(CHEST_SLOTS_SPRITE, CHEST_SLOTS_TEX_WIDTH, CHEST_SLOTS_TEX_HEIGHT, 0, 0,
left + 79, top + 17, storageWidth, CHEST_SLOTS_TEX_HEIGHT);
guiGraphics.blitSprite(CHEST_SLOTS_SPRITE, CHEST_SLOTS_TEX_WIDTH, CHEST_SLOTS_TEX_HEIGHT, 0, 0,
left + 7, top + 17, 18, 18);
DorrieEntity dorrie = this.menu.dorrie();
if (dorrie != null) {
InventoryScreen.renderEntityInInventoryFollowsMouse(
guiGraphics,
left + 8,
top + 18,
left + 71,
top + 70,
24,
0.25F,
this.xMouse,
this.yMouse,
dorrie
);
}
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.xMouse = mouseX;
this.yMouse = mouseY;
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
super.render(guiGraphics, mouseX, mouseY, partialTick);
this.renderTooltip(guiGraphics, mouseX, mouseY);
}
}
@@ -1,68 +0,0 @@
package com.craisinlord.antarchy.content.effect;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.GlimmerParticles;
import net.minecraft.core.Holder;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeMap;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
public final class GlimmersGraceMobEffect extends MobEffect {
private static final ResourceLocation SPEED_MODIFIER_ID =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "glimmers_grace_speed");
private static final ResourceLocation JUMP_MODIFIER_ID =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "glimmers_grace_jump");
private static final ResourceLocation FALL_DAMAGE_MODIFIER_ID =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "glimmers_grace_fall_damage");
public GlimmersGraceMobEffect() {
super(MobEffectCategory.BENEFICIAL, 0x7FDBFF);
}
@Override
public void addAttributeModifiers(AttributeMap attributeMap, int amplifier) {
addModifier(attributeMap, Attributes.MOVEMENT_SPEED, SPEED_MODIFIER_ID, 0.35D * (amplifier + 1));
addModifier(attributeMap, Attributes.JUMP_STRENGTH, JUMP_MODIFIER_ID, 0.5D * (amplifier + 1));
addModifier(attributeMap, Attributes.FALL_DAMAGE_MULTIPLIER, FALL_DAMAGE_MODIFIER_ID, -0.6D * (amplifier + 1));
}
@Override
public void removeAttributeModifiers(AttributeMap attributeMap) {
removeModifier(attributeMap, Attributes.MOVEMENT_SPEED, SPEED_MODIFIER_ID);
removeModifier(attributeMap, Attributes.JUMP_STRENGTH, JUMP_MODIFIER_ID);
removeModifier(attributeMap, Attributes.FALL_DAMAGE_MULTIPLIER, FALL_DAMAGE_MODIFIER_ID);
}
@Override
public boolean shouldApplyEffectTickThisTick(int duration, int amplifier) {
return true;
}
@Override
public boolean applyEffectTick(LivingEntity livingEntity, int amplifier) {
GlimmerParticles.tickAmbient(livingEntity, livingEntity.getDeltaMovement().horizontalDistanceSqr() > 0.01D);
return true;
}
private static void addModifier(AttributeMap attributeMap, Holder<Attribute> attribute, ResourceLocation id, double amount) {
AttributeInstance instance = attributeMap.getInstance(attribute);
if (instance == null) {
return;
}
instance.addTransientModifier(new AttributeModifier(id, amount, AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL));
}
private static void removeModifier(AttributeMap attributeMap, Holder<Attribute> attribute, ResourceLocation id) {
AttributeInstance instance = attributeMap.getInstance(attribute);
if (instance == null) {
return;
}
instance.removeModifier(id);
}
}
@@ -6,6 +6,7 @@ import java.util.List;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.network.syncher.SynchedEntityData;
@@ -42,6 +43,8 @@ import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.level.pathfinder.PathType;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@@ -57,6 +60,9 @@ import software.bernie.geckolib.util.GeckoLibUtil;
public class DorrieEntity extends Animal implements GeoEntity { public class DorrieEntity extends Animal implements GeoEntity {
private static final ResourceLocation CHEEP_ITEM_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "cheep"); private static final ResourceLocation CHEEP_ITEM_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "cheep");
private static final String INVENTORY_TAG = "Inventory";
private static final int SADDLE_SLOT = 0;
private static final int INVENTORY_SIZE = 17;
private static final EntityDataAccessor<Boolean> HAS_SADDLE = private static final EntityDataAccessor<Boolean> HAS_SADDLE =
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.BOOLEAN); SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.BOOLEAN);
@@ -67,12 +73,13 @@ public class DorrieEntity extends Animal implements GeoEntity {
private static final EntityDataAccessor<Integer> QUIRK_TICKS = private static final EntityDataAccessor<Integer> QUIRK_TICKS =
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.INT); SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.INT);
private static final int MAX_CHARGE_TICKS = 40; private static final int MAX_CHARGE_TICKS = 90;
private static final float WATER_RIDE_SPEED = 0.78F; private static final float WATER_RIDE_SPEED = 0.42F;
private static final float LAND_BEACH_SPEED = 0.10F; private static final float LAND_BEACH_SPEED = 0.07F;
private static final double ASCEND_SPEED = 0.06D;
private static final int QUIRK_DURATION_TICKS = 65; private static final int QUIRK_DURATION_TICKS = 65;
private static final int MIN_QUIRK_COOLDOWN_TICKS = 140; private static final int MIN_QUIRK_COOLDOWN_TICKS = 260;
private static final int MAX_QUIRK_COOLDOWN_TICKS = 260; private static final int MAX_QUIRK_COOLDOWN_TICKS = 420;
private static final double MOVING_ANIM_THRESHOLD_SQ = 0.0025D; private static final double MOVING_ANIM_THRESHOLD_SQ = 0.0025D;
private static final RawAnimation IDLE_GROUND_ANIM = RawAnimation.begin().thenLoop("idle_ground"); private static final RawAnimation IDLE_GROUND_ANIM = RawAnimation.begin().thenLoop("idle_ground");
@@ -87,6 +94,13 @@ public class DorrieEntity extends Animal implements GeoEntity {
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private final MoveControl landMoveControl; private final MoveControl landMoveControl;
private final MoveControl waterMoveControl; private final MoveControl waterMoveControl;
private final SimpleContainer inventory = new SimpleContainer(INVENTORY_SIZE) {
@Override
public void setChanged() {
super.setChanged();
DorrieEntity.this.onInventoryChanged();
}
};
private static final int RIDES_TO_TAME = 4; private static final int RIDES_TO_TAME = 4;
private static final int BUCK_DELAY_TICKS = 60; private static final int BUCK_DELAY_TICKS = 60;
@@ -101,6 +115,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
private int swimForwardTicks = 0; private int swimForwardTicks = 0;
private int quirkCooldownTicks = MIN_QUIRK_COOLDOWN_TICKS; private int quirkCooldownTicks = MIN_QUIRK_COOLDOWN_TICKS;
private boolean usingLandNavigation = true; private boolean usingLandNavigation = true;
private boolean pressingJump = false;
public DorrieEntity(EntityType<? extends DorrieEntity> entityType, Level level) { public DorrieEntity(EntityType<? extends DorrieEntity> entityType, Level level) {
super(entityType, level); super(entityType, level);
@@ -131,8 +146,8 @@ public class DorrieEntity extends Animal implements GeoEntity {
this.goalSelector.addGoal(0, new TryFindWaterGoal(this)); this.goalSelector.addGoal(0, new TryFindWaterGoal(this));
this.goalSelector.addGoal(1, new HuntCheepGoal(this)); this.goalSelector.addGoal(1, new HuntCheepGoal(this));
this.goalSelector.addGoal(2, new DolphinSwimGoal()); this.goalSelector.addGoal(2, new DolphinSwimGoal());
this.goalSelector.addGoal(3, new RandomSwimmingGoal(this, 1.35D, 8)); this.goalSelector.addGoal(3, new RandomSwimmingGoal(this, 1.45D, 4));
this.goalSelector.addGoal(4, new WaterAvoidingRandomStrollGoal(this, 0.72D, 40)); this.goalSelector.addGoal(4, new WaterAvoidingRandomStrollGoal(this, 0.9D, 10));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
} }
@@ -153,6 +168,10 @@ public class DorrieEntity extends Animal implements GeoEntity {
this.entityData.set(HAS_SADDLE, saddle); this.entityData.set(HAS_SADDLE, saddle);
} }
public SimpleContainer inventory() {
return this.inventory;
}
public boolean isTamed() { public boolean isTamed() {
return this.entityData.get(TAMED); return this.entityData.get(TAMED);
} }
@@ -181,16 +200,38 @@ public class DorrieEntity extends Animal implements GeoEntity {
public void addAdditionalSaveData(CompoundTag tag) { public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag); super.addAdditionalSaveData(tag);
tag.putBoolean("Tamed", this.isTamed()); tag.putBoolean("Tamed", this.isTamed());
tag.putBoolean("HasSaddle", this.hasSaddle());
tag.putInt("RideAttempts", this.rideAttempts); tag.putInt("RideAttempts", this.rideAttempts);
ListTag inventoryTag = new ListTag();
for (int slot = 0; slot < this.inventory.getContainerSize(); slot++) {
ItemStack stack = this.inventory.getItem(slot);
if (stack.isEmpty()) {
continue;
}
CompoundTag stackTag = new CompoundTag();
stackTag.putByte("Slot", (byte) slot);
stackTag.put("Item", stack.save(this.registryAccess()));
inventoryTag.add(stackTag);
}
tag.put(INVENTORY_TAG, inventoryTag);
} }
@Override @Override
public void readAdditionalSaveData(CompoundTag tag) { public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag); super.readAdditionalSaveData(tag);
this.setTamed(tag.getBoolean("Tamed")); this.setTamed(tag.getBoolean("Tamed"));
this.setSaddle(tag.getBoolean("HasSaddle"));
this.rideAttempts = tag.getInt("RideAttempts"); this.rideAttempts = tag.getInt("RideAttempts");
this.inventory.clearContent();
ListTag inventoryTag = tag.getList(INVENTORY_TAG, 10);
for (int i = 0; i < inventoryTag.size(); i++) {
CompoundTag stackTag = inventoryTag.getCompound(i);
int slot = stackTag.getByte("Slot") & 255;
if (slot < 0 || slot >= this.inventory.getContainerSize()) {
continue;
}
this.inventory.setItem(slot, ItemStack.parseOptional(this.registryAccess(), stackTag.getCompound("Item")));
}
this.syncSaddleState();
} }
@Override @Override
@@ -204,38 +245,24 @@ public class DorrieEntity extends Animal implements GeoEntity {
return InteractionResult.sidedSuccess(this.level().isClientSide); return InteractionResult.sidedSuccess(this.level().isClientSide);
} }
if (this.hasSaddle() && !player.isPassengerOfSameVehicle(this)) { if (player.isSecondaryUseActive()) {
if (!stack.isEmpty()) { this.openInventory(player);
return super.mobInteract(player, hand); return InteractionResult.sidedSuccess(this.level().isClientSide);
} }
if (!player.isShiftKeyDown()) {
if (!this.level().isClientSide) {
player.startRiding(this);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (this.hasSaddle() && stack.isEmpty() && !player.isPassengerOfSameVehicle(this)) {
if (!this.level().isClientSide) { if (!this.level().isClientSide) {
this.setSaddle(false); player.startRiding(this);
this.spawnAtLocation(new ItemStack(Items.SADDLE));
this.playSound(SoundEvents.HORSE_SADDLE, 1.0F, 1.0F);
} }
return InteractionResult.sidedSuccess(this.level().isClientSide); return InteractionResult.sidedSuccess(this.level().isClientSide);
} }
if (!this.hasSaddle() && stack.is(Items.SADDLE)) { if (!stack.isEmpty() && this.isFood(stack)) {
if (!this.level().isClientSide) { return super.mobInteract(player, hand);
this.setSaddle(true);
if (!player.getAbilities().instabuild) {
stack.shrink(1);
}
this.playSound(SoundEvents.HORSE_SADDLE, 1.0F, 1.0F);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
} }
return super.mobInteract(player, hand); this.openInventory(player);
return InteractionResult.sidedSuccess(this.level().isClientSide);
} }
@Override @Override
@@ -268,7 +295,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
@Override @Override
protected float getRiddenSpeed(Player player) { protected float getRiddenSpeed(Player player) {
float speed = (this.isInWater() || this.isInLava()) float speed = (this.isInWater() || this.isInLava())
? WATER_RIDE_SPEED * (1.0F + Math.min(this.swimForwardTicks, 60) * 0.03F) ? WATER_RIDE_SPEED * (1.0F + Math.min(this.swimForwardTicks, 60) * 0.0125F)
: LAND_BEACH_SPEED; : LAND_BEACH_SPEED;
return (float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * speed; return (float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * speed;
} }
@@ -300,6 +327,10 @@ public class DorrieEntity extends Animal implements GeoEntity {
this.swimForwardTicks = 0; this.swimForwardTicks = 0;
} }
if (this.isVehicle() && this.getQuirkTicks() > 0) {
this.setQuirkTicks(0);
}
if (!this.level().isClientSide && !this.isTamed() && this.getFirstPassenger() instanceof Player rider) { if (!this.level().isClientSide && !this.isTamed() && this.getFirstPassenger() instanceof Player rider) {
buckTimer++; buckTimer++;
if (buckTimer >= BUCK_DELAY_TICKS) { if (buckTimer >= BUCK_DELAY_TICKS) {
@@ -338,7 +369,9 @@ public class DorrieEntity extends Animal implements GeoEntity {
wasInAir = inAirNow; wasInAir = inAirNow;
} }
public void setPressingJump(boolean pressing) {} public void setPressingJump(boolean pressing) {
this.pressingJump = pressing;
}
public void startJumpCharge() { public void startJumpCharge() {
if (!this.level().isClientSide) { if (!this.level().isClientSide) {
@@ -365,16 +398,22 @@ public class DorrieEntity extends Animal implements GeoEntity {
Vec3 riddenInput = this.getRiddenInput(rider, travelVector); Vec3 riddenInput = this.getRiddenInput(rider, travelVector);
this.setSpeed(this.getRiddenSpeed(rider)); this.setSpeed(this.getRiddenSpeed(rider));
if (this.isLeaping) {
super.travel(travelVector);
return;
}
if (this.isInWater() || this.isInLava()) { if (this.isInWater() || this.isInLava()) {
this.moveRelative(this.getSpeed(), new Vec3(riddenInput.x, 0.0D, riddenInput.z)); this.moveRelative(this.getSpeed(), new Vec3(riddenInput.x, 0.0D, riddenInput.z));
Vec3 motion = this.getDeltaMovement(); Vec3 motion = this.getDeltaMovement();
double vertical = 0.0D; double vertical = 0.0D;
if (!this.isLeaping) { if (this.isUnderWater()) {
if (this.isUnderWater()) { vertical = riddenInput.y * 0.025D;
vertical = riddenInput.y * 0.025D; } else if (riddenInput.y < 0.0D) {
} else if (riddenInput.y < 0.0D) { vertical = riddenInput.y * 0.02D;
vertical = riddenInput.y * 0.02D; }
} if (this.pressingJump) {
vertical += ASCEND_SPEED;
} }
this.setDeltaMovement( this.setDeltaMovement(
motion.x * 0.92D, motion.x * 0.92D,
@@ -396,7 +435,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
Vec3 look = this.getLookAngle(); Vec3 look = this.getLookAngle();
Vec3 current = this.getDeltaMovement(); Vec3 current = this.getDeltaMovement();
double forwardAdd = 0.18D + power * 0.42D; double forwardAdd = 0.18D + power * 0.42D;
double vertical = 0.28D + power * 0.62D; double vertical = 0.85D + power * 1.35D;
this.setDeltaMovement( this.setDeltaMovement(
current.x + look.x * forwardAdd, current.x + look.x * forwardAdd,
current.y + vertical, current.y + vertical,
@@ -429,9 +468,34 @@ public class DorrieEntity extends Animal implements GeoEntity {
@Override @Override
protected void dropEquipment() { protected void dropEquipment() {
super.dropEquipment(); super.dropEquipment();
if (this.hasSaddle()) { if (this.level().isClientSide) {
this.spawnAtLocation(new ItemStack(Items.SADDLE)); return;
} }
for (int slot = 0; slot < this.inventory.getContainerSize(); slot++) {
ItemStack stack = this.inventory.removeItemNoUpdate(slot);
if (!stack.isEmpty()) {
this.spawnAtLocation(stack);
}
}
}
private void onInventoryChanged() {
this.syncSaddleState();
}
private void syncSaddleState() {
ItemStack saddleStack = this.inventory.getItem(SADDLE_SLOT);
this.setSaddle(!saddleStack.isEmpty() && saddleStack.is(Items.SADDLE));
}
private void openInventory(Player player) {
if (this.level().isClientSide) {
return;
}
player.openMenu(new SimpleMenuProvider(
(containerId, playerInventory, ignored) -> new com.craisinlord.antarchy.content.menu.DorrieInventoryMenu(containerId, playerInventory, this.inventory, this),
this.getDisplayName()
));
} }
@Override @Override
@@ -567,7 +631,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
private boolean canUseQuirkAnimation() { private boolean canUseQuirkAnimation() {
return this.isInWater() return this.isInWater()
&& !this.isUnderWater() && !this.isUnderWater()
&& this.getControllingPassenger() == null && !this.isVehicle()
&& !this.isLeaping && !this.isLeaping
&& this.landAnimTicks <= 0 && this.landAnimTicks <= 0
&& !this.isCharging && !this.isCharging
@@ -653,7 +717,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
private class DolphinSwimGoal extends Goal { private class DolphinSwimGoal extends Goal {
private static final int COOLDOWN_MIN_TICKS = 120; private static final int COOLDOWN_MIN_TICKS = 120;
private static final int COOLDOWN_MAX_TICKS = 240; private static final int COOLDOWN_MAX_TICKS = 180;
private static final int DIVE_MIN_TICKS = 50; private static final int DIVE_MIN_TICKS = 50;
private static final int DIVE_MAX_TICKS = 110; private static final int DIVE_MAX_TICKS = 110;
private static final int ROAM_MIN_TICKS = 70; private static final int ROAM_MIN_TICKS = 70;
@@ -691,7 +755,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
this.cooldownTicks--; this.cooldownTicks--;
return false; return false;
} }
if (DorrieEntity.this.random.nextInt(6) != 0) { if (DorrieEntity.this.random.nextInt(3) != 0) {
return false; return false;
} }
this.targetPos = DorrieEntity.this.isUnderWater() this.targetPos = DorrieEntity.this.isUnderWater()
@@ -0,0 +1,448 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.content.AntarchyObjects;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.BreedGoal;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.TemptGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.core.BlockPos;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class ElkaEntity extends Animal implements GeoEntity {
private static final EntityDataAccessor<Boolean> SITTING =
SynchedEntityData.defineId(ElkaEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Integer> QUIRK_TICKS =
SynchedEntityData.defineId(ElkaEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> QUIRK_VARIANT =
SynchedEntityData.defineId(ElkaEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> ATTACK_TICKS =
SynchedEntityData.defineId(ElkaEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> CRY_TICKS =
SynchedEntityData.defineId(ElkaEntity.class, EntityDataSerializers.INT);
private static final String SITTING_KEY = "Sitting";
private static final String SIT_TICKS_KEY = "SitTicks";
private static final String SIT_COOLDOWN_KEY = "SitCooldown";
private static final String QUIRK_TICKS_KEY = "QuirkTicks";
private static final String QUIRK_VARIANT_KEY = "QuirkVariant";
private static final String MAIN_CONTROLLER = "main_controller";
private static final String SIT_TRANSITION_CONTROLLER = "sit_transition_controller";
private static final String START_SIT_TRIGGER = "start_sit";
private static final String GET_UP_TRIGGER = "get_up";
private static final int IDLE_ANIMATION_CYCLE_TICKS = 60;
private static final int QUIRK_ANIMATION_TICKS = 40;
private static final int QUIRK_CHANCE = 60;
private static final int SIT_MIN_TICKS = 20 * 60;
private static final int SIT_MAX_TICKS = 20 * 60 * 3;
private static final int SIT_COOLDOWN_TICKS = 20 * 60 * 6;
private static final int SIT_CHECK_CHANCE_SHADE = 300;
private static final int SIT_CHECK_CHANCE_OPEN = 1200;
private static final int ATTACK_ANIM_TICKS = 20;
private static final int CRY_ANIM_TICKS = 60;
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation SIT_IDLE_ANIM = RawAnimation.begin().thenLoop("sit_idle");
private static final RawAnimation QUIRK_ANIM = RawAnimation.begin().thenPlay("quirk");
private static final RawAnimation QUIRK_2_ANIM = RawAnimation.begin().thenPlay("quirk_2");
private static final RawAnimation QUIRK_3_ANIM = RawAnimation.begin().thenPlay("quirk_3");
private static final RawAnimation ATTACK_ANIM = RawAnimation.begin().thenPlay("attack");
private static final RawAnimation CRY_ANIM = RawAnimation.begin().thenPlay("cry");
private static final RawAnimation START_SIT_ANIM = RawAnimation.begin().thenPlay("sit");
private static final RawAnimation GET_UP_ANIM = RawAnimation.begin().thenPlay("get_up");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int sitTicksRemaining;
private int sitCooldownTicks;
private int idleAnimationCycleTicks;
public ElkaEntity(EntityType<? extends ElkaEntity> entityType, net.minecraft.world.level.Level level) {
super(entityType, level);
this.sitCooldownTicks = SIT_COOLDOWN_TICKS;
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, 34.0D)
.add(Attributes.MOVEMENT_SPEED, 0.22D)
.add(Attributes.ATTACK_DAMAGE, 7.0D)
.add(Attributes.FOLLOW_RANGE, 24.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 0.3D);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(SITTING, false);
builder.define(QUIRK_TICKS, 0);
builder.define(QUIRK_VARIANT, 0);
builder.define(ATTACK_TICKS, 0);
builder.define(CRY_TICKS, 0);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
if (!this.isBaby()) {
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2D, true));
}
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
this.goalSelector.addGoal(3, new TemptGoal(this, 1.1D, Ingredient.of(AntarchyObjects.PEACH.get()), false));
this.goalSelector.addGoal(4, new ElkaSitGoal());
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new ElkaHurtByTargetGoal(this));
}
@Override
public boolean isFood(ItemStack stack) {
return stack.is(AntarchyObjects.PEACH.get());
}
@Override
public ElkaEntity getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
return AntarchyObjects.ELKA.get().create(level);
}
@Override
public boolean requiresCustomPersistence() {
return true;
}
@Override
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
return false;
}
public boolean isSitting() {
return this.entityData.get(SITTING);
}
private void startSitting() {
this.entityData.set(SITTING, true);
this.sitTicksRemaining = Mth.nextInt(this.random, SIT_MIN_TICKS, SIT_MAX_TICKS);
this.getNavigation().stop();
if (!this.level().isClientSide) {
this.triggerAnim(SIT_TRANSITION_CONTROLLER, START_SIT_TRIGGER);
}
}
private void stopSitting() {
if (!this.isSitting()) {
return;
}
this.entityData.set(SITTING, false);
this.sitTicksRemaining = 0;
this.sitCooldownTicks = SIT_COOLDOWN_TICKS;
if (!this.level().isClientSide) {
this.triggerAnim(SIT_TRANSITION_CONTROLLER, GET_UP_TRIGGER);
}
}
private boolean isEligibleToSit() {
return this.onGround()
&& !this.isInWaterOrBubble()
&& !this.isOnFire()
&& !this.isLeashed()
&& !this.isPassenger()
&& !this.isVehicle()
&& !this.isInLove()
&& !this.isBaby()
&& this.getTarget() == null
&& this.getDeltaMovement().horizontalDistanceSqr() < 1.0E-4D
&& this.getNavigation().isDone();
}
private boolean isNearShade() {
BlockPos base = this.blockPosition();
for (BlockPos pos : BlockPos.betweenClosed(base.offset(-3, -1, -3), base.offset(3, 3, 3))) {
if (this.level().getBlockState(pos).is(BlockTags.LEAVES)) {
return true;
}
}
return false;
}
private void tickSitDecision() {
if (this.isSitting()) {
if (this.getTarget() != null || this.isInWaterOrBubble() || this.isOnFire() || this.hurtTime > 0
|| this.isLeashed() || this.isPassenger() || this.isVehicle() || this.isInLove() || !this.onGround()) {
this.stopSitting();
return;
}
if (this.sitTicksRemaining > 0) {
this.sitTicksRemaining--;
if (this.sitTicksRemaining <= 0) {
this.stopSitting();
}
}
return;
}
if (this.sitCooldownTicks > 0) {
this.sitCooldownTicks--;
return;
}
if (!this.isEligibleToSit()) {
return;
}
int chance = this.isNearShade() ? SIT_CHECK_CHANCE_SHADE : SIT_CHECK_CHANCE_OPEN;
if (this.random.nextInt(chance) == 0) {
this.startSitting();
}
}
private void tickIdleQuirk() {
if (this.entityData.get(QUIRK_TICKS) > 0 || this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-4D
|| this.getTarget() != null) {
this.idleAnimationCycleTicks = 0;
return;
}
this.idleAnimationCycleTicks++;
if (this.idleAnimationCycleTicks < IDLE_ANIMATION_CYCLE_TICKS) {
return;
}
this.idleAnimationCycleTicks = 0;
if (this.random.nextInt(QUIRK_CHANCE) != 0) {
return;
}
this.entityData.set(QUIRK_TICKS, QUIRK_ANIMATION_TICKS);
if (this.isBaby()) {
this.entityData.set(QUIRK_VARIANT, 0);
} else if (this.isSitting()) {
this.entityData.set(QUIRK_VARIANT, 2);
} else {
this.entityData.set(QUIRK_VARIANT, this.random.nextBoolean() ? 0 : 1);
}
}
public void triggerCry() {
this.entityData.set(CRY_TICKS, CRY_ANIM_TICKS);
}
public void triggerAttackAnim() {
this.entityData.set(ATTACK_TICKS, ATTACK_ANIM_TICKS);
}
@Override
public boolean doHurtTarget(net.minecraft.world.entity.Entity target) {
boolean hurt = super.doHurtTarget(target);
if (hurt) {
this.triggerAttackAnim();
}
return hurt;
}
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) {
return;
}
int quirkTicks = this.entityData.get(QUIRK_TICKS);
if (quirkTicks > 0) {
this.entityData.set(QUIRK_TICKS, quirkTicks - 1);
}
int attackTicks = this.entityData.get(ATTACK_TICKS);
if (attackTicks > 0) {
this.entityData.set(ATTACK_TICKS, attackTicks - 1);
}
int cryTicks = this.entityData.get(CRY_TICKS);
if (cryTicks > 0) {
this.entityData.set(CRY_TICKS, cryTicks - 1);
}
this.tickSitDecision();
this.tickIdleQuirk();
}
@Override
public boolean hurt(DamageSource source, float amount) {
this.stopSitting();
return super.hurt(source, amount);
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
return this.isBaby()
? EntityDimensions.scalable(1.0F, 1.5F)
: EntityDimensions.scalable(2.2F, 2.75F);
}
@Override
protected void ageBoundaryReached() {
super.ageBoundaryReached();
this.refreshDimensions();
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putBoolean(SITTING_KEY, this.isSitting());
tag.putInt(SIT_TICKS_KEY, this.sitTicksRemaining);
tag.putInt(SIT_COOLDOWN_KEY, this.sitCooldownTicks);
tag.putInt(QUIRK_TICKS_KEY, this.entityData.get(QUIRK_TICKS));
tag.putInt(QUIRK_VARIANT_KEY, this.entityData.get(QUIRK_VARIANT));
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.entityData.set(SITTING, tag.getBoolean(SITTING_KEY));
this.sitTicksRemaining = tag.getInt(SIT_TICKS_KEY);
this.sitCooldownTicks = tag.contains(SIT_COOLDOWN_KEY) ? tag.getInt(SIT_COOLDOWN_KEY) : SIT_COOLDOWN_TICKS;
this.entityData.set(QUIRK_TICKS, tag.getInt(QUIRK_TICKS_KEY));
this.entityData.set(QUIRK_VARIANT, tag.getInt(QUIRK_VARIANT_KEY));
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.PANDA_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.PANDA_BITE;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.PANDA_DEATH;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, MAIN_CONTROLLER, 0, this::mainAnimController));
if (!this.isBaby()) {
controllers.add(new AnimationController<>(this, SIT_TRANSITION_CONTROLLER, 0, state -> PlayState.STOP)
.triggerableAnim(START_SIT_TRIGGER, START_SIT_ANIM)
.triggerableAnim(GET_UP_TRIGGER, GET_UP_ANIM));
}
}
private PlayState mainAnimController(AnimationState<ElkaEntity> state) {
if (this.entityData.get(ATTACK_TICKS) > 0) {
return state.setAndContinue(ATTACK_ANIM);
}
if (this.entityData.get(CRY_TICKS) > 0) {
return state.setAndContinue(CRY_ANIM);
}
if (state.isMoving()) {
return state.setAndContinue(WALK_ANIM);
}
if (this.entityData.get(QUIRK_TICKS) > 0) {
return switch (this.entityData.get(QUIRK_VARIANT)) {
case 1 -> state.setAndContinue(QUIRK_2_ANIM);
case 2 -> state.setAndContinue(QUIRK_3_ANIM);
default -> state.setAndContinue(QUIRK_ANIM);
};
}
if (this.isSitting()) {
return state.setAndContinue(SIT_IDLE_ANIM);
}
return state.setAndContinue(IDLE_ANIM);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
private final class ElkaSitGoal extends Goal {
private ElkaSitGoal() {
this.setFlags(java.util.EnumSet.of(Flag.MOVE, Flag.JUMP, Flag.LOOK));
}
@Override
public boolean canUse() {
return ElkaEntity.this.isSitting();
}
@Override
public boolean canContinueToUse() {
return ElkaEntity.this.isSitting();
}
@Override
public void start() {
ElkaEntity.this.getNavigation().stop();
ElkaEntity.this.setDeltaMovement(0.0D, ElkaEntity.this.getDeltaMovement().y, 0.0D);
}
@Override
public void tick() {
ElkaEntity.this.getNavigation().stop();
ElkaEntity.this.setZza(0.0F);
ElkaEntity.this.setXxa(0.0F);
}
}
private static final class ElkaHurtByTargetGoal extends HurtByTargetGoal {
private ElkaHurtByTargetGoal(ElkaEntity elka) {
super(elka);
}
@Override
public void start() {
super.start();
if (this.mob instanceof ElkaEntity elka && elka.isBaby()) {
this.alertOthers();
this.stop();
}
}
@Override
protected void alertOther(Mob mob, LivingEntity target) {
if (mob instanceof ElkaEntity elka && !elka.isBaby()) {
elka.triggerCry();
super.alertOther(mob, target);
}
}
}
}
@@ -0,0 +1,38 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.animal.FrogVariant;
import net.minecraft.world.entity.animal.frog.Frog;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
public final class ElythiaFrog {
public static final ResourceKey<Level> ELYTHIA = ResourceKey.create(
net.minecraft.core.registries.Registries.DIMENSION,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "elythia")
);
public static final ResourceKey<FrogVariant> VARIANT = ResourceKey.create(
net.minecraft.core.registries.Registries.FROG_VARIANT,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "elythia")
);
public static final ResourceLocation TEXTURE =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/entity/frog/frog_elythia.png");
private ElythiaFrog() {
}
public static boolean shouldBecomeElythiaFrog(ServerLevelAccessor level) {
return level.getLevel().dimension().equals(ELYTHIA);
}
public static void applyVariant(Frog frog) {
BuiltInRegistries.FROG_VARIANT.getHolder(VARIANT).ifPresent(frog::setVariant);
}
public static boolean isElythiaFrog(Frog frog) {
return frog.getVariant().unwrapKey().filter(VARIANT::equals).isPresent();
}
}
@@ -1,430 +0,0 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.content.GlimmerParticles;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.FollowParentGoal;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.PanicGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.TemptGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.ai.util.DefaultRandomPos;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.phys.Vec3;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class GlimmerEntity extends Animal implements GeoEntity {
private static final EntityDataAccessor<Integer> GLOW_TICKS =
SynchedEntityData.defineId(GlimmerEntity.class, EntityDataSerializers.INT);
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation RUN_ANIM = RawAnimation.begin().thenLoop("run");
private static final String QUIRK_CONTROLLER = "quirk_controller";
private static final String QUIRK_TRIGGER = "quirk";
private static final int TRUST_REQUIRED_FEEDS = 3;
private static final int GRACE_DURATION_TICKS = 2400;
private static final int GRACE_COOLDOWN_TICKS = 6000;
private static final int GLOW_LIGHT_THRESHOLD = 7;
private static final int GLOW_CHECK_INTERVAL_TICKS = 20;
private static final int GLOW_TRANSITION_TICKS = 10;
private static final double WALK_SPEED_THRESHOLD_SQR = 0.0009D;
private static final double RUN_SPEED_THRESHOLD_SQR = 0.0035D;
private static final int MIN_QUIRK_COOLDOWN = 20 * 8;
private static final int MAX_QUIRK_COOLDOWN = 20 * 25;
private static final String TRUST_KEY = "GlimmerTrust";
private static final String TRUST_PLAYER_KEY = "Player";
private static final String TRUST_FEED_COUNT_KEY = "FeedCount";
private static final String TRUST_TRUSTED_KEY = "Trusted";
private static final String TRUST_COOLDOWN_KEY = "GraceCooldown";
private static final String GLOWING_SAVE_KEY = "Glowing";
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private final Map<UUID, TrustEntry> trustByPlayer = new HashMap<>();
private boolean glowing;
private int quirkCooldown = createQuirkCooldown();
public GlimmerEntity(EntityType<? extends GlimmerEntity> entityType, Level level) {
super(entityType, level);
}
public static AttributeSupplier.Builder createAttributes() {
return net.minecraft.world.entity.Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.glimmerHealth())
.add(Attributes.MOVEMENT_SPEED, 0.3D)
.add(Attributes.FOLLOW_RANGE, 24.0D);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(GLOW_TICKS, 0);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 1.6D));
this.goalSelector.addGoal(2, new TemptGoal(this, 1.1D, Ingredient.of(AntarchyTags.Items.GLIMMER_FOOD), false));
this.goalSelector.addGoal(3, new AvoidUntrustedPlayerGoal(10.0F, 1.15D));
this.goalSelector.addGoal(4, new net.minecraft.world.entity.ai.goal.AvoidEntityGoal<>(this, Monster.class, 8.0F, 1.0D, 1.2D));
this.goalSelector.addGoal(5, new FollowParentGoal(this, 1.25D));
this.goalSelector.addGoal(6, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
}
@Override
public boolean isFood(ItemStack stack) {
return stack.is(AntarchyTags.Items.GLIMMER_FOOD);
}
@Override
public GlimmerEntity getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
return null;
}
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (stack.is(AntarchyTags.Items.GLIMMER_FOOD)) {
return this.feedGlimmerFood(player, stack, hand);
}
return super.mobInteract(player, hand);
}
private InteractionResult feedGlimmerFood(Player player, ItemStack stack, InteractionHand hand) {
if (this.level().isClientSide) {
return InteractionResult.CONSUME;
}
this.usePlayerItem(player, hand, stack);
this.playSound(SoundEvents.GENERIC_EAT, 1.0F, 1.0F + (this.random.nextFloat() - 0.5F) * 0.2F);
if (!this.isBaby()) {
TrustEntry entry = this.trustByPlayer.computeIfAbsent(player.getUUID(), id -> new TrustEntry());
if (!entry.trusted) {
entry.feedCount++;
if (entry.feedCount >= TRUST_REQUIRED_FEEDS) {
entry.trusted = true;
}
} else if (this.glowing && entry.graceCooldown <= 0) {
player.addEffect(new MobEffectInstance(AntarchyObjects.GLIMMERS_GRACE.get(), GRACE_DURATION_TICKS, 0, false, true, true));
entry.graceCooldown = GRACE_COOLDOWN_TICKS;
}
}
return InteractionResult.SUCCESS;
}
public boolean isTrusted(UUID playerId) {
TrustEntry entry = this.trustByPlayer.get(playerId);
return entry != null && entry.trusted;
}
private boolean hasAnyTrustProgress() {
for (TrustEntry entry : this.trustByPlayer.values()) {
if (entry.trusted || entry.feedCount > 0) {
return true;
}
}
return false;
}
@Override
public boolean isPersistenceRequired() {
return super.isPersistenceRequired() || (!this.isBaby() && this.hasAnyTrustProgress());
}
public boolean isGlowingState() {
return this.getGlowTicks() > 0;
}
public float getGlowBlend(float partialTick) {
return Mth.clamp((this.getGlowTicks() + partialTick) / (float) GLOW_TRANSITION_TICKS, 0.0F, 1.0F);
}
@Override
public void tick() {
super.tick();
if (this.quirkCooldown > 0) {
this.quirkCooldown--;
}
if (!this.level().isClientSide && this.tickCount % GLOW_CHECK_INTERVAL_TICKS == 0) {
this.glowing = this.level().getMaxLocalRawBrightness(this.blockPosition()) <= GLOW_LIGHT_THRESHOLD;
}
if (!this.level().isClientSide) {
int glowTicks = this.getGlowTicks();
if (this.glowing && glowTicks < GLOW_TRANSITION_TICKS) {
this.setGlowTicks(glowTicks + 1);
} else if (!this.glowing && glowTicks > 0) {
this.setGlowTicks(glowTicks - 1);
}
}
if (this.isGlowingState()) {
GlimmerParticles.tickAmbient(this, this.getDeltaMovement().horizontalDistanceSqr() > RUN_SPEED_THRESHOLD_SQR);
}
if (this.level().isClientSide) {
return;
}
for (TrustEntry entry : this.trustByPlayer.values()) {
if (entry.graceCooldown > 0) {
entry.graceCooldown--;
}
}
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putBoolean(GLOWING_SAVE_KEY, this.glowing);
ListTag trustList = new ListTag();
for (Map.Entry<UUID, TrustEntry> entry : this.trustByPlayer.entrySet()) {
CompoundTag entryTag = new CompoundTag();
entryTag.putUUID(TRUST_PLAYER_KEY, entry.getKey());
entryTag.putInt(TRUST_FEED_COUNT_KEY, entry.getValue().feedCount);
entryTag.putBoolean(TRUST_TRUSTED_KEY, entry.getValue().trusted);
entryTag.putInt(TRUST_COOLDOWN_KEY, entry.getValue().graceCooldown);
trustList.add(entryTag);
}
tag.put(TRUST_KEY, trustList);
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.glowing = tag.getBoolean(GLOWING_SAVE_KEY);
this.setGlowTicks(this.glowing ? GLOW_TRANSITION_TICKS : 0);
this.trustByPlayer.clear();
for (net.minecraft.nbt.Tag rawEntry : tag.getList(TRUST_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
CompoundTag entryTag = (CompoundTag) rawEntry;
TrustEntry entry = new TrustEntry();
entry.feedCount = entryTag.getInt(TRUST_FEED_COUNT_KEY);
entry.trusted = entryTag.getBoolean(TRUST_TRUSTED_KEY);
entry.graceCooldown = entryTag.getInt(TRUST_COOLDOWN_KEY);
this.trustByPlayer.put(entryTag.getUUID(TRUST_PLAYER_KEY), entry);
}
}
@Override
@Nullable
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData spawnGroupData) {
GlimmerSpawnData glimmerSpawnData = spawnGroupData instanceof GlimmerSpawnData data ? data : new GlimmerSpawnData();
SpawnGroupData result = super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
if ((spawnReason == MobSpawnType.NATURAL || spawnReason == MobSpawnType.CHUNK_GENERATION)) {
if (glimmerSpawnData.adultSpawned) {
this.setAge(-24000);
} else {
glimmerSpawnData.adultSpawned = true;
}
}
return result != null ? result : glimmerSpawnData;
}
@Override
public net.minecraft.world.entity.EntityDimensions getDefaultDimensions(net.minecraft.world.entity.Pose pose) {
net.minecraft.world.entity.EntityDimensions dimensions = super.getDefaultDimensions(pose);
if (this.isBaby()) {
return dimensions;
}
return net.minecraft.world.entity.EntityDimensions.scalable(dimensions.width() * 2.0F, dimensions.height() * 2.0F);
}
@Override
protected void ageBoundaryReached() {
super.ageBoundaryReached();
this.refreshDimensions();
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.FOX_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.FOX_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.FOX_DEATH;
}
@Override
protected float getSoundVolume() {
return 0.8F;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 0, this::mainAnimController));
controllers.add(new AnimationController<>(this, QUIRK_CONTROLLER, 0, state -> PlayState.STOP)
.triggerableAnim(QUIRK_TRIGGER, RawAnimation.begin().thenPlay("quirk")));
}
private PlayState mainAnimController(AnimationState<GlimmerEntity> state) {
double speedSqr = this.getDeltaMovement().horizontalDistanceSqr();
if (speedSqr > RUN_SPEED_THRESHOLD_SQR) {
return state.setAndContinue(RUN_ANIM);
}
if (speedSqr > WALK_SPEED_THRESHOLD_SQR) {
return state.setAndContinue(WALK_ANIM);
}
if (this.quirkCooldown <= 0) {
this.quirkCooldown = this.createQuirkCooldown();
this.triggerAnim(QUIRK_CONTROLLER, QUIRK_TRIGGER);
}
return state.setAndContinue(IDLE_ANIM);
}
private int createQuirkCooldown() {
return MIN_QUIRK_COOLDOWN + this.random.nextInt(MAX_QUIRK_COOLDOWN - MIN_QUIRK_COOLDOWN + 1);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
private int getGlowTicks() {
return this.entityData.get(GLOW_TICKS);
}
private void setGlowTicks(int glowTicks) {
this.entityData.set(GLOW_TICKS, Mth.clamp(glowTicks, 0, GLOW_TRANSITION_TICKS));
}
private static final class TrustEntry {
int feedCount;
boolean trusted;
int graceCooldown;
}
private static final class GlimmerSpawnData implements SpawnGroupData {
private boolean adultSpawned;
}
private final class AvoidUntrustedPlayerGoal extends Goal {
private final float maxDistance;
private final double speedModifier;
private Player avoidTarget;
private Path path;
AvoidUntrustedPlayerGoal(float maxDistance, double speedModifier) {
this.maxDistance = maxDistance;
this.speedModifier = speedModifier;
this.setFlags(EnumSet.of(Flag.MOVE));
}
@Override
public boolean canUse() {
if (GlimmerEntity.this.isBaby()) {
return false;
}
Player nearest = GlimmerEntity.this.level().getNearestPlayer(GlimmerEntity.this, this.maxDistance);
if (nearest == null || nearest.isSpectator() || nearest.isCreative()
|| GlimmerEntity.this.isTrusted(nearest.getUUID())
|| GlimmerEntity.this.distanceToSqr(nearest) > (double) (this.maxDistance * this.maxDistance)) {
return false;
}
Path escapePath = this.findEscapePath(nearest);
if (escapePath == null) {
return false;
}
this.avoidTarget = nearest;
this.path = escapePath;
return true;
}
@Override
public boolean canContinueToUse() {
return this.avoidTarget != null
&& this.avoidTarget.isAlive()
&& !GlimmerEntity.this.isTrusted(this.avoidTarget.getUUID())
&& !GlimmerEntity.this.getNavigation().isDone();
}
@Override
public void start() {
GlimmerEntity.this.getNavigation().moveTo(this.path, this.speedModifier);
}
@Override
public void stop() {
this.avoidTarget = null;
this.path = null;
}
@Nullable
private Path findEscapePath(Player toAvoid) {
Vec3 escapeVec = DefaultRandomPos.getPosAway(GlimmerEntity.this, 16, 7, toAvoid.position());
if (escapeVec == null) {
return null;
}
return GlimmerEntity.this.getNavigation().createPath(BlockPos.containing(escapeVec), 0);
}
}
}
@@ -0,0 +1,303 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchyTags;
import java.util.EnumSet;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.BreedGoal;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.FollowParentGoal;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.PanicGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.TemptGoal;
import net.minecraft.world.entity.ai.util.DefaultRandomPos;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.phys.Vec3;
import net.minecraft.server.level.ServerLevel;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class OuranwoodDeerEntity extends Animal implements GeoEntity {
public enum Variant { BUCK, DOE }
private static final EntityDataAccessor<Integer> VARIANT =
SynchedEntityData.defineId(OuranwoodDeerEntity.class, EntityDataSerializers.INT);
private static final String VARIANT_KEY = "Variant";
private static final float ADULT_HITBOX_SCALE = 1.5F;
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final RawAnimation RUN_ANIM = RawAnimation.begin().thenLoop("run");
private static final String QUIRK_CONTROLLER = "quirk_controller";
private static final String QUIRK_TRIGGER = "quirk";
private static final double WALK_SPEED_THRESHOLD_SQR = 0.0009D;
private static final double RUN_SPEED_THRESHOLD_SQR = 0.0035D;
private static final double WALK_STROLL_SPEED = 1.0D;
private static final double RUN_STROLL_SPEED = 1.45D;
private static final int MIN_QUIRK_COOLDOWN = 20 * 8;
private static final int MAX_QUIRK_COOLDOWN = 20 * 25;
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int quirkCooldown = createQuirkCooldown();
public OuranwoodDeerEntity(EntityType<? extends OuranwoodDeerEntity> entityType, Level level) {
super(entityType, level);
}
public static AttributeSupplier.Builder createAttributes() {
return net.minecraft.world.entity.Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.ouranwoodDeerHealth())
.add(Attributes.MOVEMENT_SPEED, 0.3D)
.add(Attributes.FOLLOW_RANGE, 24.0D);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(VARIANT, Variant.DOE.ordinal());
}
public Variant getVariant() {
return Variant.values()[Mth.clamp(this.entityData.get(VARIANT), 0, Variant.values().length - 1)];
}
public void setVariant(Variant variant) {
this.entityData.set(VARIANT, variant.ordinal());
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 1.8D));
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
this.goalSelector.addGoal(3, new TemptGoal(this, 1.1D, Ingredient.of(AntarchyTags.Items.OURANWOOD_DEER_FOOD), false));
this.goalSelector.addGoal(4, new net.minecraft.world.entity.ai.goal.AvoidEntityGoal<>(this, Monster.class, 8.0F, 1.1D, 1.5D));
this.goalSelector.addGoal(5, new FollowParentGoal(this, 1.25D));
this.goalSelector.addGoal(6, new OuranwoodDeerStrollGoal());
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
}
@Override
public boolean isFood(ItemStack stack) {
return stack.is(AntarchyTags.Items.OURANWOOD_DEER_FOOD);
}
@Override
public OuranwoodDeerEntity getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
return AntarchyObjects.OURANWOOD_DEER.get().create(level);
}
@Override
public void tick() {
super.tick();
if (this.quirkCooldown > 0) {
this.quirkCooldown--;
}
if (this.level().isClientSide) {
return;
}
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putString(VARIANT_KEY, this.getVariant().name());
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
if (tag.contains(VARIANT_KEY)) {
try {
this.setVariant(Variant.valueOf(tag.getString(VARIANT_KEY)));
} catch (IllegalArgumentException ignored) {
this.setVariant(Variant.DOE);
}
}
}
@Override
@Nullable
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData spawnGroupData) {
DeerSpawnData deerSpawnData = spawnGroupData instanceof DeerSpawnData data ? data : new DeerSpawnData();
SpawnGroupData result = super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
if (spawnReason == MobSpawnType.NATURAL || spawnReason == MobSpawnType.CHUNK_GENERATION || spawnReason == MobSpawnType.SPAWN_EGG) {
this.setVariant(Variant.values()[this.random.nextInt(Variant.values().length)]);
}
if ((spawnReason == MobSpawnType.NATURAL || spawnReason == MobSpawnType.CHUNK_GENERATION)) {
if (deerSpawnData.adultSpawned) {
this.setAge(-24000);
} else {
deerSpawnData.adultSpawned = true;
}
}
return result != null ? result : deerSpawnData;
}
@Override
public net.minecraft.world.entity.EntityDimensions getDefaultDimensions(net.minecraft.world.entity.Pose pose) {
net.minecraft.world.entity.EntityDimensions dimensions = super.getDefaultDimensions(pose);
if (this.isBaby()) {
return dimensions;
}
return net.minecraft.world.entity.EntityDimensions.scalable(
dimensions.width() * ADULT_HITBOX_SCALE,
dimensions.height() * ADULT_HITBOX_SCALE
);
}
@Override
protected void ageBoundaryReached() {
super.ageBoundaryReached();
this.refreshDimensions();
}
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.FOX_AMBIENT;
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.FOX_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return SoundEvents.FOX_DEATH;
}
@Override
protected float getSoundVolume() {
return 0.8F;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 0, this::mainAnimController));
controllers.add(new AnimationController<>(this, QUIRK_CONTROLLER, 0, state -> PlayState.STOP)
.triggerableAnim(QUIRK_TRIGGER, RawAnimation.begin().thenPlay("quirk")));
}
private PlayState mainAnimController(AnimationState<OuranwoodDeerEntity> state) {
double speedSqr = this.getDeltaMovement().horizontalDistanceSqr();
if (this.hurtTime > 0 || speedSqr > RUN_SPEED_THRESHOLD_SQR) {
return state.setAndContinue(RUN_ANIM);
}
if (speedSqr > WALK_SPEED_THRESHOLD_SQR) {
return state.setAndContinue(WALK_ANIM);
}
if (this.quirkCooldown <= 0) {
this.quirkCooldown = this.createQuirkCooldown();
this.triggerAnim(QUIRK_CONTROLLER, QUIRK_TRIGGER);
}
return state.setAndContinue(IDLE_ANIM);
}
private int createQuirkCooldown() {
return MIN_QUIRK_COOLDOWN + this.random.nextInt(MAX_QUIRK_COOLDOWN - MIN_QUIRK_COOLDOWN + 1);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
private static final class DeerSpawnData implements SpawnGroupData {
private boolean adultSpawned;
}
private final class OuranwoodDeerStrollGoal extends Goal {
private static final int RUN_CHANCE = 5;
@Nullable
private Vec3 targetPos;
private double speedModifier;
private OuranwoodDeerStrollGoal() {
this.setFlags(EnumSet.of(Flag.MOVE));
}
@Override
public boolean canUse() {
if (OuranwoodDeerEntity.this.isVehicle() || !OuranwoodDeerEntity.this.getNavigation().isDone()) {
return false;
}
if (OuranwoodDeerEntity.this.getRandom().nextInt(reducedTickDelay(60)) != 0) {
return false;
}
this.targetPos = DefaultRandomPos.getPos(OuranwoodDeerEntity.this, 10, 7);
if (this.targetPos == null) {
return false;
}
this.speedModifier = this.pickSpeed();
return true;
}
@Override
public boolean canContinueToUse() {
return this.targetPos != null && !OuranwoodDeerEntity.this.getNavigation().isDone();
}
@Override
public void start() {
if (this.targetPos != null) {
OuranwoodDeerEntity.this.getNavigation().moveTo(this.targetPos.x, this.targetPos.y, this.targetPos.z, this.speedModifier);
}
}
@Override
public void stop() {
this.targetPos = null;
}
private double pickSpeed() {
if (OuranwoodDeerEntity.this.hurtTime > 0) {
return RUN_STROLL_SPEED;
}
return OuranwoodDeerEntity.this.random.nextInt(RUN_CHANCE) == 0 ? RUN_STROLL_SPEED : WALK_STROLL_SPEED;
}
}
}
@@ -0,0 +1,21 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level;
public class PeachBoatEntity extends Boat {
public PeachBoatEntity(EntityType<? extends Boat> entityType, Level level) {
super(entityType, level);
this.setVariant(Type.OAK);
}
@Override
public Item getDropItem() {
return BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "peach_boat"));
}
}
@@ -0,0 +1,22 @@
package com.craisinlord.antarchy.content.entity;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.entity.vehicle.ChestBoat;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.Level;
public class PeachChestBoatEntity extends ChestBoat {
public PeachChestBoatEntity(EntityType<? extends Boat> entityType, Level level) {
super(entityType, level);
this.setVariant(Type.OAK);
}
@Override
public Item getDropItem() {
return BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "peach_chest_boat"));
}
}
@@ -370,7 +370,7 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
Player shoulderPlayer = this.shoulderPlayerId != null Player shoulderPlayer = this.shoulderPlayerId != null
? this.level().getPlayerByUUID(this.shoulderPlayerId) ? this.level().getPlayerByUUID(this.shoulderPlayerId)
: null; : null;
if (shoulderPlayer == null || !shoulderPlayer.isAlive()) { if (shoulderPlayer == null || !shoulderPlayer.isAlive() || shoulderPlayer.isSpectator()) {
this.dismountShoulder(); this.dismountShoulder();
} else { } else {
this.tickClimbingToShoulder(shoulderPlayer); this.tickClimbingToShoulder(shoulderPlayer);
@@ -381,7 +381,7 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
if (this.isOnShoulder()) { if (this.isOnShoulder()) {
Player shoulderPlayer = this.getShoulderPlayer(); Player shoulderPlayer = this.getShoulderPlayer();
if (shoulderPlayer == null || !shoulderPlayer.isAlive()) { if (shoulderPlayer == null || !shoulderPlayer.isAlive() || shoulderPlayer.isSpectator()) {
this.dismountShoulder(); this.dismountShoulder();
} else if (this.shoulderSneakGraceTicks <= 0 && shoulderPlayer.isShiftKeyDown()) { } else if (this.shoulderSneakGraceTicks <= 0 && shoulderPlayer.isShiftKeyDown()) {
this.glideOffShoulder(); this.glideOffShoulder();
@@ -555,6 +555,10 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
return false; return false;
} }
if (player.isSpectator()) {
return true;
}
if (this.isOnShoulder()) { if (this.isOnShoulder()) {
if (!this.level().isClientSide) { if (!this.level().isClientSide) {
this.glideOffShoulder(); this.glideOffShoulder();
@@ -629,6 +633,12 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
this.pickupAnimTicks = 0; this.pickupAnimTicks = 0;
return finalized; return finalized;
} }
@Override
public boolean shouldBeSaved() {
return super.shouldBeSaved() && !this.isOnShoulder() && !this.isClimbingToShoulder();
}
@Override @Override
protected SoundEvent getAmbientSound() { protected SoundEvent getAmbientSound() {
return AntarchySoundEvents.FLYING_SQUIRREL_IDLE.get(); return AntarchySoundEvents.FLYING_SQUIRREL_IDLE.get();
@@ -731,6 +741,9 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
} }
private void startShoulderMountSequence(Player player) { private void startShoulderMountSequence(Player player) {
if (player.isSpectator()) {
return;
}
this.shoulderPlayerId = player.getUUID(); this.shoulderPlayerId = player.getUUID();
this.shoulderClimbStartPos = this.position(); this.shoulderClimbStartPos = this.position();
this.climbingToShoulderTicks = SHOULDER_CLIMB_TICKS; this.climbingToShoulderTicks = SHOULDER_CLIMB_TICKS;
@@ -0,0 +1,124 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.monster.Enemy;
import net.minecraft.world.entity.player.Player;
public class GlimmerAntBehavior implements GlimmerVariantBehavior {
private static final ResourceLocation REACH_BOOST_ID =
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "glimmer_ant_reach_boost");
private static final double REACH_BOOST_AMOUNT = 0.5D;
private static final float LARGE_ENEMY_MIN_HEALTH = 40.0F;
private static final int ABILITY_COOLDOWN_TICKS = 20 * 120;
private static final int STRENGTH_BURST_DURATION = 20 * 12;
private static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
@Override
public void registerGoals(GlimmerEntity entity) {
entity.addGoal(0, new FloatGoal(entity));
entity.addGoal(5, new WaterAvoidingRandomStrollGoal(entity, 1.1D));
entity.addGoal(6, new LookAtPlayerGoal(entity, Player.class, 8.0F));
entity.addGoal(7, new RandomLookAroundGoal(entity));
}
@Override
public ResourceLocation modelGeo() {
return rl("geo/ant.geo.json");
}
@Override
public ResourceLocation animationFile() {
return rl("animations/ant.animation.json");
}
@Override
public ResourceLocation texture(GlimmerEntity entity) {
return rl("textures/entity/glimmer/glimmer_ant.png");
}
@Override
public ResourceLocation emissiveTexture(GlimmerEntity entity) {
return rl("textures/entity/glimmer/glimmer_ant_emmisive.png");
}
@Override
public SoundEvent ambientSound() {
return AntarchySoundEvents.ANT_AMBIENT.get();
}
@Override
public SoundEvent hurtSound() {
return AntarchySoundEvents.ANT_HURT.get();
}
@Override
public SoundEvent deathSound() {
return AntarchySoundEvents.ANT_HURT.get();
}
@Override
public EntityDimensions adultDimensions() {
// Matches the real ant's hitbox.
return EntityDimensions.scalable(0.6375F, 0.2125F);
}
@Override
public void tickPassiveEveryTick(GlimmerEntity entity, Player owner) {
AttributeInstance instance = owner.getAttribute(Attributes.ENTITY_INTERACTION_RANGE);
if (instance == null) {
return;
}
if (instance.getModifier(REACH_BOOST_ID) == null) {
instance.addTransientModifier(new AttributeModifier(REACH_BOOST_ID, REACH_BOOST_AMOUNT, AttributeModifier.Operation.ADD_VALUE));
}
}
@Override
public void clearPassive(GlimmerEntity entity, Player owner) {
AttributeInstance instance = owner.getAttribute(Attributes.ENTITY_INTERACTION_RANGE);
if (instance != null) {
instance.removeModifier(REACH_BOOST_ID);
}
}
@Override
public int abilityCooldownTicks() {
return ABILITY_COOLDOWN_TICKS;
}
@Override
public void tickAbilityCheck(GlimmerEntity entity, Player owner) {
if (owner.getLastHurtMobTimestamp() != owner.tickCount) {
return;
}
LivingEntity target = owner.getLastHurtMob();
if (!(target instanceof Enemy) || target.getMaxHealth() < LARGE_ENEMY_MIN_HEALTH) {
return;
}
owner.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, STRENGTH_BURST_DURATION, 1, false, true, true));
owner.level().playSound(null, owner.blockPosition(), SoundEvents.PLAYER_ATTACK_STRONG, owner.getSoundSource(), 1.0F, 0.8F);
this.sendAbilityMessage(owner, Component.translatable("entity.antarchy.glimmer.ability.ant", target.getDisplayName()));
entity.startAbilityCooldown();
}
}
@@ -0,0 +1,140 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchyObjects;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodData;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
public class GlimmerAppleCowBehavior implements GlimmerVariantBehavior {
private static final int SHEAR_REGROW_TICKS = 20 * 60 * 3;
private static final float EXHAUSTION_REDUCTION = 0.25F;
private static final int LOW_FOOD_THRESHOLD = 16;
private static final int ABILITY_COOLDOWN_TICKS = 20 * 120;
private static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
@Override
public void registerGoals(GlimmerEntity entity) {
entity.addGoal(0, new FloatGoal(entity));
entity.addGoal(5, new WaterAvoidingRandomStrollGoal(entity, 1.0D));
entity.addGoal(6, new LookAtPlayerGoal(entity, Player.class, 8.0F));
entity.addGoal(7, new RandomLookAroundGoal(entity));
}
@Override
public ResourceLocation modelGeo() {
return rl("geo/apple_cow.geo.json");
}
@Override
public ResourceLocation animationFile() {
return rl("animations/apple_cow.animation.json");
}
@Override
public ResourceLocation texture(GlimmerEntity entity) {
return entity.isShearCooldownActive()
? rl("textures/entity/glimmer/apple_cow_glimmer_sheared.png")
: rl("textures/entity/glimmer/apple_cow_glimmer.png");
}
@Override
public ResourceLocation emissiveTexture(GlimmerEntity entity) {
return entity.isShearCooldownActive()
? rl("textures/entity/glimmer/apple_cow_glimmer_sheared_emissive.png")
: rl("textures/entity/glimmer/apple_cow_glimmer_emissive.png");
}
@Override
public SoundEvent ambientSound() {
return SoundEvents.COW_AMBIENT;
}
@Override
public SoundEvent hurtSound() {
return SoundEvents.COW_HURT;
}
@Override
public SoundEvent deathSound() {
return SoundEvents.COW_DEATH;
}
@Override
public EntityDimensions adultDimensions() {
// Matches the real Apple Cow's hitbox (extends vanilla Cow).
return EntityDimensions.scalable(0.9F, 1.4F);
}
@Override
@Nullable
public InteractionResult onInteract(GlimmerEntity entity, Player player, ItemStack stack, InteractionHand hand) {
if (entity.isTame() || entity.isShearCooldownActive() || !stack.is(Items.SHEARS)) {
return null;
}
if (entity.level().isClientSide) {
return InteractionResult.CONSUME;
}
entity.startShearCooldown(SHEAR_REGROW_TICKS);
entity.playSound(SoundEvents.SHEEP_SHEAR, 1.0F, 1.0F);
stack.hurtAndBreak(1, player, hand == InteractionHand.MAIN_HAND
? net.minecraft.world.entity.EquipmentSlot.MAINHAND
: net.minecraft.world.entity.EquipmentSlot.OFFHAND);
entity.spawnAtLocation(new net.minecraft.world.item.ItemStack(AntarchyObjects.SPIRIT_APPLE.get()), entity.getBbHeight() * 0.6F);
return InteractionResult.SUCCESS;
}
@Override
public void tickPassiveEveryTick(GlimmerEntity entity, Player owner) {
FoodData food = owner.getFoodData();
float current = food.getExhaustionLevel();
float last = entity.getLastOwnerExhaustion();
if (last >= 0.0F && current > last) {
owner.causeFoodExhaustion(-(current - last) * EXHAUSTION_REDUCTION);
}
entity.setLastOwnerExhaustion(food.getExhaustionLevel());
}
@Override
public void clearPassive(GlimmerEntity entity, Player owner) {
entity.setLastOwnerExhaustion(-1.0F);
}
@Override
public int abilityCooldownTicks() {
return ABILITY_COOLDOWN_TICKS;
}
@Override
public void tickAbilityCheck(GlimmerEntity entity, Player owner) {
if (owner.getFoodData().getFoodLevel() > LOW_FOOD_THRESHOLD) {
return;
}
owner.addEffect(new MobEffectInstance(MobEffects.SATURATION, 20, 1, false, false, true));
owner.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 20 * 8, 1, false, true, true));
entity.playSound(SoundEvents.COW_AMBIENT, 1.2F, 0.8F);
this.sendAbilityMessage(owner, Component.translatable("entity.antarchy.glimmer.ability.apple_cow"));
entity.startAbilityCooldown();
}
}
@@ -0,0 +1,94 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.saveddata.SavedData;
/**
* Tracks each player's current Glimmer companion so taming a new one despawns the old,
* even if the old companion is in a different loaded dimension.
*/
public class GlimmerCompanionSavedData extends SavedData {
private static final String ID = "antarchy_glimmer_companions";
private static final String ENTRIES_KEY = "Entries";
private static final String OWNER_KEY = "Owner";
private static final String COMPANION_KEY = "Companion";
private final Map<UUID, UUID> companionByOwner = new HashMap<>();
public static GlimmerCompanionSavedData create() {
return new GlimmerCompanionSavedData();
}
public static GlimmerCompanionSavedData load(CompoundTag tag, HolderLookup.Provider registries) {
GlimmerCompanionSavedData data = new GlimmerCompanionSavedData();
for (net.minecraft.nbt.Tag rawEntry : tag.getList(ENTRIES_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
CompoundTag entryTag = (CompoundTag) rawEntry;
data.companionByOwner.put(entryTag.getUUID(OWNER_KEY), entryTag.getUUID(COMPANION_KEY));
}
return data;
}
@Override
public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) {
ListTag entries = new ListTag();
for (Map.Entry<UUID, UUID> entry : this.companionByOwner.entrySet()) {
CompoundTag entryTag = new CompoundTag();
entryTag.putUUID(OWNER_KEY, entry.getKey());
entryTag.putUUID(COMPANION_KEY, entry.getValue());
entries.add(entryTag);
}
tag.put(ENTRIES_KEY, entries);
return tag;
}
private static GlimmerCompanionSavedData get(MinecraftServer server) {
return server.overworld().getDataStorage().computeIfAbsent(
new SavedData.Factory<GlimmerCompanionSavedData>(GlimmerCompanionSavedData::create, GlimmerCompanionSavedData::load, null),
ID
);
}
/**
* Records {@code newCompanion} as {@code owner}'s current Glimmer. Any previous
* companion found in a currently loaded dimension isn't removed - it just loses
* its owner and becomes a wild, re-tameable Glimmer again.
*/
public static void replaceCompanion(MinecraftServer server, UUID owner, UUID newCompanion) {
GlimmerCompanionSavedData data = get(server);
UUID previous = data.companionByOwner.put(owner, newCompanion);
data.setDirty();
if (previous == null || previous.equals(newCompanion)) {
return;
}
for (ServerLevel level : server.getAllLevels()) {
Entity entity = level.getEntity(previous);
if (entity instanceof GlimmerEntity glimmer) {
glimmer.setTame(false, true);
glimmer.setOwnerUUID(null);
return;
}
}
}
/**
* Removes {@code owner}'s companion record if it currently points at {@code companion},
* used when that Glimmer is captured into a bottle rather than replaced by a new one.
*/
public static void clearCompanion(MinecraftServer server, UUID owner, UUID companion) {
GlimmerCompanionSavedData data = get(server);
if (companion.equals(data.companionByOwner.get(owner))) {
data.companionByOwner.remove(owner);
data.setDirty();
}
}
}
@@ -0,0 +1,148 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.bloodglass.BloodglassAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.monster.Enemy;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class GlimmerElkaBehavior implements GlimmerVariantBehavior {
private static final int PASSIVE_REFRESH_DURATION = 20 * 4;
private static final int WARD_AMPLIFIER = 1;
private static final int ABILITY_COOLDOWN_TICKS = 20 * 60;
private static final int CRY_ANIM_TICKS = 40;
private static final double SHOCKWAVE_RADIUS = 6.0D;
private static final double SHOCKWAVE_KNOCKBACK = 1.3D;
private static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
@Override
public void registerGoals(GlimmerEntity entity) {
entity.addGoal(0, new FloatGoal(entity));
entity.addGoal(5, new WaterAvoidingRandomStrollGoal(entity, 1.0D));
entity.addGoal(6, new LookAtPlayerGoal(entity, Player.class, 8.0F));
entity.addGoal(7, new RandomLookAroundGoal(entity));
}
@Override
public ResourceLocation modelGeo() {
return rl("geo/elka.geo.json");
}
@Override
public ResourceLocation animationFile() {
return rl("animations/elka.animation.json");
}
@Override
public ResourceLocation texture(GlimmerEntity entity) {
return rl("textures/entity/glimmer/elka_glimmer.png");
}
@Override
public ResourceLocation emissiveTexture(GlimmerEntity entity) {
return rl("textures/entity/glimmer/elka_glimmer_emmisive.png");
}
@Override
public SoundEvent ambientSound() {
return SoundEvents.PANDA_AMBIENT;
}
@Override
public SoundEvent hurtSound() {
return SoundEvents.PANDA_BITE;
}
@Override
public SoundEvent deathSound() {
return SoundEvents.PANDA_DEATH;
}
@Override
public EntityDimensions adultDimensions() {
return EntityDimensions.scalable(2.2F, 3.5F);
}
@Override
public EntityDimensions babyDimensions() {
return EntityDimensions.scalable(1.0F, 1.5F);
}
@Override
public void tickPassive(GlimmerEntity entity, Player owner) {
owner.addEffect(new MobEffectInstance(AntarchyObjects.BLOODGLASS_WARD.get(), PASSIVE_REFRESH_DURATION, WARD_AMPLIFIER, false, false, true));
}
@Override
public void tickPassiveEveryTick(GlimmerEntity entity, Player owner) {
if (!(owner instanceof BloodglassAccess access)) {
return;
}
long lostNow = access.antarchy$getArmorShieldLostCount() + access.antarchy$getAppleShieldLostCount();
long lastLost = entity.getLastOwnerShieldLostCount();
if (lastLost >= 0L && lostNow > lastLost && !entity.isAbilityOnCooldown()) {
this.shatterWard(entity, owner);
}
entity.setLastOwnerShieldLostCount(lostNow);
}
@Override
public void clearPassive(GlimmerEntity entity, Player owner) {
entity.setLastOwnerShieldLostCount(-1L);
}
@Override
public int abilityCooldownTicks() {
return ABILITY_COOLDOWN_TICKS;
}
@Override
@Nullable
public software.bernie.geckolib.animation.RawAnimation abilityAnimation() {
return software.bernie.geckolib.animation.RawAnimation.begin().thenPlay("cry");
}
private void shatterWard(GlimmerEntity entity, Player owner) {
entity.playAbilityAnimation(CRY_ANIM_TICKS);
entity.playSound(SoundEvents.PANDA_CANT_BREED, 1.2F, 0.7F);
entity.level().playSound(null, entity.blockPosition(), SoundEvents.GLASS_BREAK, entity.getSoundSource(), 1.0F, 0.6F);
AABB area = owner.getBoundingBox().inflate(SHOCKWAVE_RADIUS);
for (Entity nearby : owner.level().getEntities(owner, area)) {
if (nearby instanceof Projectile projectile) {
Vec3 away = projectile.position().subtract(owner.position()).normalize();
projectile.setDeltaMovement(away.scale(1.5D));
continue;
}
if (nearby instanceof LivingEntity living && (nearby instanceof Enemy || living.getLastHurtByMob() == owner)) {
Vec3 away = living.position().subtract(owner.position());
double horizontalDistance = Math.max(0.1D, away.horizontalDistance());
living.knockback(SHOCKWAVE_KNOCKBACK, -away.x / horizontalDistance, -away.z / horizontalDistance);
}
}
this.sendAbilityMessage(owner, Component.translatable("entity.antarchy.glimmer.ability.elka"));
entity.startAbilityCooldown();
}
}
@@ -0,0 +1,551 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.client.particle.GlimmerParticles;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.network.chat.Component;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.util.Mth;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.TamableAnimal;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.AnimationState;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class GlimmerEntity extends TamableAnimal implements GeoEntity {
private static final ResourceKey<Biome> GLIMMERING_POOLS = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "glimmering_pools")
);
private static final RawAnimation IDLE_ANIM = RawAnimation.begin().thenLoop("idle");
private static final RawAnimation WALK_ANIM = RawAnimation.begin().thenLoop("walk");
private static final EntityDataAccessor<Integer> VARIANT =
SynchedEntityData.defineId(GlimmerEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> SHEAR_COOLDOWN =
SynchedEntityData.defineId(GlimmerEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> FADE_TICKS =
SynchedEntityData.defineId(GlimmerEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Boolean> FOLLOWING =
SynchedEntityData.defineId(GlimmerEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Integer> ABILITY_COOLDOWN =
SynchedEntityData.defineId(GlimmerEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Float> GROWTH_SCALE =
SynchedEntityData.defineId(GlimmerEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Integer> ABILITY_ANIM_TICKS =
SynchedEntityData.defineId(GlimmerEntity.class, EntityDataSerializers.INT);
public static final int FADE_DURATION_TICKS = 40;
private static final int PASSIVE_RANGE = 16;
private static final int PASSIVE_EFFECT_REFRESH_TICKS = 60;
private static final String VARIANT_KEY = "GlimmerVariant";
private static final String SHEAR_COOLDOWN_KEY = "ShearCooldown";
private static final String VARIANT_LOCKED_KEY = "VariantLocked";
private static final String FOLLOWING_KEY = "GlimmerFollowing";
private static final String ABILITY_COOLDOWN_KEY = "AbilityCooldown";
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private boolean variantLocked;
private float lastOwnerExhaustion = -1.0F;
private long lastOwnerShieldLostCount = -1L;
public GlimmerEntity(EntityType<? extends GlimmerEntity> entityType, Level level) {
super(entityType, level);
this.noPhysics = true;
this.setInvulnerable(true);
}
public static AttributeSupplier.Builder createAttributes() {
return TamableAnimal.createMobAttributes()
.add(Attributes.MAX_HEALTH, AntarchySettings.glimmerHealth())
.add(Attributes.MOVEMENT_SPEED, 0.3D)
.add(Attributes.FOLLOW_RANGE, 24.0D);
}
@Override
public boolean isPushable() {
return false;
}
@Override
public void push(Entity entity) {
// no-op: Glimmers pass through other entities
}
@Override
public boolean isInvulnerableTo(DamageSource damageSource) {
return true;
}
@Override
public int getMaxHeadYRot() {
return this.getVariant() == GlimmerVariant.FROG ? 5 : super.getMaxHeadYRot();
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(VARIANT, GlimmerVariant.APPLE_COW.ordinal());
builder.define(SHEAR_COOLDOWN, 0);
builder.define(FADE_TICKS, 0);
builder.define(FOLLOWING, true);
builder.define(ABILITY_COOLDOWN, 0);
builder.define(GROWTH_SCALE, 1.0F);
builder.define(ABILITY_ANIM_TICKS, 0);
}
public void playAbilityAnimation(int ticks) {
this.entityData.set(ABILITY_ANIM_TICKS, ticks);
}
/**
* Ticks remaining in the daytime despawn fade-out, or 0 if not currently fading.
* Used client-side to fade the render alpha to nothing before the entity is removed.
*/
public int getFadeTicks() {
return this.entityData.get(FADE_TICKS);
}
public float getFadeAlpha(float partialTick) {
int ticks = this.getFadeTicks();
if (ticks <= 0) {
return 1.0F;
}
return Mth.clamp((ticks - partialTick) / (float) FADE_DURATION_TICKS, 0.0F, 1.0F);
}
public boolean isFollowingOwner() {
return this.entityData.get(FOLLOWING);
}
public boolean isAbilityOnCooldown() {
return this.entityData.get(ABILITY_COOLDOWN) > 0;
}
public void startAbilityCooldown() {
this.entityData.set(ABILITY_COOLDOWN, this.getVariant().getBehavior().abilityCooldownTicks());
}
public float getAbilityCooldownFraction(float partialTick) {
int max = this.getVariant().getBehavior().abilityCooldownTicks();
if (max <= 0) {
return 0.0F;
}
int ticks = this.entityData.get(ABILITY_COOLDOWN);
return Mth.clamp((ticks - partialTick) / (float) max, 0.0F, 1.0F);
}
public float getLastOwnerExhaustion() {
return this.lastOwnerExhaustion;
}
public void setLastOwnerExhaustion(float value) {
this.lastOwnerExhaustion = value;
}
public long getLastOwnerShieldLostCount() {
return this.lastOwnerShieldLostCount;
}
public void setLastOwnerShieldLostCount(long value) {
this.lastOwnerShieldLostCount = value;
}
public float getGrowthScale() {
return this.entityData.get(GROWTH_SCALE);
}
public void setGrowthScale(float scale) {
this.entityData.set(GROWTH_SCALE, scale);
this.refreshDimensions();
}
private void cycleMovementState(Player player) {
if (this.isOrderedToSit()) {
this.setOrderedToSit(false);
this.entityData.set(FOLLOWING, true);
player.displayClientMessage(Component.translatable("entity.antarchy.glimmer.set_follow"), true);
} else if (this.isFollowingOwner()) {
this.entityData.set(FOLLOWING, false);
player.displayClientMessage(Component.translatable("entity.antarchy.glimmer.set_wander"), true);
} else {
this.setOrderedToSit(true);
this.getNavigation().stop();
player.displayClientMessage(Component.translatable("entity.antarchy.glimmer.set_sit"), true);
}
}
public GlimmerVariant getVariant() {
int ordinal = Mth.clamp(this.entityData.get(VARIANT), 0, GlimmerVariant.values().length - 1);
return GlimmerVariant.values()[ordinal];
}
public void setVariant(GlimmerVariant variant) {
this.entityData.set(VARIANT, variant.ordinal());
}
public void lockVariant(GlimmerVariant variant) {
this.setVariant(variant);
this.variantLocked = true;
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> key) {
super.onSyncedDataUpdated(key);
if (VARIANT.equals(key)) {
this.refreshDimensions();
if (!this.level().isClientSide) {
this.goalSelector.removeAllGoals(goal -> true);
this.targetSelector.removeAllGoals(goal -> true);
this.registerGoals();
}
}
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
GlimmerVariantBehavior behavior = this.getVariant().getBehavior();
EntityDimensions base = this.isBaby() ? behavior.babyDimensions() : behavior.adultDimensions();
float growth = this.getGrowthScale();
return growth == 1.0F ? base : base.scale(growth);
}
@Override
protected void ageBoundaryReached() {
super.ageBoundaryReached();
this.refreshDimensions();
}
public boolean isShearCooldownActive() {
return this.entityData.get(SHEAR_COOLDOWN) > 0;
}
public void startShearCooldown(int ticks) {
this.entityData.set(SHEAR_COOLDOWN, ticks);
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(3, new GlimmerFollowOwnerGoal(this, this.getVariant().getBehavior().followSpeedModifier()));
this.getVariant().getBehavior().registerGoals(this);
}
public void addGoal(int priority, Goal goal) {
this.goalSelector.addGoal(priority, goal);
}
public void addTargetGoal(int priority, Goal goal) {
this.targetSelector.addGoal(priority, goal);
}
@Override
@Nullable
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
return null;
}
@Override
public boolean isFood(ItemStack stack) {
return false;
}
@Override
public boolean doHurtTarget(Entity target) {
boolean hurt = super.doHurtTarget(target);
if (hurt) {
this.getVariant().getBehavior().onHurtTarget(this, target);
}
return hurt;
}
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (!this.isTame() && stack.is(AntarchyObjects.SPIRIT_APPLE.get())) {
return this.tameWithSpiritApple(player, hand, stack);
}
if (this.isTame() && this.isOwnedBy(player) && stack.is(net.minecraft.world.item.Items.GLASS_BOTTLE)) {
return this.captureInBottle(player, hand, stack);
}
if (this.isTame() && this.isOwnedBy(player) && stack.isEmpty() && !player.isSecondaryUseActive()) {
if (!this.level().isClientSide) {
this.cycleMovementState(player);
}
return InteractionResult.SUCCESS;
}
InteractionResult variantResult = this.getVariant().getBehavior().onInteract(this, player, stack, hand);
if (variantResult != null) {
return variantResult;
}
return super.mobInteract(player, hand);
}
private InteractionResult tameWithSpiritApple(Player player, InteractionHand hand, ItemStack stack) {
if (this.level().isClientSide) {
return InteractionResult.CONSUME;
}
this.usePlayerItem(player, hand, stack);
this.tame(player);
this.setOrderedToSit(false);
this.level().broadcastEntityEvent(this, (byte) 7);
GlimmerParticles.tickBurst(this);
if (this.level() instanceof ServerLevel serverLevel) {
GlimmerCompanionSavedData.replaceCompanion(serverLevel.getServer(), player.getUUID(), this.getUUID());
}
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
com.craisinlord.antarchy.content.AntarchyAdvancements.award(serverPlayer, this.getVariant().tameAdvancementId());
}
return InteractionResult.SUCCESS;
}
private InteractionResult captureInBottle(Player player, InteractionHand hand, ItemStack stack) {
if (this.level().isClientSide) {
return InteractionResult.CONSUME;
}
ItemStack filled = com.craisinlord.antarchy.content.item.GlimmerBottleItem.create(this.getVariant());
ItemStack result = net.minecraft.world.item.ItemUtils.createFilledResult(stack, player, filled);
player.setItemInHand(hand, result);
this.playSound(net.minecraft.sounds.SoundEvents.BOTTLE_FILL, 1.0F, 1.0F);
if (this.level() instanceof ServerLevel serverLevel) {
GlimmerCompanionSavedData.clearCompanion(serverLevel.getServer(), player.getUUID(), this.getUUID());
}
this.discard();
return InteractionResult.SUCCESS;
}
@Override
@Nullable
public SpawnGroupData finalizeSpawn(ServerLevelAccessor level, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData spawnGroupData) {
SpawnGroupData result = super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
if (!this.variantLocked) {
this.setVariant(selectNaturalVariant(level, this.blockPosition()));
this.variantLocked = true;
}
return result;
}
private GlimmerVariant selectNaturalVariant(ServerLevelAccessor level, BlockPos pos) {
Holder<Biome> biome = level.getBiome(pos);
if (biome.is(GLIMMERING_POOLS)) {
GlimmerVariant[] poolVariants = new GlimmerVariant[] {
GlimmerVariant.FROG,
GlimmerVariant.OURANWOOD_DEER,
GlimmerVariant.APPLE_COW
};
return poolVariants[this.random.nextInt(poolVariants.length)];
}
return GlimmerVariant.random(this.random);
}
@Override
public void tick() {
super.tick();
GlimmerParticles.tickAmbient(this, this.getDeltaMovement().horizontalDistanceSqr() > 0.01D);
if (!this.level().isClientSide) {
int fadeTicks = this.entityData.get(FADE_TICKS);
if (this.isTame()) {
if (fadeTicks > 0) {
this.entityData.set(FADE_TICKS, 0);
}
} else if (fadeTicks > 0 || this.level().isDay()) {
if (fadeTicks <= 0) {
fadeTicks = FADE_DURATION_TICKS;
}
fadeTicks--;
this.entityData.set(FADE_TICKS, fadeTicks);
if (fadeTicks <= 0) {
this.discard();
return;
}
}
int shearCooldown = this.entityData.get(SHEAR_COOLDOWN);
if (shearCooldown > 0) {
this.entityData.set(SHEAR_COOLDOWN, shearCooldown - 1);
}
this.applyGroundSupport();
this.getVariant().getBehavior().customTick(this);
this.tickAbilitiesAndPassive();
this.tickGrowthDecay();
}
}
private void tickAbilitiesAndPassive() {
int cooldown = this.entityData.get(ABILITY_COOLDOWN);
if (cooldown > 0) {
this.entityData.set(ABILITY_COOLDOWN, cooldown - 1);
}
int animTicks = this.entityData.get(ABILITY_ANIM_TICKS);
if (animTicks > 0) {
this.entityData.set(ABILITY_ANIM_TICKS, animTicks - 1);
}
if (!this.isTame() || !(this.getOwner() instanceof Player owner)) {
return;
}
boolean nearby = owner.level() == this.level()
&& this.distanceToSqr(owner) <= (double) (PASSIVE_RANGE * PASSIVE_RANGE);
if (!nearby) {
this.getVariant().getBehavior().clearPassive(this, owner);
return;
}
if (this.tickCount % PASSIVE_EFFECT_REFRESH_TICKS == 0) {
this.getVariant().getBehavior().tickPassive(this, owner);
}
this.getVariant().getBehavior().tickPassiveEveryTick(this, owner);
if (cooldown <= 0) {
this.getVariant().getBehavior().tickAbilityCheck(this, owner);
}
}
private void tickGrowthDecay() {
float scale = this.getGrowthScale();
if (scale == 1.0F) {
return;
}
double decayPerTick = this.getVariant().getBehavior().growthDecayPerTick();
if (decayPerTick <= 0.0D) {
return;
}
float next = scale > 1.0F
? (float) Math.max(1.0D, scale - decayPerTick)
: (float) Math.min(1.0D, scale + decayPerTick);
if (next != scale) {
this.setGrowthScale(next);
}
}
private void applyGroundSupport() {
BlockPos below = BlockPos.containing(this.getX(), this.getY() - 0.05D, this.getZ());
VoxelShape shape = this.level().getBlockState(below).getCollisionShape(this.level(), below);
if (!shape.isEmpty()) {
double top = below.getY() + shape.max(Direction.Axis.Y);
if (this.getY() <= top + 0.2D && this.getDeltaMovement().y <= 0.0D) {
this.setPos(this.getX(), top, this.getZ());
this.setDeltaMovement(this.getDeltaMovement().x, 0.0D, this.getDeltaMovement().z);
this.setOnGround(true);
return;
}
}
this.setOnGround(false);
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putString(VARIANT_KEY, this.getVariant().name());
tag.putInt(SHEAR_COOLDOWN_KEY, this.entityData.get(SHEAR_COOLDOWN));
tag.putBoolean(VARIANT_LOCKED_KEY, this.variantLocked);
tag.putBoolean(FOLLOWING_KEY, this.isFollowingOwner());
tag.putInt(ABILITY_COOLDOWN_KEY, this.entityData.get(ABILITY_COOLDOWN));
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
if (tag.contains(VARIANT_KEY)) {
this.setVariant(GlimmerVariant.byName(tag.getString(VARIANT_KEY)));
}
this.entityData.set(SHEAR_COOLDOWN, tag.getInt(SHEAR_COOLDOWN_KEY));
this.variantLocked = tag.getBoolean(VARIANT_LOCKED_KEY);
this.entityData.set(FOLLOWING, !tag.contains(FOLLOWING_KEY) || tag.getBoolean(FOLLOWING_KEY));
this.entityData.set(ABILITY_COOLDOWN, tag.getInt(ABILITY_COOLDOWN_KEY));
}
@Override
protected SoundEvent getAmbientSound() {
return this.getVariant().getBehavior().ambientSound();
}
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return this.getVariant().getBehavior().hurtSound();
}
@Override
protected SoundEvent getDeathSound() {
return this.getVariant().getBehavior().deathSound();
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "main_controller", 0, this::mainAnimController));
}
private PlayState mainAnimController(AnimationState<GlimmerEntity> state) {
if (this.entityData.get(ABILITY_ANIM_TICKS) > 0) {
RawAnimation abilityAnim = this.getVariant().getBehavior().abilityAnimation();
if (abilityAnim != null) {
return state.setAndContinue(abilityAnim);
}
}
if (state.isMoving()) {
RawAnimation movingAnim = this.getVariant().getBehavior().movingAnimation(this);
return state.setAndContinue(movingAnim != null ? movingAnim : WALK_ANIM);
}
return state.setAndContinue(IDLE_ANIM);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
}
@@ -0,0 +1,77 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.Goal;
import java.util.EnumSet;
public class GlimmerFollowOwnerGoal extends Goal {
private static final float START_DISTANCE = 8.0F;
private static final float STOP_DISTANCE = 3.0F;
private static final float TELEPORT_DISTANCE = 20.0F;
private final GlimmerEntity glimmer;
private final double speedModifier;
private LivingEntity owner;
public GlimmerFollowOwnerGoal(GlimmerEntity glimmer, double speedModifier) {
this.glimmer = glimmer;
this.speedModifier = speedModifier;
this.setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
}
@Override
public boolean canUse() {
if (!this.glimmer.isTame() || this.glimmer.isOrderedToSit() || !this.glimmer.isFollowingOwner()) {
return false;
}
LivingEntity owner = this.glimmer.getOwner();
if (owner == null || owner.isSpectator()) {
return false;
}
if (this.glimmer.distanceToSqr(owner) < START_DISTANCE * START_DISTANCE) {
return false;
}
this.owner = owner;
return true;
}
@Override
public boolean canContinueToUse() {
if (this.owner == null || !this.glimmer.isTame() || this.glimmer.isOrderedToSit() || !this.glimmer.isFollowingOwner()) {
return false;
}
return this.glimmer.distanceToSqr(this.owner) > STOP_DISTANCE * STOP_DISTANCE;
}
@Override
public void start() {
this.glimmer.getNavigation().stop();
}
@Override
public void stop() {
this.owner = null;
this.glimmer.getNavigation().stop();
}
@Override
public void tick() {
if (this.owner == null) {
return;
}
this.glimmer.getLookControl().setLookAt(this.owner, 10.0F, this.glimmer.getMaxHeadXRot());
double distanceSq = this.glimmer.distanceToSqr(this.owner);
if (distanceSq > TELEPORT_DISTANCE * TELEPORT_DISTANCE) {
this.glimmer.teleportTo(this.owner.getX(), this.owner.getY(), this.owner.getZ());
return;
}
this.glimmer.getNavigation().moveTo(this.owner, this.speedModifier);
}
}
@@ -0,0 +1,172 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.multipart.MultipartEntityOwner;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import net.minecraft.world.entity.boss.wither.WitherBoss;
import net.minecraft.world.entity.monster.Enemy;
import net.minecraft.world.entity.monster.MagmaCube;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
public class GlimmerFrogBehavior implements GlimmerVariantBehavior {
private static final int SMALL_SLIME_SIZE = 1;
private static final TagKey<EntityType<?>> NO_EAT_TAG =
TagKey.create(Registries.ENTITY_TYPE, ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "frog_glimmer_no_eat"));
private static final int PASSIVE_REFRESH_DURATION = 20 * 4;
private static final int ABILITY_COOLDOWN_TICKS = 20 * 120;
private static final double EAT_RANGE = 8.0D;
private static final float GROWTH_PER_EAT_MIN = 0.05F;
private static final float GROWTH_PER_EAT_MAX = 0.6F;
private static final float GROWTH_MAX_SCALE = 2.2F;
private static final double GROWTH_DECAY_PER_TICK = 0.0006D;
private static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
@Override
public void registerGoals(GlimmerEntity entity) {
entity.addGoal(0, new FloatGoal(entity));
entity.addGoal(1, new MeleeAttackGoal(entity, 1.1D, true));
entity.addGoal(5, new WaterAvoidingRandomStrollGoal(entity, 1.0D));
entity.addGoal(6, new LookAtPlayerGoal(entity, Player.class, 8.0F));
entity.addGoal(7, new RandomLookAroundGoal(entity));
entity.addTargetGoal(2, new NearestAttackableTargetGoal<>(entity, MagmaCube.class, 10, true, false,
magmaCube -> ((MagmaCube) magmaCube).getSize() == SMALL_SLIME_SIZE));
}
@Override
public void onHurtTarget(GlimmerEntity entity, Entity target) {
if (!(target instanceof MagmaCube magmaCube) || magmaCube.isAlive() || magmaCube.getSize() != SMALL_SLIME_SIZE) {
return;
}
entity.spawnAtLocation(AntarchyObjects.LUMEN_FROGLIGHT.get());
}
@Override
public ResourceLocation modelGeo() {
return rl("geo/apple_cow.geo.json");
}
@Override
public ResourceLocation animationFile() {
return rl("animations/apple_cow.animation.json");
}
@Override
public ResourceLocation texture(GlimmerEntity entity) {
return rl("textures/entity/glimmer/frog_glimmer.png");
}
@Override
public ResourceLocation emissiveTexture(GlimmerEntity entity) {
return rl("textures/entity/glimmer/frog_glimmer_emmisive.png");
}
@Override
public SoundEvent ambientSound() {
return SoundEvents.FROG_AMBIENT;
}
@Override
public SoundEvent hurtSound() {
return SoundEvents.FROG_HURT;
}
@Override
public SoundEvent deathSound() {
return SoundEvents.FROG_DEATH;
}
@Override
public EntityDimensions adultDimensions() {
// Matches vanilla Frog's real hitbox.
return EntityDimensions.scalable(0.5F, 0.5F);
}
@Override
public void tickPassive(GlimmerEntity entity, Player owner) {
owner.addEffect(new MobEffectInstance(MobEffects.JUMP, PASSIVE_REFRESH_DURATION, 0, false, false, true));
}
@Override
public int abilityCooldownTicks() {
return ABILITY_COOLDOWN_TICKS;
}
@Override
public double growthDecayPerTick() {
return GROWTH_DECAY_PER_TICK;
}
@Override
public void tickAbilityCheck(GlimmerEntity entity, Player owner) {
LivingEntity target = findEdibleTarget(entity, owner);
if (target != null) {
this.eatTarget(entity, owner, target);
}
}
private static LivingEntity findEdibleTarget(GlimmerEntity entity, Player owner) {
AABB area = owner.getBoundingBox().inflate(EAT_RANGE);
LivingEntity best = null;
double bestDistSq = EAT_RANGE * EAT_RANGE;
for (Entity candidate : owner.level().getEntities(entity, area)) {
if (!(candidate instanceof Enemy) || !(candidate instanceof LivingEntity living) || !living.isAlive()) {
continue;
}
if (isExcluded(living)) {
continue;
}
double distSq = candidate.distanceToSqr(owner);
if (distSq > bestDistSq) {
continue;
}
bestDistSq = distSq;
best = living;
}
return best;
}
private static boolean isExcluded(LivingEntity target) {
if (target instanceof MultipartEntityOwner || target instanceof EnderDragon || target instanceof WitherBoss) {
return true;
}
return target.getType().is(NO_EAT_TAG);
}
private void eatTarget(GlimmerEntity entity, Player owner, LivingEntity target) {
float growthAdd = Mth.clamp(target.getMaxHealth() / 40.0F, GROWTH_PER_EAT_MIN, GROWTH_PER_EAT_MAX);
entity.setGrowthScale(Math.min(GROWTH_MAX_SCALE, entity.getGrowthScale() + growthAdd));
Component targetName = target.getDisplayName();
entity.playSound(SoundEvents.FROG_EAT, 1.2F, 0.9F);
target.teleportTo(entity.getX(), entity.getY() + entity.getBbHeight() * 0.5D, entity.getZ());
target.remove(Entity.RemovalReason.DISCARDED);
this.sendAbilityMessage(owner, Component.translatable("entity.antarchy.glimmer.ability.frog", targetName));
entity.startAbilityCooldown();
}
}
@@ -0,0 +1,175 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import com.craisinlord.antarchy.Antarchy;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
import net.minecraft.world.entity.monster.Enemy;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import software.bernie.geckolib.animation.RawAnimation;
public class GlimmerOuranwoodDeerBehavior implements GlimmerVariantBehavior {
private static final int PASSIVE_REFRESH_DURATION = 20 * 4;
private static final int ABILITY_COOLDOWN_TICKS = 20 * 10;
private static final double FOLLOW_SPEED_MODIFIER = 1.75D;
private static final RawAnimation RUN_ANIM = RawAnimation.begin().thenLoop("run");
private static final double CHARGE_RANGE = 12.0D;
private static final double CHARGE_CONE_DOT = 0.5D;
private static final double CHARGE_CORRIDOR_WIDTH = 1.75D;
private static final float CHARGE_DAMAGE = 9.0F;
private static final double CHARGE_KNOCKBACK = 1.6D;
private static final int CHARGE_ANIM_TICKS = 45;
private static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
@Override
public void registerGoals(GlimmerEntity entity) {
entity.addGoal(0, new FloatGoal(entity));
entity.addGoal(5, new WaterAvoidingRandomStrollGoal(entity, 1.15D));
entity.addGoal(6, new LookAtPlayerGoal(entity, Player.class, 8.0F));
entity.addGoal(7, new RandomLookAroundGoal(entity));
}
@Override
public ResourceLocation modelGeo() {
return rl("geo/ouranwood_deer.geo.json");
}
@Override
public ResourceLocation animationFile() {
return rl("animations/ouranwood_deer.animation.json");
}
@Override
public ResourceLocation texture(GlimmerEntity entity) {
return rl("textures/entity/glimmer/deer_glimmer.png");
}
@Override
public ResourceLocation emissiveTexture(GlimmerEntity entity) {
return rl("textures/entity/glimmer/deer_glimmer_emissive.png");
}
@Override
public SoundEvent ambientSound() {
return SoundEvents.FOX_AMBIENT;
}
@Override
public SoundEvent hurtSound() {
return SoundEvents.FOX_HURT;
}
@Override
public SoundEvent deathSound() {
return SoundEvents.FOX_DEATH;
}
@Override
public EntityDimensions adultDimensions() {
return EntityDimensions.scalable(1.35F, 2.1F);
}
@Override
public EntityDimensions babyDimensions() {
return EntityDimensions.scalable(0.9F, 1.4F);
}
@Override
public void tickPassive(GlimmerEntity entity, Player owner) {
owner.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, PASSIVE_REFRESH_DURATION, 0, false, false, true));
}
@Override
public double followSpeedModifier() {
return FOLLOW_SPEED_MODIFIER;
}
@Override
@Nullable
public RawAnimation movingAnimation(GlimmerEntity entity) {
return entity.isFollowingOwner() ? RUN_ANIM : null;
}
@Override
public int abilityCooldownTicks() {
return ABILITY_COOLDOWN_TICKS;
}
@Override
@Nullable
public RawAnimation abilityAnimation() {
return RawAnimation.begin().thenPlay("charge_start").thenPlay("charge_loop").thenPlay("charge_end");
}
@Override
public void tickAbilityCheck(GlimmerEntity entity, Player owner) {
if (!owner.isSprinting()) {
return;
}
Vec3 look = owner.getLookAngle();
LivingEntity bestTarget = null;
double bestDistSq = CHARGE_RANGE * CHARGE_RANGE;
for (Entity candidate : owner.level().getEntities(owner, owner.getBoundingBox().inflate(CHARGE_RANGE))) {
if (!(candidate instanceof Enemy) || !(candidate instanceof LivingEntity living) || !living.isAlive()) {
continue;
}
Vec3 toTarget = candidate.position().subtract(owner.position());
double distSq = toTarget.lengthSqr();
if (distSq > bestDistSq) {
continue;
}
if (toTarget.normalize().dot(look) < CHARGE_CONE_DOT) {
continue;
}
bestDistSq = distSq;
bestTarget = living;
}
if (bestTarget != null) {
this.performCharge(entity, owner, bestTarget);
}
}
private void performCharge(GlimmerEntity entity, Player owner, LivingEntity target) {
Vec3 from = entity.position();
Vec3 direction = target.position().subtract(from);
double horizontalDistance = Math.max(1.0D, direction.horizontalDistance());
double dirX = direction.x / horizontalDistance;
double dirZ = direction.z / horizontalDistance;
entity.setDeltaMovement(dirX * 1.4D, entity.getDeltaMovement().y, dirZ * 1.4D);
entity.playAbilityAnimation(CHARGE_ANIM_TICKS);
entity.playSound(SoundEvents.RAVAGER_ROAR, 1.0F, 1.3F);
AABB corridor = new AABB(from, target.position()).inflate(CHARGE_CORRIDOR_WIDTH, 1.5D, CHARGE_CORRIDOR_WIDTH);
for (Entity candidate : owner.level().getEntities(entity, corridor)) {
if (!(candidate instanceof Enemy) || !(candidate instanceof LivingEntity living) || !living.isAlive()) {
continue;
}
living.hurt(owner.damageSources().mobAttack(entity), CHARGE_DAMAGE);
Vec3 away = living.position().subtract(from);
double awayDistance = Math.max(0.1D, away.horizontalDistance());
living.knockback(CHARGE_KNOCKBACK, -away.x / awayDistance, -away.z / awayDistance);
}
this.sendAbilityMessage(owner, Component.translatable("entity.antarchy.glimmer.ability.deer"));
entity.startAbilityCooldown();
}
}
@@ -0,0 +1,54 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.serialization.Codec;
import java.util.Locale;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
public enum GlimmerVariant {
APPLE_COW(new GlimmerAppleCowBehavior(), "apple_of_my_eye"),
OURANWOOD_DEER(new GlimmerOuranwoodDeerBehavior(), "deerly_beloved"),
FROG(new GlimmerFrogBehavior(), "hoppy_together"),
ANT(new GlimmerAntBehavior(), "spirant_animal"),
ELKA(new GlimmerElkaBehavior(), "elkcellent_choice");
public static final Codec<GlimmerVariant> CODEC = Codec.STRING.xmap(GlimmerVariant::byName, GlimmerVariant::name);
public static final StreamCodec<io.netty.buffer.ByteBuf, GlimmerVariant> STREAM_CODEC =
ByteBufCodecs.STRING_UTF8.map(GlimmerVariant::byName, GlimmerVariant::name);
private final GlimmerVariantBehavior behavior;
private final ResourceLocation tameAdvancementId;
GlimmerVariant(GlimmerVariantBehavior behavior, String tameAdvancementPath) {
this.behavior = behavior;
this.tameAdvancementId = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, tameAdvancementPath);
}
public GlimmerVariantBehavior getBehavior() {
return this.behavior;
}
public ResourceLocation tameAdvancementId() {
return this.tameAdvancementId;
}
public Component displayName() {
return Component.translatable("glimmer_variant.antarchy." + this.name().toLowerCase(Locale.ROOT));
}
public static GlimmerVariant random(net.minecraft.util.RandomSource random) {
GlimmerVariant[] values = values();
return values[random.nextInt(values.length)];
}
public static GlimmerVariant byName(String name) {
try {
return GlimmerVariant.valueOf(name);
} catch (IllegalArgumentException ignored) {
return APPLE_COW;
}
}
}
@@ -0,0 +1,147 @@
package com.craisinlord.antarchy.content.entity.glimmer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
/**
* Per-variant behavior for the composite {@link GlimmerEntity}: AI, model/texture selection, and sounds.
* One concrete implementation per {@link GlimmerVariant}, chosen via composition rather than subclassing
* so a single EntityType can be reliably reconstructed across save/load.
*/
public interface GlimmerVariantBehavior {
void registerGoals(GlimmerEntity entity);
ResourceLocation modelGeo();
ResourceLocation animationFile();
ResourceLocation texture(GlimmerEntity entity);
ResourceLocation emissiveTexture(GlimmerEntity entity);
SoundEvent ambientSound();
SoundEvent hurtSound();
SoundEvent deathSound();
/**
* Optional variant-specific interaction (e.g. shearing the Apple Cow variant for a Spirit Apple).
* Return null to fall through to the entity's default interaction handling.
*/
@Nullable
default InteractionResult onInteract(GlimmerEntity entity, Player player, ItemStack stack, InteractionHand hand) {
return null;
}
/**
* Optional per-tick hook for variant-specific server logic (e.g. the Basilisk's petrify gaze).
*/
default void customTick(GlimmerEntity entity) {
}
/**
* Optional hook fired after {@code entity} lands a successful hit on {@code target}
* (e.g. the Frog variant dropping Lumen Froglight when it kills a small Magma Cube).
*/
default void onHurtTarget(GlimmerEntity entity, Entity target) {
}
/**
* Called periodically (roughly once a second) while this Glimmer is tamed and its
* owner is nearby and loaded. Should (re)apply the variant's passive effect with a
* short duration so it naturally lapses if the owner leaves range.
*/
default void tickPassive(GlimmerEntity entity, Player owner) {
}
/**
* Called every server tick (not just the coarse refresh interval) while nearby, for
* passives that need finer-grained bookkeeping than a periodic effect refresh (e.g.
* the Apple Cow's exhaustion dampening).
*/
default void tickPassiveEveryTick(GlimmerEntity entity, Player owner) {
}
/**
* Called once the tick the owner leaves range (or the companion is otherwise no
* longer eligible) so any directly-applied (non-MobEffect) passive state can be undone.
*/
default void clearPassive(GlimmerEntity entity, Player owner) {
}
/**
* Animation (from this variant's own animation file) to play while
* {@link GlimmerEntity#playAbilityAnimation(int)} is active. Null plays no override.
*/
@Nullable
default software.bernie.geckolib.animation.RawAnimation abilityAnimation() {
return null;
}
/**
* Animation to play in place of the default walk loop while moving. Null uses the
* default walk animation (e.g. the Deer switches to its run clip while following its owner).
*/
@Nullable
default software.bernie.geckolib.animation.RawAnimation movingAnimation(GlimmerEntity entity) {
return null;
}
/**
* Navigation speed multiplier used while following the owner (see {@link GlimmerFollowOwnerGoal}).
*/
default double followSpeedModifier() {
return 1.15D;
}
/**
* Shared action-bar feedback helper for ability-use messages.
*/
default void sendAbilityMessage(Player owner, net.minecraft.network.chat.Component message) {
owner.displayClientMessage(message, true);
}
/**
* Called every server tick while the owner is nearby and the ability is off cooldown.
* Implementations should check their own trigger condition and, if it fires, perform
* the ability and call {@link GlimmerEntity#startAbilityCooldown()}.
*/
default void tickAbilityCheck(GlimmerEntity entity, Player owner) {
}
/**
* Cooldown duration for this variant's active ability, in ticks.
*/
default int abilityCooldownTicks() {
return 20 * 120;
}
/**
* Per-tick shrink/growth-recovery rate used to decay {@link GlimmerEntity#getGrowthScale()}
* back toward 1.0 after an ability temporarily changes it. 0 means the variant never changes scale.
*/
default double growthDecayPerTick() {
return 0.0D;
}
/**
* Hitbox size matching the real creature this variant is a "spirit" of, rather than
* a single hitbox shared by every variant.
*/
EntityDimensions adultDimensions();
/**
* Defaults to half the adult size, matching vanilla's typical baby scaling.
*/
default EntityDimensions babyDimensions() {
return this.adultDimensions().scale(0.5F);
}
}
@@ -80,6 +80,7 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
private static final int ATTACK_GRAB = 1; private static final int ATTACK_GRAB = 1;
private static final int ATTACK_SWING_LEFT = 2; private static final int ATTACK_SWING_LEFT = 2;
private static final int ATTACK_SWING_RIGHT = 3; private static final int ATTACK_SWING_RIGHT = 3;
private static final int ATTACK_SLAM = 4;
// Synced animation state values // Synced animation state values
private static final int ANIM_NONE = 0; private static final int ANIM_NONE = 0;
@@ -87,7 +88,8 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
private static final int ANIM_GRAB = 2; private static final int ANIM_GRAB = 2;
private static final int ANIM_SWING_LEFT = 3; private static final int ANIM_SWING_LEFT = 3;
private static final int ANIM_SWING_RIGHT = 4; private static final int ANIM_SWING_RIGHT = 4;
private static final int ANIM_DEATH = 5; private static final int ANIM_SLAM = 5;
private static final int ANIM_DEATH = 6;
private static final int DEATH_ANIM_TICKS = 60; private static final int DEATH_ANIM_TICKS = 60;
@@ -105,8 +107,11 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
private static final RawAnimation GRAB_ANIM = RawAnimation.begin().thenPlay("grab"); private static final RawAnimation GRAB_ANIM = RawAnimation.begin().thenPlay("grab");
private static final RawAnimation SWING_LEFT_ANIM = RawAnimation.begin().thenPlay("swing_left"); private static final RawAnimation SWING_LEFT_ANIM = RawAnimation.begin().thenPlay("swing_left");
private static final RawAnimation SWING_RIGHT_ANIM = RawAnimation.begin().thenPlay("swing_right"); private static final RawAnimation SWING_RIGHT_ANIM = RawAnimation.begin().thenPlay("swing_right");
private static final RawAnimation SLAM_ANIM = RawAnimation.begin().thenPlay("attack");
private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlay("death"); private static final RawAnimation DEATH_ANIM = RawAnimation.begin().thenPlay("death");
private static final Vec3 GRAB_ANCHOR_LOCAL = new Vec3(22.0D / 16.0D, 223.5D / 16.0D, -12.5D / 16.0D);
private final ServerBossEvent bossEvent = new ServerBossEvent(Component.literal("The Kraken"), BossEvent.BossBarColor.BLUE, BossEvent.BossBarOverlay.PROGRESS); private final ServerBossEvent bossEvent = new ServerBossEvent(Component.literal("The Kraken"), BossEvent.BossBarColor.BLUE, BossEvent.BossBarOverlay.PROGRESS);
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
@@ -208,6 +213,7 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
case ANIM_GRAB -> state.setAndContinue(GRAB_ANIM); case ANIM_GRAB -> state.setAndContinue(GRAB_ANIM);
case ANIM_SWING_LEFT -> state.setAndContinue(SWING_LEFT_ANIM); case ANIM_SWING_LEFT -> state.setAndContinue(SWING_LEFT_ANIM);
case ANIM_SWING_RIGHT -> state.setAndContinue(SWING_RIGHT_ANIM); case ANIM_SWING_RIGHT -> state.setAndContinue(SWING_RIGHT_ANIM);
case ANIM_SLAM -> state.setAndContinue(SLAM_ANIM);
case ANIM_DEATH -> state.setAndContinue(DEATH_ANIM); case ANIM_DEATH -> state.setAndContinue(DEATH_ANIM);
default -> PlayState.STOP; default -> PlayState.STOP;
}; };
@@ -551,6 +557,7 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
case ATTACK_GRAB -> this.tickGrabAttack(target); case ATTACK_GRAB -> this.tickGrabAttack(target);
case ATTACK_SWING_LEFT -> this.tickSwingAttack(false); case ATTACK_SWING_LEFT -> this.tickSwingAttack(false);
case ATTACK_SWING_RIGHT -> this.tickSwingAttack(true); case ATTACK_SWING_RIGHT -> this.tickSwingAttack(true);
case ATTACK_SLAM -> this.tickSlamAttack();
default -> this.finishAttack(); default -> this.finishAttack();
} }
} }
@@ -562,6 +569,9 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
if (attackState == ATTACK_GRAB) { if (attackState == ATTACK_GRAB) {
this.actionTicks = 55; this.actionTicks = 55;
this.playSound(AntarchySoundEvents.KRAKEN_ATTACK.get(), 1.45F, 0.9F); this.playSound(AntarchySoundEvents.KRAKEN_ATTACK.get(), 1.45F, 0.9F);
} else if (attackState == ATTACK_SLAM) {
this.actionTicks = 40;
this.playSound(AntarchySoundEvents.KRAKEN_ATTACK.get(), 1.55F, this.isPhaseTwo() ? 0.78F : 0.84F);
} else if (attackState == ATTACK_SWING_LEFT || attackState == ATTACK_SWING_RIGHT) { } else if (attackState == ATTACK_SWING_LEFT || attackState == ATTACK_SWING_RIGHT) {
this.actionTicks = 40; this.actionTicks = 40;
this.playSound(AntarchySoundEvents.KRAKEN_SPIN.get(), 1.3F, 0.95F); this.playSound(AntarchySoundEvents.KRAKEN_SPIN.get(), 1.3F, 0.95F);
@@ -603,6 +613,16 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
} }
} }
private void tickSlamAttack() {
if (this.actionTicks == 20) {
this.performSlam();
}
if (--this.actionTicks <= 0) {
this.finishAttack();
}
}
private void finishAttack() { private void finishAttack() {
int finished = this.getAttackState(); int finished = this.getAttackState();
this.setAttackState(ATTACK_NONE); this.setAttackState(ATTACK_NONE);
@@ -652,14 +672,16 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
double distSqr = this.distanceToSqr(target); double distSqr = this.distanceToSqr(target);
boolean canGrab = distSqr <= 64.0D; boolean canGrab = distSqr <= 64.0D;
boolean canSwing = distSqr <= 81.0D; boolean canSwing = distSqr <= 81.0D;
boolean canSlam = distSqr <= 121.0D;
if (!canGrab && !canSwing) { if (!canGrab && !canSwing && !canSlam) {
return ATTACK_NONE; return ATTACK_NONE;
} }
int grabWeight = canGrab ? (this.lastAttackState == ATTACK_GRAB ? 1 : 4) : 0; int grabWeight = canGrab ? (this.lastAttackState == ATTACK_GRAB ? 1 : 4) : 0;
int swingWeight = canSwing ? (this.isSwingAttack(this.lastAttackState) ? 1 : 5) : 0; int swingWeight = canSwing ? (this.isSwingAttack(this.lastAttackState) ? 1 : 5) : 0;
int total = grabWeight + swingWeight; int slamWeight = canSlam ? (this.lastAttackState == ATTACK_SLAM ? 1 : 3) : 0;
int total = grabWeight + swingWeight + slamWeight;
if (total == 0) { if (total == 0) {
return ATTACK_NONE; return ATTACK_NONE;
} }
@@ -668,6 +690,10 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
if (roll < grabWeight) { if (roll < grabWeight) {
return ATTACK_GRAB; return ATTACK_GRAB;
} }
roll -= grabWeight;
if (roll < slamWeight) {
return ATTACK_SLAM;
}
return this.resolveSwingDirection(target); return this.resolveSwingDirection(target);
} }
@@ -675,6 +701,10 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
return attackState == ATTACK_SWING_LEFT || attackState == ATTACK_SWING_RIGHT; return attackState == ATTACK_SWING_LEFT || attackState == ATTACK_SWING_RIGHT;
} }
private boolean isSlamAttack(int attackState) {
return attackState == ATTACK_SLAM;
}
// Determines left vs right based on which side the target is on relative to the Kraken. // Determines left vs right based on which side the target is on relative to the Kraken.
// Kraken's right vector is forward x world-up. If target is on the Kraken's right side, // Kraken's right vector is forward x world-up. If target is on the Kraken's right side,
// use swing_right (which knocks them left). If on the left, use swing_left (knocks them right). // use swing_right (which knocks them left). If on the left, use swing_left (knocks them right).
@@ -707,10 +737,28 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
} }
} }
private void performSlam() {
if (!(this.level() instanceof ServerLevel serverLevel)) {
return;
}
AABB hitBox = this.getBoundingBox().inflate(9.0D, 4.0D, 9.0D);
List<Player> players = this.level().getEntitiesOfClass(Player.class, hitBox, LivingEntity::isAlive);
float damage = (float) this.getAttributeValue(Attributes.ATTACK_DAMAGE) * 1.4F;
for (Player player : players) {
if (!player.hurt(AntarchyDamageSources.krakenMauling(serverLevel, this), damage)) {
continue;
}
this.knockAway(player, 3.2D, 0.75D);
}
serverLevel.sendParticles(ParticleTypes.SPLASH, this.getX(), this.getY() + 0.8D, this.getZ(), 40, 2.0D, 0.8D, 2.0D, 0.12D);
serverLevel.sendParticles(ParticleTypes.BUBBLE, this.getX(), this.getY() + 0.6D, this.getZ(), 30, 1.8D, 0.6D, 1.8D, 0.05D);
}
private void holdGrabbedTarget(LivingEntity target) { private void holdGrabbedTarget(LivingEntity target) {
Vec3 anchor = this.position() Vec3 anchor = this.getGrabAnchorWorldPos();
.add(this.getViewVector(1.0F).normalize().scale(this.getBbWidth() * 0.65D))
.add(0.0D, this.getBbHeight() * 0.28D, 0.0D);
target.setDeltaMovement(anchor.subtract(target.position()).scale(0.35D)); target.setDeltaMovement(anchor.subtract(target.position()).scale(0.35D));
target.resetFallDistance(); target.resetFallDistance();
if (target instanceof ServerPlayer serverPlayer) { if (target instanceof ServerPlayer serverPlayer) {
@@ -721,6 +769,33 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
target.hurtMarked = true; target.hurtMarked = true;
} }
private Vec3 getGrabAnchorWorldPos() {
Vec3 localAnchor = this.getGrabAnchorOffset();
Vec3 rotatedAnchor = localToWorldOffset(localAnchor);
return this.position().add(rotatedAnchor);
}
private Vec3 getGrabAnchorOffset() {
if (this.getAttackState() != ATTACK_GRAB) {
return new Vec3(this.getBbWidth() * 0.65D, this.getBbHeight() * 0.28D, 0.0D);
}
double progress = 1.0D - (this.actionTicks / 55.0D);
double forward = Mth.lerp(progress, this.getBbWidth() * 0.45D, GRAB_ANCHOR_LOCAL.z);
double vertical = Mth.lerp(progress, this.getBbHeight() * 0.18D, GRAB_ANCHOR_LOCAL.y);
double lateral = Mth.lerp(progress, this.getBbWidth() * 0.18D, GRAB_ANCHOR_LOCAL.x);
return new Vec3(lateral, vertical, forward);
}
private Vec3 localToWorldOffset(Vec3 localOffset) {
float bodyYawRadians = this.yBodyRot * Mth.DEG_TO_RAD;
double cos = Math.cos(bodyYawRadians);
double sin = Math.sin(bodyYawRadians);
double worldX = localOffset.x * cos - localOffset.z * sin;
double worldZ = localOffset.z * cos + localOffset.x * sin;
return new Vec3(worldX, localOffset.y, worldZ);
}
private void knockAway(LivingEntity target, double horizontalStrength, double verticalStrength) { private void knockAway(LivingEntity target, double horizontalStrength, double verticalStrength) {
Vec3 direction = target.position().subtract(this.position()); Vec3 direction = target.position().subtract(this.position());
Vec3 horizontal = new Vec3(direction.x, 0.0D, direction.z); Vec3 horizontal = new Vec3(direction.x, 0.0D, direction.z);
@@ -1123,6 +1198,10 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
this.setAnimationState(ANIM_SWING_RIGHT); this.setAnimationState(ANIM_SWING_RIGHT);
return; return;
} }
case ATTACK_SLAM -> {
this.setAnimationState(ANIM_SLAM);
return;
}
default -> { default -> {
} }
} }
@@ -0,0 +1,109 @@
package com.craisinlord.antarchy.content.fluid;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FlowingFluid;
public class LumenLiquidBlock extends LiquidBlock {
private static final int SOURCE_PARTICLE_INTERVAL = 8;
private static final int FLOW_PARTICLE_INTERVAL = 20;
public LumenLiquidBlock(FlowingFluid fluid, BlockBehaviour.Properties properties) {
super(fluid, properties);
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
super.onPlace(state, level, pos, oldState, movedByPiston);
scheduleCurrentLumenTick(level, pos);
}
@Override
protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, BlockPos neighborPos, boolean movedByPiston) {
super.neighborChanged(state, level, pos, neighborBlock, neighborPos, movedByPiston);
scheduleCurrentLumenTick(level, pos);
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
scheduleCurrentLumenTick(level, pos);
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
@Override
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
if (!state.is(newState.getBlock())) {
scheduleDependentLumenTicks(level, pos);
}
super.onRemove(state, level, pos, newState, movedByPiston);
}
@Override
public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) {
super.animateTick(state, level, pos, random);
if (!isSurfaceExposed(level, pos)) {
return;
}
int chance = state.getValue(LEVEL) == 0 ? SOURCE_PARTICLE_INTERVAL : FLOW_PARTICLE_INTERVAL;
if (random.nextInt(chance) != 0) {
return;
}
double x = pos.getX() + 0.15D + random.nextDouble() * 0.7D;
double y = pos.getY() + 0.76D + random.nextDouble() * 0.18D;
double z = pos.getZ() + 0.15D + random.nextDouble() * 0.7D;
level.addParticle(ParticleTypes.GLOW, x, y, z, 0.0D, 0.02D, 0.0D);
if (random.nextInt(4) == 0) {
level.addParticle(ParticleTypes.ENCHANT, x, y + 0.02D, z, 0.0D, 0.015D, 0.0D);
}
}
@Override
public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
super.entityInside(state, level, pos, entity);
if (!level.isClientSide || !(entity instanceof LivingEntity) || !isSurfaceExposed(level, pos)) {
return;
}
RandomSource random = level.getRandom();
if (random.nextInt(10) != 0) {
return;
}
double x = entity.getX() + (random.nextDouble() - 0.5D) * entity.getBbWidth();
double y = entity.getY() + 0.1D + random.nextDouble() * Math.max(0.2D, entity.getBbHeight() * 0.2D);
double z = entity.getZ() + (random.nextDouble() - 0.5D) * entity.getBbWidth();
level.addParticle(ParticleTypes.GLOW, x, y, z, 0.0D, 0.03D, 0.0D);
if (random.nextBoolean()) {
level.addParticle(ParticleTypes.ENCHANT, x, y + 0.05D, z, 0.0D, 0.04D, 0.0D);
}
}
private static boolean isSurfaceExposed(Level level, BlockPos pos) {
BlockState above = level.getBlockState(pos.above());
return above.isAir() || !above.canOcclude();
}
private void scheduleCurrentLumenTick(LevelAccessor level, BlockPos pos) {
level.scheduleTick(pos, this.fluid, this.fluid.getTickDelay(level));
}
private void scheduleDependentLumenTicks(LevelAccessor level, BlockPos pos) {
level.scheduleTick(pos.above(), this.fluid, this.fluid.getTickDelay(level));
for (Direction direction : Direction.Plane.HORIZONTAL) {
level.scheduleTick(pos.relative(direction), this.fluid, this.fluid.getTickDelay(level));
}
}
}
@@ -0,0 +1,85 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.client.particle.GlimmerParticles;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerCompanionSavedData;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerEntity;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant;
import java.util.List;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ItemUtils;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
public class GlimmerBottleItem extends Item {
public GlimmerBottleItem() {
super(new Item.Properties().stacksTo(1));
}
public static ItemStack create(GlimmerVariant variant) {
ItemStack stack = new ItemStack(AntarchyObjects.GLIMMER_BOTTLE.get());
stack.set(AntarchyObjects.GLIMMER_VARIANT_COMPONENT.get(), variant);
return stack;
}
@Nullable
public static GlimmerVariant getVariant(ItemStack stack) {
return stack.get(AntarchyObjects.GLIMMER_VARIANT_COMPONENT.get());
}
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
GlimmerVariant variant = getVariant(stack);
if (variant == null) {
return InteractionResultHolder.fail(stack);
}
if (level.isClientSide) {
return InteractionResultHolder.consume(stack);
}
GlimmerEntity glimmer = AntarchyObjects.GLIMMER.get().create(level);
if (glimmer == null) {
return InteractionResultHolder.fail(stack);
}
glimmer.moveTo(player.getX(), player.getY(), player.getZ(), player.getYRot(), 0.0F);
glimmer.lockVariant(variant);
glimmer.tame(player);
glimmer.setOrderedToSit(false);
level.addFreshEntity(glimmer);
GlimmerCompanionSavedData.replaceCompanion(((ServerLevel) level).getServer(), player.getUUID(), glimmer.getUUID());
GlimmerParticles.tickBurst(glimmer);
level.playSound(null, player.getX(), player.getY(), player.getZ(), SoundEvents.BOTTLE_EMPTY, SoundSource.PLAYERS, 1.0F, 1.0F);
if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
com.craisinlord.antarchy.content.AntarchyAdvancements.award(serverPlayer, variant.tameAdvancementId());
}
ItemStack result = ItemUtils.createFilledResult(stack, player, new ItemStack(Items.GLASS_BOTTLE));
return InteractionResultHolder.success(result);
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltip, TooltipFlag flag) {
super.appendHoverText(stack, context, tooltip, flag);
GlimmerVariant variant = getVariant(stack);
if (variant != null) {
tooltip.add(Component.translatable("item.antarchy.glimmer_bottle.contains", variant.displayName()));
String key = variant.name().toLowerCase(java.util.Locale.ROOT);
tooltip.add(Component.translatable("item.antarchy.glimmer_bottle.passive." + key).withStyle(net.minecraft.ChatFormatting.GRAY));
tooltip.add(Component.translatable("item.antarchy.glimmer_bottle.ability." + key).withStyle(net.minecraft.ChatFormatting.DARK_GRAY));
}
}
}
@@ -0,0 +1,64 @@
package com.craisinlord.antarchy.content.item;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ItemUtils;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.UseAnim;
import net.minecraft.world.level.Level;
public class HighFructoseCornSyrupItem extends Item {
public HighFructoseCornSyrupItem(Properties properties) {
super(properties);
}
@Override
public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity livingEntity) {
ItemStack result = super.finishUsingItem(stack, level, livingEntity);
if (!level.isClientSide) {
livingEntity.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 80, 0));
}
if (livingEntity instanceof ServerPlayer serverPlayer) {
CriteriaTriggers.CONSUME_ITEM.trigger(serverPlayer, stack);
serverPlayer.awardStat(Stats.ITEM_USED.get(this));
}
if (stack.isEmpty()) {
return new ItemStack(Items.GLASS_BOTTLE);
}
if (livingEntity instanceof Player player && !player.getAbilities().instabuild) {
ItemStack bottle = new ItemStack(Items.GLASS_BOTTLE);
if (!player.getInventory().add(bottle)) {
player.drop(bottle, false);
}
}
return result;
}
@Override
public int getUseDuration(ItemStack stack, LivingEntity entity) {
return 32;
}
@Override
public UseAnim getUseAnimation(ItemStack stack) {
return UseAnim.DRINK;
}
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand usedHand) {
return ItemUtils.startUsingInstantly(level, player, usedHand);
}
}
@@ -0,0 +1,173 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.AntarchyTags;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
public final class MinersDreamCaveGenerator {
private static final double MIN_HORIZONTAL_RADIUS = 2.0D;
private static final double MAX_HORIZONTAL_RADIUS = 5.0D;
private static final double MIN_VERTICAL_RADIUS = 1.5D;
private static final double MAX_VERTICAL_RADIUS = 4.0D;
private static final double MAX_VERTICAL_DEVIATION = 8.0D;
private static final double SOFT_VERTICAL_DEVIATION = 4.0D;
private static final int WORLD_MARGIN = 3;
private MinersDreamCaveGenerator() {
}
public record CaveNode(BlockPos center, double horizontalRadius, double verticalRadius) {
}
public record CaveGenerationResult(List<CaveNode> nodes, Set<Integer> torchCheckpoints) {
}
public static CaveGenerationResult generate(BlockPos start, Direction initialDirection, int length,
int torchSpacing, int minBuildHeight, int maxBuildHeight, RandomSource random) {
List<CaveNode> nodes = new ArrayList<>(length);
Set<Integer> torchCheckpoints = new HashSet<>();
double x = start.getX() + 0.5D;
double y = start.getY() + 0.5D;
double z = start.getZ() + 0.5D;
double startY = y;
double headingX = initialDirection.getStepX();
double headingZ = initialDirection.getStepZ();
if (headingX == 0.0D && headingZ == 0.0D) {
headingX = 1.0D;
}
double verticalVelocity = 0.0D;
double horizontalRadius = 3.0D;
double verticalRadius = 2.0D;
int minY = minBuildHeight + WORLD_MARGIN;
int maxY = maxBuildHeight - WORLD_MARGIN;
int spacing = Math.max(4, torchSpacing);
int traveledSinceTorch = 0;
int nextTorchDistance = spacing + random.nextInt(5) - 2;
for (int i = 0; i < length; i++) {
BlockPos center = BlockPos.containing(x, Mth.clamp(y, minY, maxY), z);
nodes.add(new CaveNode(center, horizontalRadius, verticalRadius));
traveledSinceTorch++;
if (traveledSinceTorch >= Math.max(1, nextTorchDistance)) {
torchCheckpoints.add(i);
traveledSinceTorch = 0;
nextTorchDistance = spacing + random.nextInt(5) - 2;
}
double turnDegrees = random.nextGaussian() * 8.0D;
if (random.nextFloat() < 0.1F) {
turnDegrees = random.nextGaussian() * 15.0D;
}
turnDegrees = Mth.clamp(turnDegrees, -25.0D, 25.0D);
double turnRadians = Math.toRadians(turnDegrees);
double cos = Math.cos(turnRadians);
double sin = Math.sin(turnRadians);
double newHeadingX = headingX * cos - headingZ * sin;
double newHeadingZ = headingX * sin + headingZ * cos;
double headingLength = Math.sqrt(newHeadingX * newHeadingX + newHeadingZ * newHeadingZ);
headingX = newHeadingX / headingLength;
headingZ = newHeadingZ / headingLength;
verticalVelocity += random.nextGaussian() * 0.15D;
double deviation = y - startY;
verticalVelocity -= deviation * 0.06D;
if (deviation > SOFT_VERTICAL_DEVIATION) {
verticalVelocity -= 0.08D;
} else if (deviation < -SOFT_VERTICAL_DEVIATION) {
verticalVelocity += 0.08D;
}
verticalVelocity = Mth.clamp(verticalVelocity, -0.6D, 0.6D);
x += headingX;
z += headingZ;
y = Mth.clamp(y + verticalVelocity, startY - MAX_VERTICAL_DEVIATION, startY + MAX_VERTICAL_DEVIATION);
horizontalRadius = Mth.clamp(horizontalRadius + random.nextGaussian() * 0.15D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS);
verticalRadius = Mth.clamp(verticalRadius + random.nextGaussian() * 0.12D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS);
if (random.nextFloat() < 0.05F) {
horizontalRadius = Mth.clamp(horizontalRadius + 0.6D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS);
verticalRadius = Mth.clamp(verticalRadius + 0.5D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS);
}
}
return new CaveGenerationResult(nodes, torchCheckpoints);
}
public static void collectNodeCandidates(ServerLevel level, CaveNode node, LongOpenHashSet visited, RandomSource random, List<BlockPos> out) {
BlockPos center = node.center();
int hr = Mth.ceil(node.horizontalRadius());
double upExtent = node.verticalRadius() * 1.25D;
double downExtent = node.verticalRadius() * 0.65D;
int upR = Mth.ceil(upExtent);
int downR = Mth.ceil(downExtent);
int minBuildHeight = level.getMinBuildHeight();
int maxBuildHeight = level.getMaxBuildHeight();
for (int dy = -downR; dy <= upR; dy++) {
double vExtent = dy >= 0 ? upExtent : downExtent;
if (vExtent <= 0.0D) {
continue;
}
double vTerm = (dy * dy) / (vExtent * vExtent);
if (vTerm > 1.05D) {
continue;
}
for (int dx = -hr; dx <= hr; dx++) {
double hxTerm = (dx * dx) / (node.horizontalRadius() * node.horizontalRadius());
for (int dz = -hr; dz <= hr; dz++) {
double hzTerm = (dz * dz) / (node.horizontalRadius() * node.horizontalRadius());
double distance = hxTerm + vTerm + hzTerm;
if (distance > 1.0D) {
continue;
}
if (distance > 0.7D) {
double edgeChance = (1.0D - distance) / 0.3D * 0.85D;
if (random.nextFloat() > edgeChance) {
continue;
}
}
BlockPos pos = center.offset(dx, dy, dz);
if (pos.getY() < minBuildHeight || pos.getY() >= maxBuildHeight) {
continue;
}
long packed = pos.asLong();
if (visited.contains(packed)) {
continue;
}
BlockState state = level.getBlockState(pos);
if (!state.is(AntarchyTags.Blocks.MINERS_DREAM_CARVABLE)) {
continue;
}
if (level.getBlockEntity(pos) != null) {
continue;
}
visited.add(packed);
out.add(pos);
}
}
}
}
public static boolean isChunkLoaded(ServerLevel level, BlockPos pos) {
return level.isLoaded(pos);
}
}
@@ -0,0 +1,152 @@
package com.craisinlord.antarchy.content.item;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.joml.Vector3f;
public final class MinersDreamExcavation {
private static final Vector3f TRIFFID_GOO_COLOR = new Vector3f(0.35F, 0.85F, 0.4F);
private static final int SLICES_BETWEEN_RUMBLE = 4;
private final UUID owner;
private final ResourceKey<Level> dimension;
private final List<MinersDreamCaveGenerator.CaveNode> nodes;
private final Set<Integer> torchCheckpoints;
private final LongOpenHashSet visited = new LongOpenHashSet();
private int nodeIndex = 0;
private List<BlockPos> currentNodePositions = null;
private int positionCursor = 0;
private int slicesSinceRumble = 0;
private boolean finished = false;
public MinersDreamExcavation(UUID owner, ResourceKey<Level> dimension, List<MinersDreamCaveGenerator.CaveNode> nodes, Set<Integer> torchCheckpoints) {
this.owner = owner;
this.dimension = dimension;
this.nodes = nodes;
this.torchCheckpoints = torchCheckpoints;
}
public UUID owner() {
return owner;
}
public ResourceKey<Level> dimension() {
return dimension;
}
public boolean isFinished() {
return finished;
}
public void tick(ServerLevel level, int blockBudget) {
RandomSource random = level.getRandom();
int remaining = blockBudget;
while (remaining > 0 && !finished) {
if (currentNodePositions == null) {
if (nodeIndex >= nodes.size()) {
finished = true;
playCompletionSound(level);
return;
}
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
if (!MinersDreamCaveGenerator.isChunkLoaded(level, node.center())) {
advanceNode(level, node);
continue;
}
currentNodePositions = new ArrayList<>();
MinersDreamCaveGenerator.collectNodeCandidates(level, node, visited, random, currentNodePositions);
positionCursor = 0;
}
if (currentNodePositions.isEmpty()) {
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
advanceNode(level, node);
continue;
}
int endIndex = Math.min(currentNodePositions.size(), positionCursor + remaining);
for (; positionCursor < endIndex; positionCursor++) {
BlockPos pos = currentNodePositions.get(positionCursor);
BlockState removedState = level.getBlockState(pos);
if (removedState.isAir()) {
continue;
}
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
remaining--;
if (random.nextFloat() < 0.12F) {
spawnBreakParticles(level, pos, removedState, random);
}
}
if (positionCursor >= currentNodePositions.size()) {
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
advanceNode(level, node);
}
}
}
private void advanceNode(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) {
RandomSource random = level.getRandom();
spawnFrontDust(level, node, random);
slicesSinceRumble++;
if (slicesSinceRumble >= SLICES_BETWEEN_RUMBLE) {
slicesSinceRumble = 0;
playRumble(level, node);
}
if (torchCheckpoints.contains(nodeIndex)) {
MinersDreamTorchPlacer.tryPlaceTorch(level, node.center(), random);
}
currentNodePositions = null;
positionCursor = 0;
nodeIndex++;
}
private void spawnBreakParticles(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
level.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state),
pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 2, 0.25D, 0.25D, 0.25D, 0.02D);
if (random.nextFloat() < 0.2F) {
level.sendParticles(new DustParticleOptions(TRIFFID_GOO_COLOR, 1.0F),
pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 1, 0.2D, 0.2D, 0.2D, 0.0D);
}
}
private void spawnFrontDust(ServerLevel level, MinersDreamCaveGenerator.CaveNode node, RandomSource random) {
BlockPos center = node.center();
level.sendParticles(ParticleTypes.CLOUD, center.getX() + 0.5D, center.getY() + 0.5D, center.getZ() + 0.5D,
3, node.horizontalRadius() * 0.4D, node.verticalRadius() * 0.3D, node.horizontalRadius() * 0.4D, 0.01D);
}
private void playRumble(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) {
BlockPos center = node.center();
level.playSound(null, center, SoundEvents.STONE_BREAK, SoundSource.BLOCKS, 0.5F, 0.7F);
}
private void playCompletionSound(ServerLevel level) {
if (nodes.isEmpty()) {
return;
}
BlockPos center = nodes.get(nodes.size() - 1).center();
level.playSound(null, center, SoundEvents.BEACON_ACTIVATE, SoundSource.PLAYERS, 0.4F, 1.4F);
}
}
@@ -0,0 +1,81 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
public final class MinersDreamExcavationManager {
private static final int MAX_ACTIVE_TASKS = 8;
private static final int SAFE_MIN_RANGE = 8;
private static final int SAFE_MAX_RANGE = 256;
private static final Map<UUID, MinersDreamExcavation> ACTIVE = new ConcurrentHashMap<>();
private MinersDreamExcavationManager() {
}
public static boolean hasActiveExcavation(UUID owner) {
return ACTIVE.containsKey(owner);
}
public static MinersDreamExcavation tryStart(ServerLevel level, UUID owner, BlockPos startPos, Direction direction) {
if (ACTIVE.containsKey(owner) || ACTIVE.size() >= MAX_ACTIVE_TASKS) {
return null;
}
int minRange = Mth.clamp(AntarchySettings.minersDreamMinimumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE);
int maxRange = Mth.clamp(AntarchySettings.minersDreamMaximumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE);
if (minRange > maxRange) {
int swap = minRange;
minRange = maxRange;
maxRange = swap;
}
RandomSource random = level.getRandom();
int length = minRange == maxRange ? minRange : minRange + random.nextInt(maxRange - minRange + 1);
MinersDreamCaveGenerator.CaveGenerationResult result = MinersDreamCaveGenerator.generate(
startPos,
direction,
length,
AntarchySettings.minersDreamTorchSpacing(),
level.getMinBuildHeight(),
level.getMaxBuildHeight(),
random
);
if (result.nodes().isEmpty()) {
return null;
}
MinersDreamExcavation excavation = new MinersDreamExcavation(owner, level.dimension(), result.nodes(), result.torchCheckpoints());
ACTIVE.put(owner, excavation);
return excavation;
}
public static void tick(ServerLevel level) {
if (ACTIVE.isEmpty()) {
return;
}
int budget = Math.max(1, AntarchySettings.minersDreamBlocksPerTick());
Iterator<Map.Entry<UUID, MinersDreamExcavation>> iterator = ACTIVE.entrySet().iterator();
while (iterator.hasNext()) {
MinersDreamExcavation excavation = iterator.next().getValue();
if (!excavation.dimension().equals(level.dimension())) {
continue;
}
excavation.tick(level, budget);
if (excavation.isFinished()) {
iterator.remove();
}
}
}
}
@@ -0,0 +1,79 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
public final class MinersDreamItem extends Item {
public MinersDreamItem(Properties properties) {
super(properties);
}
@Override
public InteractionResult useOn(UseOnContext context) {
Level level = context.getLevel();
Player player = context.getPlayer();
if (player == null) {
return InteractionResult.PASS;
}
if (!AntarchySettings.minersDreamEnabled()) {
if (!level.isClientSide()) {
player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_disabled"), true);
}
return InteractionResult.FAIL;
}
if (level.isClientSide()) {
return InteractionResult.SUCCESS;
}
ServerLevel serverLevel = (ServerLevel) level;
BlockPos clickedPos = context.getClickedPos();
Direction clickedFace = context.getClickedFace();
Direction travelDirection = clickedFace.getAxis().isHorizontal() ? clickedFace : player.getDirection();
BlockPos startPos = clickedPos.relative(travelDirection);
if (!serverLevel.isInWorldBounds(startPos) || !MinersDreamCaveGenerator.isChunkLoaded(serverLevel, startPos)) {
return InteractionResult.FAIL;
}
if (MinersDreamExcavationManager.hasActiveExcavation(player.getUUID())) {
player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_busy"), true);
return InteractionResult.FAIL;
}
MinersDreamExcavation excavation = MinersDreamExcavationManager.tryStart(serverLevel, player.getUUID(), startPos, travelDirection);
if (excavation == null) {
return InteractionResult.FAIL;
}
serverLevel.playSound(null, clickedPos, SoundEvents.TNT_PRIMED, SoundSource.PLAYERS, 0.8F, 1.1F);
if (!player.getAbilities().instabuild) {
context.getItemInHand().shrink(1);
}
return InteractionResult.CONSUME;
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line1").withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line2").withStyle(ChatFormatting.GRAY));
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
}
@@ -0,0 +1,89 @@
package com.craisinlord.antarchy.content.item;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
public final class MinersDreamTorchPlacer {
private MinersDreamTorchPlacer() {
}
public static void tryPlaceTorch(ServerLevel level, BlockPos center, RandomSource random) {
BlockPos floorSpot = findFloorSpot(level, center);
boolean floorFirst = random.nextBoolean();
if (floorFirst) {
if (floorSpot != null && placeFloorTorch(level, floorSpot)) {
return;
}
placeWallTorch(level, center, random);
return;
}
if (placeWallTorch(level, center, random)) {
return;
}
if (floorSpot != null) {
placeFloorTorch(level, floorSpot);
}
}
private static BlockPos findFloorSpot(ServerLevel level, BlockPos center) {
for (int dy = -2; dy <= 1; dy++) {
BlockPos candidate = center.offset(0, dy, 0);
if (!isOpenForTorch(level, candidate)) {
continue;
}
BlockState below = level.getBlockState(candidate.below());
if (below.isFaceSturdy(level, candidate.below(), Direction.UP)) {
return candidate;
}
}
return null;
}
private static boolean placeFloorTorch(ServerLevel level, BlockPos pos) {
if (!isOpenForTorch(level, pos)) {
return false;
}
BlockState state = Blocks.TORCH.defaultBlockState();
if (!state.canSurvive(level, pos)) {
return false;
}
level.setBlock(pos, state, 3);
level.playSound(null, pos, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
return true;
}
private static boolean placeWallTorch(ServerLevel level, BlockPos center, RandomSource random) {
List<Direction> order = Direction.Plane.HORIZONTAL.shuffledCopy(random);
for (int dy = -1; dy <= 1; dy++) {
BlockPos candidate = center.offset(0, dy, 0);
if (!isOpenForTorch(level, candidate)) {
continue;
}
for (Direction wallSide : order) {
Direction facing = wallSide.getOpposite();
BlockState state = Blocks.WALL_TORCH.defaultBlockState().setValue(BlockStateProperties.HORIZONTAL_FACING, facing);
if (!state.canSurvive(level, candidate)) {
continue;
}
level.setBlock(candidate, state, 3);
level.playSound(null, candidate, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
return true;
}
}
return false;
}
private static boolean isOpenForTorch(ServerLevel level, BlockPos pos) {
BlockState state = level.getBlockState(pos);
return (state.isAir() || state.canBeReplaced()) && level.getFluidState(pos).isEmpty();
}
}
@@ -0,0 +1,31 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.entity.PeachBoatEntity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public class PeachBoatOnlyItem extends OuranwoodBoatItem {
private final EntityType<PeachBoatEntity> entityType;
public PeachBoatOnlyItem(EntityType<PeachBoatEntity> entityType, Properties properties) {
super(properties);
this.entityType = entityType;
}
@Override
protected Boat createBoat(Level level, HitResult hitResult, ItemStack stack, Player player) {
Vec3 pos = hitResult.getLocation();
PeachBoatEntity boat = new PeachBoatEntity(this.entityType, level);
boat.setPos(pos.x, pos.y, pos.z);
boat.xo = pos.x;
boat.yo = pos.y;
boat.zo = pos.z;
this.applyDefaultStackConfig(level, stack, player, boat);
return boat;
}
}
@@ -0,0 +1,31 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.entity.PeachChestBoatEntity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public class PeachChestBoatItem extends OuranwoodBoatItem {
private final EntityType<PeachChestBoatEntity> entityType;
public PeachChestBoatItem(EntityType<PeachChestBoatEntity> entityType, Properties properties) {
super(properties);
this.entityType = entityType;
}
@Override
protected Boat createBoat(Level level, HitResult hitResult, ItemStack stack, Player player) {
Vec3 pos = hitResult.getLocation();
PeachChestBoatEntity boat = new PeachChestBoatEntity(this.entityType, level);
boat.setPos(pos.x, pos.y, pos.z);
boat.xo = pos.x;
boat.yo = pos.y;
boat.zo = pos.z;
this.applyDefaultStackConfig(level, stack, player, boat);
return boat;
}
}
@@ -0,0 +1,13 @@
package com.craisinlord.antarchy.content.item;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.Item;
public class PeachItem extends Item {
public PeachItem() {
super(new Item.Properties().food(new FoodProperties.Builder()
.nutrition(4)
.saturationModifier(2.4F)
.build()));
}
}
@@ -0,0 +1,59 @@
package com.craisinlord.antarchy.content.item;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ItemUtils;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.UseAnim;
import net.minecraft.world.level.Level;
public class RootBeerItem extends Item {
public RootBeerItem(Properties properties) {
super(properties);
}
@Override
public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity livingEntity) {
ItemStack result = super.finishUsingItem(stack, level, livingEntity);
if (livingEntity instanceof ServerPlayer serverPlayer) {
CriteriaTriggers.CONSUME_ITEM.trigger(serverPlayer, stack);
serverPlayer.awardStat(Stats.ITEM_USED.get(this));
}
if (stack.isEmpty()) {
return new ItemStack(Items.GLASS_BOTTLE);
}
if (livingEntity instanceof Player player && !player.getAbilities().instabuild) {
ItemStack bottle = new ItemStack(Items.GLASS_BOTTLE);
if (!player.getInventory().add(bottle)) {
player.drop(bottle, false);
}
}
return result;
}
@Override
public int getUseDuration(ItemStack stack, LivingEntity entity) {
return 32;
}
@Override
public UseAnim getUseAnimation(ItemStack stack) {
return UseAnim.DRINK;
}
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand usedHand) {
return ItemUtils.startUsingInstantly(level, player, usedHand);
}
}
@@ -0,0 +1,21 @@
package com.craisinlord.antarchy.content.item;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import java.util.List;
public class SpiritAppleItem extends Item {
public SpiritAppleItem(Properties properties) {
super(properties);
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.spirit_apple").withStyle(ChatFormatting.GRAY));
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
}
@@ -0,0 +1,127 @@
package com.craisinlord.antarchy.content.menu;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.DorrieEntity;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
public class DorrieInventoryMenu extends AbstractContainerMenu {
public static final int SADDLE_SLOT = 0;
public static final int STORAGE_COLUMNS = 5;
public static final int STORAGE_ROWS = 3;
public static final int DORRIE_SLOT_COUNT = 1 + STORAGE_COLUMNS * STORAGE_ROWS;
private static final int PLAYER_INV_Y = 84;
private static final int PLAYER_HOTBAR_Y = 142;
private final Container dorrieInventory;
@Nullable
private final DorrieEntity dorrie;
public DorrieInventoryMenu(int containerId, Inventory playerInventory) {
this(containerId, playerInventory, new SimpleContainer(DORRIE_SLOT_COUNT), null);
}
public DorrieInventoryMenu(int containerId, Inventory playerInventory, Container dorrieInventory, @Nullable DorrieEntity dorrie) {
super(AntarchyObjects.DORRIE_INVENTORY_MENU.get(), containerId);
checkContainerSize(dorrieInventory, DORRIE_SLOT_COUNT);
this.dorrieInventory = dorrieInventory;
this.dorrie = dorrie;
dorrieInventory.startOpen(playerInventory.player);
this.addSlot(new Slot(dorrieInventory, SADDLE_SLOT, 8, 18) {
@Override
public boolean mayPlace(ItemStack stack) {
return stack.is(Items.SADDLE) && !this.hasItem();
}
@Override
public int getMaxStackSize() {
return 1;
}
});
for (int row = 0; row < STORAGE_ROWS; row++) {
for (int col = 0; col < STORAGE_COLUMNS; col++) {
this.addSlot(new Slot(dorrieInventory, 1 + col + row * STORAGE_COLUMNS, 80 + col * 18, 18 + row * 18));
}
}
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(playerInventory, col + row * 9 + 9, 8 + col * 18, PLAYER_INV_Y + row * 18));
}
}
for (int col = 0; col < 9; col++) {
this.addSlot(new Slot(playerInventory, col, 8 + col * 18, PLAYER_HOTBAR_Y));
}
}
public Container dorrieInventory() {
return this.dorrieInventory;
}
@Nullable
public DorrieEntity dorrie() {
return this.dorrie;
}
@Override
public boolean stillValid(Player player) {
return this.dorrieInventory.stillValid(player)
&& (this.dorrie == null || this.dorrie.isAlive() && this.dorrie.distanceTo(player) < 8.0F);
}
@Override
public ItemStack quickMoveStack(Player player, int index) {
ItemStack quickMoved = ItemStack.EMPTY;
Slot slot = this.slots.get(index);
if (!slot.hasItem()) {
return ItemStack.EMPTY;
}
ItemStack stack = slot.getItem();
quickMoved = stack.copy();
int dorrieEnd = DORRIE_SLOT_COUNT;
int playerStart = dorrieEnd;
int playerEnd = this.slots.size();
if (index < dorrieEnd) {
if (!this.moveItemStackTo(stack, playerStart, playerEnd, true)) {
return ItemStack.EMPTY;
}
} else if (stack.is(Items.SADDLE)) {
if (!this.moveItemStackTo(stack, SADDLE_SLOT, SADDLE_SLOT + 1, false)) {
return ItemStack.EMPTY;
}
} else if (!this.moveItemStackTo(stack, 1, dorrieEnd, false)) {
return ItemStack.EMPTY;
}
if (stack.isEmpty()) {
slot.setByPlayer(ItemStack.EMPTY);
} else {
slot.setChanged();
}
if (stack.getCount() == quickMoved.getCount()) {
return ItemStack.EMPTY;
}
slot.onTake(player, stack);
return quickMoved;
}
@Override
public void removed(Player player) {
super.removed(player);
this.dorrieInventory.stopOpen(player);
}
}
@@ -37,6 +37,26 @@ public class ElythiaBiomeSource extends BiomeSource {
Registries.BIOME, Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "elythia_beach") ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "elythia_beach")
); );
private static final ResourceKey<Biome> OURANWOOD_FOREST = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "ouranwood_forest")
);
private static final ResourceKey<Biome> SPARSE_OURANWOOD_FOREST = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "sparse_ouranwood_forest")
);
private static final ResourceKey<Biome> PEACH_FOREST = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "peach_forest")
);
private static final ResourceKey<Biome> FUNGAL_OURANWOOD_FOREST = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "fungal_ouranwood_forest")
);
private static final ResourceKey<Biome> GLIMMERING_POOLS = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "glimmering_pools")
);
private static final int[] SURFACE_FALLBACK_BLOCK_YS = new int[]{192, 160, 128, 96, 64, 32, 0}; private static final int[] SURFACE_FALLBACK_BLOCK_YS = new int[]{192, 160, 128, 96, 64, 32, 0};
public static final MapCodec<ElythiaBiomeSource> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( public static final MapCodec<ElythiaBiomeSource> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
@@ -58,6 +78,7 @@ public class ElythiaBiomeSource extends BiomeSource {
private final int seaLevelQuartY; private final int seaLevelQuartY;
private final Holder<Biome> oceanHolder; private final Holder<Biome> oceanHolder;
private final Holder<Biome> defaultLandHolder; private final Holder<Biome> defaultLandHolder;
private final Holder<Biome> glimmeringPoolsHolder;
public ElythiaBiomeSource(Climate.ParameterList<Holder<Biome>> parameters, int molewormCavesMaxY, int surfaceBiomeSampleY, int oceanMaxY, int seaLevel) { public ElythiaBiomeSource(Climate.ParameterList<Holder<Biome>> parameters, int molewormCavesMaxY, int surfaceBiomeSampleY, int oceanMaxY, int seaLevel) {
this.parameters = parameters; this.parameters = parameters;
@@ -74,10 +95,16 @@ public class ElythiaBiomeSource extends BiomeSource {
.filter(h -> h.is(ELYTHIA_OCEAN)) .filter(h -> h.is(ELYTHIA_OCEAN))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
this.glimmeringPoolsHolder = parameters.values().stream()
.map(Pair::getSecond)
.filter(h -> h.is(GLIMMERING_POOLS))
.findFirst()
.orElse(null);
this.defaultLandHolder = parameters.values().stream() this.defaultLandHolder = parameters.values().stream()
.map(Pair::getSecond) .map(Pair::getSecond)
.filter(h -> !isOceanBiome(h) && !h.is(ELYTHIA_BEACH) .filter(h -> !isOceanBiome(h) && !h.is(ELYTHIA_BEACH)
&& !h.is(MOLEWORM_CAVES) && !h.is(ELYTHIA_LUSH_CAVES)) && !h.is(MOLEWORM_CAVES) && !h.is(ELYTHIA_LUSH_CAVES)
&& !h.is(GLIMMERING_POOLS))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} }
@@ -110,7 +137,7 @@ public class ElythiaBiomeSource extends BiomeSource {
// Cap moleworm caves below their max Y — replace with surface biome above it // Cap moleworm caves below their max Y — replace with surface biome above it
if (biome.is(MOLEWORM_CAVES) && y > this.molewormCavesMaxQuartY) { if (biome.is(MOLEWORM_CAVES) && y > this.molewormCavesMaxQuartY) {
return resolveSurfaceFallback(x, y, z, sampler, MOLEWORM_CAVES); return resolveSurfaceFallback(x, y, z, sampler, MOLEWORM_CAVES, GLIMMERING_POOLS);
} }
// Use continentalness (XZ-only noise) to classify the column. // Use continentalness (XZ-only noise) to classify the column.
@@ -138,6 +165,10 @@ public class ElythiaBiomeSource extends BiomeSource {
return resolveLandFallback(x, z, sampler); return resolveLandFallback(x, z, sampler);
} }
if (isGlimmeringPoolsCandidate(biome, target, x, z)) {
return this.glimmeringPoolsHolder != null ? this.glimmeringPoolsHolder : biome;
}
return biome; return biome;
} }
@@ -150,6 +181,44 @@ public class ElythiaBiomeSource extends BiomeSource {
return biome.is(ELYTHIA_OCEAN) || biome.is(ELYTHIA_CORAL_SPIKES); return biome.is(ELYTHIA_OCEAN) || biome.is(ELYTHIA_CORAL_SPIKES);
} }
private static boolean isOuranwoodBiome(Holder<Biome> biome) {
return biome.is(OURANWOOD_FOREST) || biome.is(SPARSE_OURANWOOD_FOREST)
|| biome.is(PEACH_FOREST) || biome.is(FUNGAL_OURANWOOD_FOREST);
}
private static boolean isGlimmeringPoolsCandidate(Holder<Biome> biome, Climate.TargetPoint target, int x, int z) {
if (!isOuranwoodBiome(biome)) {
return false;
}
long humidity = target.humidity();
long continentalness = target.continentalness();
long weirdness = target.weirdness();
long depth = target.depth();
if (humidity < Climate.quantizeCoord(0.68F) || humidity > Climate.quantizeCoord(1.0F)) {
return false;
}
if (continentalness < Climate.quantizeCoord(0.32F) || continentalness > Climate.quantizeCoord(0.88F)) {
return false;
}
if (weirdness < Climate.quantizeCoord(-0.84F) || weirdness > Climate.quantizeCoord(0.29F)) {
return false;
}
if (depth < Climate.quantizeCoord(0.0F) || depth > Climate.quantizeCoord(0.80F)) {
return false;
}
// Hash on coarse cells (not raw per-quart coords) so eligible columns
// clump into contiguous patches instead of speckling per-quart.
// Cell size bumped from 6 to 8 blocks so patches (and the pools/streams
// they host) run slightly larger than before.
long cellX = Math.floorDiv(x, 8);
long cellZ = Math.floorDiv(z, 8);
long gate = Math.floorMod(cellX * 73428767L + cellZ * 912931L, 5L);
return gate <= 2L;
}
// Used for moleworm caves — returns best Y-sample, falls back to whatever the delegate gives // Used for moleworm caves — returns best Y-sample, falls back to whatever the delegate gives
@SafeVarargs @SafeVarargs
private Holder<Biome> resolveSurfaceFallback(int x, int y, int z, Climate.Sampler sampler, ResourceKey<Biome>... excluded) { private Holder<Biome> resolveSurfaceFallback(int x, int y, int z, Climate.Sampler sampler, ResourceKey<Biome>... excluded) {
@@ -191,7 +260,8 @@ public class ElythiaBiomeSource extends BiomeSource {
private static boolean isOceanOrCave(Holder<Biome> biome) { private static boolean isOceanOrCave(Holder<Biome> biome) {
return isOceanBiome(biome) || biome.is(ELYTHIA_BEACH) return isOceanBiome(biome) || biome.is(ELYTHIA_BEACH)
|| biome.is(MOLEWORM_CAVES) || biome.is(ELYTHIA_LUSH_CAVES); || biome.is(MOLEWORM_CAVES) || biome.is(ELYTHIA_LUSH_CAVES)
|| biome.is(GLIMMERING_POOLS);
} }
@Override @Override
@@ -39,13 +39,14 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
int attempts = switch (this.variant) { int attempts = switch (this.variant) {
case FOREST -> 185; case FOREST -> 185;
case MEADOW -> 95; case MEADOW -> 95;
case PEACH_FOREST -> 120;
case TORCHFLOWER_FIELDS -> 60; case TORCHFLOWER_FIELDS -> 60;
case BUTTERFLY_FIELDS -> 150; case BUTTERFLY_FIELDS -> 150;
case FLOWER_FOREST_MILKWEED -> 14; case FLOWER_FOREST_MILKWEED -> 14;
}; };
int radius = switch (this.variant) { int radius = switch (this.variant) {
case FOREST -> 8; case FOREST -> 8;
case MEADOW, TORCHFLOWER_FIELDS -> 14; case MEADOW, PEACH_FOREST, TORCHFLOWER_FIELDS -> 14;
case BUTTERFLY_FIELDS -> 18; case BUTTERFLY_FIELDS -> 18;
case FLOWER_FOREST_MILKWEED -> 8; case FLOWER_FOREST_MILKWEED -> 8;
}; };
@@ -54,6 +55,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
int torchflowerPatchRolls = switch (this.variant) { int torchflowerPatchRolls = switch (this.variant) {
case TORCHFLOWER_FIELDS -> 3 + random.nextInt(3); case TORCHFLOWER_FIELDS -> 3 + random.nextInt(3);
case BUTTERFLY_FIELDS -> 2 + random.nextInt(3); case BUTTERFLY_FIELDS -> 2 + random.nextInt(3);
case PEACH_FOREST -> 1;
case FLOWER_FOREST_MILKWEED -> 0; case FLOWER_FOREST_MILKWEED -> 0;
default -> 1; default -> 1;
}; };
@@ -61,6 +63,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
float torchflowerChance = switch (this.variant) { float torchflowerChance = switch (this.variant) {
case FOREST -> 0.24F; case FOREST -> 0.24F;
case MEADOW -> 0.42F; case MEADOW -> 0.42F;
case PEACH_FOREST -> 0.08F;
case TORCHFLOWER_FIELDS -> 0.95F; case TORCHFLOWER_FIELDS -> 0.95F;
case BUTTERFLY_FIELDS -> 0.24F; case BUTTERFLY_FIELDS -> 0.24F;
case FLOWER_FOREST_MILKWEED -> 0.0F; case FLOWER_FOREST_MILKWEED -> 0.0F;
@@ -72,6 +75,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
if (random.nextFloat() < switch (this.variant) { if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.1F; case MEADOW -> 0.1F;
case PEACH_FOREST -> 0.0F;
case TORCHFLOWER_FIELDS -> 0.04F; case TORCHFLOWER_FIELDS -> 0.04F;
case BUTTERFLY_FIELDS -> 0.12F; case BUTTERFLY_FIELDS -> 0.12F;
case FOREST -> 0.05F; case FOREST -> 0.05F;
@@ -81,6 +85,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
} }
if (random.nextFloat() < switch (this.variant) { if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.16F; case MEADOW -> 0.16F;
case PEACH_FOREST -> 0.0F;
case TORCHFLOWER_FIELDS -> 0.08F; case TORCHFLOWER_FIELDS -> 0.08F;
case BUTTERFLY_FIELDS -> 0.95F; case BUTTERFLY_FIELDS -> 0.95F;
case FOREST -> 0.06F; case FOREST -> 0.06F;
@@ -90,6 +95,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
} }
if (random.nextFloat() < switch (this.variant) { if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.16F; case MEADOW -> 0.16F;
case PEACH_FOREST -> 0.0F;
case TORCHFLOWER_FIELDS -> 0.08F; case TORCHFLOWER_FIELDS -> 0.08F;
case BUTTERFLY_FIELDS -> 0.95F; case BUTTERFLY_FIELDS -> 0.95F;
case FOREST -> 0.06F; case FOREST -> 0.06F;
@@ -98,7 +104,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
placedAny |= this.placeMilkweedPatch(level, origin, random, AntarchyObjects.PINK_MILKWEED.get()); placedAny |= this.placeMilkweedPatch(level, origin, random, AntarchyObjects.PINK_MILKWEED.get());
} }
if (this.variant == Variant.FOREST) { if (this.variant == Variant.FOREST || this.variant == Variant.PEACH_FOREST) {
placedAny |= this.placeBigBushes(level, origin, random, radius); placedAny |= this.placeBigBushes(level, origin, random, radius);
} }
@@ -113,7 +119,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
continue; continue;
} }
maybeOvergrowGround(level, groundPos, random, this.variant != Variant.FOREST); normalizePeachForestGround(level, groundPos, this.variant);
maybeOvergrowGround(level, groundPos, random, this.variant);
if (placeSelectedPlant(level, plantPos, groundPos, random)) { if (placeSelectedPlant(level, plantPos, groundPos, random)) {
placedAny = true; placedAny = true;
} }
@@ -170,6 +177,14 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return placeDouble(level, plantPos, Blocks.LARGE_FERN.defaultBlockState()); return placeDouble(level, plantPos, Blocks.LARGE_FERN.defaultBlockState());
} }
if (this.variant == Variant.PEACH_FOREST) {
float roll = random.nextFloat();
if (roll < 0.58F) {
return placeSingle(level, plantPos, Blocks.SHORT_GRASS);
}
return placeSingle(level, plantPos, veryShortGrass());
}
float roll = random.nextFloat(); float roll = random.nextFloat();
if (this.variant == Variant.MEADOW) { if (this.variant == Variant.MEADOW) {
if (roll < 0.34F) { if (roll < 0.34F) {
@@ -223,7 +238,10 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
} }
boolean placed = false; boolean placed = false;
int bushAttempts = 12 + random.nextInt(7); int bushAttempts = switch (this.variant) {
case PEACH_FOREST -> 1 + random.nextInt(2);
default -> 12 + random.nextInt(7);
};
for (int i = 0; i < bushAttempts; i++) { for (int i = 0; i < bushAttempts; i++) {
int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius; int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius;
int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius; int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius;
@@ -402,6 +420,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
int attempts = switch (this.variant) { int attempts = switch (this.variant) {
case FOREST -> 3; case FOREST -> 3;
case MEADOW -> 5; case MEADOW -> 5;
case PEACH_FOREST -> 3;
case TORCHFLOWER_FIELDS -> 2; case TORCHFLOWER_FIELDS -> 2;
case BUTTERFLY_FIELDS -> 3; case BUTTERFLY_FIELDS -> 3;
case FLOWER_FOREST_MILKWEED -> 2; case FLOWER_FOREST_MILKWEED -> 2;
@@ -451,18 +470,42 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return isOpenPlantSpot(level, plantPos) ? plantPos : null; return isOpenPlantSpot(level, plantPos) ? plantPos : null;
} }
private static void maybeOvergrowGround(WorldGenLevel level, BlockPos groundPos, RandomSource random, boolean meadow) { private static void normalizePeachForestGround(WorldGenLevel level, BlockPos groundPos, Variant variant) {
if (!isValidGround(level, groundPos) || random.nextFloat() > (meadow ? 0.45F : 0.24F)) { if (variant != Variant.PEACH_FOREST) {
return; return;
} }
BlockState replacement = random.nextFloat() < 0.35F BlockState state = level.getBlockState(groundPos);
if (state.is(Blocks.MOSS_BLOCK) || state.is(Blocks.ROOTED_DIRT)) {
level.setBlock(groundPos, AntarchyObjects.BLUSH_MOSS_BLOCK.get().defaultBlockState(), 2);
}
}
private static void maybeOvergrowGround(WorldGenLevel level, BlockPos groundPos, RandomSource random, Variant variant) {
boolean meadow = variant != Variant.FOREST;
float overgrowChance = switch (variant) {
case PEACH_FOREST -> 0.20F;
case FOREST -> 0.24F;
default -> 0.45F;
};
if (!isValidGround(level, groundPos) || random.nextFloat() > overgrowChance) {
return;
}
BlockState replacement = variant == Variant.PEACH_FOREST
? AntarchyObjects.BLUSH_MOSS_BLOCK.get().defaultBlockState()
: random.nextFloat() < 0.35F
? Blocks.ROOTED_DIRT.defaultBlockState() ? Blocks.ROOTED_DIRT.defaultBlockState()
: Blocks.MOSS_BLOCK.defaultBlockState(); : Blocks.MOSS_BLOCK.defaultBlockState();
level.setBlock(groundPos, replacement, 2); level.setBlock(groundPos, replacement, 2);
for (DirectionHolder direction : DirectionHolder.HORIZONTAL) { for (DirectionHolder direction : DirectionHolder.HORIZONTAL) {
if (random.nextFloat() > (meadow ? 0.22F : 0.1F)) { float spreadChance = switch (variant) {
case PEACH_FOREST -> 0.07F;
case FOREST -> 0.1F;
default -> 0.22F;
};
if (random.nextFloat() > spreadChance) {
continue; continue;
} }
BlockPos spreadPos = groundPos.offset(direction.stepX, 0, direction.stepZ); BlockPos spreadPos = groundPos.offset(direction.stepX, 0, direction.stepZ);
@@ -492,7 +535,11 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
} }
private static boolean isIntendedGround(BlockState state) { private static boolean isIntendedGround(BlockState state) {
return state.is(BlockTags.DIRT) || state.is(Blocks.GRASS_BLOCK) || state.is(Blocks.MOSS_BLOCK) || state.is(Blocks.ROOTED_DIRT); return state.is(BlockTags.DIRT)
|| state.is(Blocks.GRASS_BLOCK)
|| state.is(Blocks.MOSS_BLOCK)
|| state.is(Blocks.ROOTED_DIRT)
|| state.is(AntarchyObjects.BLUSH_MOSS_BLOCK.get());
} }
private static boolean isUnderwaterGround(WorldGenLevel level, BlockPos pos) { private static boolean isUnderwaterGround(WorldGenLevel level, BlockPos pos) {
@@ -533,6 +580,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
public enum Variant { public enum Variant {
FOREST, FOREST,
MEADOW, MEADOW,
PEACH_FOREST,
TORCHFLOWER_FIELDS, TORCHFLOWER_FIELDS,
BUTTERFLY_FIELDS, BUTTERFLY_FIELDS,
FLOWER_FOREST_MILKWEED FLOWER_FOREST_MILKWEED
@@ -0,0 +1,52 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
public final class LumenLilyPadFeature extends Feature<NoneFeatureConfiguration> {
public LumenLilyPadFeature(Codec<NoneFeatureConfiguration> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos origin = context.origin();
boolean placedAny = false;
for (int i = 0; i < 24; i++) {
int x = origin.getX() + random.nextInt(17) - 8;
int z = origin.getZ() + random.nextInt(17) - 8;
int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
BlockPos fluidPos = new BlockPos(x, surfaceY, z);
if (!level.getFluidState(fluidPos).is(AntarchyTags.Fluids.LUMEN) || !level.getFluidState(fluidPos).isSource()) {
continue;
}
BlockPos padPos = fluidPos.above();
if (!level.isEmptyBlock(padPos)) {
continue;
}
BlockState lilyPad = Blocks.LILY_PAD.defaultBlockState();
if (!lilyPad.canSurvive(level, padPos)) {
continue;
}
level.setBlock(padPos, lilyPad, 2);
placedAny = true;
}
return placedAny;
}
}
@@ -0,0 +1,191 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
public final class LumenPoolFeature extends Feature<NoneFeatureConfiguration> {
private static final int SMALL_RADIUS_MIN = 2;
private static final int SMALL_RADIUS_MAX = 4;
private static final int LARGE_RADIUS_MIN = 5;
private static final int LARGE_RADIUS_MAX = 6;
private static final int HUGE_RADIUS_MIN = 11;
private static final int HUGE_RADIUS_MAX = 17;
public LumenPoolFeature(Codec<NoneFeatureConfiguration> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos origin = context.origin();
BlockPos center = findSurfaceCenter(level, origin);
if (center == null) {
return false;
}
int radius;
int maxDepth;
if (random.nextInt(20) == 0) {
radius = randomBetween(random, HUGE_RADIUS_MIN, HUGE_RADIUS_MAX);
maxDepth = randomBetween(random, 9, 13);
} else if (random.nextInt(8) == 0) {
radius = randomBetween(random, LARGE_RADIUS_MIN, LARGE_RADIUS_MAX);
maxDepth = randomBetween(random, 4, 6);
} else {
radius = randomBetween(random, SMALL_RADIUS_MIN, SMALL_RADIUS_MAX);
maxDepth = randomBetween(random, 3, 4);
}
BlockState lumenBlockState = AntarchyObjects.LUMEN_BLOCK.get().defaultBlockState();
BlockState shellstoneState = AntarchyObjects.SHELLSTONE.get().defaultBlockState();
BlockState tuffState = Blocks.TUFF.defaultBlockState();
BlockState mossState = Blocks.MOSS_BLOCK.defaultBlockState();
int waterlineY = center.getY() - 1;
int floorY = waterlineY - maxDepth;
if (floorY <= level.getMinBuildHeight() + 2) {
return false;
}
if (!hasSolidSubstrate(level, center.getX(), center.getZ(), floorY)) {
return false;
}
boolean placedAny = false;
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int dx = -radius - 2; dx <= radius + 2; dx++) {
for (int dz = -radius - 2; dz <= radius + 2; dz++) {
double distance = warpedDistance(dx, dz, random, radius);
if (distance > radius + 0.7D) {
continue;
}
int worldX = center.getX() + dx;
int worldZ = center.getZ() + dz;
int localSurfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, worldX, worldZ) - 1;
if (localSurfaceY < center.getY() - 2 || localSurfaceY > center.getY() + 1) {
continue;
}
double normalized = Mth.clamp((float) (distance / Math.max(radius, 1)), 0.0F, 1.25F);
int basinDepth = Math.max(2, Mth.floor((1.0D - (normalized * normalized)) * maxDepth));
if (distance <= radius * 0.45D) {
basinDepth = Math.max(basinDepth, maxDepth - 1);
}
int localWaterlineY = Math.min(waterlineY, localSurfaceY - 1);
int localFloorY = localWaterlineY - basinDepth;
if (localFloorY <= level.getMinBuildHeight() + 1) {
continue;
}
if (!hasSolidSubstrate(level, worldX, worldZ, localFloorY)) {
continue;
}
boolean edge = distance >= radius - 0.55D;
boolean shoulder = !edge && distance >= radius - 1.2D;
boolean innerWall = !edge && distance >= radius - 0.9D;
int bankTopY = Math.max(localWaterlineY + 1, Math.min(localSurfaceY, center.getY()));
for (int y = localSurfaceY; y > localFloorY; y--) {
mutable.set(worldX, y, worldZ);
if (y > localWaterlineY) {
level.setBlock(mutable, y <= bankTopY && (edge || shoulder) ? pickWallState(random, shellstoneState, tuffState) : Blocks.AIR.defaultBlockState(), 2);
placedAny = true;
continue;
}
if (edge || (innerWall && y >= localFloorY + 1)) {
level.setBlock(mutable, pickWallState(random, shellstoneState, tuffState), 2);
placedAny = true;
continue;
}
level.setBlock(mutable, LumenWorldgenHelper.lumenState(lumenBlockState, 0), 2);
LumenWorldgenHelper.scheduleFluidTick(level, mutable.immutable());
placedAny = true;
}
mutable.set(worldX, localFloorY, worldZ);
BlockState floorState = pickFloorState(random, shellstoneState, tuffState, mossState, edge);
level.setBlock(mutable, floorState, 2);
placedAny = true;
}
}
return placedAny;
}
private static BlockPos findSurfaceCenter(WorldGenLevel level, BlockPos origin) {
int bestY = Integer.MIN_VALUE;
BlockPos bestPos = null;
for (int dx = -4; dx <= 4; dx++) {
for (int dz = -4; dz <= 4; dz++) {
int x = origin.getX() + dx;
int z = origin.getZ() + dz;
int topY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
BlockPos pos = LumenWorldgenHelper.findNaturalFloor(level, x, z, topY, level.getMinBuildHeight() + 1);
if (pos != null && pos.getY() > bestY) {
bestY = pos.getY();
bestPos = pos;
}
}
}
return bestPos;
}
private static boolean hasSolidSubstrate(WorldGenLevel level, int x, int z, int floorY) {
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int y = floorY - 1; y >= floorY - 3; y--) {
mutable.set(x, y, z);
if (LumenWorldgenHelper.isSolidSupport(level.getBlockState(mutable))) {
return true;
}
}
return false;
}
private static BlockState pickWallState(RandomSource random, BlockState shellstoneState, BlockState tuffState) {
return random.nextFloat() < 0.72F ? shellstoneState : tuffState;
}
private static BlockState pickFloorState(RandomSource random, BlockState shellstoneState, BlockState tuffState, BlockState mossState, boolean edge) {
int roll = random.nextInt(100);
if (roll < 65) {
return shellstoneState;
}
if (roll < 95) {
return tuffState;
}
return edge ? mossState : shellstoneState;
}
private static double warpedDistance(int dx, int dz, RandomSource random, int radius) {
double scaleX = 1.0D + random.nextDouble() * 0.18D;
double scaleZ = 0.85D + random.nextDouble() * 0.22D;
double warpX = Math.sin((dz * 0.42D) + random.nextDouble() * Math.PI * 2.0D) * 0.18D;
double warpZ = Math.cos((dx * 0.48D) + random.nextDouble() * Math.PI * 2.0D) * 0.18D;
double warpedX = (dx / scaleX) + warpX;
double warpedZ = (dz / scaleZ) + warpZ;
return Mth.sqrt((float) ((warpedX * warpedX) + (warpedZ * warpedZ)));
}
private static int randomBetween(RandomSource random, int minInclusive, int maxInclusive) {
int min = Math.min(minInclusive, maxInclusive);
int max = Math.max(minInclusive, maxInclusive);
return min >= max ? min : min + random.nextInt(max - min + 1);
}
}
@@ -0,0 +1,340 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
public final class LumenStreamFeature extends Feature<NoneFeatureConfiguration> {
private static final int MAX_LENGTH = 160;
private static final int MIN_LENGTH = 60;
private static final int MAX_WIDTH = 4;
public LumenStreamFeature(Codec<NoneFeatureConfiguration> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos origin = context.origin();
StreamStart start = findStart(level, origin);
if (start == null) {
return false;
}
BlockState lumenBlockState = AntarchyObjects.LUMEN_BLOCK.get().defaultBlockState();
BlockState shellstoneState = AntarchyObjects.SHELLSTONE.get().defaultBlockState();
BlockState tuffState = Blocks.TUFF.defaultBlockState();
Direction direction = pickDownhillDirection(level, start.x(), start.z(), start.surfaceY(), random);
if (direction == null) {
return false;
}
int length = randomBetween(random, MIN_LENGTH, MAX_LENGTH);
int x = start.x();
int z = start.z();
int previousSurfaceY = start.surfaceY();
boolean placedAny = false;
for (int step = 0; step < length; step++) {
int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
if (surfaceY <= level.getMinBuildHeight() + 4) {
break;
}
if (step > 0 && surfaceY - previousSurfaceY > 2) {
break;
}
int channelDepth = step == 0 ? 4 : 3 + random.nextInt(2);
int waterlineY = surfaceY - 1;
int floorY = waterlineY - channelDepth;
if (!hasSolidSubstrate(level, x, z, floorY)) {
break;
}
placedAny |= carveChannelSegment(level, random, x, z, surfaceY, waterlineY, floorY, lumenBlockState, shellstoneState, tuffState, step);
Direction nextDirection = chooseNextDirection(level, x, z, direction, random);
if (nextDirection == null) {
break;
}
direction = nextDirection;
previousSurfaceY = surfaceY;
x += direction.getStepX();
z += direction.getStepZ();
}
if (placedAny) {
placedAny |= createTerminalBasin(level, random, x, z, lumenBlockState, shellstoneState, tuffState);
}
return placedAny;
}
private static boolean carveChannelSegment(
WorldGenLevel level,
RandomSource random,
int centerX,
int centerZ,
int surfaceY,
int waterlineY,
int floorY,
BlockState lumenBlockState,
BlockState shellstoneState,
BlockState tuffState,
int step
) {
boolean placedAny = false;
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
int width = 1 + random.nextInt(MAX_WIDTH);
for (int dx = -width; dx <= width; dx++) {
for (int dz = -width; dz <= width; dz++) {
double distance = Math.sqrt(dx * dx + dz * dz);
if (distance > width + 0.4D) {
continue;
}
int worldX = centerX + dx;
int worldZ = centerZ + dz;
int localSurfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, worldX, worldZ) - 1;
if (localSurfaceY < floorY + 1) {
continue;
}
double normalized = Mth.clamp((float) (distance / Math.max(width, 1)), 0.0F, 1.25F);
int localDepth = Math.max(2, Mth.floor((1.0D - (normalized * normalized)) * (waterlineY - floorY)));
if (distance <= width * 0.45D) {
localDepth = Math.max(localDepth, waterlineY - floorY - 1);
}
int localWaterlineY = Math.min(waterlineY, localSurfaceY - 1);
int localFloorY = localWaterlineY - localDepth;
if (!hasSolidSubstrate(level, worldX, worldZ, localFloorY)) {
continue;
}
boolean edge = distance >= width - 0.45D;
boolean innerWall = !edge && distance >= width - 0.8D;
int bankTopY = Math.max(localWaterlineY + 1, Math.min(localSurfaceY, surfaceY));
for (int y = localSurfaceY; y > localFloorY; y--) {
mutable.set(worldX, y, worldZ);
if (y > localWaterlineY) {
level.setBlock(mutable, y <= bankTopY && (edge || innerWall) ? pickWallState(random, shellstoneState, tuffState) : Blocks.AIR.defaultBlockState(), 2);
placedAny = true;
continue;
}
if (edge || (innerWall && y >= localFloorY + 1)) {
level.setBlock(mutable, pickWallState(random, shellstoneState, tuffState), 2);
placedAny = true;
continue;
}
level.setBlock(mutable, LumenWorldgenHelper.lumenState(lumenBlockState, 0), 2);
LumenWorldgenHelper.scheduleFluidTick(level, mutable.immutable());
placedAny = true;
}
mutable.set(worldX, localFloorY, worldZ);
level.setBlock(mutable, random.nextFloat() < 0.65F ? shellstoneState : tuffState, 2);
placedAny = true;
}
}
return placedAny;
}
private static boolean createTerminalBasin(
WorldGenLevel level,
RandomSource random,
int centerX,
int centerZ,
BlockState lumenBlockState,
BlockState shellstoneState,
BlockState tuffState
) {
int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerX, centerZ) - 1;
int radius = 3 + random.nextInt(2);
int waterlineY = surfaceY - 1;
int maxDepth = 3 + random.nextInt(2);
int floorY = waterlineY - maxDepth;
if (!hasSolidSubstrate(level, centerX, centerZ, floorY)) {
return false;
}
boolean placedAny = false;
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int dx = -radius; dx <= radius; dx++) {
for (int dz = -radius; dz <= radius; dz++) {
double distance = Math.sqrt(dx * dx + dz * dz);
if (distance > radius + 0.6D) {
continue;
}
int worldX = centerX + dx;
int worldZ = centerZ + dz;
int localSurfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, worldX, worldZ) - 1;
if (localSurfaceY < floorY + 1) {
continue;
}
double normalized = Mth.clamp((float) (distance / Math.max(radius, 1)), 0.0F, 1.25F);
int localDepth = Math.max(2, Mth.floor((1.0D - (normalized * normalized)) * maxDepth));
int localWaterlineY = Math.min(waterlineY, localSurfaceY - 1);
int localFloorY = localWaterlineY - localDepth;
boolean edge = distance >= radius - 0.45D;
boolean innerWall = !edge && distance >= radius - 0.85D;
int bankTopY = Math.max(localWaterlineY + 1, Math.min(localSurfaceY, surfaceY));
for (int y = localSurfaceY; y > localFloorY; y--) {
mutable.set(worldX, y, worldZ);
if (y > localWaterlineY) {
level.setBlock(mutable, y <= bankTopY && (edge || innerWall) ? pickWallState(random, shellstoneState, tuffState) : Blocks.AIR.defaultBlockState(), 2);
placedAny = true;
continue;
}
if (edge || (innerWall && y >= localFloorY + 1)) {
level.setBlock(mutable, pickWallState(random, shellstoneState, tuffState), 2);
placedAny = true;
continue;
}
level.setBlock(mutable, lumenBlockState, 2);
LumenWorldgenHelper.scheduleFluidTick(level, mutable.immutable());
placedAny = true;
}
mutable.set(worldX, localFloorY, worldZ);
level.setBlock(mutable, random.nextFloat() < 0.75F ? shellstoneState : tuffState, 2);
placedAny = true;
}
}
return placedAny;
}
private static StreamStart findStart(WorldGenLevel level, BlockPos origin) {
int bestY = Integer.MIN_VALUE;
StreamStart best = null;
for (int dx = -4; dx <= 4; dx++) {
for (int dz = -4; dz <= 4; dz++) {
int x = origin.getX() + dx;
int z = origin.getZ() + dz;
int topY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1;
BlockPos floor = LumenWorldgenHelper.findNaturalFloor(level, x, z, topY, level.getMinBuildHeight() + 1);
if (floor != null && floor.getY() > bestY) {
bestY = floor.getY();
best = new StreamStart(x, z, floor.getY());
}
}
}
return best;
}
private static BlockState pickWallState(RandomSource random, BlockState shellstoneState, BlockState tuffState) {
return random.nextFloat() < 0.7F ? shellstoneState : tuffState;
}
private static Direction pickDownhillDirection(WorldGenLevel level, int x, int z, int surfaceY, RandomSource random) {
Direction bestDirection = null;
int bestDrop = Integer.MIN_VALUE;
for (Direction direction : Direction.Plane.HORIZONTAL) {
int nextX = x + direction.getStepX() * 3;
int nextZ = z + direction.getStepZ() * 3;
int nextY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, nextX, nextZ) - 1;
int drop = surfaceY - nextY;
if (drop > bestDrop) {
bestDrop = drop;
bestDirection = direction;
}
}
if (bestDirection == null) {
return null;
}
if (bestDrop < -1 && random.nextInt(4) != 0) {
return null;
}
return bestDirection;
}
private static Direction chooseNextDirection(WorldGenLevel level, int x, int z, Direction current, RandomSource random) {
Direction[] candidates = new Direction[] {
current,
turnLeft(current),
turnRight(current)
};
Direction best = null;
int bestScore = Integer.MAX_VALUE;
for (Direction candidate : candidates) {
int nextX = x + candidate.getStepX() * 2;
int nextZ = z + candidate.getStepZ() * 2;
int nextY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, nextX, nextZ) - 1;
int score = nextY * 8 + random.nextInt(3);
if (score < bestScore) {
bestScore = score;
best = candidate;
}
}
return best;
}
private static Direction turnLeft(Direction direction) {
return switch (direction) {
case NORTH -> Direction.WEST;
case WEST -> Direction.SOUTH;
case SOUTH -> Direction.EAST;
case EAST -> Direction.NORTH;
default -> direction;
};
}
private static Direction turnRight(Direction direction) {
return switch (direction) {
case NORTH -> Direction.EAST;
case EAST -> Direction.SOUTH;
case SOUTH -> Direction.WEST;
case WEST -> Direction.NORTH;
default -> direction;
};
}
private static boolean hasSolidSubstrate(WorldGenLevel level, int x, int z, int floorY) {
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int y = floorY - 1; y >= floorY - 4; y--) {
mutable.set(x, y, z);
if (LumenWorldgenHelper.isSolidSupport(level.getBlockState(mutable))) {
return true;
}
}
return false;
}
private static int randomBetween(RandomSource random, int minInclusive, int maxInclusive) {
int min = Math.min(minInclusive, maxInclusive);
int max = Math.max(minInclusive, maxInclusive);
return min >= max ? min : min + random.nextInt(max - min + 1);
}
private record StreamStart(int x, int z, int surfaceY) {
}
}
@@ -0,0 +1,71 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.craisinlord.antarchy.content.AntarchyObjects;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.FluidState;
final class LumenWorldgenHelper {
private LumenWorldgenHelper() {
}
static boolean isNaturalFloor(BlockState state) {
return state.is(net.minecraft.tags.BlockTags.DIRT)
|| state.is(net.minecraft.world.level.block.Blocks.GRASS_BLOCK)
|| state.is(net.minecraft.world.level.block.Blocks.MOSS_BLOCK)
|| state.is(net.minecraft.world.level.block.Blocks.ROOTED_DIRT)
|| state.is(AntarchyObjects.SHELLSTONE.get())
|| state.is(net.minecraft.world.level.block.Blocks.TUFF);
}
static boolean isSolidSupport(BlockState state) {
return !state.isAir()
&& state.blocksMotion()
&& state.getFluidState().isEmpty()
&& !state.is(BlockTags.LOGS)
&& !state.is(BlockTags.LEAVES);
}
static BlockPos findNaturalFloor(WorldGenLevel level, int x, int z, int topY, int minY) {
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int y = topY; y >= minY; y--) {
mutable.set(x, y, z);
BlockState state = level.getBlockState(mutable);
if (isNaturalFloor(state)) {
return mutable.immutable();
}
if (state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES)) {
continue;
}
if (!state.isAir() && state.blocksMotion()) {
break;
}
}
return null;
}
static BlockState lumenState(BlockState lumenBlockState, boolean source) {
if (source || !lumenBlockState.hasProperty(LiquidBlock.LEVEL)) {
return source ? lumenBlockState.setValue(LiquidBlock.LEVEL, 0) : lumenBlockState;
}
return lumenBlockState.setValue(LiquidBlock.LEVEL, 7);
}
static BlockState lumenState(BlockState lumenBlockState, int level) {
if (!lumenBlockState.hasProperty(LiquidBlock.LEVEL)) {
return lumenBlockState;
}
return lumenBlockState.setValue(LiquidBlock.LEVEL, net.minecraft.util.Mth.clamp(level, 0, 15));
}
static void scheduleFluidTick(WorldGenLevel level, BlockPos pos) {
FluidState fluidState = level.getFluidState(pos);
if (!fluidState.isEmpty()) {
level.scheduleTick(pos, fluidState.getType(), fluidState.getType().getTickDelay(level));
}
}
}
@@ -0,0 +1,136 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.SlabBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
public class PeachForestMossyBoulderFeature extends Feature<NoneFeatureConfiguration> {
public PeachForestMossyBoulderFeature(Codec<NoneFeatureConfiguration> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos origin = context.origin();
BlockPos surfacePos = new BlockPos(origin.getX(), level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, origin.getX(), origin.getZ()) - 1, origin.getZ());
if (!isSoil(level.getBlockState(surfacePos))) {
return false;
}
BlockPos base = surfacePos.below(random.nextInt(2));
int height = 6 + random.nextInt(5);
int baseRadius = 2 * (2 + random.nextInt(2));
boolean placed = false;
int driftX = 0;
int driftZ = 0;
int topRadius = 1;
BlockPos topCenter = base;
for (int y = 0; y < height; y++) {
double heightFraction = (double) y / (height - 1);
double taper = 1.0D - heightFraction * 0.7D;
int radius = Math.max(1, Math.round((float) (baseRadius * taper)));
driftX = Mth.clamp(driftX + random.nextInt(3) - 1, -1, 1);
driftZ = Mth.clamp(driftZ + random.nextInt(3) - 1, -1, 1);
BlockPos levelCenter = base.above(y).offset(driftX, 0, driftZ);
placed |= this.placeDisk(level, levelCenter, radius, heightFraction, random);
topCenter = levelCenter;
topRadius = radius;
}
if (placed) {
this.softCapWithSlabs(level, topCenter, topRadius, random);
}
return placed;
}
private boolean placeDisk(WorldGenLevel level, BlockPos center, int radius, double heightFraction, RandomSource random) {
boolean placed = false;
double rr = radius * radius;
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
double distance = (x * x + z * z) / Math.max(1.0D, rr);
if (distance > 1.0D + random.nextDouble() * 0.3D) {
continue;
}
BlockPos targetPos = center.offset(x, 0, z);
BlockState targetState = level.getBlockState(targetPos);
if (targetState.isAir() || targetState.canBeReplaced() || isSoil(targetState)) {
level.setBlock(targetPos, pickBlockState(heightFraction, random), 2);
placed = true;
}
}
}
return placed;
}
private void softCapWithSlabs(WorldGenLevel level, BlockPos center, int radius, RandomSource random) {
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
BlockPos topPos = center.offset(x, 0, z);
while (topPos.getY() < level.getMaxBuildHeight() - 1 && isBoulderBlock(level.getBlockState(topPos.above()))) {
topPos = topPos.above();
}
BlockState topState = level.getBlockState(topPos);
if (!isBoulderBlock(topState)) {
continue;
}
BlockPos slabPos = topPos.above();
if (!level.isEmptyBlock(slabPos) || random.nextFloat() > 0.25F) {
continue;
}
int openSides = 0;
for (Direction direction : Direction.Plane.HORIZONTAL) {
if (!isBoulderBlock(level.getBlockState(topPos.relative(direction)))) {
openSides++;
}
}
if (openSides == 0) {
continue;
}
BlockState slabState = (random.nextFloat() < 0.55F ? Blocks.MOSSY_COBBLESTONE_SLAB : Blocks.COBBLESTONE_SLAB)
.defaultBlockState().setValue(SlabBlock.TYPE, SlabType.BOTTOM);
level.setBlock(slabPos, slabState, 2);
}
}
}
private static BlockState pickBlockState(double heightFraction, RandomSource random) {
if (heightFraction < 0.35D) {
return Blocks.TUFF.defaultBlockState();
}
return random.nextFloat() < 0.55F ? Blocks.MOSSY_COBBLESTONE.defaultBlockState() : Blocks.COBBLESTONE.defaultBlockState();
}
private static boolean isBoulderBlock(BlockState state) {
return state.is(Blocks.TUFF) || state.is(Blocks.COBBLESTONE) || state.is(Blocks.MOSSY_COBBLESTONE);
}
private static boolean isSoil(BlockState state) {
return state.is(BlockTags.DIRT) || state.is(Blocks.GRASS_BLOCK) || state.is(Blocks.MOSS_BLOCK) || state.is(Blocks.ROOTED_DIRT);
}
}
@@ -0,0 +1,19 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
public record PeachForestPondConfiguration(
int minRadius,
int maxRadius,
int minDepth,
int maxDepth
) implements FeatureConfiguration {
public static final Codec<PeachForestPondConfiguration> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.intRange(1, 32).fieldOf("min_radius").forGetter(PeachForestPondConfiguration::minRadius),
Codec.intRange(1, 32).fieldOf("max_radius").forGetter(PeachForestPondConfiguration::maxRadius),
Codec.intRange(1, 16).fieldOf("min_depth").forGetter(PeachForestPondConfiguration::minDepth),
Codec.intRange(1, 16).fieldOf("max_depth").forGetter(PeachForestPondConfiguration::maxDepth)
).apply(instance, PeachForestPondConfiguration::new));
}
@@ -0,0 +1,140 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
public class PeachForestPondFeature extends Feature<PeachForestPondConfiguration> {
private static final int NOISE_SAMPLES = 12;
public PeachForestPondFeature(Codec<PeachForestPondConfiguration> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<PeachForestPondConfiguration> context) {
WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos origin = context.origin();
PeachForestPondConfiguration config = context.config();
int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, origin.getX(), origin.getZ()) - 1;
BlockPos center = new BlockPos(origin.getX(), surfaceY, origin.getZ());
if (!isSoil(level.getBlockState(center))) {
return false;
}
int radiusX = Mth.nextInt(random, config.minRadius(), config.maxRadius());
int radiusZ = Mth.clamp(radiusX + random.nextInt(3) - 1, config.minRadius(), config.maxRadius());
int depth = Mth.nextInt(random, config.minDepth(), config.maxDepth());
double[] edgeNoise = generateEdgeNoise(random);
boolean placed = false;
for (int x = -radiusX - 2; x <= radiusX + 2; x++) {
for (int z = -radiusZ - 2; z <= radiusZ + 2; z++) {
double noiseFactor = sampleEdgeNoise(edgeNoise, x, z);
double distance = ((x * x) / (double) (radiusX * radiusX) + (z * z) / (double) (radiusZ * radiusZ)) / (noiseFactor * noiseFactor);
BlockPos topPos = center.offset(x, 0, z);
if (distance <= 1.0D) {
int floorY = center.getY() - (distance < 0.4D ? depth + 1 : depth);
for (int y = center.getY(); y > floorY; y--) {
level.setBlock(new BlockPos(topPos.getX(), y, topPos.getZ()), Blocks.WATER.defaultBlockState(), 2);
}
level.setBlock(new BlockPos(topPos.getX(), floorY, topPos.getZ()), Blocks.TUFF.defaultBlockState(), 2);
clearFloatingVegetation(level, topPos, center.getY());
placed = true;
} else if (distance <= 1.2D) {
int floorY = center.getY() - depth;
for (int y = center.getY(); y >= floorY; y--) {
level.setBlock(new BlockPos(topPos.getX(), y, topPos.getZ()), Blocks.TUFF.defaultBlockState(), 2);
}
clearFloatingVegetation(level, topPos, center.getY());
placed = true;
} else if (distance <= 1.45D && isSoil(level.getBlockState(topPos))) {
level.setBlock(topPos, Blocks.TUFF.defaultBlockState(), 2);
}
}
}
if (placed) {
this.placeLilyPads(level, center, radiusX, radiusZ, edgeNoise, random);
}
return placed;
}
private static double[] generateEdgeNoise(RandomSource random) {
double[] noise = new double[NOISE_SAMPLES];
for (int i = 0; i < NOISE_SAMPLES; i++) {
noise[i] = 0.72D + random.nextDouble() * 0.56D;
}
return noise;
}
private static double sampleEdgeNoise(double[] edgeNoise, int x, int z) {
if (x == 0 && z == 0) {
return 1.0D;
}
double angle = Math.atan2(z, x);
double normalized = (angle / (Math.PI * 2.0D) + 1.0D) * NOISE_SAMPLES;
int index0 = ((int) Math.floor(normalized)) % NOISE_SAMPLES;
int index1 = (index0 + 1) % NOISE_SAMPLES;
double t = normalized - Math.floor(normalized);
return Mth.lerp(t, edgeNoise[index0], edgeNoise[index1]);
}
private static void clearFloatingVegetation(WorldGenLevel level, BlockPos topPos, int surfaceY) {
int topOfDebris = Math.max(surfaceY, level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, topPos.getX(), topPos.getZ()) - 1);
int airStreak = 0;
for (int offset = 1; offset <= 24 && airStreak < 3; offset++) {
BlockPos abovePos = new BlockPos(topPos.getX(), topOfDebris + offset, topPos.getZ());
BlockState aboveState = level.getBlockState(abovePos);
if (aboveState.isAir()) {
airStreak++;
continue;
}
airStreak = 0;
level.setBlock(abovePos, Blocks.AIR.defaultBlockState(), 2);
}
}
private void placeLilyPads(WorldGenLevel level, BlockPos center, int radiusX, int radiusZ, double[] edgeNoise, RandomSource random) {
int attempts = 6 + (radiusX + radiusZ) * 2;
for (int i = 0; i < attempts; i++) {
int x = random.nextInt(radiusX * 2 + 1) - radiusX;
int z = random.nextInt(radiusZ * 2 + 1) - radiusZ;
double noiseFactor = sampleEdgeNoise(edgeNoise, x, z);
double distance = ((x * x) / (double) (radiusX * radiusX) + (z * z) / (double) (radiusZ * radiusZ)) / (noiseFactor * noiseFactor);
if (distance > 0.85D || random.nextFloat() > 0.55F) {
continue;
}
BlockPos padPos = center.offset(x, 1, z);
if (!level.getFluidState(padPos.below()).is(FluidTags.WATER) || !level.isEmptyBlock(padPos)) {
continue;
}
BlockState lilyPad = Blocks.LILY_PAD.defaultBlockState();
if (!lilyPad.canSurvive(level, padPos)) {
continue;
}
level.setBlock(padPos, lilyPad, 2);
}
}
private static boolean isSoil(BlockState state) {
return state.is(BlockTags.DIRT) || state.is(Blocks.GRASS_BLOCK) || state.is(Blocks.MOSS_BLOCK) || state.is(Blocks.ROOTED_DIRT);
}
}
@@ -0,0 +1,29 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
public record PeachTreeConfiguration(
BlockStateProvider trunkProvider,
BlockStateProvider foliageProvider,
IntProvider height,
IntProvider canopyRadius,
IntProvider branchCount,
int trunkWidth,
float fruitChance,
float topCanopyScale
) implements FeatureConfiguration {
public static final Codec<PeachTreeConfiguration> CODEC = RecordCodecBuilder.create(instance -> instance.group(
BlockStateProvider.CODEC.fieldOf("trunk_provider").forGetter(PeachTreeConfiguration::trunkProvider),
BlockStateProvider.CODEC.fieldOf("foliage_provider").forGetter(PeachTreeConfiguration::foliageProvider),
IntProvider.codec(4, 64).fieldOf("height").forGetter(PeachTreeConfiguration::height),
IntProvider.codec(2, 16).fieldOf("canopy_radius").forGetter(PeachTreeConfiguration::canopyRadius),
IntProvider.codec(1, 12).fieldOf("branch_count").forGetter(PeachTreeConfiguration::branchCount),
Codec.intRange(1, 2).optionalFieldOf("trunk_width", 1).forGetter(PeachTreeConfiguration::trunkWidth),
Codec.floatRange(0.0F, 1.0F).optionalFieldOf("fruit_chance", 0.45F).forGetter(PeachTreeConfiguration::fruitChance),
Codec.floatRange(0.2F, 1.2F).optionalFieldOf("top_canopy_scale", 0.8F).forGetter(PeachTreeConfiguration::topCanopyScale)
).apply(instance, PeachTreeConfiguration::new));
}
@@ -0,0 +1,472 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.block.PeachLeavesBlock;
import com.mojang.serialization.Codec;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.RotatedPillarBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
public class PeachTreeFeature extends Feature<PeachTreeConfiguration> {
private static final ResourceLocation PEACH_LOG_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "peach_log");
private static final ResourceLocation PEACH_LEAVES_ID = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "peach_leaves");
public PeachTreeFeature(Codec<PeachTreeConfiguration> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<PeachTreeConfiguration> context) {
WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos origin = context.origin();
PeachTreeConfiguration config = context.config();
int height = config.height().sample(random);
int canopyRadius = config.canopyRadius().sample(random);
int trunkWidth = config.trunkWidth();
if (!canGrowOn(level, origin.below())) {
return false;
}
List<BlockPos> trunkPath = buildTrunkPath(origin, height, random);
Set<BlockPos> logPositions = new LinkedHashSet<>();
Set<BlockPos> leafPositions = new LinkedHashSet<>();
placeTrunk(level, trunkPath, logPositions, height, trunkWidth, random, config);
int branchStart = Math.max(6, height - 5);
List<BranchTip> branchTips = buildBranches(level, trunkPath, branchStart, logPositions, leafPositions, canopyRadius, trunkWidth, random, config);
BlockPos canopyCenter = trunkPath.get(trunkPath.size() - 1).offset(trunkWidth - 1, 1, trunkWidth - 1);
placeCentralCanopy(level, canopyCenter, canopyRadius, logPositions, leafPositions, random, config);
for (BranchTip tip : branchTips) {
placeBranchCanopy(level, tip, canopyCenter, canopyRadius, logPositions, leafPositions, random, config);
}
if (trunkWidth == 2) {
reinforceLargeCanopy(level, canopyCenter, branchTips, logPositions, leafPositions, random, config);
}
connectCanopy(level, canopyCenter, branchTips, logPositions, leafPositions, random);
refreshLeafDistances(level, leafPositions, logPositions);
placeHangingPeaches(level, canopyCenter, canopyRadius, leafPositions, random, config);
return !logPositions.isEmpty() || !leafPositions.isEmpty();
}
private List<BlockPos> buildTrunkPath(BlockPos origin, int height, RandomSource random) {
List<BlockPos> trunkPath = new ArrayList<>(height);
Direction leanDirection = Direction.Plane.HORIZONTAL.getRandomDirection(random);
double driftX = leanDirection.getStepX() * (0.12D + random.nextDouble() * 0.05D);
double driftZ = leanDirection.getStepZ() * (0.12D + random.nextDouble() * 0.05D);
double wobbleX = (random.nextDouble() - 0.5D) * 0.04D;
double wobbleZ = (random.nextDouble() - 0.5D) * 0.04D;
double offsetX = 0.0D;
double offsetZ = 0.0D;
for (int y = 0; y < height; y++) {
double progress = y / (double) Math.max(1, height - 1);
double bend = progress < 0.35D ? 0.0D : (progress - 0.35D) / 0.65D;
offsetX += (driftX + wobbleX) * bend;
offsetZ += (driftZ + wobbleZ) * bend;
trunkPath.add(origin.offset(Mth.floor(offsetX), y, Mth.floor(offsetZ)));
}
return trunkPath;
}
private void placeTrunk(WorldGenLevel level, List<BlockPos> trunkPath, Set<BlockPos> logPositions, int height, int trunkWidth, RandomSource random, PeachTreeConfiguration config) {
Direction leanDirection = Direction.NORTH;
if (trunkPath.size() >= 2) {
BlockPos base = trunkPath.get(0);
BlockPos next = trunkPath.get(1);
leanDirection = directionFromDelta(next.getX() - base.getX(), next.getZ() - base.getZ());
}
for (int i = 0; i < trunkPath.size(); i++) {
BlockPos pos = trunkPath.get(i);
placeTrunkColumn(level, pos, trunkWidth, Direction.Axis.Y, logPositions, config, random);
if (trunkWidth == 1 && height >= 13 && i < 2) {
placeLog(level, pos.relative(leanDirection.getOpposite()), Direction.Axis.Y, logPositions, config, random);
}
if (trunkWidth == 1 && height >= 15 && i == 0) {
placeLog(level, pos.relative(leanDirection.getClockWise()), Direction.Axis.Y, logPositions, config, random);
}
}
}
private List<BranchTip> buildBranches(
WorldGenLevel level,
List<BlockPos> trunkPath,
int branchStart,
Set<BlockPos> logPositions,
Set<BlockPos> leafPositions,
int canopyRadius,
int trunkWidth,
RandomSource random
, PeachTreeConfiguration config
) {
int branchCount = config.branchCount().sample(random);
double baseAngle = random.nextDouble() * Math.PI * 2.0D;
List<BranchTip> tips = new ArrayList<>(branchCount);
for (int i = 0; i < branchCount; i++) {
int attachIndex = Math.min(trunkPath.size() - 1, branchStart + random.nextInt(Math.max(1, trunkPath.size() - branchStart)));
BlockPos anchor = trunkPath.get(attachIndex).offset(trunkWidth - 1, 0, trunkWidth - 1);
double angle = baseAngle + (Math.PI * 2.0D * i / branchCount) + (random.nextDouble() - 0.5D) * 0.55D;
int length = trunkWidth == 2 ? 6 + random.nextInt(5) : 3 + random.nextInt(4);
int rise = trunkWidth == 2 ? 2 + random.nextInt(3) : 1 + random.nextInt(2);
BlockPos tip = placeBranch(level, anchor, angle, length, rise, logPositions, leafPositions, random, config);
tips.add(new BranchTip(anchor, tip, angle));
if (random.nextFloat() < 0.30F) {
double forkAngle = angle + (random.nextBoolean() ? Math.PI / 3.0D : -Math.PI / 3.0D);
placeBranch(level, tip, forkAngle, trunkWidth == 2 ? 2 + random.nextInt(3) : 1 + random.nextInt(3), random.nextInt(2), logPositions, leafPositions, random, config);
}
}
return tips;
}
private BlockPos placeBranch(
WorldGenLevel level,
BlockPos start,
double angle,
int length,
int rise,
Set<BlockPos> logPositions,
Set<BlockPos> leafPositions,
RandomSource random,
PeachTreeConfiguration config
) {
double dirX = Math.cos(angle);
double dirZ = Math.sin(angle);
double perpX = -dirZ;
double perpZ = dirX;
double curveStrength = 0.75D + random.nextDouble() * 0.55D;
BlockPos tip = start;
for (int step = 1; step <= length; step++) {
double t = step / (double) length;
double curve = Math.sin(t * Math.PI) * curveStrength;
int x = Mth.floor(Mth.lerp(t, start.getX(), start.getX() + dirX * length) + perpX * curve);
int y = Mth.floor(Mth.lerp(t, start.getY(), start.getY() + rise) + Math.sin(t * Math.PI) * 0.35D);
int z = Mth.floor(Mth.lerp(t, start.getZ(), start.getZ() + dirZ * length) + perpZ * curve);
BlockPos current = new BlockPos(x, y, z);
BlockPos previous = tip;
tip = current;
Direction.Axis axis = axisForSegment(previous, current);
placeLog(level, current, axis, logPositions, config, random);
if (step >= length - 1) {
placeLeafCluster(level, current.above(), 2 + random.nextInt(2), leafPositions, random);
}
}
return tip;
}
private void placeCentralCanopy(WorldGenLevel level, BlockPos center, int canopyRadius, Set<BlockPos> logPositions, Set<BlockPos> leafPositions, RandomSource random, PeachTreeConfiguration config) {
placeCanopyLobe(level, center.below(), canopyRadius + 1, 1, canopyRadius, 0.35D, leafPositions, random);
placeCanopyLobe(level, center, canopyRadius + 1, 2, canopyRadius + 1, 0.42D, leafPositions, random);
int topRadius = Math.max(2, Mth.floor(canopyRadius * config.topCanopyScale()));
placeCanopyLobe(level, center.above(), topRadius, 1, topRadius, 0.24D, leafPositions, random);
placeLeafCluster(level, center.above(2), Math.max(2, topRadius - 1), leafPositions, random);
placeLog(level, center.below(), Direction.Axis.Y, logPositions, config, random);
}
private void placeBranchCanopy(
WorldGenLevel level,
BranchTip tip,
BlockPos canopyCenter,
int canopyRadius,
Set<BlockPos> logPositions,
Set<BlockPos> leafPositions,
RandomSource random,
PeachTreeConfiguration config
) {
int lobeRadius = Math.max(3, canopyRadius - 1 + random.nextInt(2));
BlockPos lobeCenter = tip.tip().above(random.nextInt(2));
placeCanopyLobe(level, lobeCenter, lobeRadius, 2, lobeRadius, 0.36D, leafPositions, random);
placeCanopyLobe(level, lobeCenter.above(), Math.max(2, lobeRadius - 1), 1, Math.max(2, lobeRadius - 1), 0.22D, leafPositions, random);
BlockPos bridgeCenter = tip.tip().offset(
Mth.floor(Math.cos(tip.angle()) * 2.0D),
0,
Mth.floor(Math.sin(tip.angle()) * 2.0D)
).above(1);
placeLeafCluster(level, bridgeCenter, Math.max(2, canopyRadius - 2), leafPositions, random);
placeLog(level, canopyCenter, Direction.Axis.Y, logPositions, config, random);
}
private void reinforceLargeCanopy(
WorldGenLevel level,
BlockPos canopyCenter,
List<BranchTip> branchTips,
Set<BlockPos> logPositions,
Set<BlockPos> leafPositions,
RandomSource random,
PeachTreeConfiguration config
) {
placeLog(level, canopyCenter.above(), Direction.Axis.Y, logPositions, config, random);
placeLog(level, canopyCenter.above(2), Direction.Axis.Y, logPositions, config, random);
placeLog(level, canopyCenter.below(), Direction.Axis.Y, logPositions, config, random);
for (BranchTip tip : branchTips) {
BlockPos mid = interpolate(tip.tip(), canopyCenter, 0.5D, 0.0D).above(1);
placeLog(level, mid, axisForSegment(tip.tip(), mid), logPositions, config, random);
BlockPos nearTip = interpolate(tip.tip(), canopyCenter, 0.25D, 0.0D);
placeLog(level, nearTip.above(), axisForSegment(nearTip, canopyCenter), logPositions, config, random);
BlockPos supportStart = tip.anchor().above(1);
BlockPos supportEnd = tip.tip().above(1);
placeLog(level, supportStart, axisForSegment(supportStart, supportEnd), logPositions, config, random);
placeLog(level, supportEnd, axisForSegment(supportStart, supportEnd), logPositions, config, random);
}
placeLeafCluster(level, canopyCenter.above(1), Math.max(2, config.canopyRadius().sample(random) - 1), leafPositions, random);
}
private void connectCanopy(
WorldGenLevel level,
BlockPos canopyCenter,
List<BranchTip> branchTips,
Set<BlockPos> logPositions,
Set<BlockPos> leafPositions,
RandomSource random
) {
for (BranchTip tip : branchTips) {
BlockPos start = tip.tip();
int steps = 2 + random.nextInt(2);
for (int i = 1; i <= steps; i++) {
double t = i / (double) (steps + 1);
BlockPos pos = interpolate(start, canopyCenter, t, 0.0D).above(random.nextInt(2));
placeLeaf(level, pos, leafPositions);
}
}
}
private void placeCanopyLobe(
WorldGenLevel level,
BlockPos center,
int radiusX,
int radiusY,
int radiusZ,
double edgeNoise,
Set<BlockPos> leafPositions,
RandomSource random
) {
int minY = -radiusY;
int maxY = radiusY;
for (int dy = minY; dy <= maxY; dy++) {
double vertical = dy / (double) Math.max(1, radiusY);
for (int dx = -radiusX; dx <= radiusX; dx++) {
for (int dz = -radiusZ; dz <= radiusZ; dz++) {
double nx = dx / (double) Math.max(1, radiusX);
double nz = dz / (double) Math.max(1, radiusZ);
double normalized = nx * nx + (vertical * vertical * 1.2D) + nz * nz;
if (normalized > 1.0D + edgeNoise + random.nextDouble() * 0.04D) {
continue;
}
if (dy < 0 && normalized > 0.65D && random.nextFloat() < 0.55F) {
continue;
}
if (dy > 0 && normalized < 0.25D && random.nextFloat() < 0.18F) {
continue;
}
BlockPos pos = center.offset(dx, dy, dz);
placeLeaf(level, pos, leafPositions);
}
}
}
}
private void placeLeafCluster(WorldGenLevel level, BlockPos center, int radius, Set<BlockPos> leafPositions, RandomSource random) {
int radiusSq = radius * radius;
for (int x = -radius; x <= radius; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -radius; z <= radius; z++) {
int distanceSq = x * x + z * z + y * y;
if (distanceSq > radiusSq) {
continue;
}
if (distanceSq > radiusSq - radius && random.nextFloat() < 0.20F) {
continue;
}
placeLeaf(level, center.offset(x, y, z), leafPositions);
}
}
}
}
private void placeHangingPeaches(WorldGenLevel level, BlockPos canopyCenter, int canopyRadius, Set<BlockPos> leafPositions, RandomSource random, PeachTreeConfiguration config) {
int fruitAttempts = 12 + random.nextInt(10);
List<BlockPos> candidates = new ArrayList<>(leafPositions);
for (int i = 0; i < fruitAttempts && !candidates.isEmpty(); i++) {
BlockPos leafPos = candidates.get(random.nextInt(candidates.size()));
int dx = Math.abs(leafPos.getX() - canopyCenter.getX());
int dz = Math.abs(leafPos.getZ() - canopyCenter.getZ());
int horizontal = dx + dz;
if (horizontal < Math.max(2, canopyRadius - 2)) {
continue;
}
BlockPos fruitPos = leafPos.below();
if (!level.getBlockState(fruitPos).isAir()) {
continue;
}
if (random.nextFloat() > config.fruitChance()) {
continue;
}
setBlock(level, fruitPos, PeachLeavesBlock.createHangingPeachState());
}
}
private void refreshLeafDistances(WorldGenLevel level, Set<BlockPos> leafPositions, Set<BlockPos> logPositions) {
ArrayDeque<BlockPos> queue = new ArrayDeque<>(logPositions);
java.util.Map<BlockPos, Integer> distances = new java.util.HashMap<>();
for (BlockPos logPos : logPositions) {
distances.put(logPos, 0);
}
while (!queue.isEmpty()) {
BlockPos current = queue.removeFirst();
int currentDistance = distances.getOrDefault(current, 0);
if (currentDistance >= 6) {
continue;
}
for (Direction direction : Direction.values()) {
BlockPos next = current.relative(direction);
if (distances.containsKey(next)) {
continue;
}
BlockState state = level.getBlockState(next);
if (!(state.getBlock() instanceof LeavesBlock)) {
continue;
}
distances.put(next, currentDistance + 1);
queue.addLast(next);
}
}
for (BlockPos pos : leafPositions) {
BlockState state = level.getBlockState(pos);
if (!(state.getBlock() instanceof LeavesBlock)) {
continue;
}
IntegerProperty property = state.getBlock() instanceof PeachLeavesBlock ? LeavesBlock.DISTANCE : null;
if (property == null) {
continue;
}
int distance = Math.min(6, distances.getOrDefault(pos, 6));
if (state.getValue(property) != distance) {
setBlock(level, pos, state.setValue(property, distance));
}
}
}
private void placeTrunkColumn(WorldGenLevel level, BlockPos pos, int trunkWidth, Direction.Axis axis, Set<BlockPos> logPositions, PeachTreeConfiguration config, RandomSource random) {
for (int dx = 0; dx < trunkWidth; dx++) {
for (int dz = 0; dz < trunkWidth; dz++) {
placeLog(level, pos.offset(dx, 0, dz), axis, logPositions, config, random);
}
}
}
private boolean placeLog(WorldGenLevel level, BlockPos pos, Direction.Axis axis, Set<BlockPos> logPositions, PeachTreeConfiguration config, RandomSource random) {
if (!canReplace(level, pos)) {
return false;
}
BlockState state = config.trunkProvider().getState(random, pos);
if (state.hasProperty(RotatedPillarBlock.AXIS)) {
state = state.setValue(RotatedPillarBlock.AXIS, axis);
}
setBlock(level, pos, state);
logPositions.add(pos);
return true;
}
private boolean placeLeaf(WorldGenLevel level, BlockPos pos, Set<BlockPos> leafPositions) {
if (!canReplace(level, pos)) {
return false;
}
setBlock(level, pos, leafState());
leafPositions.add(pos);
return true;
}
private BlockState leafState() {
return peachBlock(PEACH_LEAVES_ID).defaultBlockState();
}
private static Block peachBlock(ResourceLocation id) {
return BuiltInRegistries.BLOCK.getOptional(id)
.orElseThrow(() -> new IllegalStateException("Missing peach block: " + id));
}
private static boolean canGrowOn(WorldGenLevel level, BlockPos pos) {
BlockState state = level.getBlockState(pos);
return state.isFaceSturdy(level, pos, Direction.UP);
}
private static boolean canReplace(WorldGenLevel level, BlockPos pos) {
BlockState state = level.getBlockState(pos);
return state.isAir() || state.canBeReplaced() || state.getBlock() instanceof LeavesBlock;
}
private static Direction.Axis axisForSegment(BlockPos from, BlockPos to) {
int dx = Math.abs(to.getX() - from.getX());
int dy = Math.abs(to.getY() - from.getY());
int dz = Math.abs(to.getZ() - from.getZ());
if (dy > dx && dy > dz) {
return Direction.Axis.Y;
}
return dx >= dz ? Direction.Axis.X : Direction.Axis.Z;
}
private static Direction directionFromDelta(int dx, int dz) {
if (Math.abs(dx) >= Math.abs(dz)) {
return dx >= 0 ? Direction.EAST : Direction.WEST;
}
return dz >= 0 ? Direction.SOUTH : Direction.NORTH;
}
private static BlockPos interpolate(BlockPos start, BlockPos end, double t, double sidewaysOffset) {
double x = Mth.lerp(t, start.getX(), end.getX());
double y = Mth.lerp(t, start.getY(), end.getY());
double z = Mth.lerp(t, start.getZ(), end.getZ());
if (sidewaysOffset != 0.0D) {
double dx = end.getX() - start.getX();
double dz = end.getZ() - start.getZ();
double length = Math.sqrt(dx * dx + dz * dz);
if (length > 0.0D) {
x += (-dz / length) * sidewaysOffset;
z += (dx / length) * sidewaysOffset;
}
}
return new BlockPos(Mth.floor(x), Mth.floor(y), Mth.floor(z));
}
private record BranchTip(BlockPos anchor, BlockPos tip, double angle) {
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.content.worldgen.elythia;
import com.craisinlord.antarchy.Antarchy;
import java.util.Optional;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.grower.TreeGrower;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
public final class PeachTreeGrowers {
public static final ResourceKey<ConfiguredFeature<?, ?>> PEACH_LARGE_TREE = featureKey("peach_large_tree");
public static final ResourceKey<ConfiguredFeature<?, ?>> PEACH_TREE = featureKey("peach_tree");
public static final TreeGrower PEACH = new TreeGrower(
"peach",
Optional.of(PEACH_LARGE_TREE),
Optional.of(PEACH_TREE),
Optional.empty()
);
private PeachTreeGrowers() {
}
private static ResourceKey<ConfiguredFeature<?, ?>> featureKey(String path) {
return ResourceKey.create(Registries.CONFIGURED_FEATURE, ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path));
}
}
@@ -0,0 +1,61 @@
package com.craisinlord.antarchy.content.worldgen.overworld;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.block.CornCropBlock;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
public final class CornPatchFeature extends Feature<NoneFeatureConfiguration> {
public CornPatchFeature(Codec<NoneFeatureConfiguration> codec) {
super(codec);
}
@Override
public boolean place(FeaturePlaceContext<NoneFeatureConfiguration> context) {
WorldGenLevel level = context.level();
RandomSource random = context.random();
BlockPos origin = context.origin();
Block wildCorn = AntarchyObjects.WILD_CORN.get();
int radius = 4 + random.nextInt(3);
int targetPlacements = 10 + random.nextInt(11);
int attempts = targetPlacements * 4;
int placed = 0;
for (int i = 0; i < attempts && placed < targetPlacements; i++) {
int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius;
int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius;
int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE_WG, x, z);
BlockPos lowerPos = new BlockPos(x, surfaceY, z);
BlockPos upperPos = lowerPos.above();
if (!level.getBlockState(lowerPos.below()).is(net.minecraft.world.level.block.Blocks.GRASS_BLOCK)) {
continue;
}
if (!level.getBlockState(lowerPos).canBeReplaced() || !level.getBlockState(upperPos).canBeReplaced()) {
continue;
}
BlockState lowerState = wildCorn.defaultBlockState()
.setValue(CornCropBlock.AGE, 3)
.setValue(CornCropBlock.HALF, DoubleBlockHalf.LOWER);
BlockState upperState = wildCorn.defaultBlockState()
.setValue(CornCropBlock.AGE, 3)
.setValue(CornCropBlock.HALF, DoubleBlockHalf.UPPER);
level.setBlock(lowerPos, lowerState, 2);
level.setBlock(upperPos, upperState, 2);
placed++;
}
return placed > 0;
}
}
@@ -0,0 +1,35 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Player.class)
public abstract class AmericanFoodConsumeMixin {
@Inject(
method = "eat(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/food/FoodProperties;)Lnet/minecraft/world/item/ItemStack;",
at = @At("RETURN")
)
private void antarchy$applyAmericanBonus(Level level, ItemStack stack, FoodProperties foodProperties, CallbackInfoReturnable<ItemStack> cir) {
if (level.isClientSide || !stack.has(AntarchyObjects.AMERICAN_COMPONENT.get())) {
return;
}
Player self = (Player) (Object) this;
self.getFoodData().eat(AntarchySettings.americanBonusNutrition(), (float) AntarchySettings.americanBonusSaturation());
int duration = AntarchySettings.americanRegenerationDurationTicks();
if (duration > 0) {
self.addEffect(new MobEffectInstance(MobEffects.REGENERATION, duration, AntarchySettings.americanRegenerationAmplifier()));
}
}
}
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.AntarchyObjects;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ItemStack.class)
public abstract class AmericanFoodNameMixin {
@Inject(method = "getHoverName", at = @At("RETURN"), cancellable = true)
private void antarchy$prefixAmericanName(CallbackInfoReturnable<Component> cir) {
ItemStack self = (ItemStack) (Object) this;
if (self.has(AntarchyObjects.AMERICAN_COMPONENT.get())) {
cir.setReturnValue(Component.translatable("item.antarchy.american_name", cir.getReturnValue()));
}
}
}
@@ -22,16 +22,26 @@ public abstract class BlockEntityTypeSignMixin {
private static Block antarchy$ouranwoodHangingSign; private static Block antarchy$ouranwoodHangingSign;
@Unique @Unique
private static Block antarchy$ouranwoodWallHangingSign; private static Block antarchy$ouranwoodWallHangingSign;
@Unique
private static Block antarchy$peachSign;
@Unique
private static Block antarchy$peachWallSign;
@Unique
private static Block antarchy$peachHangingSign;
@Unique
private static Block antarchy$peachWallHangingSign;
@Inject(method = "isValid", at = @At("HEAD"), cancellable = true) @Inject(method = "isValid", at = @At("HEAD"), cancellable = true)
private void antarchy$allowOuranwoodSigns(BlockState state, CallbackInfoReturnable<Boolean> cir) { private void antarchy$allowOuranwoodSigns(BlockState state, CallbackInfoReturnable<Boolean> cir) {
Object self = this; Object self = this;
if (self == BlockEntityType.SIGN) { if (self == BlockEntityType.SIGN) {
if (state.is(antarchy$sign()) || state.is(antarchy$wallSign())) { if (state.is(antarchy$sign()) || state.is(antarchy$wallSign())
|| state.is(antarchy$peachSign()) || state.is(antarchy$peachWallSign())) {
cir.setReturnValue(true); cir.setReturnValue(true);
} }
} else if (self == BlockEntityType.HANGING_SIGN) { } else if (self == BlockEntityType.HANGING_SIGN) {
if (state.is(antarchy$hangingSign()) || state.is(antarchy$wallHangingSign())) { if (state.is(antarchy$hangingSign()) || state.is(antarchy$wallHangingSign())
|| state.is(antarchy$peachHangingSign()) || state.is(antarchy$peachWallHangingSign())) {
cir.setReturnValue(true); cir.setReturnValue(true);
} }
} }
@@ -69,6 +79,38 @@ public abstract class BlockEntityTypeSignMixin {
return antarchy$ouranwoodWallHangingSign; return antarchy$ouranwoodWallHangingSign;
} }
@Unique
private static Block antarchy$peachSign() {
if (antarchy$peachSign == null) {
antarchy$peachSign = antarchy$resolve("peach_sign");
}
return antarchy$peachSign;
}
@Unique
private static Block antarchy$peachWallSign() {
if (antarchy$peachWallSign == null) {
antarchy$peachWallSign = antarchy$resolve("peach_wall_sign");
}
return antarchy$peachWallSign;
}
@Unique
private static Block antarchy$peachHangingSign() {
if (antarchy$peachHangingSign == null) {
antarchy$peachHangingSign = antarchy$resolve("peach_hanging_sign");
}
return antarchy$peachHangingSign;
}
@Unique
private static Block antarchy$peachWallHangingSign() {
if (antarchy$peachWallHangingSign == null) {
antarchy$peachWallHangingSign = antarchy$resolve("peach_wall_hanging_sign");
}
return antarchy$peachWallHangingSign;
}
@Unique @Unique
private static Block antarchy$resolve(String path) { private static Block antarchy$resolve(String path) {
return BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path)); return BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path));
@@ -0,0 +1,41 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.entity.ElythiaFrog;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.animal.frog.Frog;
import net.minecraft.world.level.ServerLevelAccessor;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Frog.class)
public abstract class FrogSpawnVariantMixin {
@Inject(method = "finalizeSpawn", at = @At("TAIL"))
private void antarchy$setElythiaSpawnVariant(
ServerLevelAccessor level,
DifficultyInstance difficulty,
MobSpawnType spawnType,
SpawnGroupData spawnGroupData,
CallbackInfoReturnable<SpawnGroupData> cir
) {
if (spawnType == MobSpawnType.BUCKET || !ElythiaFrog.shouldBecomeElythiaFrog(level)) {
return;
}
ElythiaFrog.applyVariant((Frog) (Object) this);
}
@Inject(method = "getBreedOffspring", at = @At("RETURN"))
private void antarchy$setElythiaOffspringVariant(ServerLevel level, AgeableMob otherParent, CallbackInfoReturnable<AgeableMob> cir) {
if (!(cir.getReturnValue() instanceof Frog frog) || !ElythiaFrog.shouldBecomeElythiaFrog(level)) {
return;
}
ElythiaFrog.applyVariant(frog);
}
}
@@ -0,0 +1,23 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.entity.ElythiaFrog;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.animal.FrogVariant;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(FrogVariant.class)
public abstract class FrogVariantBootstrapMixin {
@Inject(method = "bootstrap", at = @At("TAIL"))
private static void antarchy$registerElythiaVariant(Registry<FrogVariant> registry, CallbackInfoReturnable<FrogVariant> cir) {
ResourceLocation id = ElythiaFrog.VARIANT.location();
if (registry.containsKey(id)) {
return;
}
Registry.register(registry, id, new FrogVariant(ElythiaFrog.TEXTURE));
}
}
@@ -0,0 +1,36 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.AntarchyTags;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.GlowSquid;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Blocks;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(GlowSquid.class)
public abstract class GlowSquidLumenSpawnMixin {
@Inject(method = "checkGlowSquidSpawnRules", at = @At("HEAD"), cancellable = true)
private static void antarchy$allowGlowSquidsInLumen(
EntityType<? extends LivingEntity> entityType,
ServerLevelAccessor level,
MobSpawnType spawnType,
BlockPos pos,
RandomSource random,
CallbackInfoReturnable<Boolean> cir
) {
if (pos.getY() > level.getSeaLevel() - 33 || level.getRawBrightness(pos, 0) != 0) {
return;
}
if (level.getBlockState(pos).is(Blocks.WATER) || level.getFluidState(pos).is(AntarchyTags.Fluids.LUMEN)) {
cir.setReturnValue(true);
}
}
}
@@ -0,0 +1,27 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.CustomBrewingRecipes;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.alchemy.PotionBrewing;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(PotionBrewing.class)
public abstract class PotionBrewingMixin {
@Inject(method = "hasMix", at = @At("HEAD"), cancellable = true)
private void antarchy$hasCustomMix(ItemStack input, ItemStack ingredient, CallbackInfoReturnable<Boolean> cir) {
if (CustomBrewingRecipes.hasMix(input, ingredient)) {
cir.setReturnValue(true);
}
}
@Inject(method = "mix", at = @At("HEAD"), cancellable = true)
private void antarchy$mixCustomRecipe(ItemStack ingredient, ItemStack input, CallbackInfoReturnable<ItemStack> cir) {
ItemStack output = CustomBrewingRecipes.getOutput(input, ingredient);
if (!output.isEmpty()) {
cir.setReturnValue(output);
}
}
}
@@ -0,0 +1,93 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.AntarchyObjects;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.world.entity.npc.VillagerProfession;
import net.minecraft.world.entity.npc.VillagerTrades;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.trading.ItemCost;
import net.minecraft.world.item.trading.MerchantOffer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(VillagerTrades.class)
public abstract class VillagerTradesMixin {
@Shadow @Mutable @Final private static Map TRADES;
@Shadow @Mutable @Final private static Int2ObjectMap WANDERING_TRADER_TRADES;
@Inject(method = "<clinit>", at = @At("TAIL"))
private static void antarchy$appendTrades(CallbackInfo ci) {
appendVillagerTrade(VillagerProfession.FARMER, 1,
emeraldForItems(AntarchyObjects.CORN.get(), 20, 16, 2));
appendVillagerTrade(VillagerProfession.BUTCHER, 1,
emeraldForItems(AntarchyObjects.COOKED_CORNDOG.get(), 5, 16, 2));
appendWanderingTrade(0,
itemsForEmerald(AntarchyObjects.CORN_SEEDS.get(), 1, 3, 12, 2));
}
private static VillagerTrades.ItemListing emeraldForItems(net.minecraft.world.item.Item item, int count, int maxUses, int villagerXp) {
return (trader, random) -> new MerchantOffer(new ItemCost(item, count), new ItemStack(Items.EMERALD), maxUses, villagerXp, 0.05F);
}
private static VillagerTrades.ItemListing itemsForEmerald(net.minecraft.world.item.Item item, int emeraldCost, int count, int maxUses, int villagerXp) {
return (trader, random) -> new MerchantOffer(new ItemCost(Items.EMERALD, emeraldCost), new ItemStack(item, count), maxUses, villagerXp, 0.05F);
}
@SuppressWarnings("unchecked")
private static void appendVillagerTrade(VillagerProfession profession, int level, VillagerTrades.ItemListing listing) {
Object tradeValue = TRADES.get(profession);
if (!(tradeValue instanceof Map<?, ?> tradeMap)) {
return;
}
Int2ObjectOpenHashMap<VillagerTrades.ItemListing[]> copy = new Int2ObjectOpenHashMap<>();
for (Map.Entry<?, ?> entry : tradeMap.entrySet()) {
if (entry.getKey() instanceof Integer key && entry.getValue() instanceof VillagerTrades.ItemListing[] listings) {
copy.put(key, listings);
}
}
VillagerTrades.ItemListing[] existing = copy.get(level);
VillagerTrades.ItemListing[] merged = Arrays.copyOf(existing == null ? new VillagerTrades.ItemListing[0] : existing,
(existing == null ? 0 : existing.length) + 1);
merged[merged.length - 1] = listing;
copy.put(level, merged);
Map<Object, Object> rebuilt = new HashMap<>();
for (Object entryObject : TRADES.entrySet()) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) entryObject;
if (entry.getKey() != profession) {
rebuilt.put(entry.getKey(), entry.getValue());
}
}
rebuilt.put(profession, copy);
TRADES = rebuilt;
}
@SuppressWarnings("unchecked")
private static void appendWanderingTrade(int group, VillagerTrades.ItemListing listing) {
Int2ObjectOpenHashMap<VillagerTrades.ItemListing[]> copy = new Int2ObjectOpenHashMap<>();
for (Object entryObject : ((Map<?, ?>) WANDERING_TRADER_TRADES).entrySet()) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) entryObject;
if (entry.getKey() instanceof Integer key && entry.getValue() instanceof VillagerTrades.ItemListing[] listings) {
copy.put((int) key, listings);
}
}
VillagerTrades.ItemListing[] existing = copy.get(group);
VillagerTrades.ItemListing[] merged = Arrays.copyOf(existing == null ? new VillagerTrades.ItemListing[0] : existing,
(existing == null ? 0 : existing.length) + 1);
merged[merged.length - 1] = listing;
copy.put(group, merged);
WANDERING_TRADER_TRADES = copy;
}
}
@@ -0,0 +1,25 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.AntarchyTags;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.WaterlilyBlock;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(WaterlilyBlock.class)
public abstract class WaterlilyLumenMixin {
@Inject(
method = "mayPlaceOn(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z",
at = @At("HEAD"),
cancellable = true
)
private void antarchy$allowLumenSupport(BlockState state, BlockGetter level, BlockPos pos, CallbackInfoReturnable<Boolean> cir) {
if (level.getFluidState(pos).is(AntarchyTags.Fluids.LUMEN)) {
cir.setReturnValue(true);
}
}
}
@@ -0,0 +1,29 @@
package com.craisinlord.antarchy.mixins.client;
import com.craisinlord.antarchy.content.entity.ElythiaFrog;
import net.minecraft.client.model.FrogModel;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.client.renderer.entity.FrogRenderer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.animal.frog.Frog;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(FrogRenderer.class)
public abstract class FrogRendererElythiaMixin extends MobRenderer<Frog, FrogModel<Frog>> {
protected FrogRendererElythiaMixin(EntityRendererProvider.Context context, FrogModel<Frog> model, float shadowRadius) {
super(context, model, shadowRadius);
}
@Inject(method = "getTextureLocation(Lnet/minecraft/world/entity/animal/frog/Frog;)Lnet/minecraft/resources/ResourceLocation;", at = @At("HEAD"), cancellable = true)
private void antarchy$useElythiaTexture(Frog frog, CallbackInfoReturnable<ResourceLocation> cir) {
if (!ElythiaFrog.isElythiaFrog(frog)) {
return;
}
cir.setReturnValue(ElythiaFrog.TEXTURE);
}
}
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.mixins.gravity.client; package com.craisinlord.antarchy.mixins.gravity.client;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi; import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil; import com.craisinlord.antarchy.content.gravity.AntarchyGravityRotationUtil;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
@@ -22,6 +23,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
* Rotates entity rendering for inverted gravity. * Rotates entity rendering for inverted gravity.
*/ */
public abstract class EntityRenderDispatcherGravityMixin { public abstract class EntityRenderDispatcherGravityMixin {
@org.spongepowered.asm.mixin.Unique
private static boolean antarchy$loggedGravityRenderFailure;
@Inject( @Inject(
method = "render", method = "render",
@@ -39,12 +42,16 @@ public abstract class EntityRenderDispatcherGravityMixin {
int packedLight, int packedLight,
CallbackInfo ci CallbackInfo ci
) { ) {
float flipProgress = AntarchyGravityApi.getGravityFlipProgress(entity, partialTick); try {
if (!AntarchyGravityApi.isGravityInverted(entity) && flipProgress <= 0.0F) { float flipProgress = AntarchyGravityApi.getGravityFlipProgress(entity, partialTick);
return; if (!AntarchyGravityApi.isGravityInverted(entity) && flipProgress <= 0.0F) {
} return;
}
poseStack.mulPose(AntarchyGravityRotationUtil.getCameraRotationQuaternion(entity, partialTick)); poseStack.mulPose(AntarchyGravityRotationUtil.getCameraRotationQuaternion(entity, partialTick));
} catch (Throwable throwable) {
antarchy$logGravityRenderFailure(entity, throwable);
}
} }
@ModifyVariable( @ModifyVariable(
@@ -62,14 +69,19 @@ public abstract class EntityRenderDispatcherGravityMixin {
float blue, float blue,
float partialTick float partialTick
) { ) {
if (!AntarchyGravityApi.isGravityInverted(entity)) { try {
if (!AntarchyGravityApi.isGravityInverted(entity)) {
return box;
}
return AntarchyGravityRotationUtil.boxWorldToPlayer(
box,
AntarchyGravityApi.getGravityDirection(entity)
);
} catch (Throwable throwable) {
antarchy$logGravityRenderFailure(entity, throwable);
return box; return box;
} }
return AntarchyGravityRotationUtil.boxWorldToPlayer(
box,
AntarchyGravityApi.getGravityDirection(entity)
);
} }
@WrapOperation( @WrapOperation(
@@ -78,13 +90,28 @@ public abstract class EntityRenderDispatcherGravityMixin {
) )
private static Vec3 antarchy$rotateRenderedHitboxViewVector(Entity entity, float partialTick, Operation<Vec3> original) { private static Vec3 antarchy$rotateRenderedHitboxViewVector(Entity entity, float partialTick, Operation<Vec3> original) {
Vec3 viewVector = original.call(entity, partialTick); Vec3 viewVector = original.call(entity, partialTick);
if (!AntarchyGravityApi.isGravityInverted(entity)) { try {
if (!AntarchyGravityApi.isGravityInverted(entity)) {
return viewVector;
}
return AntarchyGravityRotationUtil.vecWorldToPlayer(
viewVector,
AntarchyGravityApi.getGravityDirection(entity)
);
} catch (Throwable throwable) {
antarchy$logGravityRenderFailure(entity, throwable);
return viewVector; return viewVector;
} }
}
return AntarchyGravityRotationUtil.vecWorldToPlayer( @org.spongepowered.asm.mixin.Unique
viewVector, private static void antarchy$logGravityRenderFailure(Entity entity, Throwable throwable) {
AntarchyGravityApi.getGravityDirection(entity) if (antarchy$loggedGravityRenderFailure) {
); return;
}
antarchy$loggedGravityRenderFailure = true;
Antarchy.LOGGER.error("Disabling Antarchy gravity render adjustments after a client render failure on entity {}", entity, throwable);
} }
} }
@@ -5,6 +5,8 @@
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"mixins": [ "mixins": [
"AccessorRangedAttribute", "AccessorRangedAttribute",
"AmericanFoodConsumeMixin",
"AmericanFoodNameMixin",
"BlockEntityTypeSignMixin", "BlockEntityTypeSignMixin",
"BloodglassMixin", "BloodglassMixin",
"DreamFireBlockMixin", "DreamFireBlockMixin",
@@ -12,8 +14,14 @@
"FireVisualBaseFireBlockMixin", "FireVisualBaseFireBlockMixin",
"FireVisualEntityMixin", "FireVisualEntityMixin",
"FlintAndSteelPortalIgnitionMixin", "FlintAndSteelPortalIgnitionMixin",
"FrogSpawnVariantMixin",
"FrogVariantBootstrapMixin",
"GlowSquidLumenSpawnMixin",
"GlowVinesUnderLeavesMixin", "GlowVinesUnderLeavesMixin",
"NightmareSwordDamageMixin", "NightmareSwordDamageMixin",
"PotionBrewingMixin",
"VillagerTradesMixin",
"WaterlilyLumenMixin",
"ServerPlayerGameModeMixin", "ServerPlayerGameModeMixin",
"SquidzookaEnchantMixin", "SquidzookaEnchantMixin",
"SquidzookaMultishotMixin", "SquidzookaMultishotMixin",
@@ -50,6 +58,7 @@
"client": [ "client": [
"client.ElythiaSkyMixin", "client.ElythiaSkyMixin",
"client.ElytraModelAccessor", "client.ElytraModelAccessor",
"client.FrogRendererElythiaMixin",
"client.GameRendererSwingThroughGrassMixin", "client.GameRendererSwingThroughGrassMixin",
"client.LightningBoltRendererMixin", "client.LightningBoltRendererMixin",
"client.MountedVehicleCameraMixin", "client.MountedVehicleCameraMixin",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Loaded 100 of 561 files, more files were not shown because too many files have changed in this diff. Show more