Merge branch 'beta7'

This commit is contained in:
craisinlord committed 2026-07-15 22:32:25 -05:00
commit 5aa77a2d01
814 files changed
+38247 -6846

No files matched your search

+1
View File
@@ -65,6 +65,7 @@ allprojects {
maven { url "https://jitpack.io" }
maven { url "https://www.cursemaven.com" }
maven { url "https://api.modrinth.com/maven/" }
maven { url "https://maven.blamejared.com" }
}
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: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(files("$rootDir/infinite_dimensions_jars/infinity-2.7.1+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-neoforge-sources.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-neoforge-sources.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/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.
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("mezz.jei:jei-${minecraft_version}-common-api:${jei_version}")
//
// compileOnly group: 'org.ow2.asm', name: 'asm-tree', version: '9.4'
// 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());
}
}
@@ -53,6 +53,16 @@ public final class AntarchySettings {
private static double bigBerthaAttackDamage = 55.0D;
private static double bigBerthaReachBonus = 3.0D;
private static double bigBerthaAttackSpeed = -3.0D;
private static double krakensGraspAttackDamage = 16.0D;
private static double krakensGraspAttackSpeed = -2.9D;
private static double krakensGraspThrownDamage = 16.0D;
private static double krakensGraspLightningDamage = 6.0D;
private static boolean krakensGraspInnateLoyalty = true;
private static int krakensGraspInnateLoyaltyLevel = 3;
private static int krakensGraspTentacleDurationTicks = 100;
private static double krakensGraspTentacleDamage = 3.0D;
private static double krakensGraspTentacleRadius = 3.0D;
private static int krakensGraspTentacleDamageIntervalTicks = 20;
private static double scorpionWhipBaseDamage = 10.0D;
private static double scorpionWhipReachBonus = 5.0D;
private static int scorpionWhipPoisonDurationTicks = 100;
@@ -117,6 +127,10 @@ public final class AntarchySettings {
private static boolean krakenMassSpawnEnabled = true;
private static double squidzookaCooldownSeconds = 1.0D;
private static double squidzookaLaunchVelocity = 2.1D;
private static double rpoLauncherCooldownSeconds = 1.0D;
private static double rpoLauncherLaunchVelocity = 2.1D;
private static double rpoLauncherExplosionDamage = 14.0D;
private static double rpoLauncherExplosionRadius = 4.0D;
private static double sizeRayCooldownSeconds = 1.0D;
private static boolean invertProjectilesFromInvertedPlayers = false;
private static boolean gravityGunEnabled = true;
@@ -127,9 +141,19 @@ public final class AntarchySettings {
private static double gravityGunBlastStrength = 1.4D;
private static double gravityGunCooldownSeconds = 0.35D;
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 lucidPearlInvertedDurationSeconds = 6.0D;
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 mogglesVisionMaxLight = 7;
private static float mogglesVisionAlpha = 0.35f;
@@ -490,6 +514,7 @@ public final class AntarchySettings {
private static double herculesBeetleHealth = 500.0D;
private static double herculesBeetleAttackDamage = 30.0D;
private static double herculesBeetleChargeDamage = 40.0D;
private static double ouranwoodDeerHealth = 20.0D;
private static double glimmerHealth = 20.0D;
private AntarchySettings() {
@@ -763,6 +788,17 @@ public final class AntarchySettings {
return krakenAttackDamage;
}
public static double krakensGraspAttackDamage() { return krakensGraspAttackDamage; }
public static double krakensGraspAttackSpeed() { return krakensGraspAttackSpeed; }
public static double krakensGraspThrownDamage() { return krakensGraspThrownDamage; }
public static double krakensGraspLightningDamage() { return krakensGraspLightningDamage; }
public static boolean krakensGraspInnateLoyalty() { return krakensGraspInnateLoyalty; }
public static int krakensGraspInnateLoyaltyLevel() { return krakensGraspInnateLoyaltyLevel; }
public static int krakensGraspTentacleDurationTicks() { return krakensGraspTentacleDurationTicks; }
public static double krakensGraspTentacleDamage() { return krakensGraspTentacleDamage; }
public static double krakensGraspTentacleRadius() { return krakensGraspTentacleRadius; }
public static int krakensGraspTentacleDamageIntervalTicks() { return krakensGraspTentacleDamageIntervalTicks; }
public static double octopusBombHealth() {
return octopusBombHealth;
}
@@ -907,6 +943,11 @@ public final class AntarchySettings {
return squidzookaLaunchVelocity;
}
public static double rpoLauncherCooldownSeconds() { return rpoLauncherCooldownSeconds; }
public static double rpoLauncherLaunchVelocity() { return rpoLauncherLaunchVelocity; }
public static double rpoLauncherExplosionDamage() { return rpoLauncherExplosionDamage; }
public static double rpoLauncherExplosionRadius() { return rpoLauncherExplosionRadius; }
public static double sizeRayCooldownSeconds() {
return sizeRayCooldownSeconds;
}
@@ -947,6 +988,26 @@ public final class AntarchySettings {
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) {
disableInfinityBookPortalCreation = value;
}
@@ -1261,6 +1322,17 @@ public final class AntarchySettings {
krakenAttackDamage = value;
}
public static void setKrakensGraspAttackDamage(double value) { krakensGraspAttackDamage = value; }
public static void setKrakensGraspAttackSpeed(double value) { krakensGraspAttackSpeed = value; }
public static void setKrakensGraspThrownDamage(double value) { krakensGraspThrownDamage = value; }
public static void setKrakensGraspLightningDamage(double value) { krakensGraspLightningDamage = value; }
public static void setKrakensGraspInnateLoyalty(boolean value) { krakensGraspInnateLoyalty = value; }
public static void setKrakensGraspInnateLoyaltyLevel(int value) { krakensGraspInnateLoyaltyLevel = value; }
public static void setKrakensGraspTentacleDurationTicks(int value) { krakensGraspTentacleDurationTicks = value; }
public static void setKrakensGraspTentacleDamage(double value) { krakensGraspTentacleDamage = value; }
public static void setKrakensGraspTentacleRadius(double value) { krakensGraspTentacleRadius = value; }
public static void setKrakensGraspTentacleDamageIntervalTicks(int value) { krakensGraspTentacleDamageIntervalTicks = value; }
public static void setBrutalflyHealth(double value) {
brutalflyHealth = value;
}
@@ -1353,6 +1425,11 @@ public final class AntarchySettings {
squidzookaLaunchVelocity = value;
}
public static void setRpoLauncherCooldownSeconds(double value) { rpoLauncherCooldownSeconds = value; }
public static void setRpoLauncherLaunchVelocity(double value) { rpoLauncherLaunchVelocity = value; }
public static void setRpoLauncherExplosionDamage(double value) { rpoLauncherExplosionDamage = value; }
public static void setRpoLauncherExplosionRadius(double value) { rpoLauncherExplosionRadius = value; }
public static void setSizeRayCooldownSeconds(double value) {
sizeRayCooldownSeconds = value;
}
@@ -1393,6 +1470,26 @@ public final class AntarchySettings {
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() {
return lucidAttackRange;
}
@@ -1417,6 +1514,46 @@ public final class AntarchySettings {
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() {
return mogglesVisionRadius;
}
@@ -2548,6 +2685,8 @@ public final class AntarchySettings {
public static double herculesBeetleChargeDamage() { return herculesBeetleChargeDamage; }
public static void setHerculesBeetleChargeDamage(double v) { herculesBeetleChargeDamage = 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 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));
}
}
@@ -21,6 +21,8 @@ import com.craisinlord.antarchy.content.entity.flying_squirrel.FlyingSquirrelEnt
import com.craisinlord.antarchy.content.entity.MissileSquidEntity;
import com.craisinlord.antarchy.content.entity.OctopusBombEntity;
import com.craisinlord.antarchy.content.entity.kraken.KrakenEntity;
import com.craisinlord.antarchy.content.entity.kraken.KrakensGraspThrownTrident;
import com.craisinlord.antarchy.content.entity.kraken.TentacleEntity;
import com.craisinlord.antarchy.content.entity.MolewormEntity;
import com.craisinlord.antarchy.content.entity.MantisEntity;
import com.craisinlord.antarchy.content.entity.ReverieEntity;
@@ -34,12 +36,14 @@ import net.minecraft.core.particles.SimpleParticleType;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluid;
public final class AntarchyObjects {
private static final Supplier<?> UNBOUND = () -> {
@@ -55,6 +59,10 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<EntityType<MissileSquidEntity>> MISSILE_SQUID = (Supplier<EntityType<MissileSquidEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<TentacleEntity>> TENTACLE = (Supplier<EntityType<TentacleEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<KrakensGraspThrownTrident>> KRAKENS_GRASP_TRIDENT = (Supplier<EntityType<KrakensGraspThrownTrident>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<OctopusBombEntity>> OCTOPUS_BOMB = (Supplier<EntityType<OctopusBombEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<EntityType<MolewormEntity>> MOLEWORM = (Supplier<EntityType<MolewormEntity>>) UNBOUND;
@@ -99,9 +107,40 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<EntityType<HerculesBeetleEntity>> HERCULES_BEETLE = (Supplier<EntityType<HerculesBeetleEntity>>) UNBOUND;
@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")
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<Block> STAR_CORAL_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> STAR_CORAL = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> STAR_CORAL_FAN = (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")
public static Supplier<net.minecraft.world.item.Item> CHEEP_ITEM = (Supplier<net.minecraft.world.item.Item>) UNBOUND;
@SuppressWarnings("unchecked")
@@ -185,6 +224,10 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<Item> KRAKEN_TOOTH = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> KRAKEN_TENTACLE = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> KRAKENS_GRASP = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> MOGGLES = (Supplier<Item>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Item> REVERIE_BOTTLE = (Supplier<Item>) UNBOUND;
@@ -205,6 +248,16 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<Holder<MobEffect>> STINKY_EFFECT = (Supplier<Holder<MobEffect>>) UNBOUND;
@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;
@SuppressWarnings("unchecked")
public static Supplier<Block> MOSSY_OURANWOOD_LOG = (Supplier<Block>) UNBOUND;
@@ -223,12 +276,22 @@ public final class AntarchyObjects {
@SuppressWarnings("unchecked")
public static Supplier<Block> WASP_NEST = (Supplier<Block>) UNBOUND;
@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;
@SuppressWarnings("unchecked")
public static Supplier<Block> SQUIRREL_NEST_BLOCK = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> BRUTALFLY_COCOON_SPAWNER = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<Block> GIANT_LILY_PAD = (Supplier<Block>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<BlockEntityType<AntNestBlockEntity>> ANT_NEST_BLOCK_ENTITY = (Supplier<BlockEntityType<AntNestBlockEntity>>) UNBOUND;
@SuppressWarnings("unchecked")
public static Supplier<BlockEntityType<DreamCampfireBlockEntity>> DREAM_CAMPFIRE_BLOCK_ENTITY = (Supplier<BlockEntityType<DreamCampfireBlockEntity>>) UNBOUND;
@@ -240,6 +303,11 @@ public final class AntarchyObjects {
public static Supplier<SimpleParticleType> STINKY_GAS = (Supplier<SimpleParticleType>) UNBOUND;
@SuppressWarnings("unchecked")
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() {
}
@@ -319,6 +387,10 @@ public final class AntarchyObjects {
Supplier<Block> ouranwoodAcornBlock,
Supplier<Block> mossyOuranwoodLog,
Supplier<Block> mossyOuranwoodWood,
Supplier<Block> umbralMossBlock,
Supplier<Block> umbralMossCarpet,
Supplier<Block> blushMossBlock,
Supplier<Block> blushMossCarpet,
Supplier<Block> orangeMilkweed,
Supplier<Block> pinkMilkweed,
Supplier<Block> bedBugEggs,
@@ -328,12 +400,14 @@ public final class AntarchyObjects {
Supplier<Block> hushweed,
Supplier<Block> squirrelNestBlock,
Supplier<Block> brutalflyCocoonSpawner,
Supplier<Block> giantLilyPad,
Supplier<BlockEntityType<AntNestBlockEntity>> antNestBlockEntity,
Supplier<BlockEntityType<DreamCampfireBlockEntity>> dreamCampfireBlockEntity,
Supplier<BlockEntityType<WaspNestBlockEntity>> waspNestBlockEntity,
Supplier<BlockEntityType<HushweedBlockEntity>> hushweedBlockEntity,
Supplier<SimpleParticleType> stinkyGas,
Supplier<SimpleParticleType> stinkyFly,
Supplier<SimpleParticleType> peachLeaves,
Supplier<Holder<Attribute>> doubleDamageChance,
Supplier<Holder<Attribute>> bloodglassMaxHearts,
Supplier<Holder<MobEffect>> bloodglassWard
@@ -412,6 +486,10 @@ public final class AntarchyObjects {
OURANWOOD_ACORN_BLOCK = ouranwoodAcornBlock;
MOSSY_OURANWOOD_LOG = mossyOuranwoodLog;
MOSSY_OURANWOOD_WOOD = mossyOuranwoodWood;
UMBRAL_MOSS_BLOCK = umbralMossBlock;
UMBRAL_MOSS_CARPET = umbralMossCarpet;
BLUSH_MOSS_BLOCK = blushMossBlock;
BLUSH_MOSS_CARPET = blushMossCarpet;
ORANGE_MILKWEED = orangeMilkweed;
PINK_MILKWEED = pinkMilkweed;
BED_BUG_EGG = bedBugEggs;
@@ -421,12 +499,14 @@ public final class AntarchyObjects {
HUSHWEED = hushweed;
SQUIRREL_NEST_BLOCK = squirrelNestBlock;
BRUTALFLY_COCOON_SPAWNER = brutalflyCocoonSpawner;
GIANT_LILY_PAD = giantLilyPad;
ANT_NEST_BLOCK_ENTITY = antNestBlockEntity;
DREAM_CAMPFIRE_BLOCK_ENTITY = dreamCampfireBlockEntity;
WASP_NEST_BLOCK_ENTITY = waspNestBlockEntity;
HUSHWEED_BLOCK_ENTITY = hushweedBlockEntity;
STINKY_GAS = stinkyGas;
STINKY_FLY = stinkyFly;
PEACH_LEAVES_PARTICLE = peachLeaves;
DOUBLE_DAMAGE_CHANCE = doubleDamageChance;
BLOODGLASS_MAX_HEARTS = bloodglassMaxHearts;
BLOODGLASS_WARD = bloodglassWard;
@@ -440,16 +520,120 @@ public final class AntarchyObjects {
OCTOPUS_BOMB = supplier;
}
public static void setTentacle(Supplier<EntityType<TentacleEntity>> supplier) {
TENTACLE = supplier;
}
public static void setKrakensGraspTrident(Supplier<EntityType<KrakensGraspThrownTrident>> supplier) {
KRAKENS_GRASP_TRIDENT = supplier;
}
public static void setKrakenTentacle(Supplier<Item> supplier) {
KRAKEN_TENTACLE = supplier;
}
public static void setKrakensGrasp(Supplier<Item> supplier) {
KRAKENS_GRASP = supplier;
}
public static void setHerculesBeetle(Supplier<EntityType<HerculesBeetleEntity>> 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;
}
public static void setGlimmersGrace(Supplier<Holder<MobEffect>> supplier) {
GLIMMERS_GRACE = supplier;
public static void setLumen(Supplier<Fluid> 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 setStarCoralBlock(Supplier<Block> supplier) {
STAR_CORAL_BLOCK = supplier;
}
public static void setStarCoral(Supplier<Block> supplier) {
STAR_CORAL = supplier;
}
public static void setStarCoralFan(Supplier<Block> supplier) {
STAR_CORAL_FAN = 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() {
@@ -10,6 +10,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.material.Fluid;
public final class 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> 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> 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> 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> 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() {
}
@@ -94,16 +97,31 @@ 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> 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> 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() {
}
}
public static final class Fluids {
public static final TagKey<Fluid> LUMEN = TagKey.create(Registries.FLUID, id("lumen"));
public static final TagKey<Fluid> GIANT_LILY_PAD_SUPPORTING_FLUIDS = TagKey.create(Registries.FLUID, id("giant_lily_pad_supporting_fluids"));
private Fluids() {
}
}
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_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> WILD_CORN_SPAWN_BIOMES = TagKey.create(Registries.BIOME, id("wild_corn_spawn_biomes"));
private Biomes() {
}
@@ -0,0 +1,334 @@
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 "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", "camellia", "spider_lily", "hushweed", "amber_lichen", "creepvine",
"triffid_goo_block", "cloud_block", "wasp_nest",
"red_ant_nest", "brown_ant_nest", "rainbow_ant_nest", "termite_nest",
"star_coral", "star_coral_fan", "star_coral_block",
"dead_star_coral", "dead_star_coral_fan", "dead_star_coral_block" -> 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", "rpo_launcher", "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", "krakens_grasp" -> 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", "peach_pie", "cornbread", "popcorn", "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", "kraken_tentacle",
"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", "glimmer_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 "camellia" -> 2;
case "spider_lily" -> 3;
case "hushweed" -> 4;
case "amber_lichen" -> 5;
case "creepvine" -> 6;
case "triffid_goo_block" -> 7;
case "cloud_block" -> 8;
case "wasp_nest" -> 9;
case "red_ant_nest" -> 10;
case "brown_ant_nest" -> 11;
case "rainbow_ant_nest" -> 12;
case "termite_nest" -> 13;
case "star_coral" -> 14;
case "star_coral_fan" -> 15;
case "star_coral_block" -> 16;
case "dead_star_coral" -> 17;
case "dead_star_coral_fan" -> 18;
case "dead_star_coral_block" -> 19;
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 "rpo_launcher" -> 3;
case "shrink_ray" -> 4;
case "growth_ray" -> 5;
case "gravity_gun" -> 6;
case "water_cannon" -> 7;
case "nightmare_sword" -> 8;
case "lucid_pearl" -> 9;
case "scorpion_whip" -> 10;
case "blood_crystal_katana" -> 11;
case "big_bertha" -> 12;
case "big_bertha_blade" -> 13;
case "big_bertha_handle" -> 14;
case "big_bertha_hilt" -> 15;
case "ultimate_sword" -> 16;
case "ultimate_mace" -> 17;
case "krakens_grasp" -> 18;
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 "cloud_shark_fin_soup" -> 0;
case "mud_pie" -> 1;
case "peach_pie" -> 2;
case "cornbread" -> 3;
case "popcorn" -> 4;
case "rainbow_sugar" -> 5;
case "raw_corndog" -> 6;
case "cooked_corndog" -> 7;
case "raw_venison" -> 8;
case "cooked_venison" -> 9;
case "blood_crystal_apple" -> 10;
case "spirit_apple" -> 11;
case "peach" -> 12;
case "reverie_bottle" -> 0;
case "glimmer_bottle" -> 1;
case "root_beer" -> 2;
case "blood_crystal_shard" -> 0;
case "cloud_shark_fin" -> 1;
case "basilisk_fang" -> 2;
case "kraken_tooth" -> 3;
case "kraken_tentacle" -> 4;
case "emperor_scorpion_stinger" -> 5;
case "nightmare_scale" -> 6;
case "molevore_nose" -> 7;
case "moleworm" -> 8;
case "uranium_nugget" -> 9;
case "titanium_nugget" -> 10;
case "uranium_ingot" -> 11;
case "titanium_ingot" -> 12;
case "lucid_eye" -> 13;
case "mantis_claw" -> 14;
case "jumpy_bug_leg" -> 15;
case "brutalfly_wing" -> 16;
case "cornea_ear" -> 17;
case "primordial_scute" -> 18;
case "triffid_goo" -> 19;
case "vortex_eye" -> 20;
case "king_scale" -> 21;
case "queen_scale" -> 22;
case "chiten" -> 23;
case "stink_bug" -> 24;
case "corn" -> 25;
case "high_fructose_corn_syrup" -> 26;
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;
}
}
@@ -0,0 +1,41 @@
package com.craisinlord.antarchy.content;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.entity.OctopusBombEntity;
import net.minecraft.core.Direction;
import net.minecraft.core.Position;
import net.minecraft.core.dispenser.BlockSource;
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.block.DispenserBlock;
public class RpoLauncherDispenseBehavior extends DefaultDispenseItemBehavior {
@Override
protected ItemStack execute(BlockSource source, ItemStack stack) {
Direction direction = source.state().getValue(DispenserBlock.FACING);
Position position = DispenserBlock.getDispensePosition(source);
OctopusBombEntity bomb = AntarchyObjects.OCTOPUS_BOMB.get().create(source.level());
if (bomb == null) return super.execute(source, stack);
bomb.setPos(position.x(), position.y(), position.z());
Vec3 vel = new Vec3(direction.getStepX(), direction.getStepY(), direction.getStepZ())
.normalize()
.scale(AntarchySettings.rpoLauncherLaunchVelocity());
bomb.launchAsProjectile(null, vel);
source.level().addFreshEntity(bomb);
if (stack.isDamageableItem()) {
int nextDamage = stack.getDamageValue() + 1;
if (nextDamage >= stack.getMaxDamage()) {
stack.shrink(1);
} else {
stack.setDamageValue(nextDamage);
}
} else {
stack.shrink(1);
}
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,22 @@
package com.craisinlord.antarchy.content.block;
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.block.state.BlockState;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
public class CamelliaBlock extends MilkweedBlock {
public CamelliaBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
BlockPos dropPos = state.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) == DoubleBlockHalf.UPPER ? pos.below() : pos;
popResource(level, dropPos, new ItemStack(this.asItem()));
}
}
@@ -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,345 @@
package com.craisinlord.antarchy.content.block;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.mojang.serialization.MapCodec;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.item.context.BlockPlaceContext;
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.RenderShape;
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.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
public final class GiantLilyPadBlock extends Block {
public static final MapCodec<GiantLilyPadBlock> CODEC = Block.simpleCodec(GiantLilyPadBlock::new);
public static final EnumProperty<Shape> SHAPE = EnumProperty.create("shape", Shape.class);
public static final EnumProperty<TilePosition> TILE_POSITION = EnumProperty.create("tile_position", TilePosition.class);
public static final EnumProperty<PadRotation> ROTATION = EnumProperty.create("rotation", PadRotation.class);
private static final VoxelShape SHAPE_BOX = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 0.5D, 16.0D);
private static final int SEARCH_RADIUS = 2;
private static final ThreadLocal<Integer> RESTRUCTURING_DEPTH = ThreadLocal.withInitial(() -> 0);
public GiantLilyPadBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any()
.setValue(SHAPE, Shape.SINGLE)
.setValue(TILE_POSITION, TilePosition.SINGLE)
.setValue(ROTATION, PadRotation.DEG_0));
}
@Override
public MapCodec<GiantLilyPadBlock> codec() {
return CODEC;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(SHAPE, TILE_POSITION, ROTATION);
}
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return canSurvive(defaultBlockState(), context.getLevel(), context.getClickedPos()) ? defaultBlockState() : null;
}
@Override
protected RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE_BOX;
}
@Override
protected boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) {
return isSupported(level, pos);
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
if (!state.canSurvive(level, pos)) {
if (level instanceof ServerLevel serverLevel) {
recalculateNearbyPads(serverLevel, pos);
}
return Blocks.AIR.defaultBlockState();
}
if (level instanceof ServerLevel serverLevel) {
recalculateNearbyPads(serverLevel, pos);
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
super.onPlace(state, level, pos, oldState, movedByPiston);
if (!level.isClientSide && !oldState.is(state.getBlock()) && level instanceof ServerLevel serverLevel) {
recalculateNearbyPads(serverLevel, pos);
}
}
@Override
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
super.onRemove(state, level, pos, newState, movedByPiston);
if (!level.isClientSide && !state.is(newState.getBlock()) && level instanceof ServerLevel serverLevel) {
recalculateNearbyPads(serverLevel, pos);
}
}
private static boolean isSupported(LevelReader level, BlockPos pos) {
return level.getFluidState(pos).is(AntarchyTags.Fluids.GIANT_LILY_PAD_SUPPORTING_FLUIDS)
|| level.getFluidState(pos.below()).is(AntarchyTags.Fluids.GIANT_LILY_PAD_SUPPORTING_FLUIDS);
}
private static void recalculateNearbyPads(ServerLevel level, BlockPos center) {
if (isRestructuring()) {
return;
}
withRestructuringGuard(() -> {
int minX = center.getX() - SEARCH_RADIUS;
int maxX = center.getX() + SEARCH_RADIUS;
int minZ = center.getZ() - SEARCH_RADIUS;
int maxZ = center.getZ() + SEARCH_RADIUS;
int y = center.getY();
List<ShapeCandidate> candidates = new ArrayList<>();
collectCandidates(level, y, minX, maxX, minZ, maxZ, candidates);
candidates.sort(Comparator
.comparingInt(ShapeCandidate::area).reversed()
.thenComparingInt(candidate -> candidate.anchor().getX())
.thenComparingInt(candidate -> candidate.anchor().getZ())
.thenComparingInt(candidate -> candidate.shape().ordinal()));
List<BlockPos> assigned = new ArrayList<>();
for (ShapeCandidate candidate : candidates) {
boolean blocked = false;
for (BlockPos pos : candidate.positions()) {
if (assigned.contains(pos)) {
blocked = true;
break;
}
}
if (blocked) {
continue;
}
PadRotation rotation = rotationFor(level, candidate.anchor());
for (TileAssignment tile : candidate.tiles()) {
BlockPos pos = tile.pos();
BlockState newState = level.getBlockState(pos);
if (!newState.is(candidate.block())) {
continue;
}
BlockState shapedState = candidate.block().defaultBlockState()
.setValue(SHAPE, candidate.shape())
.setValue(TILE_POSITION, tile.tilePosition())
.setValue(ROTATION, rotation);
if (!newState.equals(shapedState)) {
level.setBlock(pos, shapedState, Block.UPDATE_CLIENTS);
}
assigned.add(pos);
}
}
});
}
private static void collectCandidates(ServerLevel level, int y, int minX, int maxX, int minZ, int maxZ, List<ShapeCandidate> out) {
addRectCandidates(level, y, minX, maxX, minZ, maxZ, Shape.THREE_BY_THREE, GridLayouts.THREE_BY_THREE, out);
addRectCandidates(level, y, minX, maxX, minZ, maxZ, Shape.TWO_BY_TWO, GridLayouts.TWO_BY_TWO, out);
addRectCandidates(level, y, minX, maxX, minZ, maxZ, Shape.TWO_BY_ONE, GridLayouts.TWO_BY_ONE_HORIZONTAL, out);
addRectCandidates(level, y, minX, maxX, minZ, maxZ, Shape.TWO_BY_ONE, GridLayouts.TWO_BY_ONE_VERTICAL, out);
addRectCandidates(level, y, minX, maxX, minZ, maxZ, Shape.SINGLE, GridLayouts.SINGLE, out);
}
private static void addRectCandidates(ServerLevel level, int y, int minX, int maxX, int minZ, int maxZ, Shape shape, GridLayout layout, List<ShapeCandidate> out) {
int width = layout.width();
int height = layout.height();
for (int anchorX = minX; anchorX <= maxX - width + 1; anchorX++) {
for (int anchorZ = minZ; anchorZ <= maxZ - height + 1; anchorZ++) {
BlockPos anchor = new BlockPos(anchorX, y, anchorZ);
ShapeCandidate candidate = createCandidate(level, anchor, shape, layout);
if (candidate != null) {
out.add(candidate);
}
}
}
}
@Nullable
private static ShapeCandidate createCandidate(ServerLevel level, BlockPos anchor, Shape shape, GridLayout layout) {
BlockState anchorState = level.getBlockState(anchor);
if (!(anchorState.getBlock() instanceof GiantLilyPadBlock giantLilyPadBlock)) {
return null;
}
List<TileAssignment> tiles = new ArrayList<>(layout.width() * layout.height());
for (int row = 0; row < layout.height(); row++) {
for (int column = 0; column < layout.width(); column++) {
BlockPos pos = anchor.offset(column, 0, row);
if (!level.getBlockState(pos).is(giantLilyPadBlock)) {
return null;
}
TilePosition tilePosition = layout.tiles()[row][column];
tiles.add(new TileAssignment(pos, tilePosition));
}
}
return new ShapeCandidate(giantLilyPadBlock, shape, anchor, tiles);
}
private static boolean isRestructuring() {
return RESTRUCTURING_DEPTH.get() > 0;
}
private static void withRestructuringGuard(Runnable action) {
RESTRUCTURING_DEPTH.set(RESTRUCTURING_DEPTH.get() + 1);
try {
action.run();
} finally {
RESTRUCTURING_DEPTH.set(Math.max(0, RESTRUCTURING_DEPTH.get() - 1));
}
}
private static PadRotation rotationFor(ServerLevel level, BlockPos anchor) {
long seed = level.getSeed();
long mix = seed
^ anchor.asLong()
^ Long.rotateLeft(seed, 17)
^ Long.rotateLeft(anchor.asLong(), 31);
int index = (int) Math.floorMod(mix, 4L);
return PadRotation.values()[index];
}
private record ShapeCandidate(GiantLilyPadBlock block, Shape shape, BlockPos anchor, List<TileAssignment> tiles) {
int area() {
return this.tiles.size();
}
List<BlockPos> positions() {
return this.tiles.stream().map(TileAssignment::pos).toList();
}
}
private record TileAssignment(BlockPos pos, TilePosition tilePosition) {
}
private record GridLayout(TilePosition[][] tiles) {
int width() {
return this.tiles[0].length;
}
int height() {
return this.tiles.length;
}
}
private static final class GridLayouts {
private static final GridLayout SINGLE = new GridLayout(new TilePosition[][]{
{TilePosition.SINGLE}
});
private static final GridLayout TWO_BY_ONE_HORIZONTAL = new GridLayout(new TilePosition[][]{
{TilePosition.MIDDLE_LEFT, TilePosition.MIDDLE_RIGHT}
});
private static final GridLayout TWO_BY_ONE_VERTICAL = new GridLayout(new TilePosition[][]{
{TilePosition.TOP_MIDDLE},
{TilePosition.BOTTOM_MIDDLE}
});
private static final GridLayout TWO_BY_TWO = new GridLayout(new TilePosition[][]{
{TilePosition.TOP_LEFT, TilePosition.TOP_RIGHT},
{TilePosition.BOTTOM_LEFT, TilePosition.BOTTOM_RIGHT}
});
private static final GridLayout THREE_BY_THREE = new GridLayout(new TilePosition[][]{
{TilePosition.TOP_LEFT, TilePosition.TOP_MIDDLE, TilePosition.TOP_RIGHT},
{TilePosition.MIDDLE_LEFT, TilePosition.CENTER, TilePosition.MIDDLE_RIGHT},
{TilePosition.BOTTOM_LEFT, TilePosition.BOTTOM_MIDDLE, TilePosition.BOTTOM_RIGHT}
});
private GridLayouts() {
}
}
public enum Shape implements StringRepresentable {
SINGLE("single"),
TWO_BY_ONE("two_by_one"),
TWO_BY_TWO("two_by_two"),
THREE_BY_THREE("three_by_three");
private final String name;
Shape(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return this.name;
}
}
public enum TilePosition implements StringRepresentable {
SINGLE("single"),
TOP_LEFT("top_left"),
TOP_MIDDLE("top_middle"),
TOP_RIGHT("top_right"),
MIDDLE_LEFT("middle_left"),
CENTER("center"),
MIDDLE_RIGHT("middle_right"),
BOTTOM_LEFT("bottom_left"),
BOTTOM_MIDDLE("bottom_middle"),
BOTTOM_RIGHT("bottom_right");
private final String name;
TilePosition(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return this.name;
}
}
public enum PadRotation implements StringRepresentable {
DEG_0("0"),
DEG_90("90"),
DEG_180("180"),
DEG_270("270");
private final String name;
PadRotation(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return this.name;
}
}
}
@@ -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) {
}
}
@@ -0,0 +1,30 @@
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.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 SpiderLilyBlock extends BushBlock {
public static final MapCodec<SpiderLilyBlock> CODEC = Block.simpleCodec(SpiderLilyBlock::new);
private static final VoxelShape SHAPE = Block.box(5.0D, 0.0D, 5.0D, 11.0D, 10.0D, 11.0D);
public SpiderLilyBlock(BlockBehaviour.Properties properties) {
super(properties);
}
@Override
public MapCodec<SpiderLilyBlock> codec() {
return CODEC;
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
}
@@ -0,0 +1,73 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.BlockBehaviour.Properties;
import net.minecraft.world.level.material.FluidState;
public class StarCoralBlock extends Block {
public static final MapCodec<Block> DEAD_CORAL_FIELD = BuiltInRegistries.BLOCK.byNameCodec().fieldOf("dead");
public static final MapCodec<StarCoralBlock> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(DEAD_CORAL_FIELD.forGetter(block -> block.deadBlock), propertiesCodec()).apply(instance, StarCoralBlock::new)
);
private final Block deadBlock;
public StarCoralBlock(Block deadBlock, Properties properties) {
super(properties);
this.deadBlock = deadBlock;
}
@Override
public MapCodec<StarCoralBlock> codec() {
return CODEC;
}
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (!this.scanForWater(level, pos)) {
level.setBlock(pos, this.deadBlock.defaultBlockState(), 2);
}
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
if (!this.scanForWater(level, pos)) {
level.scheduleTick(pos, this, 60 + level.getRandom().nextInt(40));
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
protected boolean scanForWater(BlockGetter level, BlockPos pos) {
for (Direction direction : Direction.values()) {
FluidState fluidState = level.getFluidState(pos.relative(direction));
if (fluidState.is(FluidTags.WATER)) {
return true;
}
}
return false;
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
if (!this.scanForWater(context.getLevel(), context.getClickedPos())) {
context.getLevel().scheduleTick(context.getClickedPos(), this, 60 + context.getLevel().getRandom().nextInt(40));
}
return this.defaultBlockState();
}
}
@@ -0,0 +1,60 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
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.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.BaseCoralFanBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.BlockBehaviour.Properties;
import net.minecraft.world.level.material.Fluids;
public class StarCoralFanBlock extends BaseCoralFanBlock {
public static final MapCodec<StarCoralFanBlock> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(StarCoralBlock.DEAD_CORAL_FIELD.forGetter(block -> block.deadBlock), propertiesCodec())
.apply(instance, StarCoralFanBlock::new)
);
private final Block deadBlock;
public StarCoralFanBlock(Block deadBlock, Properties properties) {
super(properties);
this.deadBlock = deadBlock;
}
@Override
public MapCodec<StarCoralFanBlock> codec() {
return CODEC;
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
this.tryScheduleDieTick(state, level, pos);
}
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (!scanForWater(state, level, pos)) {
level.setBlock(pos, this.deadBlock.defaultBlockState().setValue(WATERLOGGED, false), 2);
}
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
if (direction == Direction.DOWN && !state.canSurvive(level, pos)) {
return Blocks.AIR.defaultBlockState();
} else {
this.tryScheduleDieTick(state, level, pos);
if (state.getValue(WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
}
}
@@ -0,0 +1,69 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
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.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.BaseCoralPlantTypeBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.BlockBehaviour.Properties;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
public class StarCoralPlantBlock extends BaseCoralPlantTypeBlock {
public static final MapCodec<StarCoralPlantBlock> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(StarCoralBlock.DEAD_CORAL_FIELD.forGetter(block -> block.deadBlock), propertiesCodec())
.apply(instance, StarCoralPlantBlock::new)
);
private final Block deadBlock;
protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 15.0, 14.0);
public StarCoralPlantBlock(Block deadBlock, Properties properties) {
super(properties);
this.deadBlock = deadBlock;
}
@Override
public MapCodec<StarCoralPlantBlock> codec() {
return CODEC;
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
this.tryScheduleDieTick(state, level, pos);
}
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (!scanForWater(state, level, pos)) {
level.setBlock(pos, this.deadBlock.defaultBlockState().setValue(WATERLOGGED, false), 2);
}
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
if (direction == Direction.DOWN && !state.canSurvive(level, pos)) {
return Blocks.AIR.defaultBlockState();
} else {
this.tryScheduleDieTick(state, level, pos);
if (state.getValue(WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
}
@Override
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return SHAPE;
}
}
@@ -0,0 +1,64 @@
package com.craisinlord.antarchy.content.block;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
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.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.BaseCoralWallFanBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.BlockBehaviour.Properties;
import net.minecraft.world.level.material.Fluids;
public class StarCoralWallFanBlock extends BaseCoralWallFanBlock {
public static final MapCodec<StarCoralWallFanBlock> CODEC = RecordCodecBuilder.mapCodec(
instance -> instance.group(StarCoralBlock.DEAD_CORAL_FIELD.forGetter(block -> block.deadBlock), propertiesCodec())
.apply(instance, StarCoralWallFanBlock::new)
);
private final Block deadBlock;
public StarCoralWallFanBlock(Block deadBlock, Properties properties) {
super(properties);
this.deadBlock = deadBlock;
}
@Override
public MapCodec<StarCoralWallFanBlock> codec() {
return CODEC;
}
@Override
protected void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston) {
this.tryScheduleDieTick(state, level, pos);
}
@Override
protected void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
if (!scanForWater(state, level, pos)) {
level.setBlock(
pos,
this.deadBlock.defaultBlockState().setValue(WATERLOGGED, false).setValue(FACING, state.getValue(FACING)),
2
);
}
}
@Override
protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor level, BlockPos pos, BlockPos neighborPos) {
if (direction.getOpposite() == state.getValue(FACING) && !state.canSurvive(level, pos)) {
return Blocks.AIR.defaultBlockState();
} else {
if (state.getValue(WATERLOGGED)) {
level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
}
this.tryScheduleDieTick(state, level, pos);
return super.updateShape(state, direction, neighborState, level, pos, neighborPos);
}
}
}
@@ -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,22 @@
package com.craisinlord.antarchy.content.client.model;
import com.craisinlord.antarchy.content.entity.kraken.TentacleEntity;
import net.minecraft.resources.ResourceLocation;
import software.bernie.geckolib.model.GeoModel;
public class TentacleModel extends GeoModel<TentacleEntity> {
@Override
public ResourceLocation getModelResource(TentacleEntity animatable) {
return ResourceLocation.fromNamespaceAndPath("antarchy", "geo/tentacle.geo.json");
}
@Override
public ResourceLocation getTextureResource(TentacleEntity animatable) {
return ResourceLocation.fromNamespaceAndPath("antarchy", "textures/entity/tentacle.png");
}
@Override
public ResourceLocation getAnimationResource(TentacleEntity animatable) {
return ResourceLocation.fromNamespaceAndPath("antarchy", "animations/tenctacle.animation.json");
}
}
@@ -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.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);
}
}
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,117 @@
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 static final float ACCELERATION_SCALE = 0.0025F;
private static final int INITIAL_LIFETIME = 300;
private static final float WIND_BIG = 2.0F;
private static final boolean SWIRL = false;
private static final boolean FLOW_AWAY = true;
private static final float GRAVITY_SCALE = 1.0F;
private static final float INITIAL_Y_SPEED = 0.0F;
private float rotSpeed;
private final float spinAcceleration;
private final double xaFlowScale;
private final double zaFlowScale;
private final double swirlPeriod;
private final boolean swirl;
private final boolean flowAway;
private final SpriteSet sprites;
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.setSprite(sprites.get(level.random));
this.rotSpeed = (float) Math.toRadians(this.random.nextBoolean() ? -30.0D : 30.0D);
this.spinAcceleration = (float) Math.toRadians(this.random.nextBoolean() ? -5.0D : 5.0D);
this.swirl = SWIRL;
this.flowAway = FLOW_AWAY;
this.lifetime = INITIAL_LIFETIME;
this.gravity = GRAVITY_SCALE * 1.2F * ACCELERATION_SCALE;
float size = this.random.nextBoolean() ? 0.05F : 0.075F;
this.quadSize = size;
this.setSize(size, size);
this.friction = 1.0F;
this.yd = -INITIAL_Y_SPEED;
float randomAngle = this.random.nextFloat();
double flowAngle = Math.toRadians(randomAngle * 60.0F);
this.xaFlowScale = Math.cos(flowAngle) * WIND_BIG;
this.zaFlowScale = Math.sin(flowAngle) * WIND_BIG;
this.swirlPeriod = Math.toRadians(1000.0F + randomAngle * 3000.0F);
}
@Override
public void tick() {
this.xo = this.x;
this.yo = this.y;
this.zo = this.z;
if (this.lifetime-- <= 0) {
this.remove();
return;
}
float ageProgress = Math.min((INITIAL_LIFETIME - this.lifetime) / (float) INITIAL_LIFETIME, 1.0F);
double windX = 0.0D;
double windZ = 0.0D;
if (this.flowAway) {
double flowScale = Math.pow(ageProgress, 1.25D);
windX += this.xaFlowScale * flowScale;
windZ += this.zaFlowScale * flowScale;
}
if (this.swirl) {
double swirlScale = ageProgress * ageProgress;
windX += swirlScale * Math.cos(swirlScale * this.swirlPeriod) * WIND_BIG;
windZ += swirlScale * Math.sin(swirlScale * this.swirlPeriod) * WIND_BIG;
}
this.xd += windX * ACCELERATION_SCALE;
this.zd += windZ * ACCELERATION_SCALE;
this.yd -= this.gravity;
this.rotSpeed += this.spinAcceleration / 20.0F;
this.oRoll = this.roll;
this.roll += this.rotSpeed / 20.0F;
this.move(this.xd, this.yd, this.zd);
if (this.onGround || this.lifetime < INITIAL_LIFETIME - 1 && this.xd == 0.0D && this.zd == 0.0D) {
this.remove();
return;
}
this.xd *= this.friction;
this.yd *= this.friction;
this.zd *= this.friction;
this.setSprite(this.sprites.get(INITIAL_LIFETIME - this.lifetime, INITIAL_LIFETIME));
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_OPAQUE;
}
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,
MultiBufferSource bufferSource, @Nullable VertexConsumer buffer, float partialTick,
int packedLight, int packedOverlay) {
if (!(animatable instanceof EnchantedGoldenAppleCow)) {
if (!(animatable instanceof EnchantedGoldenAppleCow) || animatable.isBaby()) {
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,13 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.content.client.model.TentacleModel;
import com.craisinlord.antarchy.content.entity.kraken.TentacleEntity;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import software.bernie.geckolib.renderer.GeoEntityRenderer;
public class TentacleRenderer extends GeoEntityRenderer<TentacleEntity> {
public TentacleRenderer(EntityRendererProvider.Context context) {
super(context, new TentacleModel());
this.shadowRadius = 0.0F;
}
}
@@ -0,0 +1,137 @@
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();
float growth = entity.getGrowthScale();
poseStack.scale(growth, growth, growth);
// Compute body yaw directly from the entity's own tracked fields rather than trusting
// the ambient entityYaw parameter, so the body pose and the head-offset math below are
// always self-consistent (this is what caused the head to look "detached" from the body).
float bodyYaw = Mth.rotLerp(partialTick, entity.yBodyRotO, entity.yBodyRot);
poseStack.mulPose(Axis.YP.rotationDegrees(180.0F - bodyYaw));
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 rawHeadYaw = Mth.rotLerp(partialTick, entity.yHeadRotO, entity.yHeadRot) - bodyYaw;
float maxHeadYaw = entity.getMaxHeadYRot();
float netHeadYaw = Mth.clamp(Mth.wrapDegrees(rawHeadYaw), -maxHeadYaw, maxHeadYaw);
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.registries.BuiltInRegistries;
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;
@@ -42,6 +43,8 @@ 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.Items;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.pathfinder.PathType;
import net.minecraft.world.phys.Vec3;
@@ -57,6 +60,9 @@ import software.bernie.geckolib.util.GeckoLibUtil;
public class DorrieEntity extends Animal implements GeoEntity {
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 =
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.BOOLEAN);
@@ -67,12 +73,13 @@ public class DorrieEntity extends Animal implements GeoEntity {
private static final EntityDataAccessor<Integer> QUIRK_TICKS =
SynchedEntityData.defineId(DorrieEntity.class, EntityDataSerializers.INT);
private static final int MAX_CHARGE_TICKS = 40;
private static final float WATER_RIDE_SPEED = 0.78F;
private static final float LAND_BEACH_SPEED = 0.10F;
private static final int MAX_CHARGE_TICKS = 90;
private static final float WATER_RIDE_SPEED = 0.42F;
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 MIN_QUIRK_COOLDOWN_TICKS = 140;
private static final int MAX_QUIRK_COOLDOWN_TICKS = 260;
private static final int MIN_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 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 MoveControl landMoveControl;
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 BUCK_DELAY_TICKS = 60;
@@ -101,6 +115,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
private int swimForwardTicks = 0;
private int quirkCooldownTicks = MIN_QUIRK_COOLDOWN_TICKS;
private boolean usingLandNavigation = true;
private boolean pressingJump = false;
public DorrieEntity(EntityType<? extends DorrieEntity> entityType, Level 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(1, new HuntCheepGoal(this));
this.goalSelector.addGoal(2, new DolphinSwimGoal());
this.goalSelector.addGoal(3, new RandomSwimmingGoal(this, 1.35D, 8));
this.goalSelector.addGoal(4, new WaterAvoidingRandomStrollGoal(this, 0.72D, 40));
this.goalSelector.addGoal(3, new RandomSwimmingGoal(this, 1.45D, 4));
this.goalSelector.addGoal(4, new WaterAvoidingRandomStrollGoal(this, 0.9D, 10));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
}
@@ -153,6 +168,10 @@ public class DorrieEntity extends Animal implements GeoEntity {
this.entityData.set(HAS_SADDLE, saddle);
}
public SimpleContainer inventory() {
return this.inventory;
}
public boolean isTamed() {
return this.entityData.get(TAMED);
}
@@ -181,16 +200,38 @@ public class DorrieEntity extends Animal implements GeoEntity {
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putBoolean("Tamed", this.isTamed());
tag.putBoolean("HasSaddle", this.hasSaddle());
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
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.setTamed(tag.getBoolean("Tamed"));
this.setSaddle(tag.getBoolean("HasSaddle"));
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
@@ -204,38 +245,24 @@ public class DorrieEntity extends Animal implements GeoEntity {
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (this.hasSaddle() && !player.isPassengerOfSameVehicle(this)) {
if (!stack.isEmpty()) {
return super.mobInteract(player, hand);
}
if (!player.isShiftKeyDown()) {
if (!this.level().isClientSide) {
player.startRiding(this);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (player.isSecondaryUseActive()) {
this.openInventory(player);
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (this.hasSaddle() && stack.isEmpty() && !player.isPassengerOfSameVehicle(this)) {
if (!this.level().isClientSide) {
this.setSaddle(false);
this.spawnAtLocation(new ItemStack(Items.SADDLE));
this.playSound(SoundEvents.HORSE_SADDLE, 1.0F, 1.0F);
player.startRiding(this);
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
if (!this.hasSaddle() && stack.is(Items.SADDLE)) {
if (!this.level().isClientSide) {
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);
if (!stack.isEmpty() && this.isFood(stack)) {
return super.mobInteract(player, hand);
}
return super.mobInteract(player, hand);
this.openInventory(player);
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
@Override
@@ -268,7 +295,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
@Override
protected float getRiddenSpeed(Player player) {
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;
return (float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * speed;
}
@@ -300,6 +327,10 @@ public class DorrieEntity extends Animal implements GeoEntity {
this.swimForwardTicks = 0;
}
if (this.isVehicle() && this.getQuirkTicks() > 0) {
this.setQuirkTicks(0);
}
if (!this.level().isClientSide && !this.isTamed() && this.getFirstPassenger() instanceof Player rider) {
buckTimer++;
if (buckTimer >= BUCK_DELAY_TICKS) {
@@ -338,7 +369,9 @@ public class DorrieEntity extends Animal implements GeoEntity {
wasInAir = inAirNow;
}
public void setPressingJump(boolean pressing) {}
public void setPressingJump(boolean pressing) {
this.pressingJump = pressing;
}
public void startJumpCharge() {
if (!this.level().isClientSide) {
@@ -365,16 +398,22 @@ public class DorrieEntity extends Animal implements GeoEntity {
Vec3 riddenInput = this.getRiddenInput(rider, travelVector);
this.setSpeed(this.getRiddenSpeed(rider));
if (this.isLeaping) {
super.travel(travelVector);
return;
}
if (this.isInWater() || this.isInLava()) {
this.moveRelative(this.getSpeed(), new Vec3(riddenInput.x, 0.0D, riddenInput.z));
Vec3 motion = this.getDeltaMovement();
double vertical = 0.0D;
if (!this.isLeaping) {
if (this.isUnderWater()) {
vertical = riddenInput.y * 0.025D;
} else if (riddenInput.y < 0.0D) {
vertical = riddenInput.y * 0.02D;
}
if (this.isUnderWater()) {
vertical = riddenInput.y * 0.025D;
} else if (riddenInput.y < 0.0D) {
vertical = riddenInput.y * 0.02D;
}
if (this.pressingJump) {
vertical += ASCEND_SPEED;
}
this.setDeltaMovement(
motion.x * 0.92D,
@@ -396,7 +435,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
Vec3 look = this.getLookAngle();
Vec3 current = this.getDeltaMovement();
double forwardAdd = 0.18D + power * 0.42D;
double vertical = 0.28D + power * 0.62D;
double vertical = 0.85D + power * 1.35D;
this.setDeltaMovement(
current.x + look.x * forwardAdd,
current.y + vertical,
@@ -429,9 +468,34 @@ public class DorrieEntity extends Animal implements GeoEntity {
@Override
protected void dropEquipment() {
super.dropEquipment();
if (this.hasSaddle()) {
this.spawnAtLocation(new ItemStack(Items.SADDLE));
if (this.level().isClientSide) {
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
@@ -567,7 +631,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
private boolean canUseQuirkAnimation() {
return this.isInWater()
&& !this.isUnderWater()
&& this.getControllingPassenger() == null
&& !this.isVehicle()
&& !this.isLeaping
&& this.landAnimTicks <= 0
&& !this.isCharging
@@ -653,7 +717,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
private class DolphinSwimGoal extends Goal {
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_MAX_TICKS = 110;
private static final int ROAM_MIN_TICKS = 70;
@@ -691,7 +755,7 @@ public class DorrieEntity extends Animal implements GeoEntity {
this.cooldownTicks--;
return false;
}
if (DorrieEntity.this.random.nextInt(6) != 0) {
if (DorrieEntity.this.random.nextInt(3) != 0) {
return false;
}
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);
}
}
}
@@ -86,6 +86,11 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
// Kraken spawn flag
private boolean spawnedByKraken;
// Explosive impact (fired from the R.P.O. Launcher)
private boolean explosiveImpact;
private java.util.UUID explosiveLauncherId;
private int explosiveArmTicks;
public OctopusBombEntity(EntityType<? extends Monster> entityType, Level level) {
super(entityType, level);
this.moveControl = new SmoothSwimmingMoveControl(this, 80, 8, 0.06F, 0.12F, true);
@@ -189,6 +194,14 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
super.tick();
this.setNoGravity(this.isInWater() || this.canFly());
if (this.explosiveImpact && !this.level().isClientSide) {
this.explosiveArmTicks++;
if (this.explosiveArmTicks > 3 && (this.horizontalCollision || this.verticalCollision || this.isInWater() || this.hasHitExplosiveTarget())) {
this.detonateInkExplosion();
return;
}
}
if (this.level().isClientSide) {
this.updateSwimRotation();
return;
@@ -326,6 +339,13 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
this.setDeltaMovement(velocity);
}
public void launchAsExplosiveProjectile(net.minecraft.world.entity.Entity launcher, Vec3 velocity) {
this.explosiveImpact = true;
this.explosiveLauncherId = launcher != null ? launcher.getUUID() : null;
this.setDeltaMovement(velocity);
this.hasImpulse = true;
}
// -------------------------------------------------------------------------
// Squid-style wander
// -------------------------------------------------------------------------
@@ -352,6 +372,44 @@ public class OctopusBombEntity extends Monster implements GeoEntity {
// Ink spray
// -------------------------------------------------------------------------
private boolean hasHitExplosiveTarget() {
AABB box = this.getBoundingBox();
return !this.level().getEntitiesOfClass(LivingEntity.class, box,
e -> e.isAlive() && e != this && !e.getUUID().equals(this.explosiveLauncherId)).isEmpty();
}
private void detonateInkExplosion() {
if (!(this.level() instanceof ServerLevel serverLevel)) {
return;
}
double radius = com.craisinlord.antarchy.config.AntarchySettings.rpoLauncherExplosionRadius();
float damage = (float) com.craisinlord.antarchy.config.AntarchySettings.rpoLauncherExplosionDamage();
AABB blastArea = this.getBoundingBox().inflate(radius);
for (LivingEntity victim : serverLevel.getEntitiesOfClass(LivingEntity.class, blastArea,
e -> e.isAlive() && !e.isSpectator())) {
double distance = Math.sqrt(victim.distanceToSqr(this));
float falloff = (float) Mth.clamp(1.0D - distance / radius, 0.0D, 1.0D);
if (falloff <= 0.0F) {
continue;
}
victim.hurt(this.damageSources().explosion(this, this), damage * falloff);
Vec3 knockback = victim.position().subtract(this.position());
if (knockback.lengthSqr() > 1.0E-4D) {
Vec3 normalized = knockback.normalize().scale(0.6D * falloff);
victim.push(normalized.x, 0.4D * falloff, normalized.z);
}
victim.hurtMarked = true;
}
serverLevel.playSound(null, this.getX(), this.getY(), this.getZ(),
net.minecraft.sounds.SoundEvents.GENERIC_EXPLODE, net.minecraft.sounds.SoundSource.HOSTILE, 2.0F, 0.9F);
serverLevel.sendParticles(ParticleTypes.SQUID_INK, this.getX(), this.getY(), this.getZ(), 60, radius * 0.4D, radius * 0.4D, radius * 0.4D, 0.05D);
serverLevel.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(), this.getZ(), 3, 0.5D, 0.5D, 0.5D, 0.0D);
this.discard();
}
private void tickInkSpray() {
if (this.inkSprayCooldown > 0 || !(this.level() instanceof ServerLevel serverLevel)) return;
@@ -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
? this.level().getPlayerByUUID(this.shoulderPlayerId)
: null;
if (shoulderPlayer == null || !shoulderPlayer.isAlive()) {
if (shoulderPlayer == null || !shoulderPlayer.isAlive() || shoulderPlayer.isSpectator()) {
this.dismountShoulder();
} else {
this.tickClimbingToShoulder(shoulderPlayer);
@@ -381,7 +381,7 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
if (this.isOnShoulder()) {
Player shoulderPlayer = this.getShoulderPlayer();
if (shoulderPlayer == null || !shoulderPlayer.isAlive()) {
if (shoulderPlayer == null || !shoulderPlayer.isAlive() || shoulderPlayer.isSpectator()) {
this.dismountShoulder();
} else if (this.shoulderSneakGraceTicks <= 0 && shoulderPlayer.isShiftKeyDown()) {
this.glideOffShoulder();
@@ -555,6 +555,10 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
return false;
}
if (player.isSpectator()) {
return true;
}
if (this.isOnShoulder()) {
if (!this.level().isClientSide) {
this.glideOffShoulder();
@@ -629,6 +633,12 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
this.pickupAnimTicks = 0;
return finalized;
}
@Override
public boolean shouldBeSaved() {
return super.shouldBeSaved() && !this.isOnShoulder() && !this.isClimbingToShoulder();
}
@Override
protected SoundEvent getAmbientSound() {
return AntarchySoundEvents.FLYING_SQUIRREL_IDLE.get();
@@ -731,6 +741,9 @@ public class FlyingSquirrelEntity extends TamableAnimal implements GeoEntity {
}
private void startShoulderMountSequence(Player player) {
if (player.isSpectator()) {
return;
}
this.shoulderPlayerId = player.getUUID();
this.shoulderClimbStartPos = this.position();
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 = 1.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 * 2;
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.isOwnedBy(player)) || 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,152 @@
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 final float SHOCKWAVE_DAMAGE = 8.0F;
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)) {
if (nearby instanceof Enemy) {
living.hurt(owner.damageSources().mobAttack(entity), SHOCKWAVE_DAMAGE);
}
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,619 @@
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 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> FUNGAL_OURANWOOD_FOREST = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "fungal_ouranwood_forest")
);
private static final ResourceKey<Biome> ELYTHIA_MEADOW = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "elythia_meadow")
);
private static final ResourceKey<Biome> PEACH_FOREST = ResourceKey.create(
Registries.BIOME,
ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "peach_forest")
);
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 void setAbilityCooldown(int ticks) {
this.entityData.set(ABILITY_COOLDOWN, Math.max(0, ticks));
}
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(), this.entityData.get(ABILITY_COOLDOWN));
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)];
}
if (biome.is(OURANWOOD_FOREST) || biome.is(SPARSE_OURANWOOD_FOREST) || biome.is(FUNGAL_OURANWOOD_FOREST)) {
GlimmerVariant[] forestVariants = new GlimmerVariant[] {
GlimmerVariant.FROG,
GlimmerVariant.OURANWOOD_DEER
};
return forestVariants[this.random.nextInt(forestVariants.length)];
}
if (biome.is(ELYTHIA_MEADOW)) {
return GlimmerVariant.APPLE_COW;
}
if (biome.is(PEACH_FOREST)) {
return GlimmerVariant.ELKA;
}
return GlimmerVariant.random(this.random);
}
/**
* Wild Glimmers are common in Glimmering Pools, but only rarely (and only at night)
* turn up in Ouranwood Forest, the Elythia Meadow, and the Peach Forest.
*/
public static boolean checkGlimmerSpawnRules(EntityType<GlimmerEntity> type, ServerLevelAccessor level,
MobSpawnType spawnType, BlockPos pos, net.minecraft.util.RandomSource random) {
if (!net.minecraft.world.entity.animal.Animal.checkAnimalSpawnRules(type, level, spawnType, pos, random)) {
return false;
}
Holder<Biome> biome = level.getBiome(pos);
if (biome.is(GLIMMERING_POOLS)) {
return true;
}
return level.getLevel().isNight();
}
@Override
public void tick() {
super.tick();
this.clampBodyToHeadRotation();
GlimmerParticles.tickAmbient(this, this.getDeltaMovement().horizontalDistanceSqr() > 0.01D);
if (!this.level().isClientSide) {
int fadeTicks = this.entityData.get(FADE_TICKS);
if (this.isTame() || this.level().getBiome(this.blockPosition()).is(GLIMMERING_POOLS)) {
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);
}
}
/**
* With {@code noPhysics} true, Glimmers never travel through vanilla's normal movement path,
* so the usual body-follows-head catch-up never runs and a stationary Glimmer looking around
* (LookAtPlayerGoal/RandomLookAroundGoal only move the head) ends up with its head rendered
* far past the model's body — this keeps the body snapped within {@link #getMaxHeadYRot()} of it.
*/
private void clampBodyToHeadRotation() {
float maxYaw = this.getMaxHeadYRot();
float delta = Mth.wrapDegrees(this.yHeadRot - this.yBodyRot);
if (Math.abs(delta) > maxYaw) {
this.yBodyRot = Mth.wrapDegrees(this.yHeadRot - Math.copySign(maxYaw, delta));
}
}
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,229 @@
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;
import net.minecraft.world.phys.Vec3;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
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 final int TONGUE_ANIM_TICKS = 10;
private static final int TONGUE_GRAB_TICK = 6;
private final Map<UUID, PendingEat> pendingEats = new HashMap<>();
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) {
if (this.pendingEats.containsKey(entity.getUUID())) {
return;
}
LivingEntity target = findEdibleTarget(entity, owner);
if (target != null) {
this.startEat(entity, owner, target);
}
}
@Override
public void customTick(GlimmerEntity entity) {
PendingEat pending = this.pendingEats.get(entity.getUUID());
if (pending == null) {
return;
}
if (!pending.target.isAlive() || entity.distanceToSqr(pending.target) > EAT_RANGE * EAT_RANGE * 4.0D) {
this.pendingEats.remove(entity.getUUID());
return;
}
pending.ticksRemaining--;
if (pending.ticksRemaining == TONGUE_ANIM_TICKS - TONGUE_GRAB_TICK) {
// The tongue "catches" the target partway through the animation and reels it in.
entity.playSound(SoundEvents.FROG_TONGUE, 1.0F, 1.0F);
}
if (pending.ticksRemaining <= TONGUE_ANIM_TICKS - TONGUE_GRAB_TICK) {
Vec3 mouth = entity.position().add(0.0D, entity.getBbHeight() * 0.5D, 0.0D);
Vec3 pulled = pending.target.position().lerp(mouth, 0.55D);
pending.target.teleportTo(pulled.x, pulled.y, pulled.z);
}
if (pending.ticksRemaining <= 0) {
this.pendingEats.remove(entity.getUUID());
this.finishEat(entity, pending.owner, pending.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 startEat(GlimmerEntity entity, Player owner, LivingEntity target) {
PendingEat pending = new PendingEat();
pending.target = target;
pending.owner = owner;
pending.ticksRemaining = TONGUE_ANIM_TICKS;
this.pendingEats.put(entity.getUUID(), pending);
entity.playAbilityAnimation(TONGUE_ANIM_TICKS);
entity.playSound(SoundEvents.FROG_LONG_JUMP, 0.8F, 1.4F);
entity.startAbilityCooldown();
}
private void finishEat(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.remove(Entity.RemovalReason.DISCARDED);
this.sendAbilityMessage(owner, Component.translatable("entity.antarchy.glimmer.ability.frog", targetName));
}
private static final class PendingEat {
LivingEntity target;
Player owner;
int ticksRemaining;
}
}
@@ -0,0 +1,216 @@
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;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
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_HIT_RADIUS = 1.5D;
private static final double CHARGE_SPEED = 1.4D;
private static final int CHARGE_DURATION_TICKS = 14;
private static final float CHARGE_DAMAGE = 14.0F;
private static final double CHARGE_KNOCKBACK = 1.6D;
private static final int CHARGE_ANIM_TICKS = 45;
private final Map<UUID, ChargeState> activeCharges = new HashMap<>();
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.startCharge(entity, owner, bestTarget);
}
}
private void startCharge(GlimmerEntity entity, Player owner, LivingEntity target) {
Vec3 from = entity.position();
Vec3 direction = target.position().subtract(from);
double horizontalDistance = Math.max(1.0D, direction.horizontalDistance());
ChargeState state = new ChargeState();
state.dirX = direction.x / horizontalDistance;
state.dirZ = direction.z / horizontalDistance;
state.ticksRemaining = CHARGE_DURATION_TICKS;
state.owner = owner;
this.activeCharges.put(entity.getUUID(), state);
entity.playAbilityAnimation(CHARGE_ANIM_TICKS);
entity.playSound(SoundEvents.RAVAGER_ROAR, 1.0F, 1.3F);
this.sendAbilityMessage(owner, Component.translatable("entity.antarchy.glimmer.ability.deer"));
entity.startAbilityCooldown();
}
@Override
public void customTick(GlimmerEntity entity) {
ChargeState state = this.activeCharges.get(entity.getUUID());
if (state == null) {
return;
}
if (!entity.isAlive() || state.ticksRemaining <= 0) {
this.activeCharges.remove(entity.getUUID());
return;
}
state.ticksRemaining--;
entity.setDeltaMovement(state.dirX * CHARGE_SPEED, entity.getDeltaMovement().y, state.dirZ * CHARGE_SPEED);
AABB hitBox = entity.getBoundingBox().inflate(CHARGE_HIT_RADIUS);
for (Entity candidate : entity.level().getEntities(entity, hitBox)) {
if (!(candidate instanceof Enemy) || !(candidate instanceof LivingEntity living) || !living.isAlive()
|| state.alreadyHit.contains(candidate.getUUID())) {
continue;
}
living.hurt(state.owner.damageSources().mobAttack(entity), CHARGE_DAMAGE);
Vec3 away = living.position().subtract(entity.position());
double awayDistance = Math.max(0.1D, away.horizontalDistance());
living.knockback(CHARGE_KNOCKBACK, -away.x / awayDistance, -away.z / awayDistance);
state.alreadyHit.add(candidate.getUUID());
}
}
private static final class ChargeState {
double dirX;
double dirZ;
int ticksRemaining;
Player owner;
final Set<UUID> alreadyHit = new HashSet<>();
}
}
@@ -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_SWING_LEFT = 2;
private static final int ATTACK_SWING_RIGHT = 3;
private static final int ATTACK_SLAM = 4;
// Synced animation state values
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_SWING_LEFT = 3;
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;
@@ -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 SWING_LEFT_ANIM = RawAnimation.begin().thenPlay("swing_left");
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 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 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_SWING_LEFT -> state.setAndContinue(SWING_LEFT_ANIM);
case ANIM_SWING_RIGHT -> state.setAndContinue(SWING_RIGHT_ANIM);
case ANIM_SLAM -> state.setAndContinue(SLAM_ANIM);
case ANIM_DEATH -> state.setAndContinue(DEATH_ANIM);
default -> PlayState.STOP;
};
@@ -551,6 +557,7 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
case ATTACK_GRAB -> this.tickGrabAttack(target);
case ATTACK_SWING_LEFT -> this.tickSwingAttack(false);
case ATTACK_SWING_RIGHT -> this.tickSwingAttack(true);
case ATTACK_SLAM -> this.tickSlamAttack();
default -> this.finishAttack();
}
}
@@ -562,6 +569,9 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
if (attackState == ATTACK_GRAB) {
this.actionTicks = 55;
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) {
this.actionTicks = 40;
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() {
int finished = this.getAttackState();
this.setAttackState(ATTACK_NONE);
@@ -652,14 +672,16 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
double distSqr = this.distanceToSqr(target);
boolean canGrab = distSqr <= 64.0D;
boolean canSwing = distSqr <= 81.0D;
boolean canSlam = distSqr <= 121.0D;
if (!canGrab && !canSwing) {
if (!canGrab && !canSwing && !canSlam) {
return ATTACK_NONE;
}
int grabWeight = canGrab ? (this.lastAttackState == ATTACK_GRAB ? 1 : 4) : 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) {
return ATTACK_NONE;
}
@@ -668,6 +690,10 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
if (roll < grabWeight) {
return ATTACK_GRAB;
}
roll -= grabWeight;
if (roll < slamWeight) {
return ATTACK_SLAM;
}
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;
}
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.
// 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).
@@ -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) {
Vec3 anchor = this.position()
.add(this.getViewVector(1.0F).normalize().scale(this.getBbWidth() * 0.65D))
.add(0.0D, this.getBbHeight() * 0.28D, 0.0D);
Vec3 anchor = this.getGrabAnchorWorldPos();
target.setDeltaMovement(anchor.subtract(target.position()).scale(0.35D));
target.resetFallDistance();
if (target instanceof ServerPlayer serverPlayer) {
@@ -721,6 +769,33 @@ public class KrakenEntity extends Monster implements GeoEntity, MultipartEntityO
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) {
Vec3 direction = target.position().subtract(this.position());
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);
return;
}
case ATTACK_SLAM -> {
this.setAnimationState(ANIM_SLAM);
return;
}
default -> {
}
}
@@ -0,0 +1,109 @@
package com.craisinlord.antarchy.content.entity.kraken;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.item.KrakensGraspItem;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.entity.projectile.ItemSupplier;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.Vec3;
public class KrakensGraspThrownTrident extends AbstractArrow implements ItemSupplier {
private boolean dealtDamage;
private boolean returning;
public KrakensGraspThrownTrident(EntityType<? extends KrakensGraspThrownTrident> entityType, Level level) {
super(entityType, level);
}
public KrakensGraspThrownTrident(EntityType<? extends KrakensGraspThrownTrident> entityType, LivingEntity owner, Level level, ItemStack stack) {
super(entityType, owner, level, stack, null);
this.setBaseDamage(AntarchySettings.krakensGraspThrownDamage());
}
@Override
public ItemStack getItem() {
return new ItemStack(AntarchyObjects.KRAKENS_GRASP.get());
}
@Override
protected ItemStack getDefaultPickupItem() {
return new ItemStack(AntarchyObjects.KRAKENS_GRASP.get());
}
@Override
protected net.minecraft.sounds.SoundEvent getDefaultHitGroundSoundEvent() {
return SoundEvents.TRIDENT_HIT_GROUND;
}
@Override
protected double getDefaultGravity() {
return 0.05D;
}
@Override
public void tick() {
if (this.inGroundTime > 4) {
this.dealtDamage = true;
}
Entity owner = this.getOwner();
int loyalty = this.effectiveLoyaltyLevel();
if (loyalty > 0 && (this.dealtDamage || this.isNoPhysics()) && owner instanceof LivingEntity livingOwner && livingOwner.isAlive()) {
this.setNoPhysics(true);
Vec3 toOwner = owner.getEyePosition().subtract(this.position());
this.setPosRaw(this.getX(), this.getY() + toOwner.y * 0.015D * loyalty, this.getZ());
if (this.level().isClientSide) {
this.yOld = this.getY();
}
double speed = 0.05D * loyalty;
this.setDeltaMovement(this.getDeltaMovement().scale(0.95D).add(toOwner.normalize().scale(speed)));
if (!this.returning) {
this.returning = true;
this.playSound(SoundEvents.TRIDENT_RETURN, 10.0F, 1.0F);
}
}
super.tick();
}
private int effectiveLoyaltyLevel() {
return AntarchySettings.krakensGraspInnateLoyalty() ? Mth.clamp(AntarchySettings.krakensGraspInnateLoyaltyLevel(), 1, 3) : 0;
}
@Override
protected void onHitEntity(EntityHitResult result) {
super.onHitEntity(result);
if (!(result.getEntity() instanceof LivingEntity target) || !(this.level() instanceof ServerLevel serverLevel)) {
return;
}
Entity owner = this.getOwner();
DamageSource damageSource = this.damageSources().trident(this, owner != null ? owner : this);
boolean hurt = target.hurt(damageSource, (float) AntarchySettings.krakensGraspThrownDamage());
if (hurt) {
KrakensGraspItem.strikeLightning(target, serverLevel);
}
this.dealtDamage = true;
}
@Override
protected void onHitBlock(BlockHitResult result) {
super.onHitBlock(result);
if (this.level() instanceof ServerLevel serverLevel) {
TentacleEntity.spawnAt(serverLevel, result.getLocation(), this.getOwner() instanceof LivingEntity livingOwner ? livingOwner : null);
}
}
}
@@ -0,0 +1,159 @@
package com.craisinlord.antarchy.content.entity.kraken;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
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;
import java.util.List;
import java.util.UUID;
public class TentacleEntity extends Mob implements GeoEntity {
private static final RawAnimation GRASP_ANIM = RawAnimation.begin().thenLoop("animation");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
private int lifetimeTicks;
private UUID ownerId;
public TentacleEntity(EntityType<? extends TentacleEntity> entityType, Level level) {
super(entityType, level);
this.setNoAi(true);
this.noPhysics = true;
this.noCulling = true;
this.xpReward = 0;
this.setInvulnerable(true);
}
@Override
public boolean isInvulnerableTo(DamageSource damageSource) {
return true;
}
@Override
public void push(net.minecraft.world.entity.Entity entity) {
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes()
.add(Attributes.MAX_HEALTH, 20.0D)
.add(Attributes.MOVEMENT_SPEED, 0.0D)
.add(Attributes.KNOCKBACK_RESISTANCE, 1.0D);
}
public static void spawnAt(ServerLevel level, Vec3 pos, LivingEntity owner) {
TentacleEntity tentacle = new TentacleEntity(com.craisinlord.antarchy.content.AntarchyObjects.TENTACLE.get(), level);
tentacle.moveTo(pos.x, pos.y, pos.z, 0.0F, 0.0F);
if (owner != null) {
tentacle.ownerId = owner.getUUID();
}
level.addFreshEntity(tentacle);
level.playSound(null, pos.x, pos.y, pos.z, AntarchySoundEvents.KRAKEN_ATTACK.get(), SoundSource.HOSTILE, 1.0F, 1.0F);
}
@Override
protected void registerGoals() {
}
@Override
protected PathNavigation createNavigation(Level level) {
return new net.minecraft.world.entity.ai.navigation.GroundPathNavigation(this, level);
}
@Override
public boolean isPushable() {
return false;
}
@Override
public boolean canBeCollidedWith() {
return false;
}
@Override
protected boolean shouldDespawnInPeaceful() {
return false;
}
@Override
public boolean removeWhenFarAway(double distanceToClosestPlayer) {
return false;
}
@Override
public void tick() {
super.tick();
if (this.level().isClientSide) {
return;
}
this.lifetimeTicks++;
if (this.lifetimeTicks >= AntarchySettings.krakensGraspTentacleDurationTicks()) {
this.discard();
return;
}
int interval = Math.max(1, AntarchySettings.krakensGraspTentacleDamageIntervalTicks());
if (this.lifetimeTicks % interval == 0) {
this.grabNearbyEntities();
}
}
private void grabNearbyEntities() {
double radius = AntarchySettings.krakensGraspTentacleRadius();
AABB area = this.getBoundingBox().inflate(radius, radius * 0.6D, radius);
List<LivingEntity> victims = this.level().getEntitiesOfClass(LivingEntity.class, area,
entity -> entity.isAlive() && !entity.isSpectator() && !entity.getUUID().equals(this.ownerId)
&& !(entity instanceof Player player && player.isCreative()));
for (LivingEntity victim : victims) {
if (this.level() instanceof ServerLevel serverLevel) {
victim.hurt(this.damageSources().mobAttack(this), (float) AntarchySettings.krakensGraspTentacleDamage());
serverLevel.sendParticles(net.minecraft.core.particles.ParticleTypes.SPLASH,
victim.getX(), victim.getY(0.6D), victim.getZ(), 8, 0.2D, 0.2D, 0.2D, 0.05D);
}
victim.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, AntarchySettings.krakensGraspTentacleDamageIntervalTicks() + 10, 1, false, true, true));
Vec3 pull = this.position().subtract(victim.position()).multiply(1.0D, 0.0D, 1.0D);
if (pull.lengthSqr() > 1.0E-4D) {
victim.setDeltaMovement(victim.getDeltaMovement().add(pull.normalize().scale(0.12D)));
victim.hurtMarked = true;
}
}
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "grasp_controller", 0, this::graspAnimController));
}
private PlayState graspAnimController(AnimationState<TentacleEntity> state) {
return state.setAndContinue(GRASP_ANIM);
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
}
@@ -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,101 @@
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.core.component.DataComponents;
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.item.component.CustomData;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
public class GlimmerBottleItem extends Item {
private static final String ABILITY_COOLDOWN_TAG = "AbilityCooldown";
public GlimmerBottleItem() {
super(new Item.Properties().stacksTo(1));
}
public static ItemStack create(GlimmerVariant variant) {
return create(variant, 0);
}
public static ItemStack create(GlimmerVariant variant, int abilityCooldown) {
ItemStack stack = new ItemStack(AntarchyObjects.GLIMMER_BOTTLE.get());
stack.set(AntarchyObjects.GLIMMER_VARIANT_COMPONENT.get(), variant);
if (abilityCooldown > 0) {
CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putInt(ABILITY_COOLDOWN_TAG, abilityCooldown));
}
return stack;
}
@Nullable
public static GlimmerVariant getVariant(ItemStack stack) {
return stack.get(AntarchyObjects.GLIMMER_VARIANT_COMPONENT.get());
}
public static int getStoredAbilityCooldown(ItemStack stack) {
return stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag().getInt(ABILITY_COOLDOWN_TAG);
}
@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);
glimmer.setAbilityCooldown(getStoredAbilityCooldown(stack));
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,171 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer;
import com.craisinlord.antarchy.content.entity.kraken.KrakensGraspThrownTrident;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.stats.Stats;
import net.minecraft.world.entity.EquipmentSlotGroup;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.item.TridentItem;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.item.enchantment.ItemEnchantments;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.Entity;
import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.animatable.client.GeoRenderProvider;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.util.GeckoLibUtil;
import java.util.function.Consumer;
public class KrakensGraspItem extends TridentItem implements GeoItem {
private static final ResourceLocation MODEL_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "geo/krakens_grasp.geo.json");
private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/item/krakens_grasp/krakens_grasp.png");
private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/static_item.animation.json");
private static final ResourceLocation ATTACK_DAMAGE_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "krakens_grasp_attack_damage");
private static final ResourceLocation ATTACK_SPEED_MODIFIER_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "krakens_grasp_attack_speed");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public KrakensGraspItem(Item.Properties properties) {
super(properties);
GeoItem.registerSyncedAnimatable(this);
}
@Override
public ItemAttributeModifiers getDefaultAttributeModifiers() {
return ItemAttributeModifiers.builder()
.add(Attributes.ATTACK_DAMAGE, new AttributeModifier(ATTACK_DAMAGE_MODIFIER_ID, AntarchySettings.krakensGraspAttackDamage(), AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND)
.add(Attributes.ATTACK_SPEED, new AttributeModifier(ATTACK_SPEED_MODIFIER_ID, AntarchySettings.krakensGraspAttackSpeed(), AttributeModifier.Operation.ADD_VALUE), EquipmentSlotGroup.MAINHAND)
.build();
}
@Override
public net.minecraft.world.item.UseAnim getUseAnimation(ItemStack stack) {
return net.minecraft.world.item.UseAnim.NONE;
}
@Override
public void inventoryTick(ItemStack stack, Level level, Entity entity, int slotId, boolean isSelected) {
super.inventoryTick(stack, level, entity, slotId, isSelected);
if (!level.isClientSide) {
ensureInnateLoyalty(stack, level.registryAccess());
}
}
private static void ensureInnateLoyalty(ItemStack stack, HolderLookup.Provider registries) {
if (!AntarchySettings.krakensGraspInnateLoyalty()) {
return;
}
var loyalty = registries.lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(Enchantments.LOYALTY);
int desiredLevel = net.minecraft.util.Mth.clamp(AntarchySettings.krakensGraspInnateLoyaltyLevel(), 1, 3);
if (EnchantmentHelper.getItemEnchantmentLevel(loyalty, stack) >= desiredLevel) {
return;
}
ItemEnchantments.Mutable enchantments = new ItemEnchantments.Mutable(stack.getEnchantments());
enchantments.set(loyalty, desiredLevel);
EnchantmentHelper.setEnchantments(stack, enchantments.toImmutable().withTooltip(true));
}
@Override
public boolean hurtEnemy(ItemStack stack, LivingEntity target, LivingEntity attacker) {
boolean didHurt = super.hurtEnemy(stack, target, attacker);
if (didHurt && attacker.level() instanceof ServerLevel serverLevel) {
strikeLightning(target, serverLevel);
}
return didHurt;
}
public static void strikeLightning(LivingEntity target, ServerLevel level) {
LightningBolt lightningBolt = new LightningBolt(net.minecraft.world.entity.EntityType.LIGHTNING_BOLT, level);
lightningBolt.moveTo(target.getX(), target.getY(), target.getZ());
lightningBolt.setVisualOnly(true);
level.addFreshEntity(lightningBolt);
target.hurt(target.damageSources().lightningBolt(), (float) AntarchySettings.krakensGraspLightningDamage());
level.sendParticles(ParticleTypes.ELECTRIC_SPARK, target.getX(), target.getY(0.7D), target.getZ(), 10, 0.25D, 0.25D, 0.25D, 0.02D);
}
@Override
public void releaseUsing(ItemStack stack, Level level, LivingEntity user, int timeLeft) {
if (!(user instanceof Player player)) {
return;
}
int useTime = this.getUseDuration(stack, user) - timeLeft;
if (useTime < 10) {
return;
}
float spinStrength = EnchantmentHelper.getTridentSpinAttackStrength(stack, player);
if (spinStrength > 0.0F && player.isInWaterOrRain()) {
super.releaseUsing(stack, level, user, timeLeft);
return;
}
if (stack.getDamageValue() >= stack.getMaxDamage() - 1) {
return;
}
if (!level.isClientSide) {
stack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(user.getUsedItemHand()));
KrakensGraspThrownTrident trident = new KrakensGraspThrownTrident(AntarchyObjects.KRAKENS_GRASP_TRIDENT.get(), player, level, stack.copy());
trident.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, 2.5F, 1.0F);
if (player.hasInfiniteMaterials()) {
trident.pickup = net.minecraft.world.entity.projectile.AbstractArrow.Pickup.CREATIVE_ONLY;
}
level.addFreshEntity(trident);
level.playSound(null, trident, SoundEvents.TRIDENT_THROW.value(), SoundSource.PLAYERS, 1.0F, 1.0F);
if (!player.hasInfiniteMaterials()) {
player.getInventory().removeItem(stack);
}
}
player.awardStat(Stats.ITEM_USED.get(this));
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
@Override
public void createGeoRenderer(Consumer<GeoRenderProvider> consumer) {
consumer.accept(new GeoRenderProvider() {
private AnimatedHeldItemRenderer<KrakensGraspItem> renderer;
@Override
public net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer getGeoItemRenderer() {
if (this.renderer == null) {
this.renderer = new AnimatedHeldItemRenderer<>(new ResourceBackedGeoItemModel<>(MODEL_LOCATION, TEXTURE_LOCATION, ANIMATION_LOCATION));
}
return this.renderer;
}
});
}
}
@@ -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,194 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.AntarchySoundEvents;
import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer;
import com.craisinlord.antarchy.content.entity.OctopusBombEntity;
import java.util.function.Consumer;
import java.util.function.Predicate;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.ProjectileWeaponItem;
import net.minecraft.world.item.UseAnim;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.item.enchantment.ItemEnchantments;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.animatable.client.GeoRenderProvider;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.animation.AnimatableManager;
import software.bernie.geckolib.animation.AnimationController;
import software.bernie.geckolib.animation.PlayState;
import software.bernie.geckolib.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;
public class RpoLauncherItem extends ProjectileWeaponItem implements GeoItem {
private static final String FIRE_CONTROLLER = "fire_controller";
private static final String FIRE_ANIMATION = "rpo_launcher_recoil";
private static final Predicate<ItemStack> NO_PROJECTILES = stack -> false;
private static final ResourceLocation MODEL_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "geo/rpo_launcher.geo.json");
private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/models/item/rpo_launcher.png");
private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/rpo_launcher.animation.json");
private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
public RpoLauncherItem(Properties properties) {
super(properties);
GeoItem.registerSyncedAnimatable(this);
}
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand usedHand) {
ItemStack itemStack = player.getItemInHand(usedHand);
if (player.getCooldowns().isOnCooldown(this)) {
return InteractionResultHolder.fail(itemStack);
}
if (!level.isClientSide()) {
fireOctopusBomb(level, player, 0.0F);
if (hasMultishot(itemStack)) {
fireOctopusBomb(level, player, -10.0F);
fireOctopusBomb(level, player, 10.0F);
}
spawnFireParticles((ServerLevel) level, player);
itemStack.hurtAndBreak(1, player, usedHand == InteractionHand.MAIN_HAND ? EquipmentSlot.MAINHAND : EquipmentSlot.OFFHAND);
triggerFireAnimation((ServerLevel) level, player, itemStack);
}
level.playSound(
null,
player.getX(),
player.getY(),
player.getZ(),
AntarchySoundEvents.SQUIDZOOKA_FIRE.get(),
SoundSource.PLAYERS,
1.15F,
1.0F / (level.getRandom().nextFloat() * 0.4F + 1.2F) + 0.5F
);
player.awardStat(Stats.ITEM_USED.get(this));
double cooldownSeconds = AntarchySettings.rpoLauncherCooldownSeconds();
if (cooldownSeconds > 0) {
player.getCooldowns().addCooldown(this, (int) (cooldownSeconds * 20));
}
return InteractionResultHolder.consume(itemStack);
}
@Override
public UseAnim getUseAnimation(ItemStack stack) {
return UseAnim.NONE;
}
@Override
public Predicate<ItemStack> getSupportedHeldProjectiles() {
return NO_PROJECTILES;
}
@Override
public Predicate<ItemStack> getAllSupportedProjectiles() {
return NO_PROJECTILES;
}
@Override
public int getDefaultProjectileRange() {
return 0;
}
@Override
protected void shootProjectile(LivingEntity livingEntity, Projectile projectile, int i, float velocity, float inaccuracy, float angle, LivingEntity target) {
projectile.shootFromRotation(livingEntity, livingEntity.getXRot(), livingEntity.getYRot(), angle, velocity, inaccuracy);
}
@Override
public boolean isEnchantable(ItemStack stack) {
return stack.getCount() == 1;
}
@Override
public int getEnchantmentValue() {
return 1;
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, FIRE_CONTROLLER, state -> PlayState.STOP)
.triggerableAnim(FIRE_ANIMATION, RawAnimation.begin().thenPlay(FIRE_ANIMATION)));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.geoCache;
}
@Override
public void createGeoRenderer(Consumer<GeoRenderProvider> consumer) {
consumer.accept(new GeoRenderProvider() {
private AnimatedHeldItemRenderer<RpoLauncherItem> renderer;
@Override
public net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer getGeoItemRenderer() {
if (this.renderer == null) {
this.renderer = new AnimatedHeldItemRenderer<>(new ResourceBackedGeoItemModel<>(MODEL_LOCATION, TEXTURE_LOCATION, ANIMATION_LOCATION));
}
return this.renderer;
}
});
}
private void triggerFireAnimation(ServerLevel level, LivingEntity livingEntity, ItemStack stack) {
long animatableId = GeoItem.getOrAssignId(stack, level);
triggerAnim(livingEntity, animatableId, FIRE_CONTROLLER, FIRE_ANIMATION);
}
private static void fireOctopusBomb(Level level, Player player, float yawOffset) {
OctopusBombEntity projectileBomb = AntarchyObjects.OCTOPUS_BOMB.get().create(level);
if (projectileBomb == null) {
return;
}
float yaw = player.getYRot() + yawOffset;
Vec3 direction = Vec3.directionFromRotation(new Vec2(player.getXRot(), yaw));
projectileBomb.moveTo(
player.getX(),
player.getEyeY() - 0.2D,
player.getZ(),
yaw,
player.getXRot()
);
projectileBomb.launchAsExplosiveProjectile(player, direction.scale(AntarchySettings.rpoLauncherLaunchVelocity()).add(player.getDeltaMovement()));
level.addFreshEntity(projectileBomb);
}
private static boolean hasMultishot(ItemStack stack) {
return stack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY)
.keySet()
.stream()
.anyMatch(holder -> holder.is(Enchantments.MULTISHOT));
}
private static void spawnFireParticles(ServerLevel level, Player player) {
double lookX = player.getLookAngle().x;
double lookY = player.getLookAngle().y;
double lookZ = player.getLookAngle().z;
double originX = player.getX() + lookX * 0.65D;
double originY = player.getEyeY() - 0.15D + lookY * 0.15D;
double originZ = player.getZ() + lookZ * 0.65D;
level.sendParticles(ParticleTypes.SMOKE, originX, originY, originZ, 12, 0.18D, 0.12D, 0.18D, 0.02D);
level.sendParticles(ParticleTypes.CLOUD, originX, originY - 0.05D, originZ, 8, 0.16D, 0.1D, 0.16D, 0.01D);
}
}
@@ -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,
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};
public static final MapCodec<ElythiaBiomeSource> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
@@ -56,8 +76,11 @@ public class ElythiaBiomeSource extends BiomeSource {
private final int molewormCavesMaxQuartY;
private final int surfaceBiomeSampleQuartY;
private final int seaLevelQuartY;
private final int undergroundGuardMaxQuartY;
private final Holder<Biome> oceanHolder;
private final Holder<Biome> defaultLandHolder;
private final Holder<Biome> glimmeringPoolsHolder;
private final Holder<Biome> ouranwoodForestHolder;
public ElythiaBiomeSource(Climate.ParameterList<Holder<Biome>> parameters, int molewormCavesMaxY, int surfaceBiomeSampleY, int oceanMaxY, int seaLevel) {
this.parameters = parameters;
@@ -69,15 +92,31 @@ public class ElythiaBiomeSource extends BiomeSource {
this.molewormCavesMaxQuartY = QuartPos.fromBlock(molewormCavesMaxY);
this.surfaceBiomeSampleQuartY = QuartPos.fromBlock(surfaceBiomeSampleY);
this.seaLevelQuartY = QuartPos.fromBlock(seaLevel);
// Cave pockets carved into hills/mountains above sea level have no other guard —
// they just resolve via raw climate matching, which can land on peach_forest/glimmering_pools
// even though those are meant to be surface-only. Anything at or below this Y gets
// treated as underground for those two biomes specifically.
this.undergroundGuardMaxQuartY = QuartPos.fromBlock(molewormCavesMaxY + 40);
this.oceanHolder = parameters.values().stream()
.map(Pair::getSecond)
.filter(h -> h.is(ELYTHIA_OCEAN))
.findFirst()
.orElse(null);
this.glimmeringPoolsHolder = parameters.values().stream()
.map(Pair::getSecond)
.filter(h -> h.is(GLIMMERING_POOLS))
.findFirst()
.orElse(null);
this.ouranwoodForestHolder = parameters.values().stream()
.map(Pair::getSecond)
.filter(h -> h.is(OURANWOOD_FOREST))
.findFirst()
.orElse(null);
this.defaultLandHolder = parameters.values().stream()
.map(Pair::getSecond)
.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()
.orElse(null);
}
@@ -97,11 +136,6 @@ public class ElythiaBiomeSource extends BiomeSource {
protected Stream<Holder<Biome>> collectPossibleBiomes() {
return this.delegate.possibleBiomes().stream();
}
// Conservative threshold well inside the ocean entry range (-0.85 to -0.92).
// At continentalness < -0.80 the terrain is reliably below sea level.
// Transition-zone columns (-0.80 to -0.85) are treated as land to avoid
// ocean biome appearing over above-sea-level terrain.
private static final long OCEAN_CONTINENTALNESS_THRESHOLD = Climate.quantizeCoord(-0.87f);
@Override
@@ -110,10 +144,10 @@ public class ElythiaBiomeSource extends BiomeSource {
// Cap moleworm caves below their max Y — replace with surface biome above it
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 o classify the column.
// Ocean columns get the ocean biome at every Y so structures, particles,
// and ambient effects work correctly above the water surface.
// Cave biomes are always preserved even in ocean columns.
@@ -133,11 +167,25 @@ public class ElythiaBiomeSource extends BiomeSource {
return oceanHolder;
}
// Land column: prevent ocean biomes from bleeding in at any Y
// Land column prevent ocean biomes from sneakin in
if (isOceanBiome(biome)) {
return resolveLandFallback(x, z, sampler);
}
if (biome.is(SPARSE_OURANWOOD_FOREST) && !isSparseOuranwoodCandidate(target, x, z)) {
biome = this.ouranwoodForestHolder != null ? this.ouranwoodForestHolder : biome;
}
if (isGlimmeringPoolsCandidate(biome, target, x, z)) {
biome = this.glimmeringPoolsHolder != null ? this.glimmeringPoolsHolder : biome;
}
// Neither peach_forest nor glimmering_pools should ever generate underground —
// fall back to whatever a nearby surface reference sample would give instead.
if ((biome.is(PEACH_FOREST) || biome.is(GLIMMERING_POOLS)) && y <= this.undergroundGuardMaxQuartY) {
return resolveSurfaceFallback(x, y, z, sampler, PEACH_FOREST, GLIMMERING_POOLS);
}
return biome;
}
@@ -150,7 +198,65 @@ public class ElythiaBiomeSource extends BiomeSource {
return biome.is(ELYTHIA_OCEAN) || biome.is(ELYTHIA_CORAL_SPIKES);
}
// Used for moleworm caves — returns best Y-sample, falls back to whatever the delegate gives
private static boolean isOuranwoodBiome(Holder<Biome> biome) {
// peach_forest is deliberately excluded here: letting glimmering_pools carve
// into it fragments its own (already modest) territory into tiny patches.
return biome.is(OURANWOOD_FOREST) || biome.is(SPARSE_OURANWOOD_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.64F) || humidity > Climate.quantizeCoord(1.0F)) {
return false;
}
if (continentalness < Climate.quantizeCoord(0.29F) || continentalness > Climate.quantizeCoord(0.90F)) {
return false;
}
if (weirdness < Climate.quantizeCoord(-0.88F) || weirdness > Climate.quantizeCoord(0.33F)) {
return false;
}
if (depth < Climate.quantizeCoord(0.0F) || depth > Climate.quantizeCoord(0.80F)) {
return false;
}
long cellX = Math.floorDiv(x, 11);
long cellZ = Math.floorDiv(z, 11);
long gate = Math.floorMod(cellX * 73428767L + cellZ * 912931L, 5L);
return gate <= 3L;
}
private static boolean isSparseOuranwoodCandidate(Climate.TargetPoint target, int x, int z) {
if (target.temperature() > Climate.quantizeCoord(0.24F)) {
return false;
}
if (target.humidity() > Climate.quantizeCoord(0.70F)) {
return false;
}
if (target.continentalness() < Climate.quantizeCoord(0.90F)) {
return false;
}
if (target.erosion() > Climate.quantizeCoord(-0.22F)) {
return false;
}
if (target.weirdness() < Climate.quantizeCoord(0.92F)) {
return false;
}
long cellX = Math.floorDiv(x, 24);
long cellZ = Math.floorDiv(z, 24);
long gate = Math.floorMod(cellX * 1103515245L + cellZ * 2147483647L, 12L);
return gate == 0L;
}
@SafeVarargs
private Holder<Biome> resolveSurfaceFallback(int x, int y, int z, Climate.Sampler sampler, ResourceKey<Biome>... excluded) {
Holder<Biome> fallback = this.delegate.getNoiseBiome(x, this.surfaceBiomeSampleQuartY, z, sampler);
@@ -168,7 +274,6 @@ public class ElythiaBiomeSource extends BiomeSource {
return fallback;
}
// Used when ocean bleeds into a land column — guaranteed to never return an ocean/cave biome
private Holder<Biome> resolveLandFallback(int x, int z, Climate.Sampler sampler) {
Holder<Biome> fallback = this.delegate.getNoiseBiome(x, this.surfaceBiomeSampleQuartY, z, sampler);
if (!isOceanOrCave(fallback)) return fallback;
@@ -191,7 +296,8 @@ public class ElythiaBiomeSource extends BiomeSource {
private static boolean isOceanOrCave(Holder<Biome> biome) {
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
@@ -199,9 +305,6 @@ public class ElythiaBiomeSource extends BiomeSource {
try {
this.delegate.addDebugInfo(debug, pos, sampler);
} catch (NullPointerException ignored) {
// TerraBlender's MixinMultiNoiseBiomeSource.addDebugInfo reads an internal
// field (uniqueness) that it only injects into TerraBlender-registered sources.
// Our delegate is not one of those, so we swallow the NPE here.
}
debug.add("Elythia mole cave cap: y<=" + this.molewormCavesMaxY);
debug.add("Elythia sea level: " + this.seaLevel);
@@ -23,6 +23,8 @@ import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConf
public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
private static final String SIZEABLE_FOLIAGE_MODID = "sizeable_foliage";
private static final ResourceLocation CAMELLIA_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "camellia");
private static final ResourceLocation SPIDER_LILY_ID = ResourceLocation.fromNamespaceAndPath("antarchy", "spider_lily");
private final Variant variant;
@@ -39,21 +41,21 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
int attempts = switch (this.variant) {
case FOREST -> 185;
case MEADOW -> 95;
case TORCHFLOWER_FIELDS -> 60;
case PEACH_FOREST -> 120;
case BUTTERFLY_FIELDS -> 150;
case FLOWER_FOREST_MILKWEED -> 14;
};
int radius = switch (this.variant) {
case FOREST -> 8;
case MEADOW, TORCHFLOWER_FIELDS -> 14;
case MEADOW, PEACH_FOREST -> 14;
case BUTTERFLY_FIELDS -> 18;
case FLOWER_FOREST_MILKWEED -> 8;
};
boolean placedAny = false;
int torchflowerPatchRolls = switch (this.variant) {
case TORCHFLOWER_FIELDS -> 3 + random.nextInt(3);
case BUTTERFLY_FIELDS -> 2 + random.nextInt(3);
case PEACH_FOREST -> 1;
case FLOWER_FOREST_MILKWEED -> 0;
default -> 1;
};
@@ -61,7 +63,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
float torchflowerChance = switch (this.variant) {
case FOREST -> 0.24F;
case MEADOW -> 0.42F;
case TORCHFLOWER_FIELDS -> 0.95F;
case PEACH_FOREST -> 0.08F;
case BUTTERFLY_FIELDS -> 0.24F;
case FLOWER_FOREST_MILKWEED -> 0.0F;
};
@@ -72,7 +74,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.1F;
case TORCHFLOWER_FIELDS -> 0.04F;
case PEACH_FOREST -> 0.0F;
case BUTTERFLY_FIELDS -> 0.12F;
case FOREST -> 0.05F;
case FLOWER_FOREST_MILKWEED -> 0.0F;
@@ -81,7 +83,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
}
if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.16F;
case TORCHFLOWER_FIELDS -> 0.08F;
case PEACH_FOREST -> 0.0F;
case BUTTERFLY_FIELDS -> 0.95F;
case FOREST -> 0.06F;
case FLOWER_FOREST_MILKWEED -> 0.18F;
@@ -90,15 +92,18 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
}
if (random.nextFloat() < switch (this.variant) {
case MEADOW -> 0.16F;
case TORCHFLOWER_FIELDS -> 0.08F;
case PEACH_FOREST -> 0.0F;
case BUTTERFLY_FIELDS -> 0.95F;
case FOREST -> 0.06F;
case FLOWER_FOREST_MILKWEED -> 0.18F;
}) {
placedAny |= this.placeMilkweedPatch(level, origin, random, AntarchyObjects.PINK_MILKWEED.get());
}
if (this.variant == Variant.PEACH_FOREST && random.nextFloat() < 0.025F) {
placedAny |= this.placeMilkweedPatch(level, origin, random, antarchyBlock(CAMELLIA_ID));
}
if (this.variant == Variant.FOREST) {
if (this.variant == Variant.FOREST || this.variant == Variant.PEACH_FOREST) {
placedAny |= this.placeBigBushes(level, origin, random, radius);
}
@@ -113,7 +118,8 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
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)) {
placedAny = true;
}
@@ -125,20 +131,6 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
}
private boolean placeSelectedPlant(WorldGenLevel level, BlockPos plantPos, BlockPos groundPos, RandomSource random) {
if (this.variant == Variant.TORCHFLOWER_FIELDS) {
float roll = random.nextFloat();
if (roll < 0.14F) {
return placeDouble(level, plantPos, torchflowerBush().defaultBlockState());
}
if (roll < 0.58F) {
return placeSingle(level, plantPos, Blocks.TORCHFLOWER);
}
if (roll < 0.94F) {
return placeSingle(level, plantPos, Blocks.SHORT_GRASS);
}
return placeDouble(level, plantPos, Blocks.TALL_GRASS.defaultBlockState());
}
if (this.variant == Variant.BUTTERFLY_FIELDS) {
float roll = random.nextFloat();
if (roll < 0.18F) {
@@ -170,6 +162,17 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return placeDouble(level, plantPos, Blocks.LARGE_FERN.defaultBlockState());
}
if (this.variant == Variant.PEACH_FOREST) {
float roll = random.nextFloat();
if (roll < 0.01F) {
return placeSingle(level, plantPos, antarchyBlock(SPIDER_LILY_ID));
}
if (roll < 0.58F) {
return placeSingle(level, plantPos, Blocks.SHORT_GRASS);
}
return placeSingle(level, plantPos, veryShortGrass());
}
float roll = random.nextFloat();
if (this.variant == Variant.MEADOW) {
if (roll < 0.34F) {
@@ -216,6 +219,14 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return block == Blocks.AIR ? Blocks.TORCHFLOWER : block;
}
private static Block antarchyBlock(ResourceLocation id) {
Block block = BuiltInRegistries.BLOCK.get(id);
if (block == Blocks.AIR) {
throw new IllegalStateException("Missing block: " + id);
}
return block;
}
private boolean placeBigBushes(WorldGenLevel level, BlockPos origin, RandomSource random, int radius) {
Block bushBlock = BuiltInRegistries.BLOCK.get(ResourceLocation.fromNamespaceAndPath(SIZEABLE_FOLIAGE_MODID, "big_bush"));
if (!(bushBlock instanceof BigBushBlock)) {
@@ -223,7 +234,10 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
}
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++) {
int x = origin.getX() + random.nextInt(radius * 2 + 1) - radius;
int z = origin.getZ() + random.nextInt(radius * 2 + 1) - radius;
@@ -325,18 +339,23 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return false;
}
int radius = this.variant == Variant.TORCHFLOWER_FIELDS ? 4 : this.variant == Variant.MEADOW ? 3 : this.variant == Variant.BUTTERFLY_FIELDS ? 3 : 2;
int plants = this.variant == Variant.TORCHFLOWER_FIELDS
? 8 + random.nextInt(8)
: this.variant == Variant.BUTTERFLY_FIELDS
int radius = this.variant == Variant.MEADOW ? 3 : this.variant == Variant.BUTTERFLY_FIELDS ? 3 : 2;
int plants = this.variant == Variant.BUTTERFLY_FIELDS
? 4 + random.nextInt(5)
: 3 + random.nextInt(this.variant == Variant.MEADOW ? 4 : 3);
boolean bushEligible = this.variant == Variant.FOREST || this.variant == Variant.MEADOW;
boolean placed = false;
for (int i = 0; i < plants; i++) {
BlockPos plantPos = samplePatchPos(level, patchCenter, radius, random);
if (plantPos == null) {
continue;
}
if (bushEligible && random.nextFloat() < 0.12F) {
if (placeDouble(level, plantPos, torchflowerBush().defaultBlockState())) {
placed = true;
}
continue;
}
if (placeSingle(level, plantPos, Blocks.TORCHFLOWER)) {
placed = true;
}
@@ -402,7 +421,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
int attempts = switch (this.variant) {
case FOREST -> 3;
case MEADOW -> 5;
case TORCHFLOWER_FIELDS -> 2;
case PEACH_FOREST -> 3;
case BUTTERFLY_FIELDS -> 3;
case FLOWER_FOREST_MILKWEED -> 2;
};
@@ -451,18 +470,42 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
return isOpenPlantSpot(level, plantPos) ? plantPos : null;
}
private static void maybeOvergrowGround(WorldGenLevel level, BlockPos groundPos, RandomSource random, boolean meadow) {
if (!isValidGround(level, groundPos) || random.nextFloat() > (meadow ? 0.45F : 0.24F)) {
private static void normalizePeachForestGround(WorldGenLevel level, BlockPos groundPos, Variant variant) {
if (variant != Variant.PEACH_FOREST) {
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.MOSS_BLOCK.defaultBlockState();
level.setBlock(groundPos, replacement, 2);
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;
}
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) {
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) {
@@ -533,7 +580,7 @@ public class ElythiaFloraFeature extends Feature<NoneFeatureConfiguration> {
public enum Variant {
FOREST,
MEADOW,
TORCHFLOWER_FIELDS,
PEACH_FOREST,
BUTTERFLY_FIELDS,
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,195 @@
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;
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 = random.nextInt(8) == 0
? randomBetween(random, LARGE_RADIUS_MIN, LARGE_RADIUS_MAX)
: randomBetween(random, SMALL_RADIUS_MIN, SMALL_RADIUS_MAX);
int maxDepth = radius >= LARGE_RADIUS_MIN ? randomBetween(random, 4, 6) : 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;
clearFloatingVegetation(level, worldX, worldZ, localSurfaceY);
}
}
return placedAny;
}
private static void clearFloatingVegetation(WorldGenLevel level, int x, int z, int surfaceY) {
int topOfDebris = Math.max(surfaceY, level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, x, z) - 1);
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (int offset = 1; offset <= 32; offset++) {
mutable.set(x, topOfDebris + offset, z);
BlockState state = level.getBlockState(mutable);
if (!state.isAir() && state.canBeReplaced()) {
level.setBlock(mutable, Blocks.AIR.defaultBlockState(), 2);
}
}
}
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,249 @@
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 LumenSpireFeature extends Feature<NoneFeatureConfiguration> {
private static final int MIN_HEIGHT = 15;
private static final int MAX_HEIGHT = 20;
private static final int MIN_LEVELS = 3;
private static final int MAX_LEVELS = 4;
private static final int SHAFT_RADIUS = 3;
private static final int SHELF_RADIUS = SHAFT_RADIUS + 2;
private static final int POOL_RADIUS = 2;
public LumenSpireFeature(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 base = findSurfaceCenter(level, origin);
if (base == null) {
return false;
}
int height = randomBetween(random, MIN_HEIGHT, MAX_HEIGHT);
int levels = randomBetween(random, MIN_LEVELS, MAX_LEVELS);
if (base.getY() + height >= level.getMaxBuildHeight() - 2) {
return false;
}
BlockState shellstoneState = AntarchyObjects.SHELLSTONE.get().defaultBlockState();
BlockState tuffState = Blocks.TUFF.defaultBlockState();
BlockState mossState = Blocks.MOSS_BLOCK.defaultBlockState();
BlockState lumenBlockState = AntarchyObjects.LUMEN_BLOCK.get().defaultBlockState();
Direction cascadeDir = Direction.Plane.HORIZONTAL.getRandomDirection(random);
int cascadeX = cascadeDir.getStepX() * (SHAFT_RADIUS + 1);
int cascadeZ = cascadeDir.getStepZ() * (SHAFT_RADIUS + 1);
int lipX = cascadeDir.getStepX() * (SHAFT_RADIUS + 1 + POOL_RADIUS);
int lipZ = cascadeDir.getStepZ() * (SHAFT_RADIUS + 1 + POOL_RADIUS);
boolean placed = false;
for (int y = 0; y <= height; y++) {
double heightFraction = (double) y / height;
int radius = Math.max(2, Mth.floor(SHAFT_RADIUS * (1.0D - heightFraction * 0.35D)));
placed |= placePillarSlice(level, base.above(y), radius, random, shellstoneState, tuffState, mossState);
}
int[] terraceHeights = new int[levels];
for (int i = 0; i < levels; i++) {
terraceHeights[i] = Mth.floor(height * (double) (i + 1) / levels);
}
int lipWorldX = base.getX() + lipX;
int lipWorldZ = base.getZ() + lipZ;
int groundLandingY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, lipWorldX, lipWorldZ) - 1;
for (int i = levels - 1; i >= 0; i--) {
int terraceY = base.getY() + terraceHeights[i];
BlockPos shelfCenter = new BlockPos(base.getX(), terraceY, base.getZ());
placeShelfRing(level, shelfCenter, random, shellstoneState, tuffState, mossState);
BlockPos poolCenter = new BlockPos(base.getX() + cascadeX, terraceY, base.getZ() + cascadeZ);
carveTerracePool(level, poolCenter, shellstoneState, tuffState, lumenBlockState, i == levels - 1);
BlockPos lipPos = new BlockPos(base.getX() + lipX, terraceY - 1, base.getZ() + lipZ);
level.setBlock(lipPos, Blocks.AIR.defaultBlockState(), 2);
int nextTerraceY = i > 0 ? base.getY() + terraceHeights[i - 1] : groundLandingY;
for (int y = terraceY - 2; y > nextTerraceY; y--) {
level.setBlock(new BlockPos(lipWorldX, y, lipWorldZ), Blocks.AIR.defaultBlockState(), 2);
}
clearAboveShelf(level, shelfCenter);
placed = true;
}
carveBaseBasin(level, new BlockPos(lipWorldX, groundLandingY, lipWorldZ), random, shellstoneState, tuffState, mossState, lumenBlockState);
return placed;
}
private static boolean placePillarSlice(WorldGenLevel level, BlockPos center, int radius, RandomSource random, BlockState shellstoneState, BlockState tuffState, BlockState mossState) {
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) {
continue;
}
BlockPos targetPos = center.offset(x, 0, z);
BlockState existing = level.getBlockState(targetPos);
if (!LumenWorldgenHelper.isSolidSupport(existing)) {
level.setBlock(targetPos, pickBodyState(distance, random, shellstoneState, tuffState, mossState), 2);
placed = true;
}
}
}
return placed;
}
private static void placeShelfRing(WorldGenLevel level, BlockPos center, RandomSource random, BlockState shellstoneState, BlockState tuffState, BlockState mossState) {
double rr = SHELF_RADIUS * SHELF_RADIUS;
for (int x = -SHELF_RADIUS; x <= SHELF_RADIUS; x++) {
for (int z = -SHELF_RADIUS; z <= SHELF_RADIUS; z++) {
double distance = (x * x + z * z) / Math.max(1.0D, rr);
if (distance > 1.0D) {
continue;
}
BlockPos topPos = center.offset(x, 0, z);
level.setBlock(topPos, pickBodyState(distance, random, shellstoneState, tuffState, mossState), 2);
level.setBlock(topPos.below(), shellstoneState, 2);
}
}
}
private static void carveTerracePool(WorldGenLevel level, BlockPos poolCenter, BlockState shellstoneState, BlockState tuffState, BlockState lumenBlockState, boolean isApex) {
double rr = POOL_RADIUS * POOL_RADIUS;
for (int x = -POOL_RADIUS; x <= POOL_RADIUS; x++) {
for (int z = -POOL_RADIUS; z <= POOL_RADIUS; z++) {
double distance = (x * x + z * z) / Math.max(1.0D, rr);
if (distance > 1.0D) {
continue;
}
BlockPos floorPos = poolCenter.offset(x, -1, z);
BlockPos surfacePos = poolCenter.offset(x, 0, z);
level.setBlock(floorPos, shellstoneState, 2);
if (distance <= 0.55D) {
level.setBlock(surfacePos, LumenWorldgenHelper.lumenState(lumenBlockState, true), 2);
LumenWorldgenHelper.scheduleFluidTick(level, surfacePos);
} else {
level.setBlock(surfacePos, tuffState, 2);
}
}
}
if (isApex) {
level.setBlock(poolCenter, LumenWorldgenHelper.lumenState(lumenBlockState, true), 2);
LumenWorldgenHelper.scheduleFluidTick(level, poolCenter);
}
}
private static void carveBaseBasin(WorldGenLevel level, BlockPos landingPos, RandomSource random, BlockState shellstoneState, BlockState tuffState, BlockState mossState, BlockState lumenBlockState) {
int radius = 4 + random.nextInt(2);
int depth = 2 + random.nextInt(2);
int waterlineY = landingPos.getY() - 1;
int floorY = waterlineY - depth;
double rr = radius * radius;
for (int x = -radius - 1; x <= radius + 1; x++) {
for (int z = -radius - 1; z <= radius + 1; z++) {
double distance = (x * x + z * z) / Math.max(1.0D, rr);
BlockPos topPos = landingPos.offset(x, 0, z);
int surfaceY = level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, topPos.getX(), topPos.getZ()) - 1;
if (distance <= 1.0D) {
for (int y = surfaceY; y > floorY; y--) {
BlockPos fillPos = new BlockPos(topPos.getX(), y, topPos.getZ());
if (y > waterlineY) {
level.setBlock(fillPos, Blocks.AIR.defaultBlockState(), 2);
} else {
level.setBlock(fillPos, LumenWorldgenHelper.lumenState(lumenBlockState, 0), 2);
LumenWorldgenHelper.scheduleFluidTick(level, fillPos);
}
}
level.setBlock(new BlockPos(topPos.getX(), floorY, topPos.getZ()), pickBodyState(distance, random, shellstoneState, tuffState, mossState), 2);
} else if (distance <= 1.25D) {
for (int y = surfaceY; y >= floorY; y--) {
level.setBlock(new BlockPos(topPos.getX(), y, topPos.getZ()), pickBodyState(distance, random, shellstoneState, tuffState, mossState), 2);
}
}
}
}
level.setBlock(landingPos, LumenWorldgenHelper.lumenState(lumenBlockState, true), 2);
LumenWorldgenHelper.scheduleFluidTick(level, landingPos);
}
private static void clearAboveShelf(WorldGenLevel level, BlockPos shelfCenter) {
for (int x = -SHELF_RADIUS; x <= SHELF_RADIUS; x++) {
for (int z = -SHELF_RADIUS; z <= SHELF_RADIUS; z++) {
double distance = (x * x + z * z) / (double) (SHELF_RADIUS * SHELF_RADIUS);
if (distance > 1.0D) {
continue;
}
BlockPos columnTop = shelfCenter.offset(x, 0, z);
for (int offset = 1; offset <= 16; offset++) {
BlockPos abovePos = columnTop.above(offset);
BlockState aboveState = level.getBlockState(abovePos);
if (!aboveState.isAir() && aboveState.canBeReplaced()) {
level.setBlock(abovePos, Blocks.AIR.defaultBlockState(), 2);
}
}
}
}
}
private static BlockState pickBodyState(double distance, RandomSource random, BlockState shellstoneState, BlockState tuffState, BlockState mossState) {
if (distance > 0.85D) {
return random.nextFloat() < 0.4F ? mossState : tuffState;
}
return random.nextFloat() < 0.7F ? shellstoneState : tuffState;
}
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 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 = 36;
private static final int MIN_LENGTH = 12;
private static final int MAX_WIDTH = 2;
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,165 @@
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> {
private static final int ANGLE_SAMPLES = 16;
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;
double[] silhouetteNoise = generateSilhouetteNoise(random);
double leanMagnitude = random.nextDouble() * 1.6D;
double leanAngle = random.nextDouble() * Math.PI * 2.0D;
double wobbleAmplitude = 0.4D + random.nextDouble() * 0.4D;
double wobblePhase = random.nextDouble() * Math.PI * 2.0D;
int topRadius = 1;
BlockPos topCenter = base;
for (int y = 0; y < height; y++) {
double heightFraction = (double) y / (height - 1);
double taper = Math.cos(heightFraction * Math.PI * 0.5D);
int radius = Math.max(1, Math.round((float) (baseRadius * taper)));
double wobble = Math.sin(heightFraction * Math.PI * 1.5D + wobblePhase) * wobbleAmplitude;
int driftX = (int) Math.round(Math.cos(leanAngle) * leanMagnitude * heightFraction + Math.cos(leanAngle + Math.PI * 0.5D) * wobble);
int driftZ = (int) Math.round(Math.sin(leanAngle) * leanMagnitude * heightFraction + Math.sin(leanAngle + Math.PI * 0.5D) * wobble);
BlockPos levelCenter = base.above(y).offset(driftX, 0, driftZ);
placed |= this.placeDisk(level, levelCenter, radius, heightFraction, silhouetteNoise, random);
topCenter = levelCenter;
topRadius = radius;
}
if (placed) {
this.softCapWithSlabs(level, topCenter, topRadius, random);
}
return placed;
}
private static double[] generateSilhouetteNoise(RandomSource random) {
double[] noise = new double[ANGLE_SAMPLES];
for (int i = 0; i < ANGLE_SAMPLES; i++) {
noise[i] = 0.88D + random.nextDouble() * 0.24D;
}
return noise;
}
private static double sampleSilhouetteNoise(double[] noise, 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) * ANGLE_SAMPLES;
int index0 = ((int) Math.floor(normalized)) % ANGLE_SAMPLES;
int index1 = (index0 + 1) % ANGLE_SAMPLES;
double t = normalized - Math.floor(normalized);
return Mth.lerp(t, noise[index0], noise[index1]);
}
private boolean placeDisk(WorldGenLevel level, BlockPos center, int radius, double heightFraction, double[] silhouetteNoise, RandomSource random) {
boolean placed = false;
double rr = radius * radius;
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
double noiseFactor = sampleSilhouetteNoise(silhouetteNoise, x, z);
double distance = (x * x + z * z) / (Math.max(1.0D, rr) * noiseFactor * noiseFactor);
if (distance > 1.0D) {
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,141 @@
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;
int rimHeight = 2 + Math.max(0, (Math.max(radiusX, radiusZ) - 10) / 6);
int rimTopY = center.getY() + rimHeight;
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--) {
BlockPos waterPos = new BlockPos(topPos.getX(), y, topPos.getZ());
level.setBlock(waterPos, Blocks.WATER.defaultBlockState(), 2);
LumenWorldgenHelper.scheduleFluidTick(level, waterPos);
}
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 = rimTopY; y >= floorY; y--) {
level.setBlock(new BlockPos(topPos.getX(), y, topPos.getZ()), Blocks.TUFF.defaultBlockState(), 2);
}
clearFloatingVegetation(level, topPos, rimTopY);
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);
for (int offset = 1; offset <= 32; offset++) {
BlockPos abovePos = new BlockPos(topPos.getX(), topOfDebris + offset, topPos.getZ());
BlockState aboveState = level.getBlockState(abovePos);
if (!aboveState.isAir() && aboveState.canBeReplaced()) {
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) {
}
}
Loaded 100 of 814 files, more files were not shown because too many files have changed in this diff. Show more