Merge branch 'beta7'
This commit is contained in:
commit
5aa77a2d01
814 files changed
+37268
-5867
No files matched your search
@@ -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
@@ -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'
|
||||
|
||||
+56
@@ -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);
|
||||
}
|
||||
}
|
||||
+64
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
}
|
||||
+136
@@ -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;
|
||||
}
|
||||
}
|
||||
-42
@@ -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;
|
||||
}
|
||||
}
|
||||
+64
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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");
|
||||
}
|
||||
}
|
||||
+50
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
@@ -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);
|
||||
}
|
||||
}
|
||||
+19
-1
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
+117
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
-108
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -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;
|
||||
}
|
||||
}
|
||||
+78
@@ -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;
|
||||
}
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
}
|
||||
+137
@@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -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);
|
||||
}
|
||||
}
|
||||
-68
@@ -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,40 +245,26 @@ 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.isSecondaryUseActive()) {
|
||||
this.openInventory(player);
|
||||
return InteractionResult.sidedSuccess(this.level().isClientSide);
|
||||
}
|
||||
|
||||
if (!player.isShiftKeyDown()) {
|
||||
if (this.hasSaddle() && stack.isEmpty() && !player.isPassengerOfSameVehicle(this)) {
|
||||
if (!this.level().isClientSide) {
|
||||
player.startRiding(this);
|
||||
}
|
||||
return InteractionResult.sidedSuccess(this.level().isClientSide);
|
||||
}
|
||||
|
||||
if (!this.level().isClientSide) {
|
||||
this.setSaddle(false);
|
||||
this.spawnAtLocation(new ItemStack(Items.SADDLE));
|
||||
this.playSound(SoundEvents.HORSE_SADDLE, 1.0F, 1.0F);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
this.openInventory(player);
|
||||
return InteractionResult.sidedSuccess(this.level().isClientSide);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public LivingEntity getControllingPassenger() {
|
||||
@@ -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.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;
|
||||
|
||||
|
||||
+303
@@ -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"));
|
||||
}
|
||||
}
|
||||
+22
@@ -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"));
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -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;
|
||||
|
||||
+124
@@ -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();
|
||||
}
|
||||
}
|
||||
+140
@@ -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();
|
||||
}
|
||||
}
|
||||
+94
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+152
@@ -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();
|
||||
}
|
||||
}
|
||||
+619
@@ -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;
|
||||
}
|
||||
}
|
||||
+77
@@ -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);
|
||||
}
|
||||
}
|
||||
+229
@@ -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;
|
||||
}
|
||||
}
|
||||
+216
@@ -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<>();
|
||||
}
|
||||
}
|
||||
+54
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+147
@@ -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);
|
||||
}
|
||||
}
|
||||
+85
-6
@@ -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 -> {
|
||||
}
|
||||
}
|
||||
|
||||
+109
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+159
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+173
@@ -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);
|
||||
}
|
||||
}
|
||||
+152
@@ -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);
|
||||
}
|
||||
}
|
||||
+81
@@ -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);
|
||||
}
|
||||
}
|
||||
+89
@@ -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);
|
||||
}
|
||||
}
|
||||
+118
-15
@@ -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);
|
||||
|
||||
+82
-35
@@ -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
|
||||
}
|
||||
|
||||
+52
@@ -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;
|
||||
}
|
||||
}
|
||||
+195
@@ -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);
|
||||
}
|
||||
}
|
||||
+249
@@ -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);
|
||||
}
|
||||
}
|
||||
+340
@@ -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) {
|
||||
}
|
||||
}
|
||||
+71
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+165
@@ -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);
|
||||
}
|
||||
}
|
||||
+19
@@ -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));
|
||||
}
|
||||
+141
@@ -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);
|
||||
}
|
||||
}
|
||||
+29
@@ -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));
|
||||
}
|
||||
+472
@@ -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
Reference in new issue
Block a user