Initial commit

This commit is contained in:
ItsBlackGear 2022-07-10 00:16:25 -04:00
commit e3e26d810a
860 changed files with 17906 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

118
.gitignore vendored Normal file
View File

@ -0,0 +1,118 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Cache of project
.gradletasknamecache
**/build/
# Common working directory
run/
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

51
build.gradle Normal file
View File

@ -0,0 +1,51 @@
plugins {
id "architectury-plugin" version "3.4-SNAPSHOT"
id "dev.architectury.loom" version "0.11.0-SNAPSHOT" apply false
}
architectury {
minecraft = rootProject.minecraft_version
}
subprojects {
apply plugin: "dev.architectury.loom"
loom {
silentMojangMappingsLicense()
}
dependencies {
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
// The following line declares the mojmap mappings, you may use other mappings as well
mappings loom.officialMojangMappings()
// The following line declares the yarn mappings you may select this one as well.
// mappings "net.fabricmc:yarn:@YARN_MAPPINGS@:v2"
}
}
allprojects {
apply plugin: "java"
apply plugin: "architectury-plugin"
apply plugin: "maven-publish"
archivesBaseName = rootProject.archives_base_name
version = rootProject.mod_version
group = rootProject.maven_group
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.release = 17
}
java {
withSourcesJar()
}
}

27
common/build.gradle Normal file
View File

@ -0,0 +1,27 @@
dependencies {
// We depend on fabric loader here to use the fabric @Environment annotations and get the mixin dependencies
// Do NOT use other classes from fabric loader
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
}
architectury {
common()
}
loom {
accessWidenerPath.set(file("src/main/resources/wildbackport.accesswidener"))
}
publishing {
publications {
mavenCommon(MavenPublication) {
artifactId = rootProject.archives_base_name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
}
}

View File

@ -0,0 +1,53 @@
package com.cursedcauldron.wildbackport;
import com.cursedcauldron.wildbackport.client.registry.WBParticleTypes;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.registry.WBBiomes;
import com.cursedcauldron.wildbackport.common.registry.WBBlockEntities;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import com.cursedcauldron.wildbackport.common.registry.WBEnchantments;
import com.cursedcauldron.wildbackport.common.registry.WBGameEvents;
import com.cursedcauldron.wildbackport.common.registry.WBItems;
import com.cursedcauldron.wildbackport.common.registry.WBPositionSources;
import com.cursedcauldron.wildbackport.common.registry.entity.WBActivities;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.registry.entity.WBSensorTypes;
import com.cursedcauldron.wildbackport.common.tag.WBBiomeTags;
import com.cursedcauldron.wildbackport.common.tag.WBBlockTags;
import com.cursedcauldron.wildbackport.common.tag.WBEntityTypeTags;
import com.cursedcauldron.wildbackport.common.tag.WBGameEventTags;
import com.cursedcauldron.wildbackport.common.tag.WBItemTags;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
//<>
public class WildBackport {
public static final String MOD_ID = "wildbackport";
public static final Logger LOGGER = LogUtils.getLogger();
public static void bootstrap() {
// Registries
WBActivities.ACTIVITIES.register();
WBBiomes.BIOMES.register();
WBBlockEntities.BLOCKS.register();
WBBlocks.BLOCKS.register();
WBEnchantments.ENCHANTMENTS.register();
WBEntities.ENTITIES.register();
WBGameEvents.EVENTS.register();
WBItems.ITEMS.register();
WBMemoryModules.MEMORIES.register();
WBParticleTypes.PARTICLES.register();
WBPositionSources.SOURCES.register();
WBSensorTypes.SENSORS.register();
WBSoundEvents.SOUNDS.register();
// Tags
WBBiomeTags.TAGS.bootstrap();
WBBlockTags.TAGS.bootstrap();
WBEntityTypeTags.TAGS.bootstrap();
WBGameEventTags.TAGS.bootstrap();
WBItemTags.TAGS.bootstrap();
}
}

View File

@ -0,0 +1,77 @@
package com.cursedcauldron.wildbackport.client;
import com.cursedcauldron.wildbackport.client.particle.SculkChargeParticle;
import com.cursedcauldron.wildbackport.client.particle.SculkChargePopParticle;
import com.cursedcauldron.wildbackport.client.particle.SculkSoulParticle;
import com.cursedcauldron.wildbackport.client.particle.ShriekParticle;
import com.cursedcauldron.wildbackport.client.particle.SonicBoomParticle;
import com.cursedcauldron.wildbackport.client.registry.WBParticleTypes;
import com.cursedcauldron.wildbackport.client.render.AllayRenderer;
import com.cursedcauldron.wildbackport.client.render.ChestBoatRenderer;
import com.cursedcauldron.wildbackport.client.render.FrogRenderer;
import com.cursedcauldron.wildbackport.client.render.TadpoleRenderer;
import com.cursedcauldron.wildbackport.client.render.WardenRenderer;
import com.cursedcauldron.wildbackport.client.render.model.AllayModel;
import com.cursedcauldron.wildbackport.client.render.model.ChestBoatModel;
import com.cursedcauldron.wildbackport.client.render.model.FrogModel;
import com.cursedcauldron.wildbackport.client.render.model.TadpoleModel;
import com.cursedcauldron.wildbackport.client.render.model.WardenModel;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import com.cursedcauldron.wildbackport.core.api.ColorRegistry;
import com.cursedcauldron.wildbackport.core.api.ParticleRegistry;
import com.cursedcauldron.wildbackport.core.api.RenderRegistry;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.renderer.BiomeColors;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.level.FoliageColor;
@Environment(EnvType.CLIENT)
public class ClientSetup {
/**
* Runs features at initializing
*/
public static void onClient() {
// Colors
ColorRegistry.register((state, getter, pos, tint) -> (getter == null || pos == null) ? FoliageColor.getDefaultColor() : BiomeColors.getAverageFoliageColor(getter, pos), WBBlocks.MANGROVE_LEAVES);
ColorRegistry.register((stack, tint) -> 9619016, WBBlocks.MANGROVE_LEAVES);
// Entity Renderers
RenderRegistry.setLayerDefinition(AllayRenderer.MODEL_LAYER, AllayModel::createBodyLayer);
RenderRegistry.setEntityRender(WBEntities.ALLAY, AllayRenderer::new);
RenderRegistry.setLayerDefinition(WardenRenderer.MODEL_LAYER, WardenModel::createBodyLayer);
RenderRegistry.setEntityRender(WBEntities.WARDEN, WardenRenderer::new);
RenderRegistry.setLayerDefinition(FrogRenderer.MODEL_LAYER, FrogModel::createBodyLayer);
RenderRegistry.setEntityRender(WBEntities.FROG, FrogRenderer::new);
RenderRegistry.setLayerDefinition(TadpoleRenderer.MODEL_LAYER, TadpoleModel::createBodyLayer);
RenderRegistry.setEntityRender(WBEntities.TADPOLE, TadpoleRenderer::new);
for (Boat.Type type : Boat.Type.values()) RenderRegistry.setLayerDefinition(ChestBoatModel.createChestBoat(type), () -> ChestBoatModel.createBodyModel(true));
RenderRegistry.setEntityRender(WBEntities.MANGROVE_BOAT, context -> new ChestBoatRenderer(context, false));
RenderRegistry.setEntityRender(WBEntities.CHEST_BOAT, context -> new ChestBoatRenderer(context, true));
// Particle Renderers
ParticleRegistry.create(WBParticleTypes.SCULK_SOUL, SculkSoulParticle.Provider::new);
ParticleRegistry.create(WBParticleTypes.SCULK_CHARGE, SculkChargeParticle.Provider::new);
ParticleRegistry.create(WBParticleTypes.SCULK_CHARGE_POP, SculkChargePopParticle.Provider::new);
ParticleRegistry.create(WBParticleTypes.SHRIEK, ShriekParticle.Provider::new);
ParticleRegistry.create(WBParticleTypes.SONIC_BOOM, SonicBoomParticle.Provider::new);
}
/**
* Runs features post bootstrap
*/
public static void onPostClient() {
// Block Render Types
RenderRegistry.setBlockRenderType(RenderType.cutout(),
WBBlocks.SCULK_VEIN.get(),
WBBlocks.SCULK_SHRIEKER.get(),
WBBlocks.FROGSPAWN.get(),
WBBlocks.MANGROVE_ROOTS.get(),
WBBlocks.MANGROVE_TRAPDOOR.get(),
WBBlocks.MANGROVE_PROPAGULE.get(),
WBBlocks.POTTED_MANGROVE_PROPAGULE.get()
);
}
}

View File

@ -0,0 +1,69 @@
package com.cursedcauldron.wildbackport.client.animation;
import com.cursedcauldron.wildbackport.client.animation.api.Animation;
import com.cursedcauldron.wildbackport.client.animation.api.AnimationHelper;
import com.cursedcauldron.wildbackport.client.animation.api.Keyframe;
import com.cursedcauldron.wildbackport.client.animation.api.Transformation;
public class FrogAnimations {
public static final Animation CROAKING = Animation.Builder.create(4.5F).looping()
.addBoneAnimation("croaking_body", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.6667F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.7083F, AnimationHelper.translate(0.0F, 1.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(3.2083F, AnimationHelper.translate(0.0F, 1.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(3.25F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("croaking_body", new Transformation(Transformation.Targets.SCALE, new Keyframe(0.6667F, AnimationHelper.scale(1.0F, 1.0F, 1.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.7083F, AnimationHelper.scale(1.0F, 1.0F, 1.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.7917F, AnimationHelper.scale(1.3F, 2.1F, 1.6F), Transformation.Interpolations.LINEAL), new Keyframe(0.875F, AnimationHelper.scale(1.3F, 2.1F, 1.6F), Transformation.Interpolations.LINEAL), new Keyframe(0.9583F, AnimationHelper.scale(1.0F, 1.0F, 1.0F), Transformation.Interpolations.LINEAL), new Keyframe(2.5F, AnimationHelper.scale(1.0F, 1.0F, 1.0F), Transformation.Interpolations.LINEAL), new Keyframe(2.5833F, AnimationHelper.scale(1.3F, 2.1F, 1.6F), Transformation.Interpolations.LINEAL), new Keyframe(2.6667F, AnimationHelper.scale(1.3F, 2.1F, 1.6F), Transformation.Interpolations.LINEAL), new Keyframe(2.75F, AnimationHelper.scale(1.0F, 1.0F, 1.0F), Transformation.Interpolations.LINEAL), new Keyframe(2.8333F, AnimationHelper.scale(1.0F, 1.0F, 1.0F), Transformation.Interpolations.LINEAL), new Keyframe(2.9167F, AnimationHelper.scale(1.3F, 2.1F, 1.6F), Transformation.Interpolations.LINEAL), new Keyframe(3.125F, AnimationHelper.scale(1.3F, 2.1F, 1.8F), Transformation.Interpolations.LINEAL), new Keyframe(3.2083F, AnimationHelper.scale(1.0F, 1.0F, 1.0F), Transformation.Interpolations.LINEAL), new Keyframe(3.25F, AnimationHelper.scale(1.0F, 1.0F, 1.0F), Transformation.Interpolations.LINEAL)))
.build();
public static final Animation WALKING = Animation.Builder.create(1.25F).looping()
.addBoneAnimation("left_arm", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(0.0F, -5.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.2917F, AnimationHelper.rotation(7.5F, -2.67F, -7.5F), Transformation.Interpolations.LINEAL), new Keyframe(0.625F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.7917F, AnimationHelper.rotation(22.5F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(1.125F, AnimationHelper.rotation(-45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.rotation(0.0F, -5.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("left_arm", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 0.1F, -2.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.2917F, AnimationHelper.translate(-0.5F, -0.25F, -0.13F), Transformation.Interpolations.LINEAL), new Keyframe(0.625F, AnimationHelper.translate(-0.5F, 0.1F, 2.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.9583F, AnimationHelper.translate(0.5F, 1.0F, -0.11F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.translate(0.0F, 0.1F, -2.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("right_arm", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.125F, AnimationHelper.rotation(22.5F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.4583F, AnimationHelper.rotation(-45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.625F, AnimationHelper.rotation(0.0F, 5.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.9583F, AnimationHelper.rotation(7.5F, 2.33F, 7.5F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("right_arm", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.5F, 0.1F, 2.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.2917F, AnimationHelper.translate(-0.5F, 1.0F, 0.12F), Transformation.Interpolations.LINEAL), new Keyframe(0.625F, AnimationHelper.translate(0.0F, 0.1F, -2.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.9583F, AnimationHelper.translate(0.5F, -0.25F, -0.13F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.translate(0.5F, 0.1F, 2.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("left_leg", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.1667F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.2917F, AnimationHelper.rotation(45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.625F, AnimationHelper.rotation(-45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.7917F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("left_leg", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 0.1F, 1.2F), Transformation.Interpolations.LINEAL), new Keyframe(0.1667F, AnimationHelper.translate(0.0F, 0.1F, 2.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.4583F, AnimationHelper.translate(0.0F, 2.0F, 1.06F), Transformation.Interpolations.LINEAL), new Keyframe(0.7917F, AnimationHelper.translate(0.0F, 0.1F, -1.0F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.translate(0.0F, 0.1F, 1.2F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("right_leg", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(-33.75F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.0417F, AnimationHelper.rotation(-45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.1667F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.7917F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.9583F, AnimationHelper.rotation(45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.rotation(-33.75F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("right_leg", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 1.14F, 0.11F), Transformation.Interpolations.LINEAL), new Keyframe(0.1667F, AnimationHelper.translate(0.0F, 0.1F, -1.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.7917F, AnimationHelper.translate(0.0F, 0.1F, 2.0F), Transformation.Interpolations.LINEAL), new Keyframe(1.125F, AnimationHelper.translate(0.0F, 2.0F, 0.95F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.translate(0.0F, 1.14F, 0.11F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("body", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(0.0F, 5.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.2917F, AnimationHelper.rotation(-7.5F, 0.33F, 7.5F), Transformation.Interpolations.LINEAL), new Keyframe(0.625F, AnimationHelper.rotation(0.0F, -5.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.9583F, AnimationHelper.rotation(-7.5F, 0.33F, -7.5F), Transformation.Interpolations.LINEAL), new Keyframe(1.25F, AnimationHelper.rotation(0.0F, 5.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.build();
public static final Animation LONG_JUMPING = Animation.Builder.create(0.5F)
.addBoneAnimation("body", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(-22.5F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.rotation(-22.5F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("body", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("left_arm", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(-56.14F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.rotation(-56.14F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("left_arm", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 1.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.translate(0.0F, 1.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("right_arm", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(-56.14F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.rotation(-56.14F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("right_arm", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 1.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.translate(0.0F, 1.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("left_leg", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.rotation(45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("left_leg", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("right_leg", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.rotation(45.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("right_leg", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL), new Keyframe(0.5F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.LINEAL)))
.build();
public static final Animation USING_TONGUE = Animation.Builder.create(0.5f)
.addBoneAnimation("head", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0f, AnimationHelper.rotation(0.0f, 0.0f, 0.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.0833f, AnimationHelper.rotation(-60.0f, 0.0f, 0.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.4167f, AnimationHelper.rotation(-60.0f, 0.0f, 0.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.5f, AnimationHelper.rotation(0.0f, 0.0f, 0.0f), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("head", new Transformation(Transformation.Targets.SCALE, new Keyframe(0.0f, AnimationHelper.rotation(1.0f, 1.0f, 1.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.0833f, AnimationHelper.rotation(0.998f, 1.0f, 1.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.4167f, AnimationHelper.rotation(0.998f, 1.0f, 1.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.5f, AnimationHelper.rotation(1.0f, 1.0f, 1.0f), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("tongue", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0f, AnimationHelper.rotation(0.0f, 0.0f, 0.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.0833f, AnimationHelper.rotation(0.0f, 0.0f, 0.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.4167f, AnimationHelper.rotation(-18.0f, 0.0f, 0.0f), Transformation.Interpolations.LINEAL), new Keyframe(0.5f, AnimationHelper.rotation(0.0f, 0.0f, 0.0f), Transformation.Interpolations.LINEAL)))
.addBoneAnimation("tongue", new Transformation(Transformation.Targets.SCALE, new Keyframe(0.0833f, AnimationHelper.scale(1.0, 1.0, 1.0), Transformation.Interpolations.LINEAL), new Keyframe(0.1667f, AnimationHelper.scale(0.5, 1.0, 5.0), Transformation.Interpolations.LINEAL), new Keyframe(0.4167f, AnimationHelper.scale(1.0, 1.0, 1.0), Transformation.Interpolations.LINEAL)))
.build();
public static final Animation SWIMMING = Animation.Builder.create(1.04167F).looping()
.addBoneAnimation("body", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.3333F, AnimationHelper.rotation(10.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.6667F, AnimationHelper.rotation(-10.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("left_arm", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(90.0F, 22.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.4583F, AnimationHelper.rotation(45.0F, 22.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.6667F, AnimationHelper.rotation(-22.5F, -22.5F, -22.5F), Transformation.Interpolations.CATMULL), new Keyframe(0.875F, AnimationHelper.rotation(-45.0F, -22.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.9583F, AnimationHelper.rotation(22.5F, 0.0F, 22.5F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.rotation(90.0F, 22.5F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("left_arm", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, -0.64F, 2.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.4583F, AnimationHelper.translate(0.0F, -0.64F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.6667F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.875F, AnimationHelper.translate(0.0F, -0.27F, -1.14F), Transformation.Interpolations.CATMULL), new Keyframe(0.9583F, AnimationHelper.translate(0.0F, -1.45F, 0.43F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.translate(0.0F, -0.64F, 2.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("right_arm", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(90.0F, -22.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.4583F, AnimationHelper.rotation(45.0F, -22.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.6667F, AnimationHelper.rotation(-22.5F, 22.5F, 22.5F), Transformation.Interpolations.CATMULL), new Keyframe(0.875F, AnimationHelper.rotation(-45.0F, 22.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.9583F, AnimationHelper.rotation(22.5F, 0.0F, -22.5F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.rotation(90.0F, -22.5F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("right_arm", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, -0.64F, 2.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.4583F, AnimationHelper.translate(0.0F, -0.64F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.6667F, AnimationHelper.translate(0.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.875F, AnimationHelper.translate(0.0F, -0.27F, -1.14F), Transformation.Interpolations.CATMULL), new Keyframe(0.9583F, AnimationHelper.translate(0.0F, -1.45F, 0.43F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.translate(0.0F, -0.64F, 2.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("left_leg", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(90.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.25F, AnimationHelper.rotation(90.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.4583F, AnimationHelper.rotation(67.5F, -45.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.7917F, AnimationHelper.rotation(90.0F, 45.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.9583F, AnimationHelper.rotation(90.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.rotation(90.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("left_leg", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(-2.5F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.25F, AnimationHelper.translate(-2.0F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.4583F, AnimationHelper.translate(1.0F, -2.0F, -1.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.7917F, AnimationHelper.translate(0.58F, 0.0F, -2.83F), Transformation.Interpolations.CATMULL), new Keyframe(0.9583F, AnimationHelper.translate(-2.5F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.translate(-2.5F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("right_leg", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(90.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.25F, AnimationHelper.rotation(90.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.4583F, AnimationHelper.rotation(67.5F, 45.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.7917F, AnimationHelper.rotation(90.0F, -45.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.9583F, AnimationHelper.rotation(90.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.rotation(90.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("right_leg", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(2.5F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.25F, AnimationHelper.translate(2.0F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.4583F, AnimationHelper.translate(-1.0F, -2.0F, -1.0F), Transformation.Interpolations.CATMULL), new Keyframe(0.7917F, AnimationHelper.translate(-0.58F, 0.0F, -2.83F), Transformation.Interpolations.CATMULL), new Keyframe(0.9583F, AnimationHelper.translate(2.5F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0417F, AnimationHelper.translate(2.5F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL)))
.build();
public static final Animation IDLING_IN_WATER = Animation.Builder.create(3.0F).looping()
.addBoneAnimation("body", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.625F, AnimationHelper.rotation(-10.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.rotation(0.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("left_arm", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(0.0F, 0.0F, -22.5F), Transformation.Interpolations.CATMULL), new Keyframe(2.2083F, AnimationHelper.rotation(0.0F, 0.0F, -45.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.rotation(0.0F, 0.0F, -22.5F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("left_arm", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(-1.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(2.2083F, AnimationHelper.translate(-1.0F, -0.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.translate(-1.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("right_arm", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(0.0F, 0.0F, 22.5F), Transformation.Interpolations.CATMULL), new Keyframe(2.2083F, AnimationHelper.rotation(0.0F, 0.0F, 45.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.rotation(0.0F, 0.0F, 22.5F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("right_arm", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(1.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(2.2083F, AnimationHelper.translate(1.0F, -0.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.translate(1.0F, 0.0F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("left_leg", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(22.5F, -22.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0F, AnimationHelper.rotation(22.5F, -22.5F, -45.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.rotation(22.5F, -22.5F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("left_leg", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0F, AnimationHelper.translate(0.0F, -1.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.translate(0.0F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("right_leg", new Transformation(Transformation.Targets.ROTATE, new Keyframe(0.0F, AnimationHelper.rotation(22.5F, 22.5F, 0.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0F, AnimationHelper.rotation(22.5F, 22.5F, 45.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.rotation(22.5F, 22.5F, 0.0F), Transformation.Interpolations.CATMULL)))
.addBoneAnimation("right_leg", new Transformation(Transformation.Targets.TRANSLATE, new Keyframe(0.0F, AnimationHelper.translate(0.0F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(1.0F, AnimationHelper.translate(0.0F, -1.0F, 1.0F), Transformation.Interpolations.CATMULL), new Keyframe(3.0F, AnimationHelper.translate(0.0F, 0.0F, 1.0F), Transformation.Interpolations.CATMULL)))
.build();
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,53 @@
package com.cursedcauldron.wildbackport.client.animation.api;
import com.mojang.math.Vector3f;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
//<>
public interface Animated {
static void translate(ModelPart part, Vector3f vec) {
part.x += vec.x();
part.y += vec.y();
part.z += vec.z();
}
static void rotate(ModelPart part, Vector3f vec) {
part.xRot += vec.x();
part.yRot += vec.y();
part.zRot += vec.z();
}
static void scale(ModelPart part, Vector3f vec) {
((Animated)(Object)part).increaseXScale(vec.x());
((Animated)(Object)part).increaseYScale(vec.y());
((Animated)(Object)part).increaseZScale(vec.z());
}
PartPose getDefaultPose();
void setDefaultPose(PartPose pose);
static void resetPose(ModelPart part) {
part.loadPose(((Animated)(Object)part).getDefaultPose());
}
float xScale();
void setXScale(float x);
void increaseXScale(float x);
float yScale();
void setYScale(float y);
void increaseYScale(float y);
float zScale();
void setZScale(float z);
void increaseZScale(float z);
}

View File

@ -0,0 +1,42 @@
package com.cursedcauldron.wildbackport.client.animation.api;
import com.cursedcauldron.wildbackport.core.mixin.access.ModelPartAccessor;
import com.mojang.math.Vector3f;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.model.HierarchicalModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import java.util.Optional;
import java.util.function.Function;
//<>
@Environment(EnvType.CLIENT)
public abstract class AnimatedModel<E extends Entity> extends HierarchicalModel<E> {
private static final Vector3f CACHE = new Vector3f();
public AnimatedModel() {
this(RenderType::entityCutoutNoCull);
}
public AnimatedModel(Function<ResourceLocation, RenderType> function) {
super(function);
}
public Optional<ModelPart> getChild(String name) {
return this.root().getAllParts().filter(part -> ((ModelPartAccessor)(Object)part).getChildren().containsKey(name)).findFirst().map(part -> part.getChild(name));
}
protected void animate(AnimationState state, Animation animation, float animationProgress) {
this.animate(state, animation, animationProgress, 1.0F);
}
protected void animate(AnimationState animationState, Animation animation, float animationProgress, float speedMultiplier) {
animationState.run(animationProgress, speedMultiplier);
animationState.run(state -> AnimationHelper.animate(this, animation, state.runningTime(), 1.0F, CACHE));
}
}

View File

@ -0,0 +1,40 @@
package com.cursedcauldron.wildbackport.client.animation.api;
import com.google.common.collect.Maps;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import org.apache.commons.compress.utils.Lists;
import java.util.List;
import java.util.Map;
@Environment(EnvType.CLIENT)
public record Animation(float lengthInSeconds, boolean looping, Map<String, List<Transformation>> boneAnimations) {
public static class Builder {
private final float lengthInSeconds;
private final Map<String, List<Transformation>> transformations = Maps.newHashMap();
private boolean looping;
public static Builder create(float lengthInSeconds) {
return new Builder(lengthInSeconds);
}
private Builder(float lengthInSeconds) {
this.lengthInSeconds = lengthInSeconds;
}
public Builder looping() {
this.looping = true;
return this;
}
public Builder addBoneAnimation(String name2, Transformation transformation) {
this.transformations.computeIfAbsent(name2, name -> Lists.newArrayList()).add(transformation);
return this;
}
public Animation build() {
return new Animation(this.lengthInSeconds, this.looping, this.transformations);
}
}
}

View File

@ -0,0 +1,53 @@
package com.cursedcauldron.wildbackport.client.animation.api;
import com.mojang.math.Vector3f;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.util.Mth;
import java.util.List;
import java.util.Map;
import java.util.Optional;
//<>
@Environment(EnvType.CLIENT)
public class AnimationHelper {
public static void animate(AnimatedModel<?> model, Animation animation, long runningTime, float speed, Vector3f cache) {
float runningSeconds = getRunningSeconds(animation, runningTime);
for (Map.Entry<String, List<Transformation>> animations : animation.boneAnimations().entrySet()) {
Optional<ModelPart> modelPart = model.getChild(animations.getKey());
List<Transformation> transformations = animations.getValue();
modelPart.ifPresent(part -> transformations.forEach(transformation -> {
Keyframe[] keyframes = transformation.keyframes();
int start = Math.max(0, Mth.binarySearch(0, keyframes.length, i -> runningSeconds <= keyframes[i].timestamp()) - 1);
int end = Math.min(keyframes.length - 1, start + 1);
Keyframe frameStart = keyframes[start];
Keyframe frameEnd = keyframes[end];
float current = runningSeconds - frameStart.timestamp();
float delta = Mth.clamp(current / (frameEnd.timestamp() - frameStart.timestamp()), 0.0f, 1.0f);
frameEnd.interpolation().apply(cache, delta, keyframes, start, end, speed);
transformation.target().apply(part, cache);
}));
}
}
private static float getRunningSeconds(Animation animation, long runningTime) {
float time = (float)runningTime / 1000.0f;
return animation.looping() ? time % animation.lengthInSeconds() : time;
}
public static Vector3f translate(float x, float y, float z) {
return new Vector3f(x, -y, z);
}
public static Vector3f rotation(float x, float y, float z) {
return new Vector3f(x * ((float)Math.PI / 180), y * ((float)Math.PI / 180), z * ((float)Math.PI / 180));
}
public static Vector3f scale(double x, double y, double z) {
return new Vector3f((float)(x - 1.0), (float)(y - 1.0), (float)(z - 1.0));
}
}

View File

@ -0,0 +1,45 @@
package com.cursedcauldron.wildbackport.client.animation.api;
import net.minecraft.util.Mth;
import java.util.function.Consumer;
//<>
public class AnimationState {
private long startedAt = Long.MAX_VALUE;
private long runningTime;
public void start(int ticks) {
this.startedAt = (long)ticks * 1000L / 20L;
this.runningTime = 0L;
}
public void startIfNotRunning(int ticks) {
if (!this.isRunning()) this.start(ticks);
}
public void stop() {
this.startedAt = Long.MAX_VALUE;
}
public void run(Consumer<AnimationState> consumer) {
if (this.isRunning()) consumer.accept(this);
}
public void run(float animationProgress, float speedMultiplier) {
if (this.isRunning()) {
long runningAt = Mth.lfloor(animationProgress * 1000.0F / 20.0F);
this.runningTime += (long)((float)(runningAt - this.startedAt) * speedMultiplier);
this.startedAt = runningAt;
}
}
public long runningTime() {
return this.runningTime;
}
public boolean isRunning() {
return this.startedAt != Long.MAX_VALUE;
}
}

View File

@ -0,0 +1,8 @@
package com.cursedcauldron.wildbackport.client.animation.api;
import com.mojang.math.Vector3f;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Environment(EnvType.CLIENT)
public record Keyframe(float timestamp, Vector3f target, Transformation.Interpolation interpolation) {}

View File

@ -0,0 +1,42 @@
package com.cursedcauldron.wildbackport.client.animation.api;
import com.cursedcauldron.wildbackport.common.utils.MathUtils;
import com.mojang.math.Vector3f;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.util.Mth;
@Environment(EnvType.CLIENT)
public record Transformation(Target target, Keyframe... keyframes) {
public interface Target {
void apply(ModelPart part, Vector3f vector);
}
public static class Interpolations {
public static final Interpolation LINEAL = (cache, delta, keyframes, start, end, speed) -> {
Vector3f frameStart = keyframes[start].target();
Vector3f frameEnd = keyframes[end].target();
cache.set(Mth.lerp(delta, frameStart.x(), frameEnd.x()) * speed, Mth.lerp(delta, frameStart.y(), frameEnd.y()) * speed, Mth.lerp(delta, frameStart.z(), frameEnd.z()) * speed);
return cache;
};
public static final Interpolation CATMULL = (cache, delta, keyframes, start, end, speed) -> {
Vector3f frameStartPoint = keyframes[Math.max(0, start - 1)].target();
Vector3f frameStart = keyframes[start].target();
Vector3f frameEnd = keyframes[end].target();
Vector3f frameEndPoint = keyframes[Math.min(keyframes.length - 1, end + 1)].target();
cache.set(MathUtils.catmullrom(delta, frameStartPoint.x(), frameStart.x(), frameEnd.x(), frameEndPoint.x()) * speed, MathUtils.catmullrom(delta, frameStartPoint.y(), frameStart.y(), frameEnd.y(), frameEndPoint.y()) * speed, MathUtils.catmullrom(delta, frameStartPoint.z(), frameStart.z(), frameEnd.z(), frameEndPoint.z()) * speed);
return cache;
};
}
public static class Targets {
public static final Target TRANSLATE = Animated::translate;
public static final Target ROTATE = Animated::rotate;
public static final Target SCALE = Animated::scale;
}
public interface Interpolation {
Vector3f apply(Vector3f cache, float delta, Keyframe[] keyframes, int start, int end, float speed);
}
}

View File

@ -0,0 +1,52 @@
package com.cursedcauldron.wildbackport.client.particle;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
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;
@Environment(EnvType.CLIENT)
public class SculkChargeParticle extends TextureSheetParticle {
private final SpriteSet sprites;
public SculkChargeParticle(ClientLevel level, double x, double y, double z, double xMotion, double yMotion, double zMotion, SpriteSet sprites) {
super(level, x, y, z, xMotion, yMotion, zMotion);
this.sprites = sprites;
this.friction = 0.96F;
this.scale(1.5F);
this.hasPhysics = false;
this.setSpriteFromAge(sprites);
}
@Override
protected int getLightColor(float delta) {
return 240;
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
}
@Override
public void tick() {
super.tick();
this.setSpriteFromAge(this.sprites);
}
public record Provider(SpriteSet sprites) implements ParticleProvider<SculkChargeParticleOptions> {
@Override
public Particle createParticle(SculkChargeParticleOptions options, ClientLevel level, double x, double y, double z, double xMotion, double yMotion, double zMotion) {
SculkChargeParticle particle = new SculkChargeParticle(level, x, y, z, xMotion, yMotion, zMotion, this.sprites);
particle.setAlpha(1.0F);
particle.setParticleSpeed(xMotion, yMotion, zMotion);
particle.oRoll = particle.roll = options.roll();
particle.setLifetime(level.getRandom().nextInt(12) + 8);
return particle;
}
}
}

View File

@ -0,0 +1,48 @@
package com.cursedcauldron.wildbackport.client.particle;
import com.cursedcauldron.wildbackport.client.registry.WBParticleTypes;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Registry;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.network.FriendlyByteBuf;
import java.util.Locale;
public record SculkChargeParticleOptions(float roll) implements ParticleOptions {
public static final Codec<SculkChargeParticleOptions> CODEC = RecordCodecBuilder.create(instance -> {
return instance.group(Codec.FLOAT.fieldOf("roll").forGetter(options -> {
return options.roll;
})).apply(instance, SculkChargeParticleOptions::new);
});
public static final Deserializer<SculkChargeParticleOptions> DESERIALIZER = new Deserializer<>() {
@Override
public SculkChargeParticleOptions fromCommand(ParticleType<SculkChargeParticleOptions> type, StringReader reader) throws CommandSyntaxException {
return new SculkChargeParticleOptions(reader.readFloat());
}
@Override
public SculkChargeParticleOptions fromNetwork(ParticleType<SculkChargeParticleOptions> type, FriendlyByteBuf buf) {
return new SculkChargeParticleOptions(buf.readFloat());
}
};
@Override
public ParticleType<?> getType() {
return WBParticleTypes.SCULK_CHARGE.get();
}
@Override
public void writeToNetwork(FriendlyByteBuf buf) {
buf.writeFloat(this.roll);
}
@Override
public String writeToString() {
return String.format(Locale.ROOT, "%s %.2f", Registry.PARTICLE_TYPE.getId(this.getType()), this.roll);
}
}

View File

@ -0,0 +1,54 @@
package com.cursedcauldron.wildbackport.client.particle;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
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;
//<>
@Environment(EnvType.CLIENT)
public class SculkChargePopParticle extends TextureSheetParticle {
private final SpriteSet sprites;
public SculkChargePopParticle(ClientLevel level, double x, double y, double z, double xMotion, double yMotion, double zMotion, SpriteSet sprites) {
super(level, x, y, z, xMotion, yMotion, zMotion);
this.sprites = sprites;
this.friction = 0.96F;
this.scale(1.0F);
this.hasPhysics = false;
this.setSpriteFromAge(sprites);
}
@Override
protected int getLightColor(float delta) {
return 240;
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
}
@Override
public void tick() {
super.tick();
this.setSpriteFromAge(this.sprites);
}
public record Provider(SpriteSet sprites) implements ParticleProvider<SimpleParticleType> {
@Override
public Particle createParticle(SimpleParticleType type, ClientLevel level, double x, double y, double z, double xMotion, double yMotion, double zMotion) {
SculkChargePopParticle particle = new SculkChargePopParticle(level, x, y, z, xMotion, yMotion, zMotion, this.sprites);
particle.setAlpha(1.0F);
particle.setParticleSpeed(xMotion, yMotion, zMotion);
particle.setLifetime(level.getRandom().nextInt(4) + 6);
return particle;
}
}
}

View File

@ -0,0 +1,51 @@
package com.cursedcauldron.wildbackport.client.particle;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
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.RisingParticle;
import net.minecraft.client.particle.SpriteSet;
import net.minecraft.core.particles.SimpleParticleType;
//<>
@Environment(EnvType.CLIENT)
public class SculkSoulParticle extends RisingParticle {
private final SpriteSet sprites;
public SculkSoulParticle(ClientLevel level, double x, double y, double z, double xMotion, double yMotion, double zMotion, SpriteSet sprites) {
super(level, x, y, z, xMotion, yMotion, zMotion);
this.sprites = sprites;
this.getQuadSize(1.5F);
this.setSpriteFromAge(sprites);
}
@Override
protected int getLightColor(float delta) {
return 240;
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
}
@Override
public void tick() {
super.tick();
this.setSpriteFromAge(this.sprites);
}
@Environment(EnvType.CLIENT)
public record Provider(SpriteSet sprites) implements ParticleProvider<SimpleParticleType> {
@Override
public Particle createParticle(SimpleParticleType type, ClientLevel level, double x, double y, double z, double xMotion, double yMotion, double zMotion) {
SculkSoulParticle particle = new SculkSoulParticle(level, x, y, z, xMotion, yMotion, zMotion, this.sprites);
particle.setAlpha(1.0F);
return particle;
}
}
}

View File

@ -0,0 +1,112 @@
package com.cursedcauldron.wildbackport.client.particle;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f;
import net.minecraft.Util;
import net.minecraft.client.Camera;
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.util.Mth;
import net.minecraft.world.phys.Vec3;
import java.util.function.Consumer;
public class ShriekParticle extends TextureSheetParticle {
private static final Vector3f AXIS = Util.make(new Vector3f(0.5F, 0.5F, 0.5F), Vector3f::normalize);
private static final Vector3f OFFSET = new Vector3f(-1.0F, -1.0F, 0.0F);
private int delay;
protected ShriekParticle(ClientLevel level, double x, double y, double z, int delay) {
super(level, x, y, z);
this.quadSize = 0.85F;
this.delay = delay;
this.lifetime = 30;
this.gravity = 0.0F;
this.xd = 0.0D;
this.yd = 0.1D;
this.zd = 0.0D;
}
@Override
public float getQuadSize(float tickDelta) {
return this.quadSize * Mth.clamp(((float)this.age + tickDelta) / (float)this.lifetime * 0.75F, 0.0F, 1.0F);
}
@Override
public void render(VertexConsumer vertex, Camera camera, float tickDelta) {
if (this.delay > 0) return;
this.alpha = 1.0F - Mth.clamp(((float)this.age + tickDelta) / (float)this.lifetime * 0.75F, 0.0F, 1.0F);
this.render(vertex, camera, tickDelta, quaternion -> {
quaternion.mul(Vector3f.YP.rotation(0.0F));
quaternion.mul(Vector3f.XP.rotation(-1.0472F));
});
this.render(vertex, camera, tickDelta, quaternion -> {
quaternion.mul(Vector3f.YP.rotation((float)(-Math.PI)));
quaternion.mul(Vector3f.XP.rotation(1.0472F));
});
}
private void render(VertexConsumer vertex, Camera camera, float tickDelta, Consumer<Quaternion> consumer) {
Vec3 pos = camera.getPosition();
float x = (float)(Mth.lerp(tickDelta, this.xo, this.x) - pos.x());
float y = (float)(Mth.lerp(tickDelta, this.yo, this.y) - pos.y());
float z = (float)(Mth.lerp(tickDelta, this.zo, this.z) - pos.z());
Quaternion quaternion = new Quaternion(AXIS, 0.0F, true);
consumer.accept(quaternion);
OFFSET.transform(quaternion);
Vector3f[] vectors = new Vector3f[] {new Vector3f(-1.0F, -1.0F, 0.0F), new Vector3f(-1.0F, 1.0F, 0.0F), new Vector3f(1.0F, 1.0F, 0.0F), new Vector3f(1.0F, -1.0F, 0.0F)};
float quadSize = this.getQuadSize(tickDelta);
for (int i = 0; i < 4; i++) {
Vector3f vector = vectors[i];
vector.transform(quaternion);
vector.mul(quadSize);
vector.add(x, y, z);
}
int light = this.getLightColor(tickDelta);
this.addVertex(vertex, vectors[0], this.getU1(), this.getV1(), light);
this.addVertex(vertex, vectors[1], this.getU1(), this.getV0(), light);
this.addVertex(vertex, vectors[2], this.getU0(), this.getV0(), light);
this.addVertex(vertex, vectors[3], this.getU0(), this.getV1(), light);
}
private void addVertex(VertexConsumer vertex, Vector3f vector, float u, float v, int light) {
vertex.vertex(vector.x(), vector.y(), vector.z()).uv(u, v).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(light).endVertex();
}
@Override
protected int getLightColor(float tint) {
return 240;
}
@Override
public ParticleRenderType getRenderType() {
return ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT;
}
@Override
public void tick() {
if (this.delay > 0) {
--this.delay;
return;
}
super.tick();
}
public record Provider(SpriteSet spriteSet) implements ParticleProvider<ShriekParticleOptions> {
@Override
public Particle createParticle(ShriekParticleOptions options, ClientLevel level, double x, double y, double z, double xMotion, double yMotion, double zMotion) {
ShriekParticle particle = new ShriekParticle(level, x, y, z, options.delay());
particle.pickSprite(this.spriteSet);
particle.setAlpha(1.0F);
return particle;
}
}
}

View File

@ -0,0 +1,53 @@
package com.cursedcauldron.wildbackport.client.particle;
import com.cursedcauldron.wildbackport.client.registry.WBParticleTypes;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Registry;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.network.FriendlyByteBuf;
import java.util.Locale;
public record ShriekParticleOptions(int delay) implements ParticleOptions {
public static final Codec<ShriekParticleOptions> CODEC = RecordCodecBuilder.create(instance -> {
return instance.group(Codec.INT.fieldOf("delay").forGetter(options -> {
return options.delay;
})).apply(instance, ShriekParticleOptions::new);
});
public static final ParticleOptions.Deserializer<ShriekParticleOptions> DESERIALIZER = new ParticleOptions.Deserializer<>() {
@Override
public ShriekParticleOptions fromCommand(ParticleType<ShriekParticleOptions> type, StringReader reader) throws CommandSyntaxException {
reader.expect(' ');
return new ShriekParticleOptions(reader.readInt());
}
@Override
public ShriekParticleOptions fromNetwork(ParticleType<ShriekParticleOptions> type, FriendlyByteBuf buf) {
return new ShriekParticleOptions(buf.readVarInt());
}
};
@Override
public void writeToNetwork(FriendlyByteBuf buf) {
buf.writeVarInt(this.delay);
}
@Override
public String writeToString() {
return String.format(Locale.ROOT, "%s %d", Registry.PARTICLE_TYPE.getId(this.getType()), this.delay);
}
@Override
public ParticleType<?> getType() {
return WBParticleTypes.SHRIEK.get();
}
public int getDelay() {
return this.delay;
}
}

View File

@ -0,0 +1,24 @@
package com.cursedcauldron.wildbackport.client.particle;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.HugeExplosionParticle;
import net.minecraft.client.particle.Particle;
import net.minecraft.client.particle.ParticleProvider;
import net.minecraft.client.particle.SpriteSet;
import net.minecraft.core.particles.SimpleParticleType;
public class SonicBoomParticle extends HugeExplosionParticle {
public SonicBoomParticle(ClientLevel level, double x, double y, double z, double speed, SpriteSet sprites) {
super(level, x, y, z, speed, sprites);
this.lifetime = 16;
this.quadSize = 1.5F;
this.setSpriteFromAge(sprites);
}
public record Provider(SpriteSet sprites) implements ParticleProvider<SimpleParticleType> {
@Override
public Particle createParticle(SimpleParticleType particle, ClientLevel level, double x, double y, double z, double xMotion, double yMotion, double zMotion) {
return new SonicBoomParticle(level, x, y, z, xMotion, this.sprites);
}
}
}

View File

@ -0,0 +1,20 @@
package com.cursedcauldron.wildbackport.client.registry;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.core.mixin.access.CriteriaTriggersAccessor;
import net.minecraft.advancements.CriterionTrigger;
import net.minecraft.advancements.critereon.KilledTrigger;
import net.minecraft.advancements.critereon.LocationTrigger;
import net.minecraft.resources.ResourceLocation;
//<>
public class WBCriteriaTriggers {
public static final KilledTrigger KILL_MOB_NEAR_SCULK_CATALYST = create(new KilledTrigger(new ResourceLocation(WildBackport.MOD_ID, "kill_mob_near_sculk_catalyst")));
// public static final ItemUsedOnBlockTrigger ALLAY_DROP_ITEM_ON_BLOCK = create(new ItemUsedOnBlockTrigger(new ResourceLocation(WildBackport.MOD_ID, "kill_mob_near_sculk_catalyst")));
public static final LocationTrigger AVOID_VIBRATION = create(new LocationTrigger(new ResourceLocation(WildBackport.MOD_ID, "avoid_vibration")));
public static <T extends CriterionTrigger<?>> T create(T type) {
return CriteriaTriggersAccessor.callRegister(type);
}
}

View File

@ -0,0 +1,39 @@
package com.cursedcauldron.wildbackport.client.registry;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.particle.SculkChargeParticleOptions;
import com.cursedcauldron.wildbackport.client.particle.ShriekParticleOptions;
import com.cursedcauldron.wildbackport.core.api.CoreRegistry;
import com.cursedcauldron.wildbackport.core.mixin.access.SimpleParticleTypeAccessor;
import com.mojang.serialization.Codec;
import net.minecraft.core.Registry;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.core.particles.SimpleParticleType;
import java.util.function.Function;
import java.util.function.Supplier;
//<>
public class WBParticleTypes {
public static final CoreRegistry<ParticleType<?>> PARTICLES = CoreRegistry.create(Registry.PARTICLE_TYPE, WildBackport.MOD_ID);
public static final Supplier<SimpleParticleType> SCULK_SOUL = create("sculk_soul", false);
public static final Supplier<ParticleType<SculkChargeParticleOptions>> SCULK_CHARGE = create("sculk_charge", SculkChargeParticleOptions.DESERIALIZER, type -> SculkChargeParticleOptions.CODEC, true);
public static final Supplier<SimpleParticleType> SCULK_CHARGE_POP = create("sculk_charge_pop", true);
public static final Supplier<ParticleType<ShriekParticleOptions>> SHRIEK = create("shriek", ShriekParticleOptions.DESERIALIZER, type -> ShriekParticleOptions.CODEC, true);
public static final Supplier<SimpleParticleType> SONIC_BOOM = create("sonic_boom", true);
private static Supplier<SimpleParticleType> create(String key, boolean alwaysShow) {
return PARTICLES.register(key, () -> SimpleParticleTypeAccessor.createSimpleParticleType(alwaysShow));
}
private static <T extends ParticleOptions> Supplier<ParticleType<T>> create(String key, ParticleOptions.Deserializer<T> deserializer, Function<ParticleType<T>, Codec<T>> function, boolean alwaysShow) {
return PARTICLES.register(key, () -> new ParticleType<>(alwaysShow, deserializer) {
@Override public Codec<T> codec() {
return function.apply(this);
}
});
}
}

View File

@ -0,0 +1,137 @@
package com.cursedcauldron.wildbackport.client.registry;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.core.api.CoreRegistry;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import java.util.stream.IntStream;
//<>
public class WBSoundEvents {
public static final CoreRegistry<SoundEvent> SOUNDS = CoreRegistry.create(Registry.SOUND_EVENT, WildBackport.MOD_ID);
// Blocks
public static final SoundEvent BLOCK_SCULK_VEIN_BREAK = create("block.sculk_vein.break");
public static final SoundEvent BLOCK_SCULK_VEIN_FALL = create("block.sculk_vein.fall");
public static final SoundEvent BLOCK_SCULK_VEIN_HIT = create("block.sculk_vein.hit");
public static final SoundEvent BLOCK_SCULK_VEIN_PLACE = create("block.sculk_vein.place");
public static final SoundEvent BLOCK_SCULK_VEIN_STEP = create("block.sculk_vein.step");
public static final SoundEvent BLOCK_SCULK_CATALYST_BLOOM = create("block.sculk_catalyst.bloom");
public static final SoundEvent BLOCK_SCULK_CATALYST_BREAK = create("block.sculk_catalyst.break");
public static final SoundEvent BLOCK_SCULK_CATALYST_FALL = create("block.sculk_catalyst.fall");
public static final SoundEvent BLOCK_SCULK_CATALYST_HIT = create("block.sculk_catalyst.hit");
public static final SoundEvent BLOCK_SCULK_CATALYST_PLACE = create("block.sculk_catalyst.place");
public static final SoundEvent BLOCK_SCULK_CATALYST_STEP = create("block.sculk_catalyst.step");
public static final SoundEvent BLOCK_SCULK_SPREAD = create("block.sculk.spread");
public static final SoundEvent BLOCK_SCULK_CHARGE = create("block.sculk.charge");
public static final SoundEvent BLOCK_SCULK_BREAK = create("block.sculk.break");
public static final SoundEvent BLOCK_SCULK_FALL = create("block.sculk.fall");
public static final SoundEvent BLOCK_SCULK_HIT = create("block.sculk.hit");
public static final SoundEvent BLOCK_SCULK_PLACE = create("block.sculk.place");
public static final SoundEvent BLOCK_SCULK_STEP = create("block.sculk.step");
public static final SoundEvent BLOCK_SCULK_SHRIEKER_BREAK = create("block.sculk_shrieker.break");
public static final SoundEvent BLOCK_SCULK_SHRIEKER_FALL = create("block.sculk_shrieker.fall");
public static final SoundEvent BLOCK_SCULK_SHRIEKER_HIT = create("block.sculk_shrieker.hit");
public static final SoundEvent BLOCK_SCULK_SHRIEKER_PLACE = create("block.sculk_shrieker.place");
public static final SoundEvent BLOCK_SCULK_SHRIEKER_STEP = create("block.sculk_shrieker.step");
public static final SoundEvent BLOCK_SCULK_SHRIEKER_SHRIEK = create("block.sculk_shrieker.shriek");
public static final SoundEvent BLOCK_FROGLIGHT_BREAK = create("block.froglight.break");
public static final SoundEvent BLOCK_FROGLIGHT_FALL = create("block.froglight.fall");
public static final SoundEvent BLOCK_FROGLIGHT_HIT = create("block.froglight.hit");
public static final SoundEvent BLOCK_FROGLIGHT_PLACE = create("block.froglight.place");
public static final SoundEvent BLOCK_FROGLIGHT_STEP = create("block.froglight.step");
public static final SoundEvent BLOCK_FROGSPAWN_BREAK = create("block.frogspawn.break");
public static final SoundEvent BLOCK_FROGSPAWN_FALL = create("block.frogspawn.fall");
public static final SoundEvent BLOCK_FROGSPAWN_HATCH = create("block.frogspawn.hatch");
public static final SoundEvent BLOCK_FROGSPAWN_HIT = create("block.frogspawn.hit");
public static final SoundEvent BLOCK_FROGSPAWN_PLACE = create("block.frogspawn.place");
public static final SoundEvent BLOCK_FROGSPAWN_STEP = create("block.frogspawn.step");
public static final SoundEvent BLOCK_MANGROVE_ROOTS_BREAK = create("block.mangrove_roots.break");
public static final SoundEvent BLOCK_MANGROVE_ROOTS_FALL = create("block.mangrove_roots.fall");
public static final SoundEvent BLOCK_MANGROVE_ROOTS_HIT = create("block.mangrove_roots.hit");
public static final SoundEvent BLOCK_MANGROVE_ROOTS_PLACE = create("block.mangrove_roots.place");
public static final SoundEvent BLOCK_MANGROVE_ROOTS_STEP = create("block.mangrove_roots.step");
public static final SoundEvent BLOCK_MUD_BREAK = create("block.mud.break");
public static final SoundEvent BLOCK_MUD_FALL = create("block.mud.fall");
public static final SoundEvent BLOCK_MUD_HIT = create("block.mud.hit");
public static final SoundEvent BLOCK_MUD_PLACE = create("block.mud.place");
public static final SoundEvent BLOCK_MUD_STEP = create("block.mud.step");
public static final SoundEvent BLOCK_MUD_BRICKS_BREAK = create("block.mud_bricks.break");
public static final SoundEvent BLOCK_MUD_BRICKS_FALL = create("block.mud_bricks.fall");
public static final SoundEvent BLOCK_MUD_BRICKS_HIT = create("block.mud_bricks.hit");
public static final SoundEvent BLOCK_MUD_BRICKS_PLACE = create("block.mud_bricks.place");
public static final SoundEvent BLOCK_MUD_BRICKS_STEP = create("block.mud_bricks.step");
public static final SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_BREAK = create("block.muddy_mangrove_roots.break");
public static final SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_FALL = create("block.muddy_mangrove_roots.fall");
public static final SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_HIT = create("block.muddy_mangrove_roots.hit");
public static final SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_PLACE = create("block.muddy_mangrove_roots.place");
public static final SoundEvent BLOCK_MUDDY_MANGROVE_ROOTS_STEP = create("block.muddy_mangrove_roots.step");
public static final SoundEvent BLOCK_PACKED_MUD_BREAK = create("block.packed_mud.break");
public static final SoundEvent BLOCK_PACKED_MUD_FALL = create("block.packed_mud.fall");
public static final SoundEvent BLOCK_PACKED_MUD_HIT = create("block.packed_mud.hit");
public static final SoundEvent BLOCK_PACKED_MUD_PLACE = create("block.packed_mud.place");
public static final SoundEvent BLOCK_PACKED_MUD_STEP = create("block.packed_mud.step");
// Items
public static final SoundEvent BUCKED_EMPTY_TADPOLE = create("item.bucket.empty_tadpole");
public static final SoundEvent BUCKED_FILL_TADPOLE = create("item.bucket.fill_tadpole");
public static final ImmutableList<SoundEvent> GOAT_HORN_SOUND_VARIANTS = IntStream.range(0, 8).mapToObj(value -> {
return create("item.goat_horn.sound." + value);
}).collect(ImmutableList.toImmutableList());
// Entities
public static final SoundEvent ALLAY_AMBIENT_WITH_ITEM = create("entity.allay.ambient_with_item");
public static final SoundEvent ALLAY_AMBIENT_WITHOUT_ITEM = create("entity.allay.ambient_without_item");
public static final SoundEvent ALLAY_DEATH = create("entity.allay.death");
public static final SoundEvent ALLAY_HURT = create("entity.allay.hurt");
public static final SoundEvent ALLAY_ITEM_GIVEN = create("entity.allay.item_given");
public static final SoundEvent ALLAY_ITEM_TAKEN = create("entity.allay.item_taken");
public static final SoundEvent ALLAY_ITEM_THROW = create("entity.allay.item_thrown");
public static final SoundEvent FROG_AMBIENT = create("entity.frog.ambient");
public static final SoundEvent FROG_DEATH = create("entity.frog.death");
public static final SoundEvent FROG_EAT = create("entity.frog.eat");
public static final SoundEvent FROG_HURT = create("entity.frog.hurt");
public static final SoundEvent FROG_LAY_SPAWN = create("entity.frog.lay_spawn");
public static final SoundEvent FROG_LONG_JUMP = create("entity.frog.long_jump");
public static final SoundEvent FROG_STEP = create("entity.frog.step");
public static final SoundEvent FROG_TONGUE = create("entity.frog.tongue");
// public static final SoundEvent PARROT_IMITATE_WARDEN = create("entity.parrot.imitate_warden");
public static final SoundEvent TADPOLE_DEATH = create("entity.tadpole.death");
public static final SoundEvent TADPOLE_FLOP = create("entity.tadpole.flop");
public static final SoundEvent TADPOLE_GROW_UP = create("entity.tadpole.grow_up");
public static final SoundEvent TADPOLE_HURT = create("entity.tadpole.hurt");
public static final SoundEvent WARDEN_AMBIENT = create("entity.warden.ambient");
public static final SoundEvent WARDEN_LISTENING = create("entity.warden.listening");
public static final SoundEvent WARDEN_LISTENING_ANGRY = create("entity.warden.listening_angry");
public static final SoundEvent WARDEN_ANGRY = create("entity.warden.angry");
public static final SoundEvent WARDEN_HURT = create("entity.warden.hurt");
public static final SoundEvent WARDEN_DEATH = create("entity.warden.death");
public static final SoundEvent WARDEN_STEP = create("entity.warden.step");
public static final SoundEvent WARDEN_TENDRIL_CLICKS = create("entity.warden.tendril_clicks");
public static final SoundEvent WARDEN_HEARTBEAT = create("entity.warden.heartbeat");
public static final SoundEvent WARDEN_AGITATED = create("entity.warden.agitated");
public static final SoundEvent WARDEN_ATTACK_IMPACT = create("entity.warden.attack_impact");
public static final SoundEvent WARDEN_ROAR = create("entity.warden.roar");
public static final SoundEvent WARDEN_SNIFF = create("entity.warden.sniff");
public static final SoundEvent WARDEN_EMERGE = create("entity.warden.emerge");
public static final SoundEvent WARDEN_DIG = create("entity.warden.dig");
public static final SoundEvent WARDEN_NEARBY_CLOSEST = create("entity.warden.nearby_closest");
public static final SoundEvent WARDEN_NEARBY_CLOSER = create("entity.warden.nearby_closer");
public static final SoundEvent WARDEN_NEARBY_CLOSE = create("entity.warden.nearby_close");
public static final SoundEvent WARDEN_SONIC_BOOM = create("entity.warden.sonic_boom");
public static final SoundEvent WARDEN_SONIC_CHARGE = create("entity.warden.sonic_charge");
// Music
public static final SoundEvent MUSIC_DISC_5 = create("music_disc.5");
// public static final SoundEvent MUSIC_BIOME_DEEP_DARK = create("music.overworld.deep_dark");
public static SoundEvent create(String key) {
SoundEvent sound = new SoundEvent(new ResourceLocation(WildBackport.MOD_ID, key));
SOUNDS.register(key, () -> sound);
return sound;
}
}

View File

@ -0,0 +1,25 @@
package com.cursedcauldron.wildbackport.client.registry;
import com.cursedcauldron.wildbackport.client.sound.CoreSoundType;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.level.block.SoundType;
//<>
public class WBSoundTypes {
public static final SoundType SCULK = create(WBSoundEvents.BLOCK_SCULK_BREAK, WBSoundEvents.BLOCK_SCULK_STEP, WBSoundEvents.BLOCK_SCULK_PLACE, WBSoundEvents.BLOCK_SCULK_HIT, WBSoundEvents.BLOCK_SCULK_FALL);
public static final SoundType SCULK_CATALYST = create(WBSoundEvents.BLOCK_SCULK_CATALYST_BREAK, WBSoundEvents.BLOCK_SCULK_CATALYST_STEP, WBSoundEvents.BLOCK_SCULK_CATALYST_PLACE, WBSoundEvents.BLOCK_SCULK_CATALYST_HIT, WBSoundEvents.BLOCK_SCULK_CATALYST_FALL);
public static final SoundType SCULK_VEIN = create(WBSoundEvents.BLOCK_SCULK_VEIN_BREAK, WBSoundEvents.BLOCK_SCULK_VEIN_STEP, WBSoundEvents.BLOCK_SCULK_VEIN_PLACE, WBSoundEvents.BLOCK_SCULK_VEIN_HIT, WBSoundEvents.BLOCK_SCULK_VEIN_FALL);
public static final SoundType SCULK_SHRIEKER = create(WBSoundEvents.BLOCK_SCULK_SHRIEKER_BREAK, WBSoundEvents.BLOCK_SCULK_SHRIEKER_STEP, WBSoundEvents.BLOCK_SCULK_SHRIEKER_PLACE, WBSoundEvents.BLOCK_SCULK_SHRIEKER_HIT, WBSoundEvents.BLOCK_SCULK_SHRIEKER_FALL);
public static final SoundType FROGLIGHT = create(WBSoundEvents.BLOCK_FROGLIGHT_BREAK, WBSoundEvents.BLOCK_FROGLIGHT_STEP, WBSoundEvents.BLOCK_FROGLIGHT_PLACE, WBSoundEvents.BLOCK_FROGLIGHT_HIT, WBSoundEvents.BLOCK_FROGLIGHT_FALL);
public static final SoundType FROGSPAWN = create(WBSoundEvents.BLOCK_FROGSPAWN_BREAK, WBSoundEvents.BLOCK_FROGSPAWN_STEP, WBSoundEvents.BLOCK_FROGSPAWN_PLACE, WBSoundEvents.BLOCK_FROGSPAWN_HIT, WBSoundEvents.BLOCK_FROGSPAWN_FALL);
public static final SoundType MANGROVE_ROOTS = create(WBSoundEvents.BLOCK_MANGROVE_ROOTS_BREAK, WBSoundEvents.BLOCK_MANGROVE_ROOTS_STEP, WBSoundEvents.BLOCK_MANGROVE_ROOTS_PLACE, WBSoundEvents.BLOCK_MANGROVE_ROOTS_HIT, WBSoundEvents.BLOCK_MANGROVE_ROOTS_FALL);
public static final SoundType MUD = create(WBSoundEvents.BLOCK_MUD_BREAK, WBSoundEvents.BLOCK_MUD_STEP, WBSoundEvents.BLOCK_MUD_PLACE, WBSoundEvents.BLOCK_MUD_HIT, WBSoundEvents.BLOCK_MUD_FALL);
public static final SoundType MUD_BRICKS = create(WBSoundEvents.BLOCK_MUD_BRICKS_BREAK, WBSoundEvents.BLOCK_MUD_BRICKS_STEP, WBSoundEvents.BLOCK_MUD_BRICKS_PLACE, WBSoundEvents.BLOCK_MUD_BRICKS_HIT, WBSoundEvents.BLOCK_MUD_BRICKS_FALL);
public static final SoundType MUDDY_MANGROVE_ROOTS = create(WBSoundEvents.BLOCK_MUDDY_MANGROVE_ROOTS_BREAK, WBSoundEvents.BLOCK_MUDDY_MANGROVE_ROOTS_STEP, WBSoundEvents.BLOCK_MUDDY_MANGROVE_ROOTS_PLACE, WBSoundEvents.BLOCK_MUDDY_MANGROVE_ROOTS_HIT, WBSoundEvents.BLOCK_MUDDY_MANGROVE_ROOTS_FALL);
public static final SoundType PACKED_MUD = create(WBSoundEvents.BLOCK_PACKED_MUD_BREAK, WBSoundEvents.BLOCK_PACKED_MUD_STEP, WBSoundEvents.BLOCK_PACKED_MUD_PLACE, WBSoundEvents.BLOCK_PACKED_MUD_HIT, WBSoundEvents.BLOCK_PACKED_MUD_FALL);
public static SoundType create(SoundEvent breakSnd, SoundEvent stepSnd, SoundEvent placeSnd, SoundEvent hitSnd, SoundEvent fallSnd) {
return new CoreSoundType(() -> breakSnd, () -> stepSnd, () -> placeSnd, () -> hitSnd, () -> fallSnd);
}
}

View File

@ -0,0 +1,31 @@
package com.cursedcauldron.wildbackport.client.render;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.render.model.AllayModel;
import com.cursedcauldron.wildbackport.common.entities.Allay;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.client.renderer.entity.layers.ItemInHandLayer;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
public class AllayRenderer extends MobRenderer<Allay, AllayModel> {
public static final ModelLayerLocation MODEL_LAYER = new ModelLayerLocation(new ResourceLocation(WildBackport.MOD_ID, "allay"), "main");
private static final ResourceLocation TEXTURE = new ResourceLocation(WildBackport.MOD_ID, "textures/entity/allay/allay.png");
public AllayRenderer(EntityRendererProvider.Context context) {
super(context, new AllayModel(context.bakeLayer(MODEL_LAYER)), 0.4F);
this.addLayer(new ItemInHandLayer<>(this));
}
@Override
public ResourceLocation getTextureLocation(Allay entity) {
return TEXTURE;
}
@Override
protected int getBlockLightLevel(Allay entity, BlockPos pos) {
return 15;
}
}

View File

@ -0,0 +1,86 @@
package com.cursedcauldron.wildbackport.client.render;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.render.model.ChestBoatModel;
import com.cursedcauldron.wildbackport.common.entities.MangroveBoat;
import com.cursedcauldron.wildbackport.common.entities.access.api.BoatTypes;
import com.google.common.collect.ImmutableMap;
import com.ibm.icu.impl.Pair;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f;
import net.minecraft.client.model.geom.ModelLayerLocation;
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.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 java.util.Map;
import java.util.stream.Stream;
//<>
public class ChestBoatRenderer extends EntityRenderer<MangroveBoat> {
private final Map<Boat.Type, Pair<ResourceLocation, ChestBoatModel>> boatResources;
public ChestBoatRenderer(EntityRendererProvider.Context context, boolean chest) {
super(context);
this.shadowRadius = 0.8F;
this.boatResources = Stream.of(Boat.Type.values()).collect(ImmutableMap.toImmutableMap(type -> type, type -> Pair.of(getTexture(type, chest), this.createModel(context, type, chest))));
}
private ChestBoatModel createModel(EntityRendererProvider.Context context, Boat.Type type, boolean chest) {
ModelLayerLocation layer = chest ? ChestBoatModel.createChestBoat(type) : ModelLayers.createBoatModelName(type);
return new ChestBoatModel(context.bakeLayer(layer), chest);
}
private static ResourceLocation getTexture(Boat.Type type, boolean chested) {
if (chested) {
return new ResourceLocation(WildBackport.MOD_ID, "textures/entity/chest_boat/" + type.getName() + ".png");
} else {
return new ResourceLocation(type == BoatTypes.MANGROVE.get() ? WildBackport.MOD_ID: "minecraft", "textures/entity/boat/" + type.getName() + ".png");
}
}
@Override
public void render(MangroveBoat boat, float yaw, float angle, PoseStack stack, MultiBufferSource buffer, int light) {
stack.pushPose();
stack.translate(0.0D, 0.375D, 0.0D);
stack.mulPose(Vector3f.YP.rotationDegrees(180.0F - yaw));
float hurtTilt = (float)boat.getHurtTime() - angle;
float damageTilt = boat.getDamage() - angle;
if (damageTilt < 0.0F) damageTilt = 0.0F;
if (hurtTilt > 0.0F) stack.mulPose(Vector3f.XP.rotationDegrees(Mth.sin(hurtTilt) * hurtTilt * damageTilt / 10.0F * (float)boat.getHurtDir()));
float bubbleTilt = boat.getBubbleAngle(angle);
if (!Mth.equal(bubbleTilt, 0.0F)) stack.mulPose(new Quaternion(new Vector3f(1.0F, 0.0F, 1.0F), boat.getBubbleAngle(angle), true));
Pair<ResourceLocation, ChestBoatModel> resource = this.boatResources.get(boat.getBoatType());
ResourceLocation location = resource.first;
ChestBoatModel model = resource.second;
stack.scale(-1.0F, -1.0F, 1.0F);
stack.mulPose(Vector3f.YP.rotationDegrees(90.0F));
model.setupAnim(boat, angle, 0.0F, -0.1F, 0.0F, 0.0F);
VertexConsumer render = buffer.getBuffer(model.renderType(location));
model.renderToBuffer(stack, render, light, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F);
if (!boat.isUnderWater()) {
VertexConsumer waterRender = buffer.getBuffer(RenderType.waterMask());
model.waterPatch().render(stack, waterRender, light, OverlayTexture.NO_OVERLAY);
}
stack.popPose();
super.render(boat, yaw, angle, stack, buffer, light);
}
@Override
public ResourceLocation getTextureLocation(MangroveBoat boat) {
return this.boatResources.get(boat.getBoatType()).first;
}
}

View File

@ -0,0 +1,32 @@
package com.cursedcauldron.wildbackport.client.render;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.render.model.FrogModel;
import com.cursedcauldron.wildbackport.common.entities.Frog;
import com.google.common.collect.Maps;
import net.minecraft.Util;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.resources.ResourceLocation;
import java.util.Map;
public class FrogRenderer extends MobRenderer<Frog, FrogModel<Frog>> {
public static final ModelLayerLocation MODEL_LAYER = new ModelLayerLocation(new ResourceLocation(WildBackport.MOD_ID, "frog"), "main");
private static final Map<Frog.Variant, ResourceLocation> TEXTURES = Util.make(Maps.newHashMap(), hashMap -> {
for (Frog.Variant variant : Frog.Variant.values()) {
hashMap.put(variant, new ResourceLocation(WildBackport.MOD_ID, String.format("textures/entity/frog/%s_frog.png", variant.getName())));
}
});
public FrogRenderer(EntityRendererProvider.Context context) {
super(context, new FrogModel<>(context.bakeLayer(MODEL_LAYER)), 0.3f);
}
@Override
public ResourceLocation getTextureLocation(Frog frog) {
return TEXTURES.get(frog.getVariant());
}
}

View File

@ -0,0 +1,24 @@
package com.cursedcauldron.wildbackport.client.render;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.render.model.TadpoleModel;
import com.cursedcauldron.wildbackport.common.entities.Tadpole;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.resources.ResourceLocation;
public class TadpoleRenderer extends MobRenderer<Tadpole, TadpoleModel<Tadpole>> {
public static final ModelLayerLocation MODEL_LAYER = new ModelLayerLocation(new ResourceLocation(WildBackport.MOD_ID, "tadpole"), "main");
private static final ResourceLocation TEXTURE = new ResourceLocation(WildBackport.MOD_ID, "textures/entity/tadpole/tadpole.png");
public TadpoleRenderer(EntityRendererProvider.Context context) {
super(context, new TadpoleModel<>(context.bakeLayer(MODEL_LAYER)), 0.14F);
}
@Override
public ResourceLocation getTextureLocation(Tadpole entity) {
return TEXTURE;
}
}

View File

@ -0,0 +1,63 @@
package com.cursedcauldron.wildbackport.client.render;
import com.cursedcauldron.wildbackport.client.render.model.Drawable;
import com.cursedcauldron.wildbackport.client.render.model.WardenModel;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.resources.ResourceLocation;
import java.util.List;
//<>
@Environment(EnvType.CLIENT)
public class WardenLayerRenderer<T extends Warden, M extends WardenModel<T>> extends RenderLayer<T, M> {
private final ResourceLocation texture;
private final AnimationAngleAdjuster<T> animationAngleAdjuster;
private final ModelPartVisibility<T, M> modelPartVisibility;
public WardenLayerRenderer(RenderLayerParent<T, M> ctx, ResourceLocation texture, AnimationAngleAdjuster<T> animationAngleAdjuster, ModelPartVisibility<T, M> modelPartVisibility) {
super(ctx);
this.texture = texture;
this.animationAngleAdjuster = animationAngleAdjuster;
this.modelPartVisibility = modelPartVisibility;
}
@Override
public void render(PoseStack stack, MultiBufferSource source, int light, T entity, float angle, float distance, float tickDelta, float animationProgress, float yaw, float pitch) {
if (!entity.isInvisible()) {
this.updateModelPartVisibility();
VertexConsumer consumer = source.getBuffer(RenderType.entityCutoutNoCull(this.texture));
this.getParentModel().renderToBuffer(stack, consumer, light, LivingEntityRenderer.getOverlayCoords(entity, 0.0F), 1.0F, 1.0F, 1.0F, this.animationAngleAdjuster.apply(entity, tickDelta, animationProgress));
this.unhideAllModelParts();
}
}
private void updateModelPartVisibility() {
List<ModelPart> parts = this.modelPartVisibility.getPartsToDraw(this.getParentModel());
this.getParentModel().root().getAllParts().forEach(part -> ((Drawable)(Object)part).setSkipDraw(true));
parts.forEach(part -> ((Drawable)(Object)part).setSkipDraw(false));
}
private void unhideAllModelParts() {
this.getParentModel().root().getAllParts().forEach(part -> ((Drawable)(Object)part).setSkipDraw(false));
}
public interface AnimationAngleAdjuster<T extends Warden> {
float apply(T entity, float tickDelta, float animationProgress);
}
public interface ModelPartVisibility<T extends Warden, M extends EntityModel<T>> {
List<ModelPart> getPartsToDraw(M parts);
}
}

View File

@ -0,0 +1,39 @@
package com.cursedcauldron.wildbackport.client.render;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.render.model.WardenModel;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.MobRenderer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
//<>
@Environment(EnvType.CLIENT)
public class WardenRenderer extends MobRenderer<Warden, WardenModel<Warden>> {
public static final ModelLayerLocation MODEL_LAYER = new ModelLayerLocation(new ResourceLocation(WildBackport.MOD_ID, "warden"), "main");
private static final ResourceLocation TEXTURE = new ResourceLocation(WildBackport.MOD_ID, "textures/entity/warden/warden.png");
private static final ResourceLocation BIOLUMINESCENT_LAYER_TEXTURE = new ResourceLocation(WildBackport.MOD_ID, "textures/entity/warden/warden_bioluminescent_layer.png");
private static final ResourceLocation HEART_TEXTURE = new ResourceLocation(WildBackport.MOD_ID, "textures/entity/warden/warden_heart.png");
private static final ResourceLocation PULSATING_SPOTS_1_TEXTURE = new ResourceLocation(WildBackport.MOD_ID, "textures/entity/warden/warden_pulsating_spots_1.png");
private static final ResourceLocation PULSATING_SPOTS_2_TEXTURE = new ResourceLocation(WildBackport.MOD_ID, "textures/entity/warden/warden_pulsating_spots_2.png");
public WardenRenderer(EntityRendererProvider.Context ctx) {
super(ctx, new WardenModel<>(ctx.bakeLayer(MODEL_LAYER)), 0.9F);
this.addLayer(new WardenLayerRenderer<>(this, BIOLUMINESCENT_LAYER_TEXTURE, (entity, tickDelta, animationProgress) -> 1.0F, WardenModel::getHeadAndLimbs));
this.addLayer(new WardenLayerRenderer<>(this, PULSATING_SPOTS_1_TEXTURE, (entity, tickDelta, animationProgress) -> Math.max(0.0F, Mth.cos(animationProgress * 0.045F) * 0.25F), WardenModel::getBodyHeadAndLimbs));
this.addLayer(new WardenLayerRenderer<>(this, PULSATING_SPOTS_2_TEXTURE, (entity, tickDelta, animationProgress) -> Math.max(0.0F, Mth.cos(animationProgress * 0.045F + (float)Math.PI) * 0.25F), WardenModel::getBodyHeadAndLimbs));
this.addLayer(new WardenLayerRenderer<>(this, TEXTURE, (entity, tickDelta, animationProgress) -> entity.getTendrilPitch(tickDelta), WardenModel::getTendrils));
this.addLayer(new WardenLayerRenderer<>(this, HEART_TEXTURE, (entity, tickDelta, animationProgress) -> entity.getHeartPitch(tickDelta), WardenModel::getBody));
}
@Override
public ResourceLocation getTextureLocation(Warden warden) {
return TEXTURE;
}
}

View File

@ -0,0 +1,102 @@
package com.cursedcauldron.wildbackport.client.render.model;
import com.cursedcauldron.wildbackport.client.animation.api.Animated;
import com.cursedcauldron.wildbackport.common.entities.Allay;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Vector3f;
import net.minecraft.client.model.ArmedModel;
import net.minecraft.client.model.HierarchicalModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeDeformation;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.model.geom.builders.PartDefinition;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.HumanoidArm;
//<>
public class AllayModel extends HierarchicalModel<Allay> implements ArmedModel {
private final ModelPart root;
private final ModelPart head;
private final ModelPart body;
private final ModelPart right_arm;
private final ModelPart left_arm;
private final ModelPart right_wing;
private final ModelPart left_wing;
public AllayModel(ModelPart part) {
this.root = part.getChild("root");
this.head = this.root.getChild("head");
this.body = this.root.getChild("body");
this.right_arm = this.body.getChild("right_arm");
this.left_arm = this.body.getChild("left_arm");
this.right_wing = this.body.getChild("right_wing");
this.left_wing = this.body.getChild("left_wing");
}
public static LayerDefinition createBodyLayer() {
MeshDefinition mesh = new MeshDefinition();
PartDefinition root = mesh.getRoot().addOrReplaceChild("root", CubeListBuilder.create(), PartPose.offset(0.0F, 23.5F, 0.0F));
root.addOrReplaceChild("head", CubeListBuilder.create().texOffs(0, 0).addBox(-2.5F, -5.0F, -2.5F, 5.0F, 5.0F, 5.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, -3.99F, 0.0F));
PartDefinition body = root.addOrReplaceChild("body", CubeListBuilder.create().texOffs(0, 10).addBox(-1.5F, 0.0F, -1.0F, 3.0F, 4.0F, 2.0F, new CubeDeformation(0.0F)).texOffs(0, 16).addBox(-1.5F, 0.0F, -1.0F, 3.0F, 5.0F, 2.0F, new CubeDeformation(-0.2F)), PartPose.offset(0.0F, -4.0F, 0.0F));
body.addOrReplaceChild("right_arm", CubeListBuilder.create().texOffs(23, 0).addBox(-0.75F, -0.5F, -1.0F, 1.0F, 4.0F, 2.0F, new CubeDeformation(-0.01F)), PartPose.offset(-1.75F, 0.5F, 0.0F));
body.addOrReplaceChild("left_arm", CubeListBuilder.create().texOffs(23, 6).addBox(-0.25F, -0.5F, -1.0F, 1.0F, 4.0F, 2.0F, new CubeDeformation(-0.01F)), PartPose.offset(1.75F, 0.5F, 0.0F));
body.addOrReplaceChild("right_wing", CubeListBuilder.create().texOffs(16, 14).addBox(0.0F, 1.0F, 0.0F, 0.0F, 5.0F, 8.0F, new CubeDeformation(0.0F)), PartPose.offset(-0.5F, 0.0F, 0.65F));
body.addOrReplaceChild("left_wing", CubeListBuilder.create().texOffs(16, 14).addBox(0.0F, 1.0F, 0.0F, 0.0F, 5.0F, 8.0F, new CubeDeformation(0.0F)), PartPose.offset(0.5F, 0.0F, 0.65F));
return LayerDefinition.create(mesh, 32, 32);
}
@Override
public void setupAnim(Allay entity, float angle, float distance, float animationProgress, float yaw, float pitch) {
this.root().getAllParts().forEach(Animated::resetPose);
this.head.xRot = pitch * ((float)Math.PI / 180F);
this.head.yRot = yaw * ((float)Math.PI / 180F);
float f = animationProgress * 20.0F * ((float)Math.PI / 180F) + distance;
float f1 = Mth.cos(f) * (float)Math.PI * 0.15F;
float f2 = animationProgress - (float)entity.tickCount;
float f3 = animationProgress * 9.0F * ((float)Math.PI / 180F);
float f4 = Math.min(distance / 0.3F, 1.0F);
float f5 = 1.0F - f4;
float holdingItemAnimation = entity.getHoldingItemAnimationProgress(f2);
this.right_wing.xRot = 0.43633232F;
this.right_wing.yRot = -0.61086524F + f1;
this.left_wing.xRot = 0.43633232F;
this.left_wing.yRot = 0.61086524F - f1;
float f7 = f4 * 0.6981317F;
this.body.xRot = f7;
float f8 = Mth.lerp(holdingItemAnimation, f7, Mth.lerp(f4, (-(float)Math.PI / 3F), (-(float)Math.PI / 4F)));
this.root.y += (float)Math.cos(f3) * 0.25F * f5;
this.right_arm.xRot = f8;
this.left_arm.xRot = f8;
float f9 = f5 * (1.0F - holdingItemAnimation);
float f10 = 0.43633232F - Mth.cos(f3 + ((float)Math.PI * 1.5F)) * (float)Math.PI * 0.075F * f9;
this.left_arm.zRot = -f10;
this.right_arm.zRot = f10;
this.right_arm.yRot = 0.27925268F * holdingItemAnimation;
this.left_arm.yRot = -0.27925268F * holdingItemAnimation;
}
@Override
public void renderToBuffer(PoseStack stack, VertexConsumer consumer, int i, int j, float f, float g, float h, float k) {
this.root.render(stack, consumer, i, j);
}
@Override
public void translateToHand(HumanoidArm arm, PoseStack stack) {
this.root.translateAndRotate(stack);
this.body.translateAndRotate(stack);
stack.translate(0.0D, -0.09375D, 0.09375D);
stack.mulPose(Vector3f.XP.rotation(this.right_arm.xRot + 0.43633232F));
stack.scale(0.7F, 0.7F, 0.7F);
stack.translate(0.0625D, 0.0D, 0.0D);
}
@Override
public ModelPart root() {
return this.root;
}
}

View File

@ -0,0 +1,85 @@
package com.cursedcauldron.wildbackport.client.render.model;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.common.entities.MangroveBoat;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.model.ListModel;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.model.geom.builders.PartDefinition;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.vehicle.Boat;
//<>
public class ChestBoatModel extends ListModel<MangroveBoat> {
private final ModelPart leftPaddle;
private final ModelPart rightPaddle;
private final ModelPart waterPatch;
private final ImmutableList<ModelPart> parts;
public ChestBoatModel(ModelPart root, boolean chest) {
this.leftPaddle = root.getChild("left_paddle");
this.rightPaddle = root.getChild("right_paddle");
this.waterPatch = root.getChild("water_patch");
ImmutableList.Builder<ModelPart> builder = new ImmutableList.Builder<>();
builder.add(root.getChild("bottom"), root.getChild("back"), root.getChild("front"), root.getChild("right"), root.getChild("left"), this.leftPaddle, this.rightPaddle);
if (chest) {
builder.add(root.getChild("chest_bottom"));
builder.add(root.getChild("chest_lid"));
builder.add(root.getChild("chest_lock"));
}
this.parts = builder.build();
}
public static LayerDefinition createBodyModel(boolean chested) {
MeshDefinition mesh = new MeshDefinition();
PartDefinition part = mesh.getRoot();
part.addOrReplaceChild("bottom", CubeListBuilder.create().texOffs(0, 0).addBox(-14.0F, -9.0F, -3.0F, 28.0F, 16.0F, 3.0F), PartPose.offsetAndRotation(0.0F, 3.0F, 1.0F, 1.5707964F, 0.0F, 0.0F));
part.addOrReplaceChild("back", CubeListBuilder.create().texOffs(0, 19).addBox(-13.0F, -7.0F, -1.0F, 18.0F, 6.0F, 2.0F), PartPose.offsetAndRotation(-15.0F, 4.0F, 4.0F, 0.0F, 4.712389F, 0.0F));
part.addOrReplaceChild("front", CubeListBuilder.create().texOffs(0, 27).addBox(-8.0F, -7.0F, -1.0F, 16.0F, 6.0F, 2.0F), PartPose.offsetAndRotation(15.0F, 4.0F, 0.0F, 0.0F, 1.5707964F, 0.0F));
part.addOrReplaceChild("right", CubeListBuilder.create().texOffs(0, 35).addBox(-14.0F, -7.0F, -1.0F, 28.0F, 6.0F, 2.0F), PartPose.offsetAndRotation(0.0F, 4.0F, -9.0F, 0.0F, 3.1415927F, 0.0F));
part.addOrReplaceChild("left", CubeListBuilder.create().texOffs(0, 43).addBox(-14.0F, -7.0F, -1.0F, 28.0F, 6.0F, 2.0F), PartPose.offset(0.0F, 4.0F, 9.0F));
if (chested) {
part.addOrReplaceChild("chest_bottom", CubeListBuilder.create().texOffs(0, 76).addBox(0.0f, 0.0f, 0.0f, 12.0f, 8.0f, 12.0f), PartPose.offsetAndRotation(-2.0f, -5.0f, -6.0f, 0.0f, -1.5707964f, 0.0f));
part.addOrReplaceChild("chest_lid", CubeListBuilder.create().texOffs(0, 59).addBox(0.0f, 0.0f, 0.0f, 12.0f, 4.0f, 12.0f), PartPose.offsetAndRotation(-2.0f, -9.0f, -6.0f, 0.0f, -1.5707964f, 0.0f));
part.addOrReplaceChild("chest_lock", CubeListBuilder.create().texOffs(0, 59).addBox(0.0f, 0.0f, 0.0f, 2.0f, 4.0f, 1.0f), PartPose.offsetAndRotation(-1.0f, -6.0f, -1.0f, 0.0f, -1.5707964f, 0.0f));
}
part.addOrReplaceChild("left_paddle", CubeListBuilder.create().texOffs(62, 0).addBox(-1.0F, 0.0F, -5.0F, 2.0F, 2.0F, 18.0F).addBox(-1.001F, -3.0F, 8.0F, 1.0F, 6.0F, 7.0F), PartPose.offsetAndRotation(3.0F, -5.0F, 9.0F, 0.0F, 0.0F, 0.19634955F));
part.addOrReplaceChild("right_paddle", CubeListBuilder.create().texOffs(62, 20).addBox(-1.0F, 0.0F, -5.0F, 2.0F, 2.0F, 18.0F).addBox(0.001F, -3.0F, 8.0F, 1.0F, 6.0F, 7.0F), PartPose.offsetAndRotation(3.0F, -5.0F, -9.0F, 0.0F, 3.1415927F, 0.19634955F));
part.addOrReplaceChild("water_patch", CubeListBuilder.create().texOffs(0, 0).addBox(-14.0F, -9.0F, -3.0F, 28.0F, 16.0F, 3.0F), PartPose.offsetAndRotation(0.0F, -3.0F, 1.0F, 1.5707964F, 0.0F, 0.0F));
return LayerDefinition.create(mesh, 128, chested ? 128 : 64);
}
@Override
public void setupAnim(MangroveBoat boat, float angle, float distance, float animationProgress, float yaw, float pitch) {
animatePaddle(boat, 0, this.leftPaddle, angle);
animatePaddle(boat, 1, this.rightPaddle, angle);
}
public ImmutableList<ModelPart> parts() {
return this.parts;
}
public ModelPart waterPatch() {
return this.waterPatch;
}
private static void animatePaddle(MangroveBoat boat, int sigma, ModelPart part, float angle) {
float time = boat.getRowingTime(sigma, angle);
part.xRot = Mth.clampedLerp(-1.0471976F, -0.2617994F, (Mth.sin(-time) + 1.0F) / 2.0F);
part.yRot = Mth.clampedLerp(-0.7853982F, 0.7853982F, (Mth.sin(-time + 1.0F) + 1.0F) / 2.0F);
if (sigma == 1) part.yRot = (float)Math.PI - part.yRot;
}
public static ModelLayerLocation createChestBoat(Boat.Type type) {
return new ModelLayerLocation(new ResourceLocation(WildBackport.MOD_ID, "chest_boat/" + type.getName()), "main");
}
}

View File

@ -0,0 +1,7 @@
package com.cursedcauldron.wildbackport.client.render.model;
public interface Drawable {
boolean skipDraw();
void setSkipDraw(boolean set);
}

View File

@ -0,0 +1,79 @@
package com.cursedcauldron.wildbackport.client.render.model;
import com.cursedcauldron.wildbackport.client.animation.FrogAnimations;
import com.cursedcauldron.wildbackport.client.animation.api.Animated;
import com.cursedcauldron.wildbackport.client.animation.api.AnimatedModel;
import com.cursedcauldron.wildbackport.common.entities.Frog;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeDeformation;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.model.geom.builders.PartDefinition;
public class FrogModel<T extends Frog> extends AnimatedModel<T> {
private final ModelPart root;
private final ModelPart body;
private final ModelPart head;
private final ModelPart eyes;
private final ModelPart tongue;
private final ModelPart leftArm;
private final ModelPart rightArm;
private final ModelPart leftLeg;
private final ModelPart rightLeg;
private final ModelPart croakingBody;
public FrogModel(ModelPart root) {
this.root = root.getChild("root");
this.body = this.root.getChild("body");
this.head = this.body.getChild("head");
this.eyes = this.head.getChild("eyes");
this.tongue = this.body.getChild("tongue");
this.leftArm = this.body.getChild("left_arm");
this.rightArm = this.body.getChild("right_arm");
this.leftLeg = this.root.getChild("left_leg");
this.rightLeg = this.root.getChild("right_leg");
this.croakingBody = this.body.getChild("croaking_body");
}
public static LayerDefinition createBodyLayer() {
MeshDefinition mesh = new MeshDefinition();
PartDefinition root = mesh.getRoot();
PartDefinition bone = root.addOrReplaceChild("root", CubeListBuilder.create(), PartPose.offset(0.0f, 24.0f, 0.0f));
PartDefinition body = bone.addOrReplaceChild("body", CubeListBuilder.create().texOffs(3, 1).addBox(-3.5f, -2.0f, -8.0f, 7.0f, 3.0f, 9.0f).texOffs(23, 22).addBox(-3.5f, -1.0f, -8.0f, 7.0f, 0.0f, 9.0f), PartPose.offset(0.0f, -2.0f, 4.0f));
PartDefinition head = body.addOrReplaceChild("head", CubeListBuilder.create().texOffs(23, 13).addBox(-3.5f, -1.0f, -7.0f, 7.0f, 0.0f, 9.0f).texOffs(0, 13).addBox(-3.5f, -2.0f, -7.0f, 7.0f, 3.0f, 9.0f), PartPose.offset(0.0f, -2.0f, -1.0f));
PartDefinition eyes = head.addOrReplaceChild("eyes", CubeListBuilder.create(), PartPose.offset(-0.5f, 0.0f, 2.0f));
eyes.addOrReplaceChild("right_eye", CubeListBuilder.create().texOffs(0, 0).addBox(-1.5f, -1.0f, -1.5f, 3.0f, 2.0f, 3.0f), PartPose.offset(-1.5f, -3.0f, -6.5f));
eyes.addOrReplaceChild("left_eye", CubeListBuilder.create().texOffs(0, 5).addBox(-1.5f, -1.0f, -1.5f, 3.0f, 2.0f, 3.0f), PartPose.offset(2.5f, -3.0f, -6.5f));
body.addOrReplaceChild("croaking_body", CubeListBuilder.create().texOffs(26, 5).addBox(-3.5f, -0.1f, -2.9f, 7.0f, 2.0f, 3.0f, new CubeDeformation(-0.1f)), PartPose.offset(0.0f, -1.0f, -5.0f));
body.addOrReplaceChild("tongue", CubeListBuilder.create().texOffs(17, 13).addBox(-2.0f, 0.0f, -7.1f, 4.0f, 0.0f, 7.0f), PartPose.offset(0.0f, -1.01f, 1.0f));
PartDefinition left_arm = body.addOrReplaceChild("left_arm", CubeListBuilder.create().texOffs(0, 32).addBox(-1.0f, 0.0f, -1.0f, 2.0f, 3.0f, 3.0f), PartPose.offset(4.0f, -1.0f, -6.5f));
left_arm.addOrReplaceChild("left_hand", CubeListBuilder.create().texOffs(18, 40).addBox(-4.0f, 0.01f, -4.0f, 8.0f, 0.0f, 8.0f), PartPose.offset(0.0f, 3.0f, -1.0f));
PartDefinition right_arm = body.addOrReplaceChild("right_arm", CubeListBuilder.create().texOffs(0, 38).addBox(-1.0f, 0.0f, -1.0f, 2.0f, 3.0f, 3.0f), PartPose.offset(-4.0f, -1.0f, -6.5f));
right_arm.addOrReplaceChild("right_hand", CubeListBuilder.create().texOffs(2, 40).addBox(-4.0f, 0.01f, -5.0f, 8.0f, 0.0f, 8.0f), PartPose.offset(0.0f, 3.0f, 0.0f));
PartDefinition left_leg = bone.addOrReplaceChild("left_leg", CubeListBuilder.create().texOffs(14, 25).addBox(-1.0f, 0.0f, -2.0f, 3.0f, 3.0f, 4.0f), PartPose.offset(3.5f, -3.0f, 4.0f));
left_leg.addOrReplaceChild("left_foot", CubeListBuilder.create().texOffs(2, 32).addBox(-4.0f, 0.01f, -4.0f, 8.0f, 0.0f, 8.0f), PartPose.offset(2.0f, 3.0f, 0.0f));
PartDefinition right_leg = bone.addOrReplaceChild("right_leg", CubeListBuilder.create().texOffs(0, 25).addBox(-2.0f, 0.0f, -2.0f, 3.0f, 3.0f, 4.0f), PartPose.offset(-3.5f, -3.0f, 4.0f));
right_leg.addOrReplaceChild("right_foot", CubeListBuilder.create().texOffs(18, 32).addBox(-4.0f, 0.01f, -4.0f, 8.0f, 0.0f, 8.0f), PartPose.offset(-2.0f, 3.0f, 0.0f));
return LayerDefinition.create(mesh, 48, 48);
}
@Override
public void setupAnim(T entity, float angle, float distance, float animationProgress, float yaw, float pitch) {
this.root.getAllParts().forEach(Animated::resetPose);
float speedMultiplier = Math.min((float)entity.getDeltaMovement().lengthSqr() * 200.0F, 8.0F);
this.animate(entity.longJumpingAnimationState, FrogAnimations.LONG_JUMPING, animationProgress);
this.animate(entity.croakingAnimationState, FrogAnimations.CROAKING, animationProgress);
this.animate(entity.usingTongueAnimationState, FrogAnimations.USING_TONGUE, animationProgress);
this.animate(entity.walkingAnimationState, FrogAnimations.WALKING, animationProgress, speedMultiplier);
this.animate(entity.swimmingAnimationState, FrogAnimations.SWIMMING, animationProgress);
this.animate(entity.idlingInWaterAnimationState, FrogAnimations.IDLING_IN_WATER, animationProgress);
this.croakingBody.visible = entity.croakingAnimationState.isRunning();
}
@Override
public ModelPart root() {
return this.root;
}
}

View File

@ -0,0 +1,48 @@
package com.cursedcauldron.wildbackport.client.render.model;
import com.cursedcauldron.wildbackport.common.entities.Tadpole;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.model.AgeableListModel;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.model.geom.builders.PartDefinition;
import net.minecraft.util.Mth;
public class TadpoleModel<T extends Tadpole> extends AgeableListModel<T> {
private final ModelPart root;
private final ModelPart tail;
public TadpoleModel(ModelPart root) {
super(true, 8.0F, 3.35F);
this.root = root;
this.tail = root.getChild("tail");
}
public static LayerDefinition createBodyLayer() {
MeshDefinition mesh = new MeshDefinition();
PartDefinition root = mesh.getRoot();
root.addOrReplaceChild("body", CubeListBuilder.create().texOffs(0, 0).addBox(-1.5F, -1.0F, 0.0F, 3.0F, 2.0F, 3.0F), PartPose.offset(0.0F, 22.0F, -3.0F));
root.addOrReplaceChild("tail", CubeListBuilder.create().texOffs(0, 0).addBox(0.0F, -1.0F, 0.0F, 0.0F, 2.0F, 7.0F), PartPose.offset(0.0F, 22.0F, 0.0F));
return LayerDefinition.create(mesh, 16, 16);
}
@Override
protected Iterable<ModelPart> headParts() {
return ImmutableList.of(this.root);
}
@Override
protected Iterable<ModelPart> bodyParts() {
return ImmutableList.of(this.tail);
}
@Override
public void setupAnim(T entity, float angle, float distance, float animationProgress, float yaw, float pitch) {
float angles = entity.isInWater() ? 1.0F : 1.5F;
this.tail.yRot = -angles * 0.25F * Mth.sin(0.3F * animationProgress);
}
}

View File

@ -0,0 +1,163 @@
package com.cursedcauldron.wildbackport.client.render.model;
import com.cursedcauldron.wildbackport.client.animation.WardenAnimations;
import com.cursedcauldron.wildbackport.client.animation.api.Animated;
import com.cursedcauldron.wildbackport.client.animation.api.AnimatedModel;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.model.geom.builders.PartDefinition;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.util.Mth;
import java.util.List;
//<>
public class WardenModel<T extends Warden> extends AnimatedModel<T> {
private final ModelPart root;
protected final ModelPart bone;
protected final ModelPart body;
protected final ModelPart head;
protected final ModelPart rightTendril;
protected final ModelPart leftTendril;
protected final ModelPart leftLeg;
protected final ModelPart leftArm;
protected final ModelPart leftRibcage;
protected final ModelPart rightArm;
protected final ModelPart rightLeg;
protected final ModelPart rightRibcage;
private final List<ModelPart> tendrils;
private final List<ModelPart> justBody;
private final List<ModelPart> headAndLimbs;
private final List<ModelPart> bodyHeadAndLimbs;
public WardenModel(ModelPart root) {
super(RenderType::entityCutoutNoCull);
this.root = root;
this.bone = root.getChild("bone");
this.body = this.bone.getChild("body");
this.head = this.body.getChild("head");
this.rightLeg = this.bone.getChild("right_leg");
this.leftLeg = this.bone.getChild("left_leg");
this.rightArm = this.body.getChild("right_arm");
this.leftArm = this.body.getChild("left_arm");
this.rightTendril = this.head.getChild("right_tendril");
this.leftTendril = this.head.getChild("left_tendril");
this.rightRibcage = this.body.getChild("right_ribcage");
this.leftRibcage = this.body.getChild("left_ribcage");
this.tendrils = ImmutableList.of(this.leftTendril, this.rightTendril);
this.justBody = ImmutableList.of(this.body);
this.headAndLimbs = ImmutableList.of(this.head, this.leftArm, this.rightArm, this.leftLeg, this.rightLeg);
this.bodyHeadAndLimbs = ImmutableList.of(this.body, this.head, this.leftArm, this.rightArm, this.leftLeg, this.rightLeg);
}
public static LayerDefinition createBodyLayer() {
MeshDefinition mesh = new MeshDefinition();
PartDefinition root = mesh.getRoot();
PartDefinition bone = root.addOrReplaceChild("bone", CubeListBuilder.create(), PartPose.offset(0.0f, 24.0f, 0.0f));
PartDefinition body = bone.addOrReplaceChild("body", CubeListBuilder.create().texOffs(0, 0).addBox(-9.0f, -13.0f, -4.0f, 18.0f, 21.0f, 11.0f), PartPose.offset(0.0f, -21.0f, 0.0f));
body.addOrReplaceChild("right_ribcage", CubeListBuilder.create().texOffs(90, 11).addBox(-2.0f, -11.0f, -0.1f, 9.0f, 21.0f, 0.0f), PartPose.offset(-7.0f, -2.0f, -4.0f));
body.addOrReplaceChild("left_ribcage", CubeListBuilder.create().texOffs(90, 11).mirror().addBox(-7.0f, -11.0f, -0.1f, 9.0f, 21.0f, 0.0f).mirror(false), PartPose.offset(7.0f, -2.0f, -4.0f));
PartDefinition head = body.addOrReplaceChild("head", CubeListBuilder.create().texOffs(0, 32).addBox(-8.0f, -16.0f, -5.0f, 16.0f, 16.0f, 10.0f), PartPose.offset(0.0f, -13.0f, 0.0f));
head.addOrReplaceChild("right_tendril", CubeListBuilder.create().texOffs(52, 32).addBox(-16.0f, -13.0f, 0.0f, 16.0f, 16.0f, 0.0f), PartPose.offset(-8.0f, -12.0f, 0.0f));
head.addOrReplaceChild("left_tendril", CubeListBuilder.create().texOffs(58, 0).addBox(0.0f, -13.0f, 0.0f, 16.0f, 16.0f, 0.0f), PartPose.offset(8.0f, -12.0f, 0.0f));
body.addOrReplaceChild("right_arm", CubeListBuilder.create().texOffs(44, 50).addBox(-4.0f, 0.0f, -4.0f, 8.0f, 28.0f, 8.0f), PartPose.offset(-13.0f, -13.0f, 1.0f));
body.addOrReplaceChild("left_arm", CubeListBuilder.create().texOffs(0, 58).addBox(-4.0f, 0.0f, -4.0f, 8.0f, 28.0f, 8.0f), PartPose.offset(13.0f, -13.0f, 1.0f));
bone.addOrReplaceChild("right_leg", CubeListBuilder.create().texOffs(76, 48).addBox(-3.1f, 0.0f, -3.0f, 6.0f, 13.0f, 6.0f), PartPose.offset(-5.9f, -13.0f, 0.0f));
bone.addOrReplaceChild("left_leg", CubeListBuilder.create().texOffs(76, 76).addBox(-2.9f, 0.0f, -3.0f, 6.0f, 13.0f, 6.0f), PartPose.offset(5.9f, -13.0f, 0.0f));
return LayerDefinition.create(mesh, 128, 128);
}
@Override
public void setupAnim(T entity, float angle, float distance, float animationProgress, float yaw, float pitch) {
this.root.getAllParts().forEach(Animated::resetPose);
float tickDelta = animationProgress - (float)entity.tickCount;
this.setHeadAngle(yaw, pitch);
this.setLimbAngles(angle, distance);
this.setHeadAndBodyAngles(animationProgress);
this.setTendrilPitches(entity, animationProgress, tickDelta);
this.animate(entity.attackingAnimationState, WardenAnimations.ATTACKING, animationProgress);
this.animate(entity.sonicBoomAnimationState, WardenAnimations.SONIC_BOOM, animationProgress);
this.animate(entity.diggingAnimationState, WardenAnimations.DIGGING, animationProgress);
this.animate(entity.emergingAnimationState, WardenAnimations.EMERGING, animationProgress);
this.animate(entity.roaringAnimationState, WardenAnimations.ROARING, animationProgress);
this.animate(entity.sniffingAnimationState, WardenAnimations.SNIFFING, animationProgress);
}
private void setHeadAngle(float yaw, float pitch) {
this.head.xRot = pitch * ((float)Math.PI / 180);
this.head.yRot = yaw * ((float)Math.PI / 180);
}
private void setHeadAndBodyAngles(float animationProgress) {
float angle = animationProgress * 0.1F;
float cos = Mth.cos(angle);
float sin = Mth.sin(angle);
this.head.zRot += 0.06F * cos;
this.head.xRot += 0.06F * sin;
this.body.zRot += 0.025F * sin;
this.body.xRot += 0.025F * cos;
}
private void setLimbAngles(float angle, float distance) {
float roll = Math.min(0.5F, 3.0F * distance);
float mod = angle * 0.8662F;
float cos = Mth.cos(mod);
float sin = Mth.sin(mod);
float pitch = Math.min(0.35F, roll);
this.head.zRot += 0.3F * sin * roll;
this.head.xRot += 1.2F * Mth.cos(mod + 1.5707964F) * pitch;
this.body.zRot = 0.1F * sin * roll;
this.body.xRot = 1.0F * cos * pitch;
this.leftLeg.xRot = 1.0F * cos * roll;
this.rightLeg.xRot = 1.0F * Mth.cos(mod + (float)Math.PI) * roll;
this.leftArm.xRot = -(0.8F * cos * roll);
this.leftArm.zRot = 0.0F;
this.rightArm.xRot = -(0.8F * sin * roll);
this.rightArm.zRot = 0.0F;
this.setArmPivots();
}
private void setArmPivots() {
this.leftArm.yRot = 0.0F;
this.leftArm.z = 1.0F;
this.leftArm.x = 13.0F;
this.leftArm.y = -13.0F;
this.rightArm.yRot = 0.0F;
this.rightArm.z = 1.0F;
this.rightArm.x = -13.0F;
this.rightArm.y = -13.0F;
}
private void setTendrilPitches(T warden, float animationProgress, float tickDelta) {
float pitch = warden.getTendrilPitch(tickDelta) * (float)(Math.cos((double)animationProgress * 2.25D) * Math.PI * (double)0.1F);
this.leftTendril.xRot = pitch;
this.rightTendril.xRot = -pitch;
}
@Override
public ModelPart root() {
return this.root;
}
public List<ModelPart> getTendrils() {
return this.tendrils;
}
public List<ModelPart> getBody() {
return this.justBody;
}
public List<ModelPart> getHeadAndLimbs() {
return this.headAndLimbs;
}
public List<ModelPart> getBodyHeadAndLimbs() {
return this.bodyHeadAndLimbs;
}
}

View File

@ -0,0 +1,49 @@
package com.cursedcauldron.wildbackport.client.sound;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.level.block.SoundType;
import java.util.function.Supplier;
public class CoreSoundType extends SoundType {
private final Supplier<SoundEvent> breakSound;
private final Supplier<SoundEvent> stepSound;
private final Supplier<SoundEvent> placeSound;
private final Supplier<SoundEvent> hitSound;
private final Supplier<SoundEvent> fallSound;
@SuppressWarnings("all")
public CoreSoundType(Supplier<SoundEvent> breakSound, Supplier<SoundEvent> stepSound, Supplier<SoundEvent> placeSound, Supplier<SoundEvent> hitSound, Supplier<SoundEvent> fallSound) {
super(1.0F, 1.0F, null, null, null, null, null);
this.breakSound = breakSound;
this.stepSound = stepSound;
this.placeSound = placeSound;
this.hitSound = hitSound;
this.fallSound = fallSound;
}
@Override
public SoundEvent getBreakSound() {
return this.breakSound.get();
}
@Override
public SoundEvent getStepSound() {
return this.stepSound.get();
}
@Override
public SoundEvent getPlaceSound() {
return this.placeSound.get();
}
@Override
public SoundEvent getHitSound() {
return this.hitSound.get();
}
@Override
public SoundEvent getFallSound() {
return this.fallSound.get();
}
}

View File

@ -0,0 +1,30 @@
package com.cursedcauldron.wildbackport.common;
import com.cursedcauldron.wildbackport.common.entities.Allay;
import com.cursedcauldron.wildbackport.common.entities.Frog;
import com.cursedcauldron.wildbackport.common.entities.Tadpole;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.events.StructureEvent;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import com.cursedcauldron.wildbackport.core.api.MobRegistry;
public class CommonSetup {
/**
* Runs features at initializing
*/
public static void onCommon() {
// Entity Attributes
MobRegistry.registerAttributes(WBEntities.ALLAY, Allay::createAttributes);
MobRegistry.registerAttributes(WBEntities.FROG, Frog::createAttributes);
MobRegistry.registerAttributes(WBEntities.TADPOLE, Tadpole::createAttributes);
MobRegistry.registerAttributes(WBEntities.WARDEN, Warden::createAttributes);
StructureEvent.bootstrap();
}
/**
* Runs features post bootstrap
*/
public static void onPostClient() {
StructureEvent.bootstrap();
}
}

View File

@ -0,0 +1,37 @@
package com.cursedcauldron.wildbackport.common.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
public class BlockProperties {
// Block Properties
public static final BooleanProperty SHRIEKING = BooleanProperty.create("shrieking");
public static final BooleanProperty CAN_SUMMON = BooleanProperty.create("can_summon");
public static final BooleanProperty BLOOM = BooleanProperty.create("bloom");
public static final IntegerProperty AGE_4 = IntegerProperty.create("age", 0, 4);
// Block values
public static boolean always(BlockState state, BlockGetter getter, BlockPos pos, EntityType<?> type) {
return true;
}
public static boolean never(BlockState state, BlockGetter getter, BlockPos pos, EntityType<?> type) {
return false;
}
public static boolean ocelotOrParrot(BlockState state, BlockGetter getter, BlockPos pos, EntityType<?> entity) {
return entity == EntityType.OCELOT || entity == EntityType.PARROT;
}
public static boolean always(BlockState state, BlockGetter getter, BlockPos pos) {
return true;
}
public static boolean never(BlockState state, BlockGetter getter, BlockPos pos) {
return true;
}
}

View File

@ -0,0 +1,114 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.Tadpole;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
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.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
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.state.BlockState;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import java.util.Random;
//<>
public class FrogspawnBlock extends Block {
protected static final VoxelShape SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 1.5D, 16.0D);
private static final int MIN_HATCH_TIME = 3600;
private static final int MAX_HATCH_TIME = 12000;
public FrogspawnBlock(Properties properties) {
super(properties);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter getter, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
public boolean canSurvive(BlockState state, LevelReader reader, BlockPos pos) {
return mayPlaceOn(reader, pos.below());
}
@Override
public void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean notify) {
level.scheduleTick(pos, this, hatchTime(level.getRandom()));
}
private static int hatchTime(Random random) {
return random.nextInt(MIN_HATCH_TIME, MAX_HATCH_TIME);
}
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor access, BlockPos pos, BlockPos neighborPos) {
return !this.canSurvive(state, access, pos) ? Blocks.AIR.defaultBlockState() : super.updateShape(state, direction, neighborState, access, pos, neighborPos);
}
@Override
public void tick(BlockState state, ServerLevel level, BlockPos pos, Random random) {
if (!this.canSurvive(state, level, pos)) {
this.hatch(level, pos);
} else {
this.onHatch(level, pos, random);
}
}
@Override
public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
if (entity.getType().equals(EntityType.FALLING_BLOCK)) {
this.hatch(level, pos);
}
}
private static boolean mayPlaceOn(LevelReader reader, BlockPos pos) {
FluidState fluidState = reader.getFluidState(pos);
FluidState topFluidState = reader.getFluidState(pos.above());
return fluidState.getType() == Fluids.WATER && topFluidState.getType() == Fluids.EMPTY;
}
private void onHatch(ServerLevel level, BlockPos pos, Random random) {
this.hatch(level, pos);
level.playSound(null, pos, WBSoundEvents.BLOCK_FROGSPAWN_HATCH, SoundSource.BLOCKS, 1.0F, 1.0F);
this.createTadpole(level, pos, random);
}
private void hatch(Level level, BlockPos blockPos) {
level.destroyBlock(blockPos, false);
}
private void createTadpole(ServerLevel level, BlockPos pos, Random random) {
int i = random.nextInt(2, 6);
for (int index = 1; index <= i; ++index) {
Tadpole tadpole = WBEntities.TADPOLE.get().create(level);
if (tadpole != null) {
double x = (double)pos.getX() + this.getSpawnOffset(random);
double z = (double)pos.getZ() + this.getSpawnOffset(random);
int yaw = random.nextInt(1, 361);
tadpole.moveTo(x, (double)pos.getY() - 0.5, z, yaw, 0.0F);
tadpole.setPersistenceRequired();
level.addFreshEntity(tadpole);
}
}
}
private double getSpawnOffset(Random random) {
double d = Tadpole.WIDTH / 2.0F;
return Mth.clamp(random.nextDouble(), d, 1.0 - d);
}
}

View File

@ -0,0 +1,34 @@
package com.cursedcauldron.wildbackport.common.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Random;
//<>
public class MangroveLeavesBlock extends LeavesBlock implements BonemealableBlock {
public MangroveLeavesBlock(Properties properties) {
super(properties);
}
@Override
public boolean isValidBonemealTarget(BlockGetter getter, BlockPos pos, BlockState state, boolean bl) {
return getter.getBlockState(pos.below()).isAir();
}
@Override
public boolean isBonemealSuccess(Level level, Random random, BlockPos pos, BlockState state) {
return true;
}
@Override
public void performBonemeal(ServerLevel level, Random random, BlockPos pos, BlockState state) {
level.setBlock(pos.below(), MangrovePropaguleBlock.createPropagule(), 2);
}
}

View File

@ -0,0 +1,130 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
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.SaplingBlock;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
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.IntegerProperty;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
//<>
public class MangrovePropaguleBlock extends SaplingBlock implements SimpleWaterloggedBlock {
private static final VoxelShape[] SHAPES = new VoxelShape[]{Block.box(7.0D, 13.0D, 7.0D, 9.0D, 16.0D, 9.0D), Block.box(7.0D, 10.0D, 7.0D, 9.0D, 16.0D, 9.0D), Block.box(7.0D, 7.0D, 7.0D, 9.0D, 16.0D, 9.0D), Block.box(7.0D, 3.0D, 7.0D, 9.0D, 16.0D, 9.0D), Block.box(7.0D, 0.0D, 7.0D, 9.0D, 16.0D, 9.0D)};
public static final IntegerProperty AGE = BlockProperties.AGE_4;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public static final BooleanProperty HANGING = BlockStateProperties.HANGING;
public MangrovePropaguleBlock(Properties properties) {
super(new MangroveTreeGrower(0.85F), properties);
this.registerDefaultState(this.stateDefinition.any().setValue(STAGE, 0).setValue(AGE, 0).setValue(WATERLOGGED, false).setValue(HANGING, false));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(STAGE, AGE, WATERLOGGED, HANGING);
}
@Override
protected boolean mayPlaceOn(BlockState state, BlockGetter getter, BlockPos pos) {
return super.mayPlaceOn(state, getter, pos) || state.is(Blocks.CLAY);
}
@Override @Nullable
public BlockState getStateForPlacement(BlockPlaceContext context) {
FluidState fluidState = context.getLevel().getFluidState(context.getClickedPos());
return super.getStateForPlacement(context).setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER).setValue(AGE, 4);
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter getter, BlockPos pos, CollisionContext context) {
Vec3 offset = state.getOffset(getter, pos);
VoxelShape shape = !state.getValue(HANGING) ? SHAPES[4] : SHAPES[state.getValue(AGE)];
return shape.move(offset.x, offset.y, offset.z);
}
@Override
public boolean canSurvive(BlockState state, LevelReader reader, BlockPos pos) {
return isHanging(state) ? reader.getBlockState(pos.above()).is(WBBlocks.MANGROVE_LEAVES.get()) : super.canSurvive(state, reader, pos);
}
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor accessor, BlockPos pos, BlockPos neighborPos) {
if (state.getValue(WATERLOGGED)) accessor.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(accessor));
return direction == Direction.UP && !state.canSurvive(accessor, pos) ? Blocks.AIR.defaultBlockState() : super.updateShape(state, direction, neighborState, accessor, pos, neighborPos);
}
@Override
public OffsetType getOffsetType() {
return OffsetType.XZ;
}
@Override
public FluidState getFluidState(BlockState state) {
return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state);
}
@Override
public void randomTick(BlockState state, ServerLevel level, BlockPos pos, Random random) {
if (!isHanging(state)) {
if (random.nextInt(7) == 0) this.advanceTree(level, pos, state, random);
} else {
if (!ageAtMax(state)) level.setBlock(pos, state.cycle(AGE), 2);
}
}
@Override
public boolean isValidBonemealTarget(BlockGetter getter, BlockPos pos, BlockState state, boolean flag) {
return !isHanging(state) || !ageAtMax(state);
}
@Override
public boolean isBonemealSuccess(Level level, Random random, BlockPos pos, BlockState state) {
return isHanging(state) ? !ageAtMax(state) : super.isBonemealSuccess(level, random, pos, state);
}
@Override
public void performBonemeal(ServerLevel level, Random random, BlockPos pos, BlockState state) {
if (isHanging(state) && !ageAtMax(state)) {
level.setBlock(pos, state.cycle(AGE), 2);
} else {
super.performBonemeal(level, random, pos, state);
}
}
private static boolean isHanging(BlockState state) {
return state.getValue(HANGING);
}
private static boolean ageAtMax(BlockState state) {
return state.getValue(AGE) == 4;
}
public static BlockState createPropagule() {
return createPropagule(0);
}
public static BlockState createPropagule(int age) {
return WBBlocks.MANGROVE_PROPAGULE.get().defaultBlockState().setValue(HANGING, true).setValue(AGE, age);
}
}

View File

@ -0,0 +1,53 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
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.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.Nullable;
public class MangroveRootsBlock extends Block implements SimpleWaterloggedBlock {
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public MangroveRootsBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(WATERLOGGED, false));
}
@Override
public boolean skipRendering(BlockState state, BlockState stateFrom, Direction direction) {
return stateFrom.is(WBBlocks.MANGROVE_ROOTS.get()) && direction.getAxis() == Direction.Axis.Y;
}
@Override @Nullable
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
FluidState state = ctx.getLevel().getFluidState(ctx.getClickedPos());
return super.getStateForPlacement(ctx).setValue(WATERLOGGED, state.getType() == Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor accessor, BlockPos pos, BlockPos neighborPos) {
if (state.getValue(WATERLOGGED)) accessor.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(accessor));
return super.updateShape(state, direction, neighborState, accessor, pos, neighborPos);
}
@Override
public FluidState getFluidState(BlockState state) {
return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(WATERLOGGED);
}
}

View File

@ -0,0 +1,21 @@
package com.cursedcauldron.wildbackport.common.blocks;
import net.minecraft.core.Holder;
import net.minecraft.world.level.block.grower.AbstractTreeGrower;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
public class MangroveTreeGrower extends AbstractTreeGrower {
private final float tallMangroveChance;
public MangroveTreeGrower(float tallMangroveChance) {
this.tallMangroveChance = tallMangroveChance;
}
@Override @Nullable
protected Holder<? extends ConfiguredFeature<?, ?>> getConfiguredFeature(Random random, boolean bl) {
return null;
}
}

View File

@ -0,0 +1,43 @@
package com.cursedcauldron.wildbackport.common.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
public class MudBlock extends Block {
private static final VoxelShape SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 14.0D, 16.0D);
public MudBlock(Properties properties) {
super(properties);
}
@Override
public VoxelShape getBlockSupportShape(BlockState state, BlockGetter getter, BlockPos pos) {
return Shapes.block();
}
@Override
public VoxelShape getVisualShape(BlockState state, BlockGetter getter, BlockPos pos, CollisionContext context) {
return Shapes.block();
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter getter, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
public boolean isPathfindable(BlockState state, BlockGetter getter, BlockPos pos, PathComputationType pathComputation) {
return false;
}
@Override
public float getShadeBrightness(BlockState state, BlockGetter getter, BlockPos pos) {
return 0.2F;
}
}

View File

@ -0,0 +1,93 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.OreBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.material.Fluids;
import java.util.Random;
public class SculkBlock extends OreBlock implements SculkSpreadable {
public SculkBlock(Properties properties) {
super(properties);
}
@Override
public int spread(SculkSpreadManager.Cursor cursor, LevelAccessor level, BlockPos pos, Random random, SculkSpreadManager manager, boolean shouldConvert) {
int charge = cursor.getCharge();
if (charge != 0 && random.nextInt(manager.getSpreadChance()) == 0) {
BlockPos blockPos = cursor.getPos();
boolean inRange = blockPos.closerThan(pos, manager.getMaxDistance());
if (!inRange && shouldNotDecay(level, blockPos)) {
int chance = manager.getExtraBlockChance();
if (random.nextInt(chance) < charge) {
BlockPos growthPos = blockPos.above();
BlockState state = this.getExtraBlockState(level, growthPos, random, manager.isWorldGen());
level.setBlock(growthPos, state, 3);
level.playSound(null, blockPos, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
}
return Math.max(0, charge - chance);
} else {
return random.nextInt(manager.getDecayChance()) != 0 ? charge : charge - (inRange ? 1 : getDecay(manager, blockPos, pos, charge));
}
} else {
return charge;
}
}
private static int getDecay(SculkSpreadManager manager, BlockPos source, BlockPos target, int charge) {
int maxDistance = manager.getMaxDistance();
float range = Mth.square((float)Math.sqrt(source.distSqr(target)) - (float)maxDistance);
int distance = Mth.square(24 - maxDistance);
float spread = Math.min(1.0F, range / (float)distance);
return Math.max(1, (int)((float)charge * spread * 0.5F));
}
private BlockState getExtraBlockState(LevelAccessor level, BlockPos pos, Random random, boolean isWorldGen) {
BlockState state = random.nextInt(11) == 0 ? WBBlocks.SCULK_SHRIEKER.get().defaultBlockState().setValue(SculkShriekerBlock.CAN_SUMMON, isWorldGen) : Blocks.SCULK_SENSOR.defaultBlockState();
return state.hasProperty(BlockStateProperties.WATERLOGGED) && !level.getFluidState(pos).isEmpty() ? state.setValue(BlockStateProperties.WATERLOGGED, true) : state;
}
private static boolean shouldNotDecay(LevelAccessor level, BlockPos pos) {
BlockState state = level.getBlockState(pos.above());
if (state.isAir() || state.is(Blocks.WATER) && state.getFluidState().is(Fluids.WATER)) {
int chance = 0;
for (BlockPos position : BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 2, 4))) {
BlockState growth = level.getBlockState(position);
if (growth.is(Blocks.SCULK_SENSOR) || growth.is(WBBlocks.SCULK_SHRIEKER.get())) {
++chance;
}
if (chance > 2) {
return false;
}
}
return true;
} else {
return false;
}
}
@Override
public void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack) {
if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, stack) == 0) this.popExperience(level, pos, 1);
}
@Override
public boolean shouldConvertToSpreadable() {
return true;
}
}

View File

@ -0,0 +1,82 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.client.registry.WBParticleTypes;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.blocks.entity.SculkCatalystBlockEntity;
import com.cursedcauldron.wildbackport.common.registry.WBBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
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.level.gameevent.GameEventListener;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
//<>
public class SculkCatalystBlock extends BaseEntityBlock {
public static final BooleanProperty BLOOM = BlockProperties.BLOOM;
public SculkCatalystBlock(Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(BLOOM, false));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(BLOOM);
}
@Override
public void tick(BlockState state, ServerLevel level, BlockPos pos, Random random) {
if (state.getValue(BLOOM)) level.setBlock(pos, state.setValue(BLOOM, false), 3);
}
public static void bloom(ServerLevel level, BlockPos pos, BlockState state, Random random) {
level.setBlock(pos, state.setValue(BLOOM, true), 3);
level.scheduleTick(pos, state.getBlock(), 8);
level.sendParticles(WBParticleTypes.SCULK_SOUL.get(), (double)pos.getX() + 0.5D, (double)pos.getY() + 1.15D, (double)pos.getZ() + 0.5D, 2, 0.2D, 0.0D, 0.2D, 0.0D);
level.playSound(null, pos, WBSoundEvents.BLOCK_SCULK_CATALYST_BLOOM, SoundSource.BLOCKS, 2.0F, 0.6F + random.nextFloat() * 0.4F);
}
@Nullable @Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new SculkCatalystBlockEntity(pos, state);
}
@Nullable @Override
public <T extends BlockEntity> GameEventListener getListener(Level level, T type) {
return type instanceof SculkCatalystBlockEntity catalyst ? catalyst : null;
}
@Nullable @Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
return level.isClientSide ? null : createTickerHelper(type, WBBlockEntities.SCULK_CATALYST.get(), SculkCatalystBlockEntity::tick);
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Override
public void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack) {
super.spawnAfterBreak(state, level, pos, stack);
if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, stack) == 0) {
this.popExperience(level, pos, 20);
}
}
}

View File

@ -0,0 +1,144 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.common.blocks.entity.SculkShriekerBlockEntity;
import com.cursedcauldron.wildbackport.common.registry.WBBlockEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
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.gameevent.GameEventListener;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
//<>
public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterloggedBlock {
public static final BooleanProperty SHRIEKING = BlockProperties.SHRIEKING;
public static final BooleanProperty CAN_SUMMON = BlockProperties.CAN_SUMMON;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
private static final VoxelShape SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 8.0D, 16.0D);
public static final double TOP_Y = SHAPE.max(Direction.Axis.Y);
public SculkShriekerBlock(Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any().setValue(SHRIEKING, false).setValue(WATERLOGGED, false).setValue(CAN_SUMMON, false));
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(SHRIEKING);
builder.add(WATERLOGGED);
builder.add(CAN_SUMMON);
}
@Override
public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
if (level instanceof ServerLevel server) {
ServerPlayer player = SculkShriekerBlockEntity.tryGetPlayer(entity);
if (player != null) {
server.getBlockEntity(pos, WBBlockEntities.SCULK_SHRIEKER.get()).ifPresent(shrieker -> shrieker.tryShriek(server, player));
}
}
super.stepOn(level, pos, state, entity);
}
@Override
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean moving) {
if (level instanceof ServerLevel server) {
if (state.getValue(SHRIEKING) && !state.is(newState.getBlock())) {
server.getBlockEntity(pos, WBBlockEntities.SCULK_SHRIEKER.get()).ifPresent(shrieker -> shrieker.tryRespond(server));
}
}
super.onRemove(state, level, pos, newState, moving);
}
@Override
public void tick(BlockState state, ServerLevel level, BlockPos pos, Random random) {
if (state.getValue(SHRIEKING)) {
level.setBlock(pos, state.setValue(SHRIEKING, false), 3);
level.getBlockEntity(pos, WBBlockEntities.SCULK_SHRIEKER.get()).ifPresent(shrieker -> shrieker.tryRespond(level));
}
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter getter, BlockPos pos, CollisionContext context) {
return SHAPE;
}
@Override
public VoxelShape getOcclusionShape(BlockState state, BlockGetter getter, BlockPos pos) {
return SHAPE;
}
@Override
public boolean useShapeForLightOcclusion(BlockState state) {
return true;
}
@Nullable @Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return new SculkShriekerBlockEntity(pos, state);
}
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState newState, LevelAccessor level, BlockPos pos, BlockPos newPos) {
if (state.getValue(WATERLOGGED)) level.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(level));
return super.updateShape(state, direction, newState, level, pos, newPos);
}
@Nullable @Override
public BlockState getStateForPlacement(BlockPlaceContext context) {
return this.defaultBlockState().setValue(WATERLOGGED, context.getLevel().getFluidState(context.getClickedPos()).getType() == Fluids.WATER);
}
@Override
public FluidState getFluidState(BlockState state) {
return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state);
}
@Override
public void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack) {
if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, stack) == 0) this.popExperience(level, pos, 5);
}
@Nullable @Override
public <T extends BlockEntity> GameEventListener getListener(Level level, T type) {
return type instanceof SculkShriekerBlockEntity shrieker ? shrieker.getListener() : null;
}
@Nullable @Override
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> type) {
return !level.isClientSide ? createTickerHelper(type, WBBlockEntities.SCULK_SHRIEKER.get(), (levelIn, posIn, stateIn, shrieker) -> shrieker.getListener().tick(levelIn)) : null;
}
}

View File

@ -0,0 +1,388 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.particle.SculkChargeParticleOptions;
import com.cursedcauldron.wildbackport.client.registry.WBParticleTypes;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.tag.WBBlockTags;
import com.cursedcauldron.wildbackport.common.utils.DirectionUtils;
import com.cursedcauldron.wildbackport.common.utils.ModUtils;
import com.cursedcauldron.wildbackport.common.utils.ParticleUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Dynamic;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.Util;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.level.ChunkPos;
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.state.BlockState;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.function.Supplier;
public class SculkSpreadManager {
final boolean isWorldGen;
private final TagKey<Block> replaceableBlocks;
private final int extraBlockChance;
private final int maxDistance;
private final int spreadChance;
private final int decayChance;
private List<Cursor> cursors = new ArrayList<>();
public SculkSpreadManager(boolean isWorldGen, TagKey<Block> replaceableBlocks, int extraBlockChance, int maxDistance, int spreadChance, int decayChance) {
this.isWorldGen = isWorldGen;
this.replaceableBlocks = replaceableBlocks;
this.extraBlockChance = extraBlockChance;
this.maxDistance = maxDistance;
this.spreadChance = spreadChance;
this.decayChance = decayChance;
}
public static SculkSpreadManager create() {
return new SculkSpreadManager(false, WBBlockTags.SCULK_REPLACEABLE, 10, 4, 10, 5);
}
public static SculkSpreadManager createWorldGen() {
return new SculkSpreadManager(true, WBBlockTags.SCULK_REPLACEABLE_WORLD_GEN, 50, 1, 5, 10);
}
public TagKey<Block> getReplaceableBlocks() {
return this.replaceableBlocks;
}
public int getExtraBlockChance() {
return this.extraBlockChance;
}
public int getMaxDistance() {
return this.maxDistance;
}
public int getSpreadChance() {
return this.spreadChance;
}
public int getDecayChance() {
return this.decayChance;
}
public boolean isWorldGen() {
return this.isWorldGen;
}
public void clearCursors() {
this.cursors.clear();
}
public void readTag(CompoundTag tag) {
if (tag.contains("cursors", 9)) {
this.cursors.clear();
List<Cursor> cursors = Cursor.CODEC.listOf().parse(new Dynamic<>(NbtOps.INSTANCE, tag.getList("cursors", 10))).resultOrPartial(WildBackport.LOGGER::error).orElseGet(ArrayList::new);
int size = Math.min(cursors.size(), 32);
for (int i = 0; i < size; i++) {
this.addCursor(cursors.get(i));
}
}
}
public void writeTag(CompoundTag tag) {
Cursor.CODEC.listOf().encodeStart(NbtOps.INSTANCE, this.cursors).resultOrPartial(WildBackport.LOGGER::error).ifPresent(value -> {
tag.put("cursors", value);
});
}
public void spread(BlockPos pos, int charge) {
while (charge > 0) {
int spread = Math.min(charge, 1000);
this.addCursor(new Cursor(pos, spread));
charge -= spread;
}
}
private void addCursor(Cursor cursor) {
if (this.cursors.size() < 32) {
this.cursors.add(cursor);
}
}
public void tick(LevelAccessor level, BlockPos pos, Random random, boolean shouldConvert) {
Level instance = level instanceof Level side ? side : null;
if (!this.cursors.isEmpty()) {
List<Cursor> cursors = new ArrayList<>();
Map<BlockPos, Cursor> cursorPositions = new HashMap<>();
Object2IntMap<BlockPos> positions = new Object2IntOpenHashMap<>();
for (Cursor cursor : this.cursors) {
cursor.spread(level, pos, random, this, shouldConvert);
if (cursor.charge <= 0) {
applySculkCharge(instance, cursor.getPos(), 0);
} else {
BlockPos position = cursor.getPos();
positions.computeInt(position, (blockPos, charge) -> (charge == null ? 0 : charge) + cursor.charge);
Cursor target = cursorPositions.get(position);
if (target == null) {
cursorPositions.put(position, cursor);
cursors.add(cursor);
} else if (!this.isWorldGen() && cursor.charge + target.charge <= 1000) {
target.merge(cursor);
} else {
cursors.add(cursor);
if (cursor.charge < target.charge) {
cursorPositions.put(position, cursor);
}
}
}
}
for (Object2IntMap.Entry<BlockPos> entry : positions.object2IntEntrySet()) {
BlockPos position = entry.getKey();
int exp = entry.getIntValue();
Cursor cursor = cursorPositions.get(position);
Set<Direction> directions = cursor == null ? null : cursor.getFacings();
if (exp > 0 && directions != null) {
int charge = (int)(Math.log1p(exp) / (double)2.3F) + 1;
int data = (charge << 6) + SculkVeinBlock.directionsToFlag(directions);
applySculkCharge(instance, cursor.getPos(), data);
}
}
this.cursors = cursors;
}
}
public static class Cursor {
private static final ObjectArrayList<Vec3i> OFFSETS = Util.make(new ObjectArrayList<>(18), positions -> {
BlockPos.betweenClosedStream(new BlockPos(-1, -1, -1), new BlockPos(1, 1, 1)).filter(pos -> {
return (pos.getX() == 0 || pos.getY() == 0 || pos.getZ() == 0) && pos != BlockPos.ZERO;
}).map(BlockPos::immutable).forEach(positions::add);
});
private BlockPos pos;
private int charge;
private int updateDelay;
private int decayDelay;
@Nullable
private Set<Direction> facings;
private static final Codec<Set<Direction>> DIRECTION_SET = Direction.CODEC.listOf().xmap(directions -> Sets.newEnumSet(directions, Direction.class), Lists::newArrayList);
public static final Codec<Cursor> CODEC = RecordCodecBuilder.create(instance -> {
return instance.group(BlockPos.CODEC.fieldOf("pos").forGetter(Cursor::getPos), Codec.intRange(0, 1000).fieldOf("charge").orElse(0).forGetter(Cursor::getCharge), Codec.intRange(0, 1).fieldOf("decay_delay").orElse(1).forGetter(Cursor::getDecayDelay), Codec.intRange(0, Integer.MAX_VALUE).fieldOf("update_delay").orElse(0).forGetter(cursor -> {
return cursor.updateDelay;
}), DIRECTION_SET.optionalFieldOf("facings").forGetter(cursor -> {
return Optional.ofNullable(cursor.getFacings());
})).apply(instance, Cursor::new);
});
private Cursor(BlockPos pos, int charge, int decayDelay, int updateDelay, Optional<Set<Direction>> facings) {
this.pos = pos;
this.charge = charge;
this.decayDelay = decayDelay;
this.updateDelay = updateDelay;
this.facings = facings.orElse(null);
}
public Cursor(BlockPos pos, int charge) {
this(pos, charge, 1, 0, Optional.empty());
}
public BlockPos getPos() {
return this.pos;
}
public int getCharge() {
return this.charge;
}
public int getDecayDelay() {
return this.decayDelay;
}
@Nullable
public Set<Direction> getFacings() {
return this.facings;
}
private boolean canSpread(LevelAccessor level, BlockPos pos, boolean isWorldGen) {
if (this.charge <= 0) {
return false;
} else if (isWorldGen) {
return true;
} else if (level instanceof ServerLevel server) {
return server.shouldTickBlocksAt(ChunkPos.asLong(pos));
} else {
return false;
}
}
public void spread(LevelAccessor level, BlockPos pos, Random random, SculkSpreadManager spreadManager, boolean shouldConvert) {
if (this.canSpread(level, pos, spreadManager.isWorldGen)) {
if (this.updateDelay > 0) {
--this.updateDelay;
} else {
BlockState state = level.getBlockState(this.pos);
SculkSpreadable spreadable = getSpreadable(state);
if (shouldConvert && spreadable.spread(level, this.pos, state, this.facings, spreadManager.isWorldGen())) {
if (spreadable.shouldConvertToSpreadable()) {
state = level.getBlockState(this.pos);
spreadable = getSpreadable(state);
}
level.playSound(null, this.pos, WBSoundEvents.BLOCK_SCULK_BREAK, SoundSource.BLOCKS, 1.0F, 1.0F);
}
this.charge = spreadable.spread(this, level, pos, random, spreadManager, shouldConvert);
if (this.charge <= 0) {
spreadable.spreadAtSamePosition(level, state, this.pos, random);
} else {
BlockPos target = getSpreadPos(level, this.pos, random);
if (target != null) {
spreadable.spreadAtSamePosition(level, state, this.pos, random);
this.pos = target.immutable();
if (spreadManager.isWorldGen() && !this.pos.closerThan(new Vec3i(pos.getX(), this.pos.getY(), pos.getZ()), 15.0D)) {
this.charge = 0;
return;
}
state = level.getBlockState(target);
}
if (state.getBlock() instanceof SculkSpreadable) {
this.facings = SculkVeinBlock.collectDirections(state);
}
this.decayDelay = spreadable.getDecay(this.decayDelay);
this.updateDelay = spreadable.getUpdate();
}
}
}
}
void merge(Cursor cursor) {
this.charge += cursor.charge;
cursor.charge = 0;
this.updateDelay = Math.min(this.updateDelay, cursor.updateDelay);
}
private static SculkSpreadable getSpreadable(BlockState state) {
return state.getBlock() instanceof SculkSpreadable spreadable ? spreadable : SculkSpreadable.DEFAULT;
}
private static List<Vec3i> shuffleOffsets(Random random) {
return ModUtils.copyShuffled(OFFSETS, random);
}
@Nullable
private static BlockPos getSpreadPos(LevelAccessor level, BlockPos pos, Random random) {
BlockPos.MutableBlockPos target = pos.mutable();
BlockPos.MutableBlockPos source = pos.mutable();
for (Vec3i offset : shuffleOffsets(random)) {
source.setWithOffset(pos, offset);
BlockState state = level.getBlockState(source);
if (state.getBlock() instanceof SculkSpreadable && canSpread(level, pos, source)) {
target.set(source);
if (SculkVeinBlock.veinCoversSculkReplaceable(level, state, source)) {
break;
}
}
}
return target.equals(pos) ? null : target;
}
private static boolean canSpread(LevelAccessor level, BlockPos source, BlockPos target) {
if (source.distManhattan(target) == 1) {
return true;
} else {
BlockPos pos = target.subtract(target);
Direction xAxis = Direction.fromAxisAndDirection(Direction.Axis.X, pos.getX() < 0 ? Direction.AxisDirection.NEGATIVE : Direction.AxisDirection.POSITIVE);
Direction yAxis = Direction.fromAxisAndDirection(Direction.Axis.Y, pos.getY() < 0 ? Direction.AxisDirection.NEGATIVE : Direction.AxisDirection.POSITIVE);
Direction zAxis = Direction.fromAxisAndDirection(Direction.Axis.Z, pos.getZ() < 0 ? Direction.AxisDirection.NEGATIVE : Direction.AxisDirection.POSITIVE);
if (pos.getX() == 0) {
return canSpread(level, source, yAxis) || canSpread(level, source, zAxis);
} else if (pos.getY() == 0) {
return canSpread(level, source, xAxis) || canSpread(level, source, zAxis);
} else {
return canSpread(level, source, xAxis) || canSpread(level, source, yAxis);
}
}
}
private static boolean canSpread(LevelAccessor level, BlockPos pos, Direction direction) {
BlockPos facing = pos.relative(direction);
return !level.getBlockState(facing).isFaceSturdy(level, facing, direction.getOpposite());
}
}
public static void applySculkCharge(Level level, BlockPos pos, int data) {
if (level == null) return;
Random random = level.getRandom();
ClientLevel client = level instanceof ClientLevel side ? side : null;
ServerLevel server = level instanceof ServerLevel side ? side : null;
int charge = data >> 6;
if (charge > 0) {
if (random.nextFloat() < (float)charge * 0.2F) {
float volume = 0.15F + 0.05F * (float)charge * (float)charge * random.nextFloat();
float pitch = 0.4F * (float)charge - 0.2F * random.nextFloat();
if (client != null) client.playLocalSound(pos, WBSoundEvents.BLOCK_SCULK_CHARGE, SoundSource.BLOCKS, volume, pitch, false);
}
int facings = (byte)(data & 63);
UniformInt spread = UniformInt.of(0, charge);
Supplier<Vec3> velocities = () -> {
return new Vec3(Mth.nextDouble(random, -0.005D, 0.005D), Mth.nextDouble(random, -0.005D, 0.005D), Mth.nextDouble(random, -0.005D, 0.005D));
};
if (facings == 0) {
for (Direction direction : Direction.values()) {
float roll = direction == Direction.DOWN ? (float)Math.PI : 0.0F;
double offset = direction == Direction.UP || direction == Direction.DOWN ? 0.32D : 0.57D;
ParticleUtils.spawnParticles(level, pos, new SculkChargeParticleOptions(roll), spread, direction, velocities, offset);
}
} else {
for (Direction direction : DirectionUtils.unpack((byte)data)) {
float roll = direction == Direction.UP ? (float)Math.PI : 0.0F;
ParticleUtils.spawnParticles(level, pos, new SculkChargeParticleOptions(roll), spread, direction, velocities, 0.35D);
}
}
} else {
if (client != null) client.playLocalSound(pos, WBSoundEvents.BLOCK_SCULK_CHARGE, SoundSource.BLOCKS, 1.0F, 1.0F, false);
boolean fullBlock = level.getBlockState(pos).isCollisionShapeFullBlock(level, pos);
int tries = fullBlock ? 40 : 20;
float spread = fullBlock ? 0.45F : 0.25F;
for (int i = 0; i < tries; i++) {
float x = 2.0F * random.nextFloat() - 1.0F;
float y = 2.0F * random.nextFloat() - 1.0F;
float z = 2.0F * random.nextFloat() - 1.0F;
if (server != null) server.sendParticles(WBParticleTypes.SCULK_CHARGE_POP.get(), (double)pos.getX() + 0.5D + (double)(x * spread), (double)pos.getY() + 0.5D + (double)(y * spread), (double)pos.getZ() + 0.5D + (double)(z * spread), 1, x * 0.07F, y * 0.07F, z * 0.07F, 0.0D);
}
}
}
}

View File

@ -0,0 +1,57 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Random;
public interface SculkSpreadable {
SculkSpreadable DEFAULT = new SculkSpreadable() {
@Override
public boolean spread(LevelAccessor level, BlockPos pos, BlockState state, @Nullable Collection<Direction> directions, boolean postProcess) {
if (directions == null) {
return ((SculkVeinBlock)WBBlocks.SCULK_VEIN.get()).getSamePositionOnlyGrower().grow(level.getBlockState(pos), level, pos, postProcess) > 0L;
} else if(!directions.isEmpty()) {
return (state.isAir() || state.getFluidState().is(Fluids.WATER)) && SculkVeinBlock.place(level, pos, state, directions);
} else {
return SculkSpreadable.super.spread(level, pos, state, directions, postProcess);
}
}
@Override
public int spread(SculkSpreadManager.Cursor cursor, LevelAccessor level, BlockPos pos, Random random, SculkSpreadManager spreadManager, boolean shouldConvert) {
return cursor.getDecayDelay() > 0 ? cursor.getCharge() : 0;
}
@Override
public int getDecay(int oldDecay) {
return Math.max(oldDecay - 1, 0);
}
};
default byte getUpdate() {
return 1;
}
default void spreadAtSamePosition(LevelAccessor level, BlockState state, BlockPos pos, Random random) {}
default boolean spread(LevelAccessor level, BlockPos pos, BlockState state, @Nullable Collection<Direction> directions, boolean postProcess) {
return ((SculkVeinBlock)WBBlocks.SCULK_VEIN.get()).getAllGrowTypeGrower().grow(state, level, pos, postProcess) > 0L;
}
default boolean shouldConvertToSpreadable() {
return true;
}
default int getDecay(int oldDecay) {
return 1;
}
int spread(SculkSpreadManager.Cursor cursor, LevelAccessor level, BlockPos pos, Random random, SculkSpreadManager manager, boolean shouldConvert);
}

View File

@ -0,0 +1,263 @@
package com.cursedcauldron.wildbackport.common.blocks;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.worldgen.VeinGrower;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import com.cursedcauldron.wildbackport.common.tag.WBBlockTags;
import com.cursedcauldron.wildbackport.common.utils.DirectionUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
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.Blocks;
import net.minecraft.world.level.block.MultifaceBlock;
import net.minecraft.world.level.block.SimpleWaterloggedBlock;
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.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.PushReaction;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Random;
import java.util.Set;
//<>
public class SculkVeinBlock extends MultifaceBlock implements SculkSpreadable, SimpleWaterloggedBlock {
private static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public final VeinGrower allGrowTypeGrower = new VeinGrower(new SculkVeinGrowChecker(VeinGrower.GROW_TYPES));
private final VeinGrower samePositionOnlyGrower = new VeinGrower(new SculkVeinGrowChecker(VeinGrower.GrowType.SAME_POSITION));
public SculkVeinBlock(Properties properties) {
super(properties);
this.registerDefaultState(this.defaultBlockState().setValue(WATERLOGGED, false));
}
public VeinGrower getAllGrowTypeGrower() {
return this.allGrowTypeGrower;
}
public VeinGrower getSamePositionOnlyGrower() {
return this.samePositionOnlyGrower;
}
public static boolean place(LevelAccessor level, BlockPos pos, BlockState state, Collection<Direction> directions) {
boolean canPlace = false;
BlockState veinState = WBBlocks.SCULK_VEIN.get().defaultBlockState();
for (Direction direction : directions) {
BlockPos blockPos = pos.relative(direction);
if (canGrowOn(level, direction, blockPos, level.getBlockState(blockPos))) {
veinState = veinState.setValue(getFaceProperty(direction), true);
canPlace = true;
}
}
if (!canPlace) {
return false;
} else {
if (!state.getFluidState().isEmpty()) {
veinState = veinState.setValue(WATERLOGGED, true);
}
level.setBlock(pos, veinState, 3);
return true;
}
}
@Override
public void spreadAtSamePosition(LevelAccessor level, BlockState state, BlockPos pos, Random random) {
if (state.is(this)) {
for (Direction direction : DIRECTIONS) {
BooleanProperty property = getFaceProperty(direction);
if (state.getValue(property) && level.getBlockState(pos.relative(direction)).is(WBBlocks.SCULK.get())) {
state = state.setValue(property, false);
}
}
if (!hasAnyFace(state)) {
FluidState fluid = level.getFluidState(pos);
state = (fluid.isEmpty() ? Blocks.AIR : Blocks.WATER).defaultBlockState();
}
level.setBlock(pos, state, 3);
SculkSpreadable.super.spreadAtSamePosition(level, state, pos, random);
}
}
@Override
public int spread(SculkSpreadManager.Cursor cursor, LevelAccessor level, BlockPos pos, Random random, SculkSpreadManager spreadManager, boolean shouldConvert) {
if (shouldConvert && this.convertToBlock(spreadManager, level, cursor.getPos(), random)) {
return cursor.getCharge() - 1;
} else {
return random.nextInt(spreadManager.getSpreadChance()) == 0 ? Mth.floor((float)cursor.getCharge() * 0.5F) : cursor.getCharge();
}
}
private boolean convertToBlock(SculkSpreadManager spreadManager, LevelAccessor level, BlockPos pos, Random random) {
BlockState state = level.getBlockState(pos);
TagKey<Block> replaceable = spreadManager.getReplaceableBlocks();
for (Direction direction : DirectionUtils.shuffle(random)) {
if (hasFace(state, direction)) {
BlockPos blockPos = pos.relative(direction);
BlockState blockState = level.getBlockState(blockPos);
if (blockState.is(replaceable)) {
BlockState sculk = WBBlocks.SCULK.get().defaultBlockState();
level.setBlock(blockPos, sculk, 3);
Block.pushEntitiesUp(blockState, sculk, (ServerLevel)level, pos);
level.playSound(null, blockPos, WBSoundEvents.BLOCK_SCULK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F);
this.allGrowTypeGrower.grow(sculk, level, blockPos, spreadManager.isWorldGen());
Direction opposite = direction.getOpposite();
for (Direction towards : DIRECTIONS) {
if (towards != opposite) {
BlockPos targetPos = blockPos.relative(towards);
BlockState targetState = level.getBlockState(targetPos);
if (targetState.is(this)) {
this.spreadAtSamePosition(level, targetState, targetPos, random);
}
}
}
return true;
}
}
}
return false;
}
public static boolean veinCoversSculkReplaceable(LevelAccessor level, BlockState state, BlockPos pos) {
if (state.is(WBBlocks.SCULK_VEIN.get())) {
for (Direction direction : DIRECTIONS) {
if (hasFace(state, direction) && level.getBlockState(pos.relative(direction)).is(WBBlockTags.SCULK_REPLACEABLE)) {
return true;
}
}
}
return false;
}
public boolean canGrowWithDirection(BlockGetter getter, BlockState state, BlockPos pos, Direction direction) {
if (this.isFaceSupported(direction) && (!state.is(this) || !hasFace(state, direction))) {
BlockPos blockPos = pos.relative(direction);
return canGrowOn(getter, direction, blockPos, getter.getBlockState(blockPos));
} else {
return false;
}
}
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
if (state.getValue(WATERLOGGED)) {
world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
}
return super.updateShape(state, direction, neighborState, world, pos, neighborPos);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.createBlockStateDefinition(builder);
builder.add(WATERLOGGED);
}
@Override
public boolean canBeReplaced(BlockState state, BlockPlaceContext context) {
return !context.getItemInHand().is(WBBlocks.SCULK_VEIN.get().asItem()) || super.canBeReplaced(state, context);
}
@Override
public FluidState getFluidState(BlockState state) {
return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state);
}
@Override
public PushReaction getPistonPushReaction(BlockState p_60584_) {
return PushReaction.DESTROY;
}
public static byte directionsToFlag(Collection<Direction> directions) {
byte flag = 0;
for (Direction direction : directions) {
flag = (byte)(flag | 1 << direction.ordinal());
}
return flag;
}
public static Set<Direction> collectDirections(BlockState state) {
if (!(state.getBlock() instanceof MultifaceBlock)) {
return Set.of();
} else {
Set<Direction> directions = EnumSet.noneOf(Direction.class);
for (Direction direction : DIRECTIONS) {
if (hasFace(state, direction)) {
directions.add(direction);
}
}
return directions;
}
}
public static boolean canGrowOn(BlockGetter getter, Direction direction, BlockPos pos, BlockState state) {
return Block.isFaceFull(state.getBlockSupportShape(getter, pos), direction.getOpposite()) || Block.isFaceFull(state.getCollisionShape(getter, pos), direction.getOpposite());
}
public static boolean hasFace(BlockState state, Direction direction) {
BooleanProperty booleanProperty = MultifaceBlock.getFaceProperty(direction);
return state.hasProperty(booleanProperty) && state.getValue(booleanProperty);
}
class SculkVeinGrowChecker extends VeinGrower.VeinGrowChecker {
private final VeinGrower.GrowType[] growTypes;
public SculkVeinGrowChecker(VeinGrower.GrowType... growTypes) {
super(SculkVeinBlock.this);
this.growTypes = growTypes;
}
@Override
public boolean canGrow(BlockGetter getter, BlockPos pos, BlockPos growPos, Direction direction, BlockState state) {
BlockPos blockPos;
BlockState blockState = getter.getBlockState(growPos.relative(direction));
boolean flag = blockState.is(WBBlocks.SCULK.get()) || blockState.is(WBBlocks.SCULK_CATALYST.get()) || blockState.is(Blocks.MOVING_PISTON);
if (flag) {
return false;
}
if (pos.distManhattan(growPos) == 2 && getter.getBlockState(blockPos = pos.relative(direction.getOpposite())).isFaceSturdy(getter, blockPos, direction)) {
return false;
}
FluidState fluidState = state.getFluidState();
if (!fluidState.isEmpty() && !fluidState.is(Fluids.WATER)) {
return false;
}
return state.getMaterial().isReplaceable() || super.canGrow(getter, pos, growPos, direction, state);
}
@Override
public VeinGrower.GrowType[] getGrowTypes() {
return this.growTypes;
}
@Override
public boolean canGrow(BlockState state) {
return !state.is(WBBlocks.SCULK_VEIN.get());
}
}
}

View File

@ -0,0 +1,11 @@
package com.cursedcauldron.wildbackport.common.blocks.entity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class MangroveSignBlockEntity extends SignBlockEntity {
public MangroveSignBlockEntity(BlockPos pos, BlockState state) {
super(pos, state);
}
}

View File

@ -0,0 +1,90 @@
package com.cursedcauldron.wildbackport.common.blocks.entity;
import com.cursedcauldron.wildbackport.client.registry.WBCriteriaTriggers;
import com.cursedcauldron.wildbackport.common.blocks.SculkCatalystBlock;
import com.cursedcauldron.wildbackport.common.blocks.SculkSpreadManager;
import com.cursedcauldron.wildbackport.common.entities.access.EntityExperience;
import com.cursedcauldron.wildbackport.common.registry.WBBlockEntities;
import com.cursedcauldron.wildbackport.common.registry.WBGameEvents;
import com.cursedcauldron.wildbackport.common.utils.PositionUtils;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.BlockPositionSource;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.gameevent.GameEventListener;
import net.minecraft.world.level.gameevent.PositionSource;
import org.jetbrains.annotations.Nullable;
//<>
public class SculkCatalystBlockEntity extends BlockEntity implements GameEventListener {
private final BlockPositionSource positionSource = new BlockPositionSource(this.worldPosition);
private final SculkSpreadManager spreadManager = SculkSpreadManager.create();
public SculkCatalystBlockEntity(BlockPos pos, BlockState state) {
super(WBBlockEntities.SCULK_CATALYST.get(), pos, state);
}
@Override
public PositionSource getListenerSource() {
return this.positionSource;
}
@Override
public int getListenerRadius() {
return 8;
}
@Override
public boolean handleGameEvent(Level level, GameEvent event, @Nullable Entity entity, BlockPos pos) {
if (!this.isRemoved()) {
if (event == WBGameEvents.ENTITY_DIE.get()) {
if (entity instanceof LivingEntity living && living instanceof EntityExperience mob) {
if (!mob.isExpDropDisabled()) {
int charge = mob.getExpToDrop();
if (!living.isBaby() && charge > 0) {
this.spreadManager.spread(new BlockPos(PositionUtils.relative(PositionUtils.toVec(pos), Direction.UP, 0.5D)), charge);
LivingEntity attacker = living.getLastHurtByMob();
if (attacker instanceof ServerPlayer player) {
DamageSource source = living.getLastDamageSource() == null ? DamageSource.playerAttack(player) : living.getLastDamageSource();
WBCriteriaTriggers.KILL_MOB_NEAR_SCULK_CATALYST.trigger(player, entity, source);
}
}
mob.disableExpDrop();
SculkCatalystBlock.bloom((ServerLevel) level, this.worldPosition, this.getBlockState(), level.getRandom());
}
return true;
}
}
}
return false;
}
public static void tick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity catalyst) {
catalyst.spreadManager.tick(level, pos, level.getRandom(), true);
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
this.spreadManager.readTag(tag);
}
@Override
protected void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
this.spreadManager.writeTag(tag);
}
}

View File

@ -0,0 +1,186 @@
package com.cursedcauldron.wildbackport.common.blocks.entity;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.particle.ShriekParticleOptions;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.blocks.SculkShriekerBlock;
import com.cursedcauldron.wildbackport.common.entities.warden.VibrationListenerSource;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.entities.warden.WardenSpawnHelper;
import com.cursedcauldron.wildbackport.common.entities.warden.WardenSpawnTracker;
import com.cursedcauldron.wildbackport.common.registry.WBBlockEntities;
import com.cursedcauldron.wildbackport.common.registry.WBGameEvents;
import com.cursedcauldron.wildbackport.common.tag.WBGameEventTags;
import com.mojang.serialization.Dynamic;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.world.Difficulty;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.BlockPositionSource;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.gameevent.GameEventListener;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.OptionalInt;
//<>
public class SculkShriekerBlockEntity extends BlockEntity implements VibrationListenerSource.VibrationConfig {
private static final Int2ObjectMap<SoundEvent> SOUND_BY_LEVEL = Util.make(new Int2ObjectOpenHashMap<>(), map -> {
map.put(1, WBSoundEvents.WARDEN_NEARBY_CLOSE);
map.put(2, WBSoundEvents.WARDEN_NEARBY_CLOSER);
map.put(3, WBSoundEvents.WARDEN_NEARBY_CLOSEST);
map.put(4, WBSoundEvents.WARDEN_LISTENING_ANGRY);
});
private int warningLevel;
private VibrationListenerSource listener = new VibrationListenerSource(new BlockPositionSource(this.worldPosition), 8, this, null, 0.0F, 0);
public SculkShriekerBlockEntity(BlockPos pos, BlockState state) {
super(WBBlockEntities.SCULK_SHRIEKER.get(), pos, state);
}
public VibrationListenerSource getListener() {
return this.listener;
}
@Override
public void load(CompoundTag tag) {
super.load(tag);
if (tag.contains("warning_level", 99)) {
this.warningLevel = tag.getInt("warning_level");
}
if (tag.contains("listener", 10)) {
VibrationListenerSource.codec(this).parse(new Dynamic<>(NbtOps.INSTANCE, tag.getCompound("listener"))).resultOrPartial(WildBackport.LOGGER::error).ifPresent(listener -> this.listener = listener);
}
}
@Override
protected void saveAdditional(CompoundTag tag) {
super.saveAdditional(tag);
tag.putInt("warning_level", this.warningLevel);
VibrationListenerSource.codec(this).encodeStart(NbtOps.INSTANCE, this.listener).resultOrPartial(WildBackport.LOGGER::error).ifPresent(listener -> tag.put("listener", listener));
}
@Override
public TagKey<GameEvent> getListenableEvents() {
return WBGameEventTags.WARDEN_CAN_LISTEN;
}
@Override
public boolean shouldListen(ServerLevel level, GameEventListener listener, BlockPos pos, GameEvent event, @Nullable Entity entity) {
return !this.isRemoved() && !this.getBlockState().getValue(SculkShriekerBlock.SHRIEKING) && tryGetPlayer(entity) != null;
}
@Nullable
public static ServerPlayer tryGetPlayer(@Nullable Entity entity) {
if (entity instanceof ServerPlayer player) {
return player;
} else {
if (entity != null) {
Entity passenger = entity.getControllingPassenger();
if (passenger instanceof ServerPlayer player) {
return player;
}
}
if (entity instanceof Projectile projectile) {
Entity owner = projectile.getOwner();
if (owner instanceof ServerPlayer player) {
return player;
}
}
return null;
}
}
@Override
public void onSignalReceive(ServerLevel level, GameEventListener listener, BlockPos pos, GameEvent event, @Nullable Entity entity, @Nullable Entity source, float distance) {
this.tryShriek(level, tryGetPlayer(source != null ? source : entity));
}
public void tryShriek(ServerLevel level, @Nullable ServerPlayer player) {
if (player != null) {
BlockState state = this.getBlockState();
if (!state.getValue(SculkShriekerBlock.SHRIEKING)) {
this.warningLevel = 0;
if (!this.canRespond(level) || this.tryToWarn(level, player)) {
this.shriek(level, player);
}
}
}
}
private boolean tryToWarn(ServerLevel level, ServerPlayer player) {
OptionalInt warning = WardenSpawnTracker.tryWarn(level, this.getBlockPos(), player);
warning.ifPresent(warningLevel -> this.warningLevel = warningLevel);
return warning.isPresent();
}
private void shriek(ServerLevel level, @Nullable Entity entity) {
BlockPos pos = this.getBlockPos();
BlockState state = this.getBlockState();
level.setBlock(pos, state.setValue(SculkShriekerBlock.SHRIEKING, true), 2);
level.scheduleTick(pos, state.getBlock(), 90);
level.playSound(null, pos.getX(), pos.getY(), pos.getZ(), WBSoundEvents.BLOCK_SCULK_SHRIEKER_SHRIEK, SoundSource.BLOCKS, 2.0F, 0.6F + level.random.nextFloat() * 0.4F);
for (int i = 0; i < 10; i++) {
int delay = i * 5;
level.sendParticles(new ShriekParticleOptions(delay), pos.getX() + 0.5D, pos.getY() + SculkShriekerBlock.TOP_Y, pos.getZ() + 0.5D, 1, 0.0D, 0.0D, 0.0D, 0.0D);
}
level.gameEvent(entity, WBGameEvents.SHRIEK.get(), pos);
}
private boolean canRespond(ServerLevel level) {
//TODO: add gamerule for warden spawning
return this.getBlockState().getValue(SculkShriekerBlock.CAN_SUMMON) && level.getDifficulty() != Difficulty.PEACEFUL;
}
public void tryRespond(ServerLevel level) {
if (this.canRespond(level) && this.warningLevel > 0) {
if (!this.trySummonWarden(level)) {
this.playWardenReplySound();
}
Warden.addDarknessEffectToClosePlayers(level, Vec3.atCenterOf(this.getBlockPos()), null, 40);
}
}
private void playWardenReplySound() {
SoundEvent sound = SOUND_BY_LEVEL.get(this.warningLevel);
if (sound != null && this.level != null) {
BlockPos pos = this.getBlockPos();
int x = pos.getX() + Mth.randomBetweenInclusive(this.level.random, -10, 10);
int y = pos.getY() + Mth.randomBetweenInclusive(this.level.random, -10, 10);
int z = pos.getZ() + Mth.randomBetweenInclusive(this.level.random, -10, 10);
this.level.playSound(null, x, y, z, sound, SoundSource.HOSTILE, 5.0F, 1.0F);
}
}
private boolean trySummonWarden(ServerLevel level) {
return this.warningLevel >= 4 && WardenSpawnHelper.trySpawnMob(EntityType.IRON_GOLEM, MobSpawnType.TRIGGERED, level, this.getBlockPos(), 20, 5, 6).isPresent();
}
@Override
public void onSignalSchedule() {
this.setChanged();
}
}

View File

@ -0,0 +1,41 @@
package com.cursedcauldron.wildbackport.common.enchantments;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentCategory;
public class SwiftSneakEnchantment extends Enchantment {
public SwiftSneakEnchantment(Rarity rarity, EquipmentSlot[] equipmentSlots) {
super(rarity, EnchantmentCategory.ARMOR_LEGS, equipmentSlots);
}
@Override
public int getMinCost(int level) {
return level * 25;
}
@Override
public int getMaxCost(int level) {
return this.getMinCost(level) + 50;
}
@Override
public boolean isTreasureOnly() {
return true;
}
@Override
public boolean isDiscoverable() {
return false;
}
@Override
public boolean isTradeable() {
return true;
}
@Override
public int getMaxLevel() {
return 3;
}
}

View File

@ -0,0 +1,375 @@
package com.cursedcauldron.wildbackport.common.entities;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.brain.AllayBrain;
import com.cursedcauldron.wildbackport.common.entities.warden.MobPositionSource;
import com.cursedcauldron.wildbackport.common.entities.warden.VibrationListenerSource;
import com.cursedcauldron.wildbackport.common.registry.WBGameEvents;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.tag.WBGameEventTags;
import com.google.common.collect.ImmutableList;
import com.mojang.serialization.Dynamic;
import net.minecraft.core.BlockPos;
import net.minecraft.core.GlobalPos;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.protocol.game.DebugPackets;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.behavior.BehaviorUtils;
import net.minecraft.world.entity.ai.control.FlyingMoveControl;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.ai.navigation.FlyingPathNavigation;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.npc.InventoryCarrier;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.gameevent.GameEventListener;
import net.minecraft.world.level.gameevent.GameEventListenerRegistrar;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.UUID;
//<>
public class Allay extends PathfinderMob implements InventoryCarrier, VibrationListenerSource.VibrationConfig {
private static final Vec3i PICKUP_REACH = new Vec3i(1, 1, 1);
protected static final ImmutableList<? extends SensorType<? extends Sensor<? super Allay>>> SENSORS = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.NEAREST_ITEMS);
protected static final ImmutableList<MemoryModuleType<?>> MEMORIES = ImmutableList.of(MemoryModuleType.PATH, MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.HURT_BY, MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, WBMemoryModules.LIKED_PLAYER.get(), WBMemoryModules.LIKED_NOTEBLOCK.get(), WBMemoryModules.LIKED_NOTEBLOCK_COOLDOWN_TICKS.get(), WBMemoryModules.ITEM_PICKUP_COOLDOWN_TICKS.get());
public static final ImmutableList<Float> SOUND_PITCHES = ImmutableList.of(0.5625F, 0.625F, 0.75F, 0.9375F, 1.0F, 1.0F, 1.125F, 1.25F, 1.5F, 1.875F, 2.0F, 2.25F, 2.5F, 3.0F, 3.75F, 4.0F);
private final GameEventListenerRegistrar registrar;
private VibrationListenerSource listener;
private final SimpleContainer inventory = new SimpleContainer(1);
private float holdingTicks;
private float holdingTicksOld;
public Allay(EntityType<? extends PathfinderMob> type, Level level) {
super(type, level);
this.moveControl = new FlyingMoveControl(this, 20, true);
this.setCanPickUpLoot(this.canPickUpLoot());
this.listener = new VibrationListenerSource(new MobPositionSource(this, this.getEyeHeight()), 16, this, null, 0.0F, 0);
this.registrar = new GameEventListenerRegistrar(this.listener);
}
@Override
protected Brain.Provider<Allay> brainProvider() {
return Brain.provider(MEMORIES, SENSORS);
}
@Override
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
return AllayBrain.makeBrain(this.brainProvider().makeBrain(dynamic));
}
@Override @SuppressWarnings("unchecked")
public Brain<Allay> getBrain() {
return (Brain<Allay>)super.getBrain();
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 20.0D).add(Attributes.FLYING_SPEED, 0.1F).add(Attributes.MOVEMENT_SPEED, 0.1F).add(Attributes.ATTACK_DAMAGE, 2.0D).add(Attributes.FOLLOW_RANGE, 48.0D);
}
@Override
protected PathNavigation createNavigation(Level level) {
FlyingPathNavigation navigation = new FlyingPathNavigation(this, level);
navigation.setCanOpenDoors(false);
navigation.setCanFloat(true);
navigation.setCanPassDoors(true);
return navigation;
}
@Override
public void travel(Vec3 movementInput) {
if (this.isEffectiveAi() || this.isControlledByLocalInstance()) {
if (this.isInWater()) {
this.moveRelative(0.02F, movementInput);
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.8F));
} else if (this.isInLava()) {
this.moveRelative(0.02F, movementInput);
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.5F));
} else {
this.moveRelative(this.getSpeed(), movementInput);
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.91F));
}
}
this.calculateEntityAnimation(this, false);
}
@Override
protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
return dimensions.height * 0.6F;
}
@Override
public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource source) {
return false;
}
@Override
public boolean hurt(DamageSource source, float amount) {
if (source.getEntity() instanceof Player player) {
Optional<UUID> likedPlayer = this.getBrain().getMemory(WBMemoryModules.LIKED_PLAYER.get());
if (likedPlayer.isPresent() && player.getUUID().equals(likedPlayer.get())) return false;
}
return super.hurt(source, amount);
}
@Override
protected void playStepSound(BlockPos pos, BlockState state) {}
@Override
protected void checkFallDamage(double fallenDistance, boolean canLand, BlockState state, BlockPos pos) {}
@Override @Nullable
protected SoundEvent getAmbientSound() {
return this.hasItemInSlot(EquipmentSlot.MAINHAND) ? WBSoundEvents.ALLAY_AMBIENT_WITH_ITEM : WBSoundEvents.ALLAY_AMBIENT_WITHOUT_ITEM;
}
@Override @Nullable
protected SoundEvent getHurtSound(DamageSource source) {
return WBSoundEvents.ALLAY_HURT;
}
@Override @Nullable
protected SoundEvent getDeathSound() {
return WBSoundEvents.ALLAY_DEATH;
}
@Override
protected float getSoundVolume() {
return 0.4F;
}
@Override
protected void customServerAiStep() {
this.level.getProfiler().push("allayBrain");
this.getBrain().tick((ServerLevel)this.level, this);
this.level.getProfiler().pop();
this.level.getProfiler().push("allayActivityUpdate");
AllayBrain.updateActivity(this);
this.level.getProfiler().pop();
super.customServerAiStep();
}
@Override
public void aiStep() {
super.aiStep();
if (!this.level.isClientSide && this.isAlive() && this.tickCount % 10 == 0) this.heal(1.0F);
}
@Override
public void tick() {
super.tick();
if (this.level.isClientSide) {
this.holdingTicksOld = this.holdingTicks;
if (this.hasItemInHand()) {
this.holdingTicks = Mth.clamp(this.holdingTicks + 1.0F, 0.0F, 5.0F);
} else {
this.holdingTicks = Mth.clamp(this.holdingTicks - 1.0F, 0.0F, 5.0F);
}
} else {
this.listener.tick(this.level);
}
}
@Override
public boolean canPickUpLoot() {
return !this.isOnPickupCooldown() && this.hasItemInHand();
}
public boolean hasItemInHand() {
return !this.getItemInHand(InteractionHand.MAIN_HAND).isEmpty();
}
@Override
public boolean canTakeItem(ItemStack stack) {
return false;
}
private boolean isOnPickupCooldown() {
return this.getBrain().checkMemory(WBMemoryModules.ITEM_PICKUP_COOLDOWN_TICKS.get(), MemoryStatus.VALUE_PRESENT);
}
@Override
protected InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack playerStack = player.getItemInHand(hand);
ItemStack allayStack = this.getItemInHand(InteractionHand.MAIN_HAND);
if (allayStack.isEmpty() && !playerStack.isEmpty()) {
ItemStack stack = playerStack.copy();
stack.setCount(1);
this.setItemInHand(InteractionHand.MAIN_HAND, stack);
if (!player.getAbilities().instabuild) playerStack.shrink(1);
this.level.playSound(player, this, WBSoundEvents.ALLAY_ITEM_GIVEN, SoundSource.NEUTRAL, 2.0F, 1.0F);
this.getBrain().setMemory(WBMemoryModules.LIKED_PLAYER.get(), player.getUUID());
return InteractionResult.SUCCESS;
} else if (!allayStack.isEmpty() && hand == InteractionHand.MAIN_HAND && playerStack.isEmpty()) {
this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
this.level.playSound(player, this, WBSoundEvents.ALLAY_ITEM_TAKEN, SoundSource.NEUTRAL, 2.0F, 1.0F);
this.swing(InteractionHand.MAIN_HAND);
for (ItemStack stack : this.getInventory().removeAllItems()) BehaviorUtils.throwItem(this, stack, this.position());
this.getBrain().eraseMemory(WBMemoryModules.LIKED_PLAYER.get());
player.addItem(allayStack);
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(player, hand);
}
}
@Override
public SimpleContainer getInventory() {
return this.inventory;
}
public static Vec3i getPickupReach() {
return PICKUP_REACH;
}
@Override
public boolean wantsToPickUp(ItemStack stack) {
ItemStack allayStack = this.getItemInHand(InteractionHand.MAIN_HAND);
return !allayStack.isEmpty() && allayStack.sameItemStackIgnoreDurability(stack) && this.inventory.canAddItem(stack);
}
@Override
protected void pickUpItem(ItemEntity itemEntity) {
ItemStack stack = itemEntity.getItem();
if (this.wantsToPickUp(stack)) {
SimpleContainer inventory = this.getInventory();
boolean canAdd = inventory.canAddItem(stack);
if (!canAdd) return;
this.onItemPickup(itemEntity);
this.take(itemEntity, stack.getCount());
ItemStack addedStack = inventory.addItem(stack);
if (addedStack.isEmpty()) {
itemEntity.discard();
} else {
stack.setCount(addedStack.getCount());
}
}
}
@Override
protected void sendDebugPackets() {
super.sendDebugPackets();
DebugPackets.sendEntityBrain(this);
}
@Override
protected boolean isFlapping() {
return !this.isOnGround();
}
@Override @Nullable
public GameEventListenerRegistrar getGameEventListenerRegistrar() {
return this.registrar;
}
public boolean isFlying() {
return this.animationSpeed > 0.3F;
}
public float getHoldingItemAnimationProgress(float animationProgress) {
return Mth.lerp(animationProgress, this.holdingTicksOld, this.holdingTicks) / 5.0F;
}
@Override
protected void dropEquipment() {
super.dropEquipment();
this.inventory.removeAllItems().forEach(this::spawnAtLocation);
ItemStack stack = this.getItemBySlot(EquipmentSlot.MAINHAND);
if (!stack.isEmpty() && !EnchantmentHelper.hasVanishingCurse(stack)) {
this.spawnAtLocation(stack);
this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
}
}
@Override
public boolean removeWhenFarAway(double distance) {
return false;
}
@Override
public boolean shouldListen(ServerLevel level, GameEventListener listener, BlockPos pos, GameEvent event, @Nullable Entity entity) {
if (this.level == level && !this.isRemoved() && !this.isNoAi()) {
if (!this.brain.hasMemoryValue(WBMemoryModules.LIKED_NOTEBLOCK.get())) {
return true;
} else {
Optional<GlobalPos> likedNoteblock = this.brain.getMemory(WBMemoryModules.LIKED_NOTEBLOCK.get());
return likedNoteblock.isPresent() && likedNoteblock.get().dimension() == level.dimension() && likedNoteblock.get().pos() == pos;
}
} else {
return false;
}
}
@Override
public void onSignalReceive(ServerLevel level, GameEventListener listener, BlockPos pos, GameEvent event, @Nullable Entity entity, @Nullable Entity source, float distance) {
if (event == WBGameEvents.NOTE_BLOCK_PLAY.get()) AllayBrain.hearNoteblock(this, new BlockPos(pos));
}
@Override
public TagKey<GameEvent> getListenableEvents() {
return WBGameEventTags.ALLAY_CAN_LISTEN;
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.put("Inventory", this.inventory.createTag());
VibrationListenerSource.codec(this).encodeStart(NbtOps.INSTANCE, this.listener).resultOrPartial(WildBackport.LOGGER::error).ifPresent(listener -> tag.put("listener", listener));
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.inventory.fromTag(tag.getList("Inventory", 10));
if (tag.contains("listener", 10)) VibrationListenerSource.codec(this).parse(new Dynamic<>(NbtOps.INSTANCE, tag.getCompound("listener"))).resultOrPartial(WildBackport.LOGGER::error).ifPresent(listener -> this.listener = listener);
}
//iterate pathfinding start node candidate positions
@Override
public Vec3 getLeashOffset() {
return new Vec3(0.0D, this.getEyeHeight() * 0.6D, this.getBbWidth() * 0.1D);
}
}

View File

@ -0,0 +1,240 @@
package com.cursedcauldron.wildbackport.common.entities;
import com.cursedcauldron.wildbackport.common.registry.WBItems;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.Containers;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SlotAccess;
import net.minecraft.world.entity.monster.piglin.PiglinAi;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import org.jetbrains.annotations.Nullable;
//<>
public class ChestBoat extends MangroveBoat implements Container, MenuProvider {
private NonNullList<ItemStack> stacks = NonNullList.withSize(27, ItemStack.EMPTY);
@Nullable
private ResourceLocation lootTable;
private long lootTableSeed;
public ChestBoat(EntityType<? extends Boat> type, Level level) {
super(type, level);
}
public ChestBoat(Level level, double x, double y, double z) {
super(WBEntities.CHEST_BOAT.get(), level);
this.setPos(x, y, z);
this.xo = x;
this.yo = y;
this.zo = z;
}
@Override
public double getPassengersRidingOffset() {
return 0.15F;
}
@Override
protected boolean canAddPassenger(Entity entity) {
return this.getPassengers().size() < 1;
}
@Override
protected void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
if (this.lootTable != null) {
tag.putString("LootTable", this.lootTable.toString());
if (this.lootTableSeed != 0L) tag.putLong("LootTableSeed", this.lootTableSeed);
} else {
ContainerHelper.saveAllItems(tag, this.stacks);
}
}
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.stacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY);
if (tag.contains("LootTable", 8)) {
this.lootTable = new ResourceLocation(tag.getString("LootTable"));
this.lootTableSeed = tag.getLong("LootTableSeed");
} else {
ContainerHelper.loadAllItems(tag, this.stacks);
}
}
@Override
protected void dropItems(DamageSource source) {
super.dropItems(source);
if (this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
Containers.dropContents(this.level, this, this);
if (!this.level.isClientSide) {
Entity entity = source.getDirectEntity();
if (entity != null && entity.getType() == EntityType.PLAYER) PiglinAi.angerNearbyPiglins((Player)entity, true);
}
}
}
@Override
public void remove(Entity.RemovalReason reason) {
if (!this.level.isClientSide && reason.shouldDestroy()) Containers.dropContents(this.level, this, this);
super.remove(reason);
}
@Override
public InteractionResult interact(Player player, InteractionHand hand) {
if (player.isCrouching()) {
player.openMenu(this);
if (!player.level.isClientSide) {
this.gameEvent(GameEvent.CONTAINER_OPEN, player);
PiglinAi.angerNearbyPiglins(player, true);
return InteractionResult.CONSUME;
} else {
return InteractionResult.SUCCESS;
}
}
return super.interact(player, hand);
}
public void openInventory(Player player) {
player.openMenu(this);
if (!player.level.isClientSide()) {
this.gameEvent(GameEvent.CONTAINER_OPEN, player);
PiglinAi.angerNearbyPiglins(player, true);
}
}
@Override @SuppressWarnings("UnnecessaryDefault")
public Item getDropItem() {
return switch (this.getBoatType()) {
case OAK -> WBItems.OAK_CHEST_BOAT.get();
case SPRUCE -> WBItems.SPRUCE_CHEST_BOAT.get();
case BIRCH -> WBItems.BIRCH_CHEST_BOAT.get();
case JUNGLE -> WBItems.JUNGLE_CHEST_BOAT.get();
case ACACIA -> WBItems.ACACIA_CHEST_BOAT.get();
case DARK_OAK -> WBItems.DARK_OAK_CHEST_BOAT.get();
default -> WBItems.MANGROVE_CHEST_BOAT.get();
};
}
public void unpackLootTable(@Nullable Player player) {
MinecraftServer server = this.level.getServer();
if (this.lootTable != null && server != null) {
LootTable lootTable = server.getLootTables().get(this.lootTable);
if (player != null) CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.lootTable);
this.lootTable = null;
LootContext.Builder builder = new LootContext.Builder((ServerLevel)this.level).withParameter(LootContextParams.ORIGIN, this.position()).withOptionalRandomSeed(this.lootTableSeed);
if (player != null) builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player);
lootTable.fill(this, builder.create(LootContextParamSets.CHEST));
}
}
@Override
public void clearContent() {
this.unpackLootTable(null);
this.stacks.clear();
}
@Override
public int getContainerSize() {
return 27;
}
@Override
public ItemStack getItem(int slot) {
this.unpackLootTable(null);
return this.stacks.get(slot);
}
@Override
public ItemStack removeItem(int slot, int amount) {
this.unpackLootTable(null);
return ContainerHelper.removeItem(this.stacks, slot, amount);
}
@Override
public ItemStack removeItemNoUpdate(int slot) {
this.unpackLootTable(null);
ItemStack stack = this.stacks.get(slot);
if (stack.isEmpty()) {
return ItemStack.EMPTY;
} else {
this.stacks.set(slot, ItemStack.EMPTY);
return stack;
}
}
@Override
public void setItem(int slot, ItemStack stack) {
this.unpackLootTable(null);
this.stacks.set(slot, stack);
if (!stack.isEmpty() && stack.getCount() > this.getMaxStackSize()) stack.setCount(this.getMaxStackSize());
}
@Override
public SlotAccess getSlot(int slot) {
return slot >= 0 && slot < this.getContainerSize() ? new SlotAccess() {
@Override public ItemStack get() {
return ChestBoat.this.getItem(slot);
}
@Override public boolean set(ItemStack stack) {
ChestBoat.this.setItem(slot, stack);
return true;
}
} : super.getSlot(slot);
}
@Override
public void setChanged() {}
@Override
public boolean stillValid(Player player) {
return !this.isRemoved() && this.position().closerThan(player.position(), 8.0D);
}
@Override
public boolean isEmpty() {
for (ItemStack stack : this.stacks) if (!stack.isEmpty()) return false;
return true;
}
@Override @Nullable
public AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
if (this.lootTable == null || !player.isSpectator()) {
this.unpackLootTable(inventory.player);
return ChestMenu.threeRows(i, inventory, this);
}
return null;
}
}

View File

@ -0,0 +1,439 @@
package com.cursedcauldron.wildbackport.common.entities;
import com.cursedcauldron.wildbackport.client.animation.api.AnimationState;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.brain.FrogBrain;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.registry.entity.WBSensorTypes;
import com.cursedcauldron.wildbackport.common.tag.WBBiomeTags;
import com.cursedcauldron.wildbackport.common.tag.WBBlockTags;
import com.cursedcauldron.wildbackport.common.tag.WBEntityTypeTags;
import com.google.common.collect.ImmutableList;
import com.mojang.serialization.Dynamic;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Holder;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.DebugPackets;
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.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.stats.Stats;
import net.minecraft.util.Unit;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.control.LookControl;
import net.minecraft.world.entity.ai.control.SmoothSwimmingMoveControl;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.ai.navigation.WaterBoundPathNavigation;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.monster.Slime;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.AmphibiousNodeEvaluator;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.level.pathfinder.PathFinder;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Random;
//<>
public class Frog extends Animal {
public static final Ingredient FOOD = Ingredient.of(Items.SLIME_BALL);
protected static final ImmutableList<? extends SensorType<? extends Sensor<? super Frog>>> SENSORS = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.HURT_BY, WBSensorTypes.FROG_ATTACKABLES.get(), WBSensorTypes.FROG_TEMPTATIONS.get(), WBSensorTypes.IS_IN_WATER.get());
protected static final ImmutableList<? extends MemoryModuleType<?>> MEMORIES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.BREED_TARGET, MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, MemoryModuleType.LONG_JUMP_MID_JUMP, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.NEAREST_ATTACKABLE, WBMemoryModules.IS_IN_WATER.get(), WBMemoryModules.IS_PREGNANT.get(), WBMemoryModules.UNREACHABLE_TONGUE_TARGETS.get());
private static final EntityDataAccessor<Integer> VARIANT_ID = SynchedEntityData.defineId(Frog.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<OptionalInt> TARGET_ID = SynchedEntityData.defineId(Frog.class, EntityDataSerializers.OPTIONAL_UNSIGNED_INT);
public final AnimationState longJumpingAnimationState = new AnimationState();
public final AnimationState croakingAnimationState = new AnimationState();
public final AnimationState usingTongueAnimationState = new AnimationState();
public final AnimationState walkingAnimationState = new AnimationState();
public final AnimationState swimmingAnimationState = new AnimationState();
public final AnimationState idlingInWaterAnimationState = new AnimationState();
public Frog(EntityType<? extends Animal> type, Level level) {
super(type, level);
this.lookControl = new FrogLookController(this);
this.setPathfindingMalus(BlockPathTypes.WATER, 4.0F);
this.setPathfindingMalus(BlockPathTypes.TRAPDOOR, -1.0F);
this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
this.maxUpStep = 1.0F;
}
@Override
protected Brain.Provider<Frog> brainProvider() {
return Brain.provider(MEMORIES, SENSORS);
}
@Override
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
return FrogBrain.create(this.brainProvider().makeBrain(dynamic));
}
@Override @SuppressWarnings("unchecked")
public Brain<Frog> getBrain() {
return (Brain<Frog>)super.getBrain();
}
@Override
protected void defineSynchedData() {
super.defineSynchedData();
this.entityData.define(VARIANT_ID, 0);
this.entityData.define(TARGET_ID, OptionalInt.empty());
}
public void clearFrogTarget() {
this.entityData.set(TARGET_ID, OptionalInt.empty());
}
public Optional<Entity> getFrogTarget() {
return this.entityData.get(TARGET_ID).stream().mapToObj(this.level::getEntity).filter(Objects::nonNull).findFirst();
}
public void setFrogTarget(Entity entity) {
this.entityData.set(TARGET_ID, OptionalInt.of(entity.getId()));
}
@Override
public int getHeadRotSpeed() {
return 35;
}
@Override
public int getMaxHeadYRot() {
return 5;
}
public Variant getVariant() {
return Variant.byId(this.entityData.get(VARIANT_ID));
}
public void setVariant(Variant variant) {
this.entityData.set(VARIANT_ID, variant.getId());
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
tag.putInt("Variant", this.getVariant().getId());
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
this.setVariant(Variant.byId(tag.getInt("Variant")));
}
@Override
public boolean canBreatheUnderwater() {
return true;
}
private boolean isMovingOnLand() {
return this.onGround && this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6D && !this.isInWaterOrBubble();
}
private boolean isMovingOnWater() {
return this.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6D && this.isInWaterOrBubble();
}
@Override
protected void customServerAiStep() {
this.level.getProfiler().push("frogBrain");
this.getBrain().tick((ServerLevel)this.level, this);
this.level.getProfiler().pop();
this.level.getProfiler().push("frogActivityUpdate");
FrogBrain.updateActivities(this);
this.level.getProfiler().pop();
super.customServerAiStep();
}
@Override
public void tick() {
if (this.level.isClientSide()) {
if (this.isMovingOnLand()) {
this.walkingAnimationState.startIfNotRunning(this.tickCount);
} else {
this.walkingAnimationState.stop();
}
if (this.isMovingOnWater()) {
this.idlingInWaterAnimationState.stop();
this.swimmingAnimationState.startIfNotRunning(this.tickCount);
} else if (this.isInWaterOrBubble()) {
this.swimmingAnimationState.stop();
this.idlingInWaterAnimationState.startIfNotRunning(this.tickCount);
} else {
this.swimmingAnimationState.stop();
this.idlingInWaterAnimationState.stop();
}
}
super.tick();
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> data) {
if (DATA_POSE.equals(data)) {
if (this.isInPose(Pose.LONG_JUMPING)) {
this.longJumpingAnimationState.start(this.tickCount);
} else {
this.longJumpingAnimationState.stop();
}
if (this.isInPose(Poses.CROAKING.get())) {
this.croakingAnimationState.start(this.tickCount);
} else {
this.croakingAnimationState.stop();
}
if (this.isInPose(Poses.USING_TONGUE.get())) {
this.usingTongueAnimationState.start(this.tickCount);
} else {
this.usingTongueAnimationState.stop();
}
}
super.onSyncedDataUpdated(data);
}
public boolean isInPose(Pose pose) {
return this.getPose() == pose;
}
@Nullable @Override
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob mob) {
Frog frog = WBEntities.FROG.get().create(level);
if (frog != null) FrogBrain.coolDownLongJump(frog, level.getRandom());
return frog;
}
@Override
public boolean isBaby() {
return false;
}
@Override
public void setBaby(boolean baby) {}
@Override
public void spawnChildFromBreeding(ServerLevel level, Animal partner) {
ServerPlayer player = this.getLoveCause();
if (player == null) player = partner.getLoveCause();
if (player != null) {
player.awardStat(Stats.ANIMALS_BRED);
CriteriaTriggers.BRED_ANIMALS.trigger(player, this, partner, null);
}
this.setAge(6000);
partner.setAge(6000);
this.resetLove();
partner.resetLove();
this.getBrain().setMemory(WBMemoryModules.IS_PREGNANT.get(), Unit.INSTANCE);
level.broadcastEntityEvent(this, (byte)18);
if (level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) level.addFreshEntity(new ExperienceOrb(level, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1));
}
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor accessor, DifficultyInstance difficulty, MobSpawnType spawnType, @Nullable SpawnGroupData groupData, @Nullable CompoundTag tag) {
Holder<Biome> biome = accessor.getBiome(this.blockPosition());
if (biome.is(WBBiomeTags.SPAWNS_COLD_VARIANT_FROGS)) {
this.setVariant(Variant.COLD);
} else if (biome.is(WBBiomeTags.SPAWNS_WARM_VARIANT_FROGS)) {
this.setVariant(Variant.WARM);
} else {
this.setVariant(Variant.TEMPERATE);
}
FrogBrain.coolDownLongJump(this, accessor.getRandom());
return super.finalizeSpawn(accessor, difficulty, spawnType, groupData, tag);
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 1.0D).add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.ATTACK_DAMAGE, 10.0D);
}
@Nullable @Override
protected SoundEvent getAmbientSound() {
return WBSoundEvents.FROG_AMBIENT;
}
@Nullable @Override
protected SoundEvent getHurtSound(DamageSource source) {
return WBSoundEvents.FROG_HURT;
}
@Nullable @Override
protected SoundEvent getDeathSound() {
return WBSoundEvents.FROG_DEATH;
}
@Override
protected void playStepSound(BlockPos pos, BlockState state) {
this.playSound(WBSoundEvents.FROG_STEP, 0.15F, 1.0F);
}
@Override
public boolean isPushedByFluid() {
return false;
}
@Override
protected void sendDebugPackets() {
super.sendDebugPackets();
DebugPackets.sendEntityBrain(this);
}
@Override
protected int calculateFallDamage(float fallDistance, float damageMultiplier) {
return super.calculateFallDamage(fallDistance, damageMultiplier) - 5;
}
@Override
public void travel(Vec3 motion) {
if (this.isEffectiveAi() && this.isInWater()) {
this.moveRelative(this.getSpeed(), motion);
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.9D));
} else {
super.travel(motion);
}
}
@Override
public boolean canCutCorner(BlockPathTypes path) {
return super.canCutCorner(path) && path != BlockPathTypes.WATER_BORDER;
}
public static boolean isValidFrogFood(LivingEntity entity) {
return (!(entity instanceof Slime slime) || slime.getSize() == 1) && entity.getType().is(WBEntityTypeTags.FROG_FOOD);
}
@Override
protected PathNavigation createNavigation(Level level) {
return new FrogPathNavigator(this, level);
}
@Override
public boolean isFood(ItemStack stack) {
return FOOD.test(stack);
}
public static boolean checkFrogSpawnRules(EntityType<? extends Animal> type, LevelAccessor accessor, MobSpawnType spawnType, BlockPos pos, Random random) {
return accessor.getBlockState(pos.below()).is(WBBlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(accessor, pos);
}
class FrogLookController extends LookControl {
FrogLookController(Mob mobEntity) {
super(mobEntity);
}
@Override
protected boolean resetXRotOnTick() {
return Frog.this.getFrogTarget().isEmpty();
}
}
static class FrogNodeEvaluator extends AmphibiousNodeEvaluator {
private final BlockPos.MutableBlockPos preferredBlock = new BlockPos.MutableBlockPos();
public FrogNodeEvaluator(boolean penalizeDeepWater) {
super(penalizeDeepWater);
}
@Override
public BlockPathTypes getBlockPathType(BlockGetter getter, int x, int y, int z) {
this.preferredBlock.set(x, y - 1, z);
BlockState state = getter.getBlockState(this.preferredBlock);
return state.is(WBBlockTags.FROG_PREFER_JUMP_TO) ? BlockPathTypes.OPEN : FrogNodeEvaluator.getBlockPathTypeStatic(getter, this.preferredBlock.move(Direction.UP));
}
}
static class FrogPathNavigator extends WaterBoundPathNavigation {
FrogPathNavigator(Frog frog, Level world) {
super(frog, world);
}
@Override
protected PathFinder createPathFinder(int range) {
this.nodeEvaluator = new FrogNodeEvaluator(true);
return new PathFinder(this.nodeEvaluator, range);
}
@Override
protected boolean canUpdatePath() {
return true;
}
@Override
public boolean isStableDestination(BlockPos pos) {
return !this.level.getBlockState(pos.below()).isAir();
}
}
public enum Variant {
TEMPERATE(0, "temperate"),
WARM(1, "warm"),
COLD(2, "cold");
private static final Variant[] VARIANTS = Arrays.stream(Variant.values()).sorted(Comparator.comparingInt(Variant::getId)).toArray(Variant[]::new);
private final int id;
private final String name;
Variant(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
public static Variant byId(int id) {
if (id < 0 || id >= VARIANTS.length) {
id = 0;
}
return VARIANTS[id];
}
}
}

View File

@ -0,0 +1,61 @@
package com.cursedcauldron.wildbackport.common.entities;
import com.cursedcauldron.wildbackport.common.entities.access.api.BoatTypes;
import com.cursedcauldron.wildbackport.common.registry.WBItems;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import net.minecraft.world.damagesource.DamageSource;
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.Item;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent;
//<>
public class MangroveBoat extends Boat {
public MangroveBoat(EntityType<? extends Boat> type, Level level) {
super(type, level);
}
public MangroveBoat(Level level, double x, double y, double z) {
super(WBEntities.MANGROVE_BOAT.get(), level);
this.setPos(x, y, z);
this.xo = x;
this.yo = y;
this.zo = z;
}
@Override
public boolean hurt(DamageSource source, float amount) {
if (this.isInvulnerableTo(source)) {
return false;
} else if (!this.level.isClientSide() && !this.isRemoved()) {
this.setHurtDir(-this.getHurtDir());
this.setHurtTime(10);
this.setDamage(this.getDamage() + amount * 10.0F);
this.markHurt();
this.gameEvent(GameEvent.ENTITY_DAMAGED, source.getEntity());
boolean isCreativePlayer = source.getEntity() instanceof Player player && player.getAbilities().instabuild;
if (isCreativePlayer || this.getDamage() > 40.0F) {
if (!isCreativePlayer && this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) this.dropItems(source);
this.discard();
}
return true;
} else {
return true;
}
}
protected void dropItems(DamageSource source) {
this.spawnAtLocation(this.getDropItem());
}
@Override
public Item getDropItem() {
return this.getBoatType() != BoatTypes.MANGROVE.get() ? super.getDropItem() : WBItems.MANGROVE_BOAT.get();
}
}

View File

@ -0,0 +1,237 @@
package com.cursedcauldron.wildbackport.common.entities;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.brain.TadpoleBrain;
import com.cursedcauldron.wildbackport.common.registry.WBItems;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import com.google.common.collect.ImmutableList;
import com.mojang.serialization.Dynamic;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.DebugPackets;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.control.SmoothSwimmingLookControl;
import net.minecraft.world.entity.ai.control.SmoothSwimmingMoveControl;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.entity.ai.navigation.WaterBoundPathNavigation;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
import net.minecraft.world.entity.animal.AbstractFish;
import net.minecraft.world.entity.animal.Bucketable;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
//<>
public class Tadpole extends AbstractFish {
public static final int MAX_TADPOLE_AGE = Math.abs(-24000);
public static final float WIDTH = 0.4F;
public static final float HEIGHT = 0.3F;
private int age;
protected static final ImmutableList<SensorType<? extends Sensor<? super Tadpole>>> SENSORS = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY);
protected static final ImmutableList<MemoryModuleType<?>> MEMORIES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT);
public Tadpole(EntityType<? extends AbstractFish> type, Level level) {
super(type, level);
this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
this.lookControl = new SmoothSwimmingLookControl(this, 10);
}
@Override
protected PathNavigation createNavigation(Level world) {
return new WaterBoundPathNavigation(this, world);
}
@Override
protected Brain.Provider<Tadpole> brainProvider() {
return Brain.provider(MEMORIES, SENSORS);
}
@Override
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
return TadpoleBrain.create(this.brainProvider().makeBrain(dynamic));
}
@Override @SuppressWarnings("unchecked")
public Brain<Tadpole> getBrain() {
return (Brain<Tadpole>) super.getBrain();
}
@Override
protected SoundEvent getFlopSound() {
return WBSoundEvents.TADPOLE_FLOP;
}
@Override
protected void customServerAiStep() {
this.level.getProfiler().push("tadpoleBrain");
this.getBrain().tick((ServerLevel)this.level, this);
this.level.getProfiler().pop();
this.level.getProfiler().push("tadpoleActivityUpdate");
TadpoleBrain.updateActivities(this);
this.level.getProfiler().pop();
super.customServerAiStep();
}
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 1.0D).add(Attributes.MAX_HEALTH, 6.0D);
}
@Override
public void aiStep() {
super.aiStep();
if (!this.level.isClientSide()) {
this.setAge(this.age + 1);
}
}
@Override
public void addAdditionalSaveData(CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
nbt.putInt("Age", this.age);
}
@Override
public void readAdditionalSaveData(CompoundTag nbt) {
super.readAdditionalSaveData(nbt);
this.setAge(nbt.getInt("Age"));
}
@Override @Nullable
protected SoundEvent getAmbientSound() {
return null;
}
@Override @Nullable
protected SoundEvent getHurtSound(DamageSource source) {
return WBSoundEvents.TADPOLE_HURT;
}
@Override @Nullable
protected SoundEvent getDeathSound() {
return WBSoundEvents.TADPOLE_DEATH;
}
@Override
protected InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack stack = player.getItemInHand(hand);
if (this.isSlimeBall(stack)) {
this.eatSlimeBall(player, stack);
return InteractionResult.sidedSuccess(this.level.isClientSide());
} else {
return Bucketable.bucketMobPickup(player, hand, this).orElse(super.mobInteract(player, hand));
}
}
@Override
protected void sendDebugPackets() {
super.sendDebugPackets();
DebugPackets.sendEntityBrain(this);
}
@Override
public boolean fromBucket() {
return true;
}
@Override
public void setFromBucket(boolean fromBucket) {}
@Override
public void saveToBucketTag(ItemStack stack) {
Bucketable.saveDefaultDataToBucketTag(this, stack);
CompoundTag nbtCompound = stack.getOrCreateTag();
nbtCompound.putInt("Age", this.getAge());
}
@Override
public void loadFromBucketTag(CompoundTag nbt) {
Bucketable.loadDefaultDataFromBucketTag(this, nbt);
if (nbt.contains("Age")) {
this.setAge(nbt.getInt("Age"));
}
}
@Override
public ItemStack getBucketItemStack() {
return new ItemStack(WBItems.TADPOLE_BUCKET.get());
}
@Override
public SoundEvent getPickupSound() {
return WBSoundEvents.BUCKED_FILL_TADPOLE;
}
private boolean isSlimeBall(ItemStack stack) {
return Frog.FOOD.test(stack);
}
private void eatSlimeBall(Player player, ItemStack stack) {
this.decrementItem(player, stack);
this.increaseAge((int)((float)(this.getTicksUntilGrowth() / 20) * 0.1F));
this.level.addParticle(ParticleTypes.HAPPY_VILLAGER, this.getRandomX(1.0D), this.getRandomY() + 0.5D, this.getRandomZ(1.0D), 0.0D, 0.0D, 0.0D);
}
private void decrementItem(Player player, ItemStack stack) {
if (!player.getAbilities().instabuild) {
stack.shrink(1);
}
}
private int getAge() {
return this.age;
}
private void increaseAge(int seconds) {
this.setAge(this.age + seconds * 20);
}
private void setAge(int age) {
this.age = age;
if (this.age >= MAX_TADPOLE_AGE) {
this.growUp();
}
}
private void growUp() {
if (this.level instanceof ServerLevel server) {
Frog frog = WBEntities.FROG.get().create(this.level);
if (frog == null) return;
frog.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
frog.finalizeSpawn(server, this.level.getCurrentDifficultyAt(frog.blockPosition()), MobSpawnType.CONVERSION, null, null);
frog.setNoAi(this.isNoAi());
if (this.hasCustomName()) {
frog.setCustomName(this.getCustomName());
frog.setCustomNameVisible(this.isCustomNameVisible());
}
frog.setPersistenceRequired();
this.playSound(WBSoundEvents.TADPOLE_GROW_UP, 0.15F, 1.0F);
server.addFreshEntityWithPassengers(frog);
this.discard();
}
}
private int getTicksUntilGrowth() {
return Math.max(0, MAX_TADPOLE_AGE - this.age);
}
@Override
protected boolean shouldDropExperience() {
return false;
}
}

View File

@ -0,0 +1,533 @@
package com.cursedcauldron.wildbackport.common.entities;
import com.cursedcauldron.wildbackport.WildBackport;
import com.cursedcauldron.wildbackport.client.animation.api.AnimationState;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.brain.WardenBrain;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.SonicBoom;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.cursedcauldron.wildbackport.common.entities.warden.Angriness;
import com.cursedcauldron.wildbackport.common.entities.warden.MobPositionSource;
import com.cursedcauldron.wildbackport.common.entities.warden.VibrationListenerSource;
import com.cursedcauldron.wildbackport.common.entities.warden.WardenAngerManager;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.tag.WBGameEventTags;
import com.cursedcauldron.wildbackport.common.utils.MobUtils;
import com.mojang.serialization.Dynamic;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket;
import net.minecraft.network.protocol.game.DebugPackets;
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.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.util.Unit;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.damagesource.IndirectEntityDamageSource;
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.EntitySelector;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.gameevent.GameEventListener;
import net.minecraft.world.level.gameevent.GameEventListenerRegistrar;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Optional;
import java.util.Random;
//<>
public class Warden extends Monster implements VibrationListenerSource.VibrationConfig {
private static final EntityDataAccessor<Integer> ANGER = SynchedEntityData.defineId(Warden.class, EntityDataSerializers.INT);
private int tendrilPitchEnd;
private int tendrilPitchStart;
private int heartPitchEnd;
private int heartPitchStart;
public AnimationState roaringAnimationState = new AnimationState();
public AnimationState sniffingAnimationState = new AnimationState();
public AnimationState emergingAnimationState = new AnimationState();
public AnimationState diggingAnimationState = new AnimationState();
public AnimationState attackingAnimationState = new AnimationState();
public AnimationState sonicBoomAnimationState = new AnimationState();
private final GameEventListenerRegistrar gameEventHandler;
private VibrationListenerSource listener;
private WardenAngerManager angerManager = new WardenAngerManager(this::isValidTarget, Collections.emptyList());
public Warden(EntityType<? extends Monster> type, Level level) {
super(type, level);
this.listener = new VibrationListenerSource(new MobPositionSource(this, this.getEyeHeight()), 16, this, null, 0.0F, 0);
this.gameEventHandler = new GameEventListenerRegistrar(this.listener);
this.xpReward = 5;
this.getNavigation().setCanFloat(true);
this.setPathfindingMalus(BlockPathTypes.UNPASSABLE_RAIL, 0.0F);
this.setPathfindingMalus(BlockPathTypes.DAMAGE_OTHER, 8.0F);
this.setPathfindingMalus(BlockPathTypes.POWDER_SNOW, 8.0F);
this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F);
this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F);
this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F);
}
@Override
public Packet<?> getAddEntityPacket() {
return new ClientboundAddEntityPacket(this, this.hasPose(Poses.EMERGING.get()) ? 1 : 0);
}
@Override
public void recreateFromPacket(ClientboundAddEntityPacket packet) {
super.recreateFromPacket(packet);
if (packet.getData() == 1) this.setPose(Poses.EMERGING.get());
}
@Override
public boolean checkSpawnObstruction(LevelReader reader) {
return super.checkSpawnObstruction(reader) && reader.noCollision(this, this.getType().getDimensions().makeBoundingBox(this.position()));
}
@Override
public float getWalkTargetValue(BlockPos pos, LevelReader reader) {
return 0.0F;
}
@Override
public boolean isInvulnerableTo(DamageSource source) {
return this.isDiggingOrEmerging() && !source.isBypassInvul() || super.isInvulnerableTo(source);
}
private boolean isDiggingOrEmerging() {
return this.hasPose(Poses.DIGGING.get()) || this.hasPose(Poses.EMERGING.get());
}
@Override
protected boolean canRide(Entity entity) {
return false;
}
@Override
protected float nextStep() {
return this.moveDist + 0.55F;
}
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 500.0D).add(Attributes.MOVEMENT_SPEED, 0.3F).add(Attributes.KNOCKBACK_RESISTANCE, 1.0D).add(Attributes.ATTACK_KNOCKBACK, 1.5D).add(Attributes.ATTACK_DAMAGE, 30.0D);
}
@Override
public boolean occludesVibrations() {
return true;
}
@Override
protected float getSoundVolume() {
return 4.0F;
}
@Nullable @Override
protected SoundEvent getAmbientSound() {
return !this.hasPose(Poses.ROARING.get()) && !this.isDiggingOrEmerging() ? this.getAngriness().getSound() : null;
}
@Override
protected SoundEvent getHurtSound(DamageSource source) {
return WBSoundEvents.WARDEN_HURT;
}
@Override
protected SoundEvent getDeathSound() {
return WBSoundEvents.WARDEN_DEATH;
}
@Override
protected void playStepSound(BlockPos pos, BlockState state) {
this.playSound(WBSoundEvents.WARDEN_STEP, 10.0F, 1.0F);
}
@Override
public boolean doHurtTarget(Entity entity) {
this.level.broadcastEntityEvent(this, (byte)4);
this.playSound(WBSoundEvents.WARDEN_ATTACK_IMPACT, 10.0F, this.getVoicePitch());
SonicBoom.setCooldown(this, 40);
return super.doHurtTarget(entity);
}
@Override
protected void defineSynchedData() {
super.defineSynchedData();
this.entityData.define(ANGER, 0);
}
public int getAnger() {
return this.entityData.get(ANGER);
}
private void updateAnger() {
this.entityData.set(ANGER, this.getPrimeSuspectAnger());
}
@Override
public void tick() {
if (this.level instanceof ServerLevel server) {
this.listener.tick(server);
if (this.isPersistenceRequired() || this.requiresCustomPersistence()) {
WardenBrain.setDigCooldown(this);
}
}
super.tick();
if (this.level.isClientSide) {
if (this.tickCount % this.getHeartRate() == 0) {
this.heartPitchEnd = 10;
if (!this.isSilent()) {
this.level.playLocalSound(this.getX(), this.getY(), this.getZ(), WBSoundEvents.WARDEN_HEARTBEAT, this.getSoundSource(), 5.0F, this.getVoicePitch(), false);
}
}
this.tendrilPitchStart = this.tendrilPitchEnd;
if (this.tendrilPitchEnd > 0) {
--this.tendrilPitchEnd;
}
this.heartPitchStart = this.heartPitchEnd;
if (this.heartPitchEnd > 0) {
--this.heartPitchEnd;
}
if (this.hasPose(Poses.EMERGING.get())) {
this.addDigParticles(this.emergingAnimationState);
}
if (this.hasPose(Poses.DIGGING.get())) {
this.addDigParticles(this.diggingAnimationState);
}
}
}
@Override
protected void customServerAiStep() {
ServerLevel level = (ServerLevel)this.level;
level.getProfiler().push("wardenBrain");
this.getBrain().tick(level, this);
this.level.getProfiler().pop();
super.customServerAiStep();
if ((this.tickCount + this.getId()) % 120 == 0) {
addDarknessEffectToClosePlayers(level, this.position(), this, 20);
}
if (this.tickCount % 20 == 0) {
this.angerManager.tick(level, this::isValidTarget);
this.updateAnger();
}
WardenBrain.updateActivity(this);
}
@Override
public void handleEntityEvent(byte status) {
if (status == 4) {
this.roaringAnimationState.stop();
this.attackingAnimationState.start(this.tickCount);
} else if (status == 61) {
this.tendrilPitchEnd = 10;
} else if (status == 62) {
this.sonicBoomAnimationState.start(this.tickCount);
} else {
super.handleEntityEvent(status);
}
}
private int getHeartRate() {
return 40 - Mth.floor(Mth.clamp((float)this.getAnger() / (float) Angriness.ANGRY.getThreshold(), 0.0F, 1.0F) * 30.0F);
}
public float getTendrilPitch(float tickDelta) {
return Mth.lerp(tickDelta, (float)this.tendrilPitchStart, (float)this.tendrilPitchEnd) / 10.0F;
}
public float getHeartPitch(float tickDelta) {
return Mth.lerp(tickDelta, (float)this.heartPitchStart, (float)this.heartPitchEnd) / 10.0F;
}
private void addDigParticles(AnimationState animationState) {
if ((float)animationState.runningTime() < 4500.0F) {
Random random = this.getRandom();
BlockState state = this.getBlockStateOn();
if (state.getRenderShape() != RenderShape.INVISIBLE) {
for (int i = 0; i < 30; i++) {
double x = this.getX() + (double)Mth.randomBetween(random, -0.7F, 0.7F);
double y = this.getY();
double z = this.getZ() + (double)Mth.randomBetween(random, -0.7F, 0.7F);
this.level.addParticle(new BlockParticleOption(ParticleTypes.BLOCK, state), x, y, z, 0.0D, 0.0D, 0.0D);
}
}
}
}
@Override
public void onSyncedDataUpdated(EntityDataAccessor<?> data) {
if (DATA_POSE.equals(data)) {
if (this.hasPose(Poses.EMERGING.get())) {
this.emergingAnimationState.start(this.tickCount);
} else if (this.hasPose(Poses.DIGGING.get())) {
this.diggingAnimationState.start(this.tickCount);
} else if (this.hasPose(Poses.ROARING.get())) {
this.roaringAnimationState.start(this.tickCount);
} else if (this.hasPose(Poses.SNIFFING.get())) {
this.sniffingAnimationState.start(this.tickCount);
}
}
super.onSyncedDataUpdated(data);
}
public boolean hasPose(Pose pose) {
return this.getPose() == pose;
}
@Override
public boolean ignoreExplosion() {
return this.isDiggingOrEmerging();
}
@Override
protected Brain<?> makeBrain(Dynamic<?> dynamic) {
return WardenBrain.makeBrain(this, dynamic);
}
@Override @SuppressWarnings("unchecked")
public Brain<Warden> getBrain() {
return (Brain<Warden>)super.getBrain();
}
@Override
protected void sendDebugPackets() {
super.sendDebugPackets();
DebugPackets.sendEntityBrain(this);
}
@Override
public TagKey<GameEvent> getListenableEvents() {
return WBGameEventTags.WARDEN_CAN_LISTEN;
}
@Override
public boolean canTriggerAvoidVibration() {
return true;
}
public boolean isValidTarget(@Nullable Entity entity) {
if (entity instanceof LivingEntity living) {
return this.level == entity.level && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) && !this.isAlliedTo(entity) && living.getType() != EntityType.ARMOR_STAND && living.getType() != WBEntities.WARDEN.get() && !living.isInvulnerable() && !living.isDeadOrDying() && this.level.getWorldBorder().isWithinBounds(living.getBoundingBox());
}
return false;
}
public static void addDarknessEffectToClosePlayers(ServerLevel level, Vec3 pos, @Nullable Entity entity, int range) {
MobEffectInstance instance = new MobEffectInstance(MobEffects.BLINDNESS, 260, 0, false, false);
MobUtils.addEffectToPlayersWithinDistance(level, entity, pos, range, instance, 200);
}
@Override
public void addAdditionalSaveData(CompoundTag tag) {
super.addAdditionalSaveData(tag);
WardenAngerManager.createCodec(this::isValidTarget).encodeStart(NbtOps.INSTANCE, this.angerManager).resultOrPartial(WildBackport.LOGGER::error).ifPresent(manager -> tag.put("anger", manager));
VibrationListenerSource.codec(this).encodeStart(NbtOps.INSTANCE, this.listener).resultOrPartial(WildBackport.LOGGER::error).ifPresent(listener -> tag.put("listener", listener));
}
@Override
public void readAdditionalSaveData(CompoundTag tag) {
super.readAdditionalSaveData(tag);
if (tag.contains("anger")) {
WardenAngerManager.createCodec(this::isValidTarget).parse(new Dynamic<>(NbtOps.INSTANCE, tag.get("anger"))).resultOrPartial(WildBackport.LOGGER::error).ifPresent(manager -> this.angerManager = manager);
this.updateAnger();
}
if (tag.contains("listener", 10)) VibrationListenerSource.codec(this).parse(new Dynamic<>(NbtOps.INSTANCE, tag.getCompound("listener"))).resultOrPartial(WildBackport.LOGGER::error).ifPresent(listener -> this.listener = listener);
}
private void playListeningSound() {
if (!this.hasPose(Poses.ROARING.get())) this.playSound(this.getAngriness().getListeningSound(), 10.0F, this.getVoicePitch());
}
public Angriness getAngriness() {
return Angriness.getForAnger(this.getPrimeSuspectAnger());
}
public int getPrimeSuspectAnger() {
return this.angerManager.getPrimeSuspectAnger();
}
public void removeSuspect(Entity entity) {
this.angerManager.removeSuspect(entity);
}
public void increaseAngerAt(Entity entity) {
this.increaseAngerAt(entity, 35, true);
}
public void increaseAngerAt(Entity entity, int amount, boolean listening) {
if (!this.isNoAi() && this.isValidTarget(entity)) {
WardenBrain.setDigCooldown(this);
boolean targetNotPlayer = !(this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null) instanceof Player);
int anger = this.angerManager.increaseAngerAt(entity, amount);
if (entity instanceof Player && targetNotPlayer && Angriness.getForAnger(anger).isAngry()) {
this.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET);
}
if (listening) this.playListeningSound();
}
}
public Optional<LivingEntity> getEntityAngryAt() {
return this.getAngriness().isAngry() ? this.angerManager.getPrimeSuspect() : Optional.empty();
}
@Nullable @Override
public LivingEntity getTarget() {
return this.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null);
}
@Override
public boolean removeWhenFarAway(double sqrDist) {
return false;
}
@Nullable @Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor accessor, DifficultyInstance difficulty, MobSpawnType spawn, @Nullable SpawnGroupData groupData, @Nullable CompoundTag tag) {
this.getBrain().setMemoryWithExpiry(WBMemoryModules.DIG_COOLDOWN.get(), Unit.INSTANCE, 1200L);
if (spawn == MobSpawnType.TRIGGERED) {
this.setPose(Poses.EMERGING.get());
this.getBrain().setMemoryWithExpiry(WBMemoryModules.IS_EMERGING.get(), Unit.INSTANCE, WardenBrain.EMERGE_DURATION);
this.playSound(WBSoundEvents.WARDEN_AGITATED, 5.0F, 1.0F);
}
return super.finalizeSpawn(accessor, difficulty, spawn, groupData, tag);
}
@Override
public boolean hurt(DamageSource source, float amount) {
boolean hurt = super.hurt(source, amount);
if (!this.level.isClientSide && !this.isNoAi() && !this.isDiggingOrEmerging()) {
Entity entity = source.getEntity();
this.increaseAngerAt(entity, Angriness.ANGRY.getThreshold() + 20, false);
if (this.brain.getMemory(MemoryModuleType.ATTACK_TARGET).isEmpty() && entity instanceof LivingEntity living) {
if (!(source instanceof IndirectEntityDamageSource) || this.closerThan(living, 5.0D)) {
this.updateAttackTarget(living);
}
}
}
return hurt;
}
public void updateAttackTarget(LivingEntity entity) {
this.getBrain().eraseMemory(WBMemoryModules.ROAR_TARGET.get());
entity.getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, entity);
entity.getBrain().eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
SonicBoom.setCooldown(this, 200);
}
@Override
public EntityDimensions getDimensions(Pose pose) {
EntityDimensions dimensions = super.getDimensions(pose);
return this.isDiggingOrEmerging() ? EntityDimensions.fixed(dimensions.width, 1.0F) : dimensions;
}
@Override
public boolean isPushable() {
return !this.isDiggingOrEmerging() && super.isPushable();
}
@Override
protected void doPush(Entity entity) {
if (!this.isNoAi() && !this.getBrain().hasMemoryValue(WBMemoryModules.TOUCH_COOLDOWN.get())) {
this.getBrain().setMemoryWithExpiry(WBMemoryModules.TOUCH_COOLDOWN.get(), Unit.INSTANCE, 20L);
this.increaseAngerAt(entity);
WardenBrain.setDisturbanceLocation(this, entity.blockPosition());
}
super.doPush(entity);
}
@Override
public boolean shouldListen(ServerLevel level, GameEventListener listener, BlockPos pos, GameEvent event, @Nullable Entity entity) {
if (!this.isNoAi() && !this.isDeadOrDying() && !this.getBrain().hasMemoryValue(WBMemoryModules.VIBRATION_COOLDOWN.get()) && !this.isDiggingOrEmerging() && level.getWorldBorder().isWithinBounds(pos) && !this.isRemoved() && this.level == level) {
if (entity instanceof LivingEntity living) {
return this.isValidTarget(living);
}
return true;
} else {
return false;
}
}
@Override
public void onSignalReceive(ServerLevel level, GameEventListener listener, BlockPos pos, GameEvent event, @Nullable Entity entity, @Nullable Entity source, float distance) {
if (!this.isDeadOrDying()) {
this.brain.setMemoryWithExpiry(WBMemoryModules.VIBRATION_COOLDOWN.get(), Unit.INSTANCE, 40L);
level.broadcastEntityEvent(this, (byte)61);
this.playSound(WBSoundEvents.WARDEN_TENDRIL_CLICKS, 5.0F, this.getVoicePitch());
BlockPos position = pos;
if (source != null) {
if (this.closerThan(source, 30.0D)) {
if (this.getBrain().hasMemoryValue(WBMemoryModules.RECENT_PROJECTILE.get())) {
if (this.isValidTarget(source)) {
position = source.blockPosition();
}
this.increaseAngerAt(source);
} else {
this.increaseAngerAt(source, 10, true);
}
}
this.getBrain().setMemoryWithExpiry(WBMemoryModules.RECENT_PROJECTILE.get(), Unit.INSTANCE, 100L);
} else {
this.increaseAngerAt(entity);
}
if (!this.getAngriness().isAngry()) {
Optional<LivingEntity> primeSuspect = this.angerManager.getPrimeSuspect();
if (source != null || primeSuspect.isEmpty() || primeSuspect.get() == entity) {
WardenBrain.setDisturbanceLocation(this, position);
}
}
}
}
@Nullable @Override
public GameEventListenerRegistrar getGameEventListenerRegistrar() {
return this.gameEventHandler;
}
}

View File

@ -0,0 +1,9 @@
package com.cursedcauldron.wildbackport.common.entities.access;
public interface EntityExperience {
void disableExpDrop();
boolean isExpDropDisabled();
int getExpToDrop();
}

View File

@ -0,0 +1,12 @@
package com.cursedcauldron.wildbackport.common.entities.access;
import com.cursedcauldron.wildbackport.common.entities.warden.WardenSpawnTracker;
import net.minecraft.world.entity.player.Player;
public interface WardenTracker {
WardenSpawnTracker getWardenSpawnTracker();
static WardenSpawnTracker getWardenSpawnTracker(Player player) {
return ((WardenTracker)player).getWardenSpawnTracker();
}
}

View File

@ -0,0 +1,32 @@
package com.cursedcauldron.wildbackport.common.entities.access.api;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
//<>
public enum BoatTypes {
MANGROVE(Blocks.OAK_PLANKS, "mangrove");
private final Block planks;
private final String name;
BoatTypes(Block planks, String name) {
this.planks = planks;
this.name = name;
}
public Block getPlanks() {
return this.planks;
}
public String getName() {
return this.name;
}
public Boat.Type get() {
return Boat.Type.byName(this.getName());
}
}

View File

@ -0,0 +1,18 @@
package com.cursedcauldron.wildbackport.common.entities.access.api;
import net.minecraft.world.entity.Pose;
//<>
public enum Poses {
ROARING,
SNIFFING,
EMERGING,
DIGGING,
CROAKING,
USING_TONGUE;
public Pose get() {
return Pose.valueOf(this.name());
}
}

View File

@ -0,0 +1,116 @@
package com.cursedcauldron.wildbackport.common.entities.brain;
import com.cursedcauldron.wildbackport.common.entities.Allay;
import com.cursedcauldron.wildbackport.common.entities.brain.allay.FlyingRandomStroll;
import com.cursedcauldron.wildbackport.common.entities.brain.allay.GoAndGiveItemsToTarget;
import com.cursedcauldron.wildbackport.common.entities.brain.allay.StayCloseToTarget;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.mojang.datafixers.util.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.core.GlobalPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.behavior.AnimalPanic;
import net.minecraft.world.entity.ai.behavior.BlockPosTracker;
import net.minecraft.world.entity.ai.behavior.CountDownCooldownTicks;
import net.minecraft.world.entity.ai.behavior.DoNothing;
import net.minecraft.world.entity.ai.behavior.EntityTracker;
import net.minecraft.world.entity.ai.behavior.GoToWantedItem;
import net.minecraft.world.entity.ai.behavior.LookAtTargetSink;
import net.minecraft.world.entity.ai.behavior.MoveToTargetSink;
import net.minecraft.world.entity.ai.behavior.PositionTracker;
import net.minecraft.world.entity.ai.behavior.RunOne;
import net.minecraft.world.entity.ai.behavior.RunSometimes;
import net.minecraft.world.entity.ai.behavior.SetEntityLookTarget;
import net.minecraft.world.entity.ai.behavior.SetWalkTargetFromLookTarget;
import net.minecraft.world.entity.ai.behavior.Swim;
import net.minecraft.world.entity.schedule.Activity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import java.util.Optional;
import java.util.UUID;
//<>
public class AllayBrain {
public static Brain<?> makeBrain(Brain<Allay> brain) {
initCoreActivity(brain);
initIdleActivity(brain);
brain.setCoreActivities(ImmutableSet.of(Activity.CORE));
brain.setDefaultActivity(Activity.IDLE);
brain.useDefaultActivity();
return brain;
}
private static void initCoreActivity(Brain<Allay> brain) {
brain.addActivity(Activity.CORE, 0, ImmutableList.of(new Swim(0.8F), new AnimalPanic(2.5F), new LookAtTargetSink(45, 90), new MoveToTargetSink(), new CountDownCooldownTicks(WBMemoryModules.LIKED_NOTEBLOCK_COOLDOWN_TICKS.get()), new CountDownCooldownTicks(WBMemoryModules.ITEM_PICKUP_COOLDOWN_TICKS.get())));
}
private static void initIdleActivity(Brain<Allay> brain) {
brain.addActivityWithConditions(Activity.IDLE, ImmutableList.of(Pair.of(0, new GoToWantedItem<>(entity -> {
return true;
}, 1.75F, true, 32)), Pair.of(1, new GoAndGiveItemsToTarget<>(AllayBrain::getItemDepositPosition, 2.25F)), Pair.of(2, new StayCloseToTarget<>(AllayBrain::getItemDepositPosition, 4, 16, 2.25F)), Pair.of(3, new RunSometimes<>(new SetEntityLookTarget(entity -> {
return true;
}, 6.0F), UniformInt.of(30, 60))), Pair.of(4, new RunOne<>(ImmutableList.of(Pair.of(new FlyingRandomStroll(1.0F), 2), Pair.of(new SetWalkTargetFromLookTarget(1.0F, 3), 2), Pair.of(new DoNothing(30, 60), 1))))), ImmutableSet.of());
}
public static void updateActivity(Allay allay) {
allay.getBrain().setActiveActivityToFirstValid(ImmutableList.of(Activity.IDLE));
}
public static void hearNoteblock(LivingEntity entity, BlockPos pos) {
Brain<?> brain = entity.getBrain();
GlobalPos globalPos = GlobalPos.of(entity.getLevel().dimension(), pos);
Optional<GlobalPos> likedNoteblock = brain.getMemory(WBMemoryModules.LIKED_NOTEBLOCK.get());
if (likedNoteblock.isEmpty()) {
brain.setMemory(WBMemoryModules.LIKED_NOTEBLOCK.get(), globalPos);
brain.setMemory(WBMemoryModules.LIKED_NOTEBLOCK_COOLDOWN_TICKS.get(), 600);
} else if (likedNoteblock.get().equals(globalPos)) {
brain.setMemory(WBMemoryModules.LIKED_NOTEBLOCK_COOLDOWN_TICKS.get(), 600);
}
}
private static Optional<PositionTracker> getItemDepositPosition(LivingEntity entity) {
Brain<?> brain = entity.getBrain();
Optional<GlobalPos> likedNoteblock = brain.getMemory(WBMemoryModules.LIKED_NOTEBLOCK.get());
if (likedNoteblock.isPresent()) {
GlobalPos pos = likedNoteblock.get();
if (shouldDepositItemsAtLikedNoteblock(entity, brain, pos)) return Optional.of(new BlockPosTracker(pos.pos().above()));
brain.eraseMemory(WBMemoryModules.LIKED_NOTEBLOCK.get());
}
return getLikedPlayerPositionTracker(entity);
}
private static boolean shouldDepositItemsAtLikedNoteblock(LivingEntity entity, Brain<?> brain, GlobalPos pos) {
Optional<Integer> cooldownTicks = brain.getMemory(WBMemoryModules.LIKED_NOTEBLOCK_COOLDOWN_TICKS.get());
Level level = entity.getLevel();
return level.dimension() == pos.dimension() && level.getBlockState(pos.pos()).is(Blocks.NOTE_BLOCK) && cooldownTicks.isPresent();
}
private static Optional<PositionTracker> getLikedPlayerPositionTracker(LivingEntity entity) {
return getLikedPlayer(entity).map(player -> new EntityTracker(player, true));
}
public static Optional<ServerPlayer> getLikedPlayer(LivingEntity entity) {
Level level = entity.getLevel();
if (!level.isClientSide && level instanceof ServerLevel server) {
Optional<UUID> likedPlayer = entity.getBrain().getMemory(WBMemoryModules.LIKED_PLAYER.get());
if (likedPlayer.isPresent()) {
if (server.getEntity(likedPlayer.get()) instanceof ServerPlayer player) {
if ((player.gameMode.isSurvival() || player.gameMode.isCreative()) && player.closerThan(entity, 64.0D)) return Optional.of(player);
}
return Optional.empty();
}
}
return Optional.empty();
}
}

View File

@ -0,0 +1,105 @@
package com.cursedcauldron.wildbackport.common.entities.brain;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.Frog;
import com.cursedcauldron.wildbackport.common.entities.brain.frog.BiasedLongJumpTask;
import com.cursedcauldron.wildbackport.common.entities.brain.frog.Croak;
import com.cursedcauldron.wildbackport.common.entities.brain.frog.FrogEat;
import com.cursedcauldron.wildbackport.common.entities.brain.frog.LayFrogSpawnTask;
import com.cursedcauldron.wildbackport.common.entities.brain.frog.WalkTowardsLand;
import com.cursedcauldron.wildbackport.common.entities.brain.frog.WalkTowardsWater;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import com.cursedcauldron.wildbackport.common.registry.entity.WBActivities;
import com.cursedcauldron.wildbackport.common.registry.entity.WBEntities;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.tag.WBBlockTags;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.mojang.datafixers.util.Pair;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.behavior.AnimalMakeLove;
import net.minecraft.world.entity.ai.behavior.AnimalPanic;
import net.minecraft.world.entity.ai.behavior.CountDownCooldownTicks;
import net.minecraft.world.entity.ai.behavior.DoNothing;
import net.minecraft.world.entity.ai.behavior.FollowTemptation;
import net.minecraft.world.entity.ai.behavior.GateBehavior;
import net.minecraft.world.entity.ai.behavior.LongJumpMidJump;
import net.minecraft.world.entity.ai.behavior.LookAtTargetSink;
import net.minecraft.world.entity.ai.behavior.MoveToTargetSink;
import net.minecraft.world.entity.ai.behavior.RandomStroll;
import net.minecraft.world.entity.ai.behavior.RunIf;
import net.minecraft.world.entity.ai.behavior.RunOne;
import net.minecraft.world.entity.ai.behavior.RunSometimes;
import net.minecraft.world.entity.ai.behavior.SetEntityLookTarget;
import net.minecraft.world.entity.ai.behavior.SetWalkTargetFromLookTarget;
import net.minecraft.world.entity.ai.behavior.StartAttacking;
import net.minecraft.world.entity.ai.behavior.StopAttackingIfTargetInvalid;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.schedule.Activity;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.block.Blocks;
import java.util.Random;
public class FrogBrain {
private static final UniformInt LONG_JUMP_COOLDOWN_RANGE = UniformInt.of(100, 140);
public static void coolDownLongJump(Frog frog, Random random) {
frog.getBrain().setMemory(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, LONG_JUMP_COOLDOWN_RANGE.sample(random));
}
public static Brain<?> create(Brain<Frog> brain) {
addCoreActivities(brain);
addIdleActivities(brain);
addSwimActivities(brain);
addLaySpawnActivities(brain);
addTongueActivities(brain);
addLongJumpActivities(brain);
brain.setCoreActivities(ImmutableSet.of(Activity.CORE));
brain.setDefaultActivity(Activity.IDLE);
brain.useDefaultActivity();
return brain;
}
private static void addCoreActivities(Brain<Frog> brain) {
brain.addActivity(Activity.CORE, 0, ImmutableList.of(new AnimalPanic(2.0F), new LookAtTargetSink(45, 90), new MoveToTargetSink(), new CountDownCooldownTicks(MemoryModuleType.TEMPTATION_COOLDOWN_TICKS), new CountDownCooldownTicks(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS)));
}
private static void addIdleActivities(Brain<Frog> brain) {
brain.addActivityWithConditions(Activity.IDLE, ImmutableList.of(Pair.of(0, new RunSometimes<LivingEntity>(new SetEntityLookTarget(EntityType.PLAYER, 6.0F), UniformInt.of(30, 60))), Pair.of(0, new AnimalMakeLove(WBEntities.FROG.get(), 1.0F)), Pair.of(1, new FollowTemptation(entity -> 1.25F)), Pair.of(2, new StartAttacking<>(FrogBrain::isNotBreeding, frog -> frog.getBrain().getMemory(MemoryModuleType.NEAREST_ATTACKABLE))), Pair.of(3, new WalkTowardsLand(6, 1.0F)), Pair.of(4, new RunOne<>(ImmutableMap.of(MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT), ImmutableList.of(Pair.of(new RandomStroll(1.0F), 1), Pair.of(new SetWalkTargetFromLookTarget(1.0F, 3), 1), Pair.of(new Croak(), 3), Pair.of(new RunIf<>(Entity::isOnGround, new DoNothing(5, 20)), 2))))), ImmutableSet.of(Pair.of(MemoryModuleType.LONG_JUMP_MID_JUMP, MemoryStatus.VALUE_ABSENT), Pair.of(WBMemoryModules.IS_IN_WATER.get(), MemoryStatus.VALUE_ABSENT)));
}
private static void addSwimActivities(Brain<Frog> brain) {
brain.addActivityWithConditions(WBActivities.SWIM.get(), ImmutableList.of(Pair.of(0, new RunSometimes<LivingEntity>(new SetEntityLookTarget(EntityType.PLAYER, 6.0F), UniformInt.of(30, 60))), Pair.of(1, new FollowTemptation(entity -> 1.25F)), Pair.of(2, new StartAttacking<>(FrogBrain::isNotBreeding, frog -> frog.getBrain().getMemory(MemoryModuleType.NEAREST_ATTACKABLE))), Pair.of(3, new WalkTowardsLand(8, 1.5F)), Pair.of(5, new GateBehavior<>(ImmutableMap.of(MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT), ImmutableSet.of(), GateBehavior.OrderPolicy.ORDERED, GateBehavior.RunningPolicy.TRY_ALL, ImmutableList.of(Pair.of(new RandomStroll(0.75F), 1), Pair.of(new RandomStroll(1.0F, true), 1), Pair.of(new SetWalkTargetFromLookTarget(1.0F, 3), 1), Pair.of(new RunIf<>(Entity::isInWaterOrBubble, new DoNothing(30, 60)), 5))))), ImmutableSet.of(Pair.of(MemoryModuleType.LONG_JUMP_MID_JUMP, MemoryStatus.VALUE_ABSENT), Pair.of(WBMemoryModules.IS_IN_WATER.get(), MemoryStatus.VALUE_PRESENT)));
}
private static void addLaySpawnActivities(Brain<Frog> brain) {
brain.addActivityWithConditions(WBActivities.LAY_SPAWN.get(), ImmutableList.of(Pair.of(0, new RunSometimes<LivingEntity>(new SetEntityLookTarget(EntityType.PLAYER, 6.0F), UniformInt.of(30, 60))), Pair.of(1, new StartAttacking<>(FrogBrain::isNotBreeding, frog -> frog.getBrain().getMemory(MemoryModuleType.NEAREST_ATTACKABLE))), Pair.of(2, new WalkTowardsWater(8, 1.0F)), Pair.of(3, new LayFrogSpawnTask(WBBlocks.FROGSPAWN.get(), WBMemoryModules.IS_PREGNANT.get())), Pair.of(4, new RunOne<>(ImmutableList.of(Pair.of(new RandomStroll(1.0F), 2), Pair.of(new SetWalkTargetFromLookTarget(1.0F, 3), 1), Pair.of(new Croak(), 2), Pair.of(new RunIf<>(Entity::isOnGround, new DoNothing(5, 20)), 1))))), ImmutableSet.of(Pair.of(MemoryModuleType.LONG_JUMP_MID_JUMP, MemoryStatus.VALUE_ABSENT), Pair.of(WBMemoryModules.IS_PREGNANT.get(), MemoryStatus.VALUE_PRESENT)));
}
private static void addLongJumpActivities(Brain<Frog> brain) {
brain.addActivityWithConditions(Activity.LONG_JUMP, ImmutableList.of(Pair.of(0, new LongJumpMidJump(LONG_JUMP_COOLDOWN_RANGE, WBSoundEvents.FROG_STEP)), Pair.of(1, new BiasedLongJumpTask<>(LONG_JUMP_COOLDOWN_RANGE, 2, 4, 1.5F, frog -> WBSoundEvents.FROG_LONG_JUMP, WBBlockTags.FROG_PREFER_JUMP_TO, 0.5F, state -> state.is(Blocks.LILY_PAD)))), ImmutableSet.of(Pair.of(MemoryModuleType.TEMPTING_PLAYER, MemoryStatus.VALUE_ABSENT), Pair.of(MemoryModuleType.BREED_TARGET, MemoryStatus.VALUE_ABSENT), Pair.of(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, MemoryStatus.VALUE_ABSENT), Pair.of(WBMemoryModules.IS_IN_WATER.get(), MemoryStatus.VALUE_ABSENT)));
}
private static void addTongueActivities(Brain<Frog> brain) {
brain.addActivityAndRemoveMemoryWhenStopped(WBActivities.TONGUE.get(), 0, ImmutableList.of(new StopAttackingIfTargetInvalid<>(), new FrogEat(WBSoundEvents.FROG_TONGUE, WBSoundEvents.FROG_EAT)), MemoryModuleType.ATTACK_TARGET);
}
private static boolean isNotBreeding(Frog frog) {
return !frog.getBrain().hasMemoryValue(MemoryModuleType.BREED_TARGET);
}
public static void updateActivities(Frog frog) {
frog.getBrain().setActiveActivityToFirstValid(ImmutableList.of(WBActivities.TONGUE.get(), WBActivities.LAY_SPAWN.get(), Activity.LONG_JUMP, WBActivities.SWIM.get(), Activity.IDLE));
}
public static Ingredient getTemptItems() {
return Frog.FOOD;
}
}

View File

@ -0,0 +1,51 @@
package com.cursedcauldron.wildbackport.common.entities.brain;
import com.cursedcauldron.wildbackport.common.entities.Tadpole;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.mojang.datafixers.util.Pair;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.behavior.AnimalPanic;
import net.minecraft.world.entity.ai.behavior.CountDownCooldownTicks;
import net.minecraft.world.entity.ai.behavior.DoNothing;
import net.minecraft.world.entity.ai.behavior.GateBehavior;
import net.minecraft.world.entity.ai.behavior.LookAtTargetSink;
import net.minecraft.world.entity.ai.behavior.MoveToTargetSink;
import net.minecraft.world.entity.ai.behavior.RandomStroll;
import net.minecraft.world.entity.ai.behavior.RandomSwim;
import net.minecraft.world.entity.ai.behavior.RunIf;
import net.minecraft.world.entity.ai.behavior.RunSometimes;
import net.minecraft.world.entity.ai.behavior.SetEntityLookTarget;
import net.minecraft.world.entity.ai.behavior.SetWalkTargetFromLookTarget;
import net.minecraft.world.entity.ai.behavior.TryFindWater;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.schedule.Activity;
public class TadpoleBrain {
public static Brain<?> create(Brain<Tadpole> brain) {
TadpoleBrain.addCoreActivities(brain);
TadpoleBrain.addIdleActivities(brain);
brain.setCoreActivities(ImmutableSet.of(Activity.CORE));
brain.setDefaultActivity(Activity.IDLE);
brain.useDefaultActivity();
return brain;
}
private static void addCoreActivities(Brain<Tadpole> brain) {
brain.addActivity(Activity.CORE, 0, ImmutableList.of(new AnimalPanic(2.0f), new LookAtTargetSink(45, 90), new MoveToTargetSink(), new CountDownCooldownTicks(MemoryModuleType.TEMPTATION_COOLDOWN_TICKS)));
}
private static void addIdleActivities(Brain<Tadpole> brain) {
brain.addActivity(Activity.IDLE, ImmutableList.of(Pair.of(0, new RunSometimes<LivingEntity>(new SetEntityLookTarget(EntityType.PLAYER, 6.0f), UniformInt.of(30, 60))), Pair.of(3, new TryFindWater(6, 0.15f)), Pair.of(4, new GateBehavior<>(ImmutableMap.of(MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT), ImmutableSet.of(), GateBehavior.OrderPolicy.ORDERED, GateBehavior.RunningPolicy.TRY_ALL, ImmutableList.of(Pair.of(new RandomSwim(0.5f), 2), Pair.of(new RandomStroll(0.15f), 2), Pair.of(new SetWalkTargetFromLookTarget(0.5f, 3), 3), Pair.of(new RunIf<>(Entity::isInWaterOrBubble, new DoNothing(30, 60)), 5))))));
}
public static void updateActivities(Tadpole tadpole) {
tadpole.getBrain().setActiveActivityToFirstValid(ImmutableList.of(Activity.IDLE));
}
}

View File

@ -0,0 +1,143 @@
package com.cursedcauldron.wildbackport.common.entities.brain;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.ForgetAttackTargetTask;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.Digging;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.Dismount;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.Emerging;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.GoToTargetLocation;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.Roar;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.SetRoarTarget;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.SetWardenLookTarget;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.Sniffing;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.SonicBoom;
import com.cursedcauldron.wildbackport.common.entities.brain.warden.TryToSniff;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.registry.entity.WBActivities;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.registry.entity.WBSensorTypes;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Dynamic;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.Unit;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BlockPosTracker;
import net.minecraft.world.entity.ai.behavior.DoNothing;
import net.minecraft.world.entity.ai.behavior.LookAtTargetSink;
import net.minecraft.world.entity.ai.behavior.MeleeAttack;
import net.minecraft.world.entity.ai.behavior.MoveToTargetSink;
import net.minecraft.world.entity.ai.behavior.RandomStroll;
import net.minecraft.world.entity.ai.behavior.RunOne;
import net.minecraft.world.entity.ai.behavior.SetEntityLookTarget;
import net.minecraft.world.entity.ai.behavior.SetWalkTargetFromAttackTargetIfTargetOutOfReach;
import net.minecraft.world.entity.ai.behavior.Swim;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
import net.minecraft.world.entity.schedule.Activity;
import java.util.List;
//<>
public class WardenBrain {
private static final int DIGGING_DURATION = Mth.ceil(100.0F);
public static final int EMERGE_DURATION = Mth.ceil(133.59999F);
public static final int ROAR_DURATION = Mth.ceil(84.0F);
private static final int SNIFFING_DURATION = Mth.ceil(83.2F);
private static final List<SensorType<? extends Sensor<? super Warden>>> SENSORS = List.of(SensorType.NEAREST_PLAYERS, WBSensorTypes.WARDEN_ENTITY_SENSOR.get());
private static final List<MemoryModuleType<?>> MEMORIES = List.of(MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_NEMESIS, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_ATTACKABLE, WBMemoryModules.ROAR_TARGET.get(), WBMemoryModules.DISTURBANCE_LOCATION.get(), WBMemoryModules.RECENT_PROJECTILE.get(), WBMemoryModules.IS_SNIFFING.get(), WBMemoryModules.IS_EMERGING.get(), WBMemoryModules.ROAR_SOUND_DELAY.get(), WBMemoryModules.DIG_COOLDOWN.get(), WBMemoryModules.ROAR_SOUND_COOLDOWN.get(), WBMemoryModules.SNIFF_COOLDOWN.get(), WBMemoryModules.TOUCH_COOLDOWN.get(), WBMemoryModules.VIBRATION_COOLDOWN.get(), WBMemoryModules.SONIC_BOOM_COOLDOWN.get(), WBMemoryModules.SONIC_BOOM_SOUND_COOLDOWN.get(), WBMemoryModules.SONIC_BOOM_SOUND_DELAY.get());
private static final Behavior<Warden> DIG_COOLDOWN_SETTER = new Behavior<>(ImmutableMap.of(WBMemoryModules.DIG_COOLDOWN.get(), MemoryStatus.REGISTERED)) {
@Override protected void start(ServerLevel level, Warden warden, long time) {
setDigCooldown(warden);
}
};
public static void updateActivity(Warden warden) {
warden.getBrain().setActiveActivityToFirstValid(ImmutableList.of(WBActivities.EMERGE.get(), WBActivities.DIG.get(), WBActivities.ROAR.get(), Activity.FIGHT, WBActivities.INVESTIGATE.get(), WBActivities.SNIFF.get(), Activity.IDLE));
}
public static Brain<?> makeBrain(Warden warden, Dynamic<?> dynamic) {
Brain.Provider<Warden> provider = Brain.provider(MEMORIES, SENSORS);
Brain<Warden> brain = provider.makeBrain(dynamic);
initCoreActivity(brain);
initEmergeActivity(brain);
initDiggingActivity(brain);
initIdleActivity(brain);
initRoarActivity(brain);
initFightActivity(warden, brain);
initInvestigateActivity(brain);
initSniffingActivity(brain);
brain.setCoreActivities(ImmutableSet.of(Activity.CORE));
brain.setDefaultActivity(Activity.IDLE);
brain.useDefaultActivity();
return brain;
}
private static void initCoreActivity(Brain<Warden> brain) {
brain.addActivity(Activity.CORE, 0, ImmutableList.of(new Swim(0.8F), new SetWardenLookTarget(), new LookAtTargetSink(45, 90), new MoveToTargetSink()));
}
private static void initEmergeActivity(Brain<Warden> brain) {
brain.addActivityAndRemoveMemoryWhenStopped(WBActivities.EMERGE.get(), 5, ImmutableList.of(new Emerging<>(EMERGE_DURATION)), WBMemoryModules.IS_EMERGING.get());
}
private static void initDiggingActivity(Brain<Warden> brain) {
brain.addActivityWithConditions(WBActivities.DIG.get(), ImmutableList.of(Pair.of(0, new Dismount()), Pair.of(1, new Digging<>(DIGGING_DURATION))), ImmutableSet.of(Pair.of(WBMemoryModules.ROAR_TARGET.get(), MemoryStatus.VALUE_ABSENT), Pair.of(WBMemoryModules.DIG_COOLDOWN.get(), MemoryStatus.VALUE_ABSENT)));
}
private static void initIdleActivity(Brain<Warden> brain) {
brain.addActivity(Activity.IDLE, 10, ImmutableList.of(new SetRoarTarget<>(Warden::getEntityAngryAt), new TryToSniff(), new RunOne<>(ImmutableMap.of(WBMemoryModules.IS_SNIFFING.get(), MemoryStatus.VALUE_ABSENT), ImmutableList.of(Pair.of(new RandomStroll(0.5F), 2), Pair.of(new DoNothing(30, 60), 1)))));
}
private static void initInvestigateActivity(Brain<Warden> brain) {
brain.addActivityAndRemoveMemoryWhenStopped(WBActivities.INVESTIGATE.get(), 5, ImmutableList.of(new SetRoarTarget<>(Warden::getEntityAngryAt), new GoToTargetLocation<>(WBMemoryModules.DISTURBANCE_LOCATION.get(), 2, 0.7F)), WBMemoryModules.DISTURBANCE_LOCATION.get());
}
private static void initSniffingActivity(Brain<Warden> brain) {
brain.addActivityAndRemoveMemoryWhenStopped(WBActivities.SNIFF.get(), 5, ImmutableList.of(new SetRoarTarget<>(Warden::getEntityAngryAt), new Sniffing<>(SNIFFING_DURATION)), WBMemoryModules.IS_SNIFFING.get());
}
private static void initRoarActivity(Brain<Warden> brain) {
brain.addActivityAndRemoveMemoryWhenStopped(WBActivities.ROAR.get(), 10, ImmutableList.of(new Roar()), WBMemoryModules.ROAR_TARGET.get());
}
private static void initFightActivity(Warden warden, Brain<Warden> brain) {
brain.addActivityAndRemoveMemoryWhenStopped(Activity.FIGHT, 10, ImmutableList.of(DIG_COOLDOWN_SETTER, new ForgetAttackTargetTask<>(target -> {
return !warden.getAngriness().isAngry() || !warden.isValidTarget(target);
}, WardenBrain::onTargetInvalid, false), new SetEntityLookTarget(target -> {
return isTarget(warden, target);
}, (float)warden.getAttributeValue(Attributes.FOLLOW_RANGE)), new SetWalkTargetFromAttackTargetIfTargetOutOfReach(1.2F), new SonicBoom(), new MeleeAttack(18)), MemoryModuleType.ATTACK_TARGET);
}
private static boolean isTarget(Warden warden, LivingEntity entity) {
return warden.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).filter(target -> target == entity).isPresent();
}
private static void onTargetInvalid(Warden warden, LivingEntity entity) {
if (!warden.isValidTarget(entity)) warden.removeSuspect(entity);
setDigCooldown(warden);
}
public static void setDigCooldown(LivingEntity entity) {
if (entity.getBrain().hasMemoryValue(WBMemoryModules.DIG_COOLDOWN.get())) entity.getBrain().setMemoryWithExpiry(WBMemoryModules.DIG_COOLDOWN.get(), Unit.INSTANCE, 1200L);
}
public static void setDisturbanceLocation(Warden warden, BlockPos pos) {
if (warden.level.getWorldBorder().isWithinBounds(pos) && warden.getEntityAngryAt().isEmpty() && warden.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).isEmpty()) {
setDigCooldown(warden);
warden.getBrain().setMemoryWithExpiry(WBMemoryModules.SNIFF_COOLDOWN.get(), Unit.INSTANCE, 100L);
warden.getBrain().setMemoryWithExpiry(MemoryModuleType.LOOK_TARGET, new BlockPosTracker(pos), 100L);
warden.getBrain().setMemoryWithExpiry(WBMemoryModules.DISTURBANCE_LOCATION.get(), pos, 100L);
warden.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET);
}
}
}

View File

@ -0,0 +1,19 @@
package com.cursedcauldron.wildbackport.common.entities.brain.allay;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.behavior.RandomStroll;
import net.minecraft.world.entity.ai.util.AirAndWaterRandomPos;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
public class FlyingRandomStroll extends RandomStroll {
public FlyingRandomStroll(float distance) {
super(distance, true);
}
@Override @Nullable
protected Vec3 getTargetPos(PathfinderMob mob) {
Vec3 vector = mob.getViewVector(0.0F);
return AirAndWaterRandomPos.getPos(mob, this.maxHorizontalDistance, this.maxVerticalDistance, -2, vector.x, vector.z, (float)Math.PI / 2.0F);
}
}

View File

@ -0,0 +1,97 @@
package com.cursedcauldron.wildbackport.common.entities.brain.allay;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.Allay;
import com.cursedcauldron.wildbackport.common.entities.brain.AllayBrain;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.utils.MobUtils;
import com.google.common.collect.ImmutableMap;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.PositionTracker;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.npc.InventoryCarrier;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import java.util.Optional;
import java.util.function.Function;
//<>
public class GoAndGiveItemsToTarget<E extends LivingEntity & InventoryCarrier> extends Behavior<E> {
private final Function<LivingEntity, Optional<PositionTracker>> targetPosition;
private final float speedModifier;
public GoAndGiveItemsToTarget(Function<LivingEntity, Optional<PositionTracker>> targetPosition, float speedModifier) {
super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED, MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED, WBMemoryModules.ITEM_PICKUP_COOLDOWN_TICKS.get(), MemoryStatus.REGISTERED));
this.targetPosition = targetPosition;
this.speedModifier = speedModifier;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, E entity) {
return this.canThrowItemToTarget(entity);
}
@Override
protected boolean canStillUse(ServerLevel level, E entity, long time) {
return this.canThrowItemToTarget(entity);
}
@Override
protected void start(ServerLevel level, E entity, long time) {
this.targetPosition.apply(entity).ifPresent(target -> MobUtils.setWalkAndLookTargetMemories(entity, target, this.speedModifier, 3));
}
@Override
protected void tick(ServerLevel level, E entity, long time) {
Optional<PositionTracker> optional = this.targetPosition.apply(entity);
if (optional.isPresent()) {
PositionTracker tracker = optional.get();
double d0 = tracker.currentPosition().distanceTo(entity.getEyePosition());
if (d0 < 3.0D) {
ItemStack itemstack = entity.getInventory().removeItem(0, 1);
if (!itemstack.isEmpty()) {
throwItem(entity, itemstack, getThrowPosition(tracker));
if (entity instanceof Allay allay) {
AllayBrain.getLikedPlayer(allay).ifPresent(p_217224_ -> this.triggerDropItemOnBlock(tracker, itemstack, p_217224_));
}
entity.getBrain().setMemory(WBMemoryModules.ITEM_PICKUP_COOLDOWN_TICKS.get(), 60);
}
}
}
}
private void triggerDropItemOnBlock(PositionTracker tracker, ItemStack stack, ServerPlayer player) {
BlockPos pos = tracker.currentBlockPosition().below();
// WBCriteriaTriggers.ALLAY_DROP_ITEM_ON_BLOCK.trigger(player, pos, stack);
}
private boolean canThrowItemToTarget(E entity) {
return !entity.getInventory().isEmpty() && this.targetPosition.apply(entity).isPresent();
}
private static Vec3 getThrowPosition(PositionTracker tracker) {
return tracker.currentPosition().add(0.0D, 1.0D, 0.0D);
}
public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 position) {
Vec3 vec3 = new Vec3(0.2F, 0.3F, 0.2F);
MobUtils.throwItem(entity, stack, position, vec3, 0.2F);
Level level = entity.level;
if (level.getGameTime() % 7L == 0L && level.random.nextDouble() < 0.9D) {
float pitch = Util.getRandom(Allay.SOUND_PITCHES, level.getRandom());
level.playSound(null, entity, WBSoundEvents.ALLAY_ITEM_THROW, SoundSource.NEUTRAL, 1.0F, pitch);
}
}
}

View File

@ -0,0 +1,39 @@
package com.cursedcauldron.wildbackport.common.entities.brain.allay;
import com.cursedcauldron.wildbackport.common.utils.MobUtils;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.PositionTracker;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import java.util.Optional;
import java.util.function.Function;
public class StayCloseToTarget<E extends LivingEntity> extends Behavior<E> {
private final Function<LivingEntity, Optional<PositionTracker>> targetPosition;
private final int closeEnough;
private final int tooFar;
private final float speedModifier;
public StayCloseToTarget(Function<LivingEntity, Optional<PositionTracker>> targetPosition, int closeEnough, int tooFar, float speedModifier) {
super(ImmutableMap.of(MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT));
this.targetPosition = targetPosition;
this.closeEnough = closeEnough;
this.tooFar = tooFar;
this.speedModifier = speedModifier;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, E entity) {
Optional<PositionTracker> tracker = this.targetPosition.apply(entity);
return tracker.isPresent() && !entity.position().closerThan(tracker.get().currentPosition(), this.tooFar);
}
@Override
protected void start(ServerLevel level, E entity, long time) {
MobUtils.setWalkAndLookTargetMemories(entity, this.targetPosition.apply(entity).get(), this.speedModifier, this.closeEnough);
}
}

View File

@ -0,0 +1,69 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.tags.TagKey;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
public class BiasedLongJumpTask<E extends Mob> extends FrogJumpToRandomPos<E> {
private final TagKey<Block> preferredBlocks;
private final float chance;
private final List<FrogJumpToRandomPos.Target> targetCandidates = new ArrayList<>();
private boolean priorityOnPreferred;
public BiasedLongJumpTask(UniformInt cooldown, int yRange, int xzRange, float range, Function<E, SoundEvent> landingSound, TagKey<Block> preferredBlocks, float chance, Predicate<BlockState> landingBlocks) {
super(cooldown, yRange, xzRange, range, landingSound, landingBlocks);
this.preferredBlocks = preferredBlocks;
this.chance = chance;
}
@Override
protected void start(ServerLevel level, E entity, long time) {
super.start(level, entity, time);
this.targetCandidates.clear();
this.priorityOnPreferred = entity.getRandom().nextFloat() < this.chance;
}
@Override
protected Optional<FrogJumpToRandomPos.Target> jumpTarget(ServerLevel level) {
if (!this.priorityOnPreferred) {
return super.jumpTarget(level);
} else {
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
while (!this.targets.isEmpty()) {
Optional<FrogJumpToRandomPos.Target> jumpTarget = super.jumpTarget(level);
if (jumpTarget.isPresent()) {
FrogJumpToRandomPos.Target target = jumpTarget.get();
if (level.getBlockState(mutable.setWithOffset(target.getPos(), Direction.DOWN)).is(this.preferredBlocks)) {
return jumpTarget;
}
this.targetCandidates.add(target);
}
}
return !this.targetCandidates.isEmpty() ? Optional.of(this.targetCandidates.remove(0)) : Optional.empty();
}
}
@Override
protected boolean canLandOn(ServerLevel level, E entity, BlockPos pos) {
return super.canLandOn(level, entity, pos) && this.cantLandInFluid(level, pos);
}
private boolean cantLandInFluid(ServerLevel level, BlockPos pos) {
return level.getFluidState(pos).isEmpty() && level.getFluidState(pos.below()).isEmpty();
}
}

View File

@ -0,0 +1,47 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import com.cursedcauldron.wildbackport.common.entities.Frog;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
public class Croak extends Behavior<Frog> {
private int ticks;
public Croak() {
super(ImmutableMap.of(MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT), 100);
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, Frog frog) {
return frog.isInPose(Pose.STANDING);
}
@Override
protected boolean canStillUse(ServerLevel level, Frog frog, long time) {
return this.ticks < 40;
}
@Override
protected void start(ServerLevel level, Frog frog, long time) {
if (!frog.isInWaterOrBubble() && !frog.isInLava()) {
frog.setPose(Poses.CROAKING.get());
this.ticks = 0;
}
}
@Override
protected void stop(ServerLevel level, Frog frog, long time) {
frog.setPose(Pose.STANDING);
}
@Override
protected void tick(ServerLevel level, Frog frog, long time) {
++this.ticks;
}
}

View File

@ -0,0 +1,31 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import com.cursedcauldron.wildbackport.common.entities.Frog;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.sensing.NearestVisibleLivingEntitySensor;
import net.minecraft.world.entity.ai.sensing.Sensor;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
//<>
public class FrogAttackablesSensor extends NearestVisibleLivingEntitySensor {
@Override
protected boolean isMatchingEntity(LivingEntity entity, LivingEntity target) {
return !entity.getBrain().hasMemoryValue(MemoryModuleType.HAS_HUNTING_COOLDOWN) && Sensor.isEntityAttackable(entity, target) && Frog.isValidFrogFood(target) && !this.cantReachTarget(entity, target) && target.closerThan(entity, 10.0D);
}
private boolean cantReachTarget(LivingEntity entity, LivingEntity target) {
List<UUID> targets = entity.getBrain().getMemory(WBMemoryModules.UNREACHABLE_TONGUE_TARGETS.get()).orElseGet(ArrayList::new);
return targets.contains(target.getUUID());
}
@Override
protected MemoryModuleType<LivingEntity> getMemory() {
return MemoryModuleType.NEAREST_ATTACKABLE;
}
}

View File

@ -0,0 +1,162 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import com.cursedcauldron.wildbackport.common.entities.Frog;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.cursedcauldron.wildbackport.common.registry.WBBlocks;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BehaviorUtils;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.ai.memory.WalkTarget;
import net.minecraft.world.entity.monster.MagmaCube;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.pathfinder.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
//<>
public class FrogEat extends Behavior<Frog> {
private int eatTick;
private int moveToTargetTick;
private final SoundEvent tongueSound;
private final SoundEvent eatSound;
private Phase phase = Phase.DONE;
public FrogEat(SoundEvent tongueSound, SoundEvent eatSound) {
super(ImmutableMap.of(MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED, MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_PRESENT), 100);
this.tongueSound = tongueSound;
this.eatSound = eatSound;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, Frog frog) {
LivingEntity target = frog.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).get();
boolean flag = this.canMoveToTarget(frog, target);
if (!flag) {
frog.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET);
this.cantReachTarget(frog, target);
}
return flag && frog.getPose() != Poses.CROAKING.get() && Frog.isValidFrogFood(target);
}
@Override
protected boolean canStillUse(ServerLevel level, Frog frog, long time) {
return frog.getBrain().hasMemoryValue(MemoryModuleType.ATTACK_TARGET) && this.phase != Phase.DONE;
}
@Override
protected void start(ServerLevel level, Frog frog, long time) {
LivingEntity entity = frog.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).get();
BehaviorUtils.lookAtEntity(frog, entity);
frog.setFrogTarget(entity);
frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, new WalkTarget(entity.position(), 2.0F, 0));
this.moveToTargetTick = 10;
this.phase = Phase.MOVE_TO_TARGET;
}
@Override
protected void stop(ServerLevel level, Frog frog, long time) {
frog.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET);
frog.clearFrogTarget();
frog.setPose(Pose.STANDING);
}
private void eat(ServerLevel level, Frog frog) {
level.playSound(null, frog, this.eatSound, SoundSource.NEUTRAL, 2.0F, 1.0F);
Optional<Entity> target = frog.getFrogTarget();
if (target.isPresent()) {
Entity entity = target.get();
if (entity.isAlive()) {
frog.doHurtTarget(entity);
if (!entity.isAlive()) {
entity.spawnAtLocation(dropStack(frog, entity));
entity.remove(Entity.RemovalReason.KILLED);
}
}
}
}
//Change this to loot table
private static ItemStack dropStack(Frog frog, Entity entity) {
if (entity instanceof MagmaCube) {
return new ItemStack(switch (frog.getVariant()) {
case TEMPERATE -> WBBlocks.OCHRE_FROGLIGHT.get().asItem();
case WARM -> WBBlocks.PEARLESCENT_FROGLIGHT.get().asItem();
case COLD -> WBBlocks.VERDANT_FROGLIGHT.get().asItem();
});
} else {
return new ItemStack(Items.SLIME_BALL);
}
}
@Override
protected void tick(ServerLevel level, Frog frog, long time) {
LivingEntity target = frog.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).get();
frog.setFrogTarget(target);
switch (this.phase) {
case MOVE_TO_TARGET:
if (target.distanceTo(frog) < 1.75F) {
level.playSound(null, frog, this.tongueSound, SoundSource.NEUTRAL, 2.0F, 1.0F);
frog.setPose(Poses.USING_TONGUE.get());
target.setDeltaMovement(target.position().vectorTo(frog.position()).normalize().scale(0.75D));
this.eatTick = 0;
this.phase = Phase.CATCH_ANIMATION;
} else if (this.moveToTargetTick <= 0) {
frog.getBrain().setMemory(MemoryModuleType.WALK_TARGET, new WalkTarget(target.position(), 2.0F, 0));
this.moveToTargetTick = 10;
} else {
--this.moveToTargetTick;
}
break;
case CATCH_ANIMATION:
if (this.eatTick++ >= 6) {
this.phase = Phase.EAT_ANIMATION;
this.eat(level, frog);
}
break;
case EAT_ANIMATION:
if (this.eatTick >= 10) {
this.phase = Phase.DONE;
} else {
++this.eatTick;
}
case DONE:
}
}
private boolean canMoveToTarget(Frog frog, LivingEntity entity) {
Path path = frog.getNavigation().createPath(entity, 0);
return path != null && path.getDistToTarget() < 1.75F;
}
private void cantReachTarget(Frog frog, LivingEntity entity) {
List<UUID> targets = frog.getBrain().getMemory(WBMemoryModules.UNREACHABLE_TONGUE_TARGETS.get()).orElseGet(ArrayList::new);
boolean notTargeting = !targets.contains(entity.getUUID());
if (targets.size() == 5 && notTargeting) targets.remove(0);
if (notTargeting) targets.add(entity.getUUID());
frog.getBrain().setMemoryWithExpiry(WBMemoryModules.UNREACHABLE_TONGUE_TARGETS.get(), targets, 100L);
}
enum Phase {
MOVE_TO_TARGET,
CATCH_ANIMATION,
EAT_ANIMATION,
DONE
}
}

View File

@ -0,0 +1,247 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.util.random.WeightedEntry;
import net.minecraft.util.random.WeightedRandom;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BlockPosTracker;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.Path;
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class FrogJumpToRandomPos<E extends Mob> extends Behavior<E> {
private static final List<Integer> ANGLES = Lists.newArrayList(65, 70, 75, 80);
private final UniformInt cooldown;
protected final int yRange;
protected final int xzRange;
protected final float maxRange;
protected List<Target> targets = Lists.newArrayList();
protected Optional<Vec3> lastPos = Optional.empty();
@Nullable
protected Vec3 lastTarget;
protected int tries;
protected long targetTime;
private final Function<E, SoundEvent> landingSound;
private final Predicate<BlockState> landingBlocks;
public FrogJumpToRandomPos(UniformInt cooldown, int yRange, int xzRange, float range, Function<E, SoundEvent> landingSound, Predicate<BlockState> landingBlocks) {
super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED, MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LONG_JUMP_MID_JUMP, MemoryStatus.VALUE_ABSENT), 200);
this.cooldown = cooldown;
this.yRange = yRange;
this.xzRange = xzRange;
this.maxRange = range;
this.landingSound = landingSound;
this.landingBlocks = landingBlocks;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, Mob entity) {
boolean canUse = entity.isOnGround() && !entity.isInWater() && !entity.isInLava() && !level.getBlockState(entity.blockPosition()).is(Blocks.HONEY_BLOCK);
if (!canUse) entity.getBrain().setMemory(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, this.cooldown.sample(level.random) / 2);
return canUse;
}
@Override
protected boolean canStillUse(ServerLevel level, Mob entity, long time) {
boolean canUse = this.lastPos.isPresent() && this.lastPos.get().equals(entity.position()) && this.tries > 0 && !entity.isInWaterOrBubble() && (this.lastTarget != null || !this.targets.isEmpty());
if (!canUse && entity.getBrain().getMemory(MemoryModuleType.LONG_JUMP_MID_JUMP).isEmpty()) {
entity.getBrain().setMemory(MemoryModuleType.LONG_JUMP_COOLDOWN_TICKS, this.cooldown.sample(level.random) / 2);
entity.getBrain().eraseMemory(MemoryModuleType.LOOK_TARGET);
}
return canUse;
}
@Override
protected void start(ServerLevel level, E entity, long time) {
this.lastTarget = null;
this.tries = 20;
this.lastPos = Optional.of(entity.position());
BlockPos pos = entity.blockPosition();
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
this.targets = BlockPos.betweenClosedStream(x - this.xzRange, y - this.yRange, z - this.xzRange, x + this.xzRange, y + this.yRange, z + this.xzRange).filter(position -> !position.equals(pos)).map(position -> new Target(position.immutable(), Mth.ceil(pos.distSqr(position)))).collect(Collectors.toCollection(Lists::newArrayList));
}
@Override
protected void tick(ServerLevel level, E entity, long time) {
if (this.lastTarget != null) {
if (time - this.targetTime >= 40L) {
entity.setYRot(entity.yBodyRot);
entity.setDiscardFriction(true);
double length = this.lastTarget.length();
double height = length + entity.getJumpBoostPower();
entity.setDeltaMovement(this.lastTarget.scale(height / length));
entity.getBrain().setMemory(MemoryModuleType.LONG_JUMP_MID_JUMP, true);
level.playSound(null, entity, this.landingSound.apply(entity), SoundSource.NEUTRAL, 1.0F, 1.0F);
}
} else {
--this.tries;
this.pickTarget(level, entity, time);
}
}
protected void pickTarget(ServerLevel level, E entity, long time) {
while (true) {
if (!this.targets.isEmpty()) {
Optional<Target> jumpTarget = this.jumpTarget(level);
if (jumpTarget.isEmpty()) continue;
Target target = jumpTarget.get();
BlockPos pos = target.getPos();
if (!canLandOn(level, entity, pos)) continue;
Vec3 center = Vec3.atCenterOf(pos);
Vec3 lastTarget = this.getRammingVelocity(entity, center);
if (lastTarget == null) continue;
entity.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, new BlockPosTracker(pos));
PathNavigation navigation = entity.getNavigation();
Path path = navigation.createPath(pos, 0, 8);
if (path != null && path.canReach()) continue;
this.lastTarget = lastTarget;
this.targetTime = time;
return;
}
return;
}
}
protected Optional<Target> jumpTarget(ServerLevel level) {
Optional<Target> target = WeightedRandom.getRandomItem(level.random, this.targets);
target.ifPresent(this.targets::remove);
return target;
}
protected boolean canLandOn(ServerLevel level, E entity, BlockPos pos) {
BlockPos position = entity.blockPosition();
int x = position.getX();
int z = position.getZ();
if (x == pos.getX() && z == pos.getZ()) {
return false;
} else if (!entity.getNavigation().isStableDestination(pos) && !this.landingBlocks.test(level.getBlockState(pos.below()))) {
return false;
} else {
return entity.getPathfindingMalus(WalkNodeEvaluator.getBlockPathTypeStatic(entity.level, pos.mutable())) == 0.0F;
}
}
@Nullable
protected Vec3 getRammingVelocity(Mob entity, Vec3 pos) {
List<Integer> angles = Lists.newArrayList(ANGLES);
Collections.shuffle(angles);
for (int angle : angles) {
Vec3 velocity = this.getRammingVelocity(entity, pos, angle);
if (velocity != null) return velocity;
}
return null;
}
@Nullable
private Vec3 getRammingVelocity(Mob entity, Vec3 pos, int angle) {
Vec3 position = entity.position();
Vec3 scale = new Vec3(pos.x - position.x, 0.0, pos.z - position.z).normalize().scale(0.5D);
pos = pos.subtract(scale);
Vec3 distance = pos.subtract(position);
float maxAngle = (float)angle * (float)Math.PI / 180.0F;
double xzRange = Math.atan2(distance.z, distance.x);
double yRange = distance.subtract(0.0D, distance.y, 0.0D).lengthSqr();
double yRadius = Math.sqrt(yRange);
double i = Math.sin(2.0F * maxAngle);
double k = Math.pow(Math.cos(maxAngle), 2.0D);
double yMax = Math.sin(maxAngle);
double xzMax = Math.cos(maxAngle);
double zOffset = Math.sin(xzRange);
double xOffset = Math.cos(xzRange);
double jumpHeight = yRange * 0.08 / (yRadius * i - 2.0 * distance.y * k);
if (jumpHeight < 0.0) {
return null;
} else {
double range = Math.sqrt(jumpHeight);
if (range > (double)this.maxRange) {
return null;
} else {
double xzDistance = range * xzMax;
double yDistance = range * yMax;
int radius = Mth.ceil(yRadius / xzDistance) * 2;
double index = 0.0;
Vec3 source = null;
for (int j = 0; j < radius - 1; ++j) {
index += yRadius / (double)radius;
double x = index * xOffset;
double y = yMax / xzMax * index - Math.pow(index, 2.0) * 0.08 / (2.0 * jumpHeight * Math.pow(xzMax, 2.0));
double z = index * zOffset;
Vec3 target = new Vec3(position.x + x, position.y + y, position.z + z);
if (source != null && !this.canReach(entity, source, target)) {
return null;
}
source = target;
}
return new Vec3(xzDistance * xOffset, yDistance, xzDistance * zOffset).scale(0.95F);
}
}
}
private boolean canReach(Mob entity, Vec3 source, Vec3 target) {
EntityDimensions dimensions = entity.getDimensions(Pose.LONG_JUMPING);
Vec3 distance = target.subtract(source);
double size = Math.min(dimensions.width, dimensions.height);
int height = Mth.ceil(distance.length() / size);
Vec3 normal = distance.normalize();
Vec3 vector = source;
for (int i = 0; i < height; ++i) {
vector = i == height - 1 ? target : vector.add(normal.scale(size * (double)0.9F));
AABB box = dimensions.makeBoundingBox(vector);
if (!entity.level.noCollision(entity, box)) {
return false;
}
}
return true;
}
public static class Target extends WeightedEntry.IntrusiveBase {
private final BlockPos pos;
public Target(BlockPos pos, int weight) {
super(weight);
this.pos = pos;
}
public BlockPos getPos() {
return this.pos;
}
}
}

View File

@ -0,0 +1,28 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableSet;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Unit;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.sensing.Sensor;
import java.util.Set;
public class IsInWaterSensor extends Sensor<LivingEntity> {
@Override
public Set<MemoryModuleType<?>> requires() {
return ImmutableSet.of(WBMemoryModules.IS_IN_WATER.get());
}
@Override
protected void doTick(ServerLevel world, LivingEntity entity) {
if (entity.isInWater()) {
entity.getBrain().setMemory(WBMemoryModules.IS_IN_WATER.get(), Unit.INSTANCE);
} else {
entity.getBrain().eraseMemory(WBMemoryModules.IS_IN_WATER.get());
}
}
}

View File

@ -0,0 +1,45 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.Frog;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
public class LayFrogSpawnTask extends Behavior<Frog> {
private final Block frogSpawn;
private final MemoryModuleType<?> triggerMemory;
public LayFrogSpawnTask(Block block, MemoryModuleType<?> memoryModuleType) {
super(ImmutableMap.of(MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_PRESENT, WBMemoryModules.IS_PREGNANT.get(), MemoryStatus.VALUE_PRESENT));
this.frogSpawn = block;
this.triggerMemory = memoryModuleType;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel serverWorld, Frog arg) {
return !arg.isInWaterOrBubble() && arg.isOnGround();
}
@Override
protected void start(ServerLevel serverWorld, Frog arg, long l) {
BlockPos blockPos = arg.blockPosition().below();
for (Direction direction : Direction.Plane.HORIZONTAL) {
BlockPos blockPos3;
BlockPos blockPos2 = blockPos.relative(direction);
if (!serverWorld.getBlockState(blockPos2).is(Blocks.WATER) || !serverWorld.getBlockState(blockPos3 = blockPos2.above()).isAir()) continue;
serverWorld.setBlock(blockPos3, this.frogSpawn.defaultBlockState(), 3);
serverWorld.playSound(null, arg, WBSoundEvents.FROG_LAY_SPAWN, SoundSource.BLOCKS, 1.0f, 1.0f);
arg.getBrain().eraseMemory(this.triggerMemory);
return;
}
}
}

View File

@ -0,0 +1,58 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BehaviorUtils;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
public class WalkTowardsLand extends Behavior<PathfinderMob> {
private final int distance;
private final float speedModifier;
private long nextStartTime;
public WalkTowardsLand(int distance, float speedModifier) {
super(ImmutableMap.of(MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED));
this.distance = distance;
this.speedModifier = speedModifier;
}
@Override
protected void stop(ServerLevel level, PathfinderMob entity, long time) {
this.nextStartTime = time + 60L;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, PathfinderMob entity) {
return entity.level.getFluidState(entity.blockPosition()).is(FluidTags.WATER);
}
@Override
protected void start(ServerLevel level, PathfinderMob entity, long time) {
if (time >= this.nextStartTime) {
BlockPos pos = entity.blockPosition();
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
CollisionContext context = CollisionContext.of(entity);
for (BlockPos position : BlockPos.withinManhattan(pos, this.distance, this.distance, this.distance)) {
if (position.getX() != pos.getX() || position.getZ() != pos.getZ()) {
BlockState state = level.getBlockState(position);
BlockState landState = level.getBlockState(mutable.setWithOffset(position, Direction.DOWN));
if (!state.is(Blocks.WATER) && level.getFluidState(position).isEmpty() && state.getCollisionShape(level, position, context).isEmpty() && landState.isFaceSturdy(level, mutable, Direction.UP)) {
this.nextStartTime = time + 60L;
BehaviorUtils.setWalkAndLookTargetMemories(entity, position.immutable(), this.speedModifier, 1);
return;
}
}
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.cursedcauldron.wildbackport.common.entities.brain.frog;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BehaviorUtils;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.shapes.CollisionContext;
//<>
public class WalkTowardsWater extends Behavior<AgeableMob> {
private final int distance;
private final float speedModifier;
private long nextStartTime;
public WalkTowardsWater(int distance, float speedModifier) {
super(ImmutableMap.of(MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED));
this.distance = distance;
this.speedModifier = speedModifier;
}
@Override
protected void stop(ServerLevel level, AgeableMob entity, long time) {
this.nextStartTime = time + 40L;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, AgeableMob entity) {
return !entity.level.getFluidState(entity.blockPosition()).is(FluidTags.WATER);
}
@Override
protected void start(ServerLevel level, AgeableMob entity, long time) {
if (time >= this.nextStartTime) {
CollisionContext context = CollisionContext.of(entity);
BlockPos pos = entity.blockPosition();
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
for (BlockPos position : BlockPos.withinManhattan(pos, this.distance, this.distance, this.distance)) {
if (position.getX() != pos.getX() || position.getZ() != pos.getZ() && level.getBlockState(position).getCollisionShape(level, position, context).isEmpty() && level.getBlockState(mutable.setWithOffset(position, Direction.DOWN)).getCollisionShape(level, position, context).isEmpty()) {
for (Direction direction : Direction.Plane.HORIZONTAL) {
mutable.setWithOffset(position, direction);
if (level.getBlockState(mutable).isAir() && level.getBlockState(mutable.move(Direction.DOWN)).is(Blocks.WATER)) {
this.nextStartTime = time + 40L;
BehaviorUtils.setWalkAndLookTargetMemories(entity, position, this.speedModifier, 0);
return;
}
}
}
}
}
}
}

View File

@ -0,0 +1,47 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
//<>
public class Digging<E extends Warden> extends Behavior<E> {
public Digging(int duration) {
super(ImmutableMap.of(MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT), duration);
}
@Override
protected boolean canStillUse(ServerLevel level, E entity, long time) {
return entity.getRemovalReason() == null;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, E entity) {
return entity.isOnGround() || entity.isInWater() || entity.isInLava();
}
@Override
protected void start(ServerLevel level, E entity, long time) {
if (entity.isOnGround()) {
entity.setPose(Poses.DIGGING.get());
entity.playSound(WBSoundEvents.WARDEN_DIG, 5.0F, 1.0F);
} else {
entity.playSound(WBSoundEvents.WARDEN_AGITATED, 5.0F, 1.0F);
this.stop(level, entity, time);
}
}
@Override
protected void stop(ServerLevel level, E entity, long time) {
if (entity.getRemovalReason() == null) {
entity.remove(Entity.RemovalReason.DISCARDED);
}
}
}

View File

@ -0,0 +1,22 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.behavior.Behavior;
public class Dismount extends Behavior<LivingEntity> {
public Dismount() {
super(ImmutableMap.of());
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, LivingEntity entity) {
return entity.isPassenger();
}
@Override
protected void start(ServerLevel level, LivingEntity entity, long time) {
entity.unRide();
}
}

View File

@ -0,0 +1,38 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
//<>
public class Emerging<E extends Warden> extends Behavior<E> {
public Emerging(int duration) {
super(ImmutableMap.of(WBMemoryModules.IS_EMERGING.get(), MemoryStatus.VALUE_PRESENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED), duration);
}
@Override
protected boolean canStillUse(ServerLevel level, E entity, long time) {
return true;
}
@Override
protected void start(ServerLevel level, E entity, long time) {
entity.setPose(Poses.EMERGING.get());
entity.playSound(WBSoundEvents.WARDEN_EMERGE, 5.0F, 1.0F);
}
@Override
protected void stop(ServerLevel level, E entity, long time) {
if (entity.hasPose(Poses.EMERGING.get())) {
entity.setPose(Pose.STANDING);
}
}
}

View File

@ -0,0 +1,65 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
public class ForgetAttackTargetTask<E extends Mob> extends Behavior<E> {
private final Predicate<LivingEntity> stopAttackingWhen;
private final BiConsumer<E, LivingEntity> onTargetErased;
private final boolean canGrowTiredOfTryingToReachTarget;
public ForgetAttackTargetTask(Predicate<LivingEntity> stopAttackingWhen, BiConsumer<E, LivingEntity> onTargetEased, boolean canGrowTiredOfTryingToReachTarget) {
super(ImmutableMap.of(MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_PRESENT, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryStatus.REGISTERED));
this.stopAttackingWhen = stopAttackingWhen;
this.onTargetErased = onTargetEased;
this.canGrowTiredOfTryingToReachTarget = canGrowTiredOfTryingToReachTarget;
}
@Override
protected void start(ServerLevel level, E entity, long time) {
LivingEntity target = this.getAttackTarget(entity);
if (!entity.canAttack(target)) {
this.clearAttackTarget(entity);
} else if (this.canGrowTiredOfTryingToReachTarget && isTiredOfTryingToReachTarget(entity)) {
this.clearAttackTarget(entity);
} else if (this.isCurrentTargetDeadOrRemoved(entity)) {
this.clearAttackTarget(entity);
} else if (this.isCurrentTargetInDifferentLevel(entity)) {
this.clearAttackTarget(entity);
} else if (this.stopAttackingWhen.test(this.getAttackTarget(entity))) {
this.clearAttackTarget(entity);
}
}
private boolean isCurrentTargetInDifferentLevel(E entity) {
return this.getAttackTarget(entity).level != entity.level;
}
private LivingEntity getAttackTarget(E entity) {
return entity.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).get();
}
private static <E extends LivingEntity> boolean isTiredOfTryingToReachTarget(E entity) {
Optional<Long> time = entity.getBrain().getMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
return time.isPresent() && entity.level.getGameTime() - time.get() > 200L;
}
private boolean isCurrentTargetDeadOrRemoved(E entity) {
Optional<LivingEntity> target = entity.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET);
return target.isPresent() && !target.get().isAlive();
}
protected void clearAttackTarget(E entity) {
this.onTargetErased.accept(entity, this.getAttackTarget(entity));
entity.getBrain().eraseMemory(MemoryModuleType.ATTACK_TARGET);
}
}

View File

@ -0,0 +1,45 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BehaviorUtils;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import java.util.Random;
public class GoToTargetLocation<E extends Mob> extends Behavior<E> {
private final MemoryModuleType<BlockPos> locationMemory;
private final int closeEnoughDistance;
private final float speedModifier;
public GoToTargetLocation(MemoryModuleType<BlockPos> locationMemory, int closeEnoughDistance, float speedModifier) {
super(ImmutableMap.of(locationMemory, MemoryStatus.VALUE_PRESENT, MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED));
this.locationMemory = locationMemory;
this.closeEnoughDistance = closeEnoughDistance;
this.speedModifier = speedModifier;
}
@Override
protected void start(ServerLevel level, E entity, long time) {
BlockPos pos = this.getTargetLocation(entity);
boolean inRange = pos.closerThan(entity.blockPosition(), this.closeEnoughDistance);
if (!inRange) BehaviorUtils.setWalkAndLookTargetMemories(entity, getNearbyPos(entity, pos), this.speedModifier, this.closeEnoughDistance);
}
private static BlockPos getNearbyPos(Mob mob, BlockPos pos) {
Random random = mob.level.getRandom();
return pos.offset(getRandomOffset(random), 0, getRandomOffset(random));
}
private static int getRandomOffset(Random random) {
return random.nextInt(3) - 1;
}
private BlockPos getTargetLocation(Mob mob) {
return mob.getBrain().getMemory(this.locationMemory).get();
}
}

View File

@ -0,0 +1,39 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.google.common.collect.ImmutableSet;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.phys.AABB;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
//<>
public class NearestLivingEntitySensor<T extends LivingEntity> extends Sensor<T> {
@Override
protected void doTick(ServerLevel level, T entity) {
AABB box = entity.getBoundingBox().inflate(this.radiusXZ(), this.radiusY(), this.radiusXZ());
List<LivingEntity> entities = level.getEntitiesOfClass(LivingEntity.class, box, (target) -> target != entity && target.isAlive());
entities.sort(Comparator.comparingDouble(entity::distanceToSqr));
entity.getBrain().setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, entities);
entity.getBrain().setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(entity, entities));
}
protected int radiusXZ() {
return 16;
}
protected int radiusY() {
return 16;
}
@Override
public Set<MemoryModuleType<?>> requires() {
return ImmutableSet.of(MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES);
}
}

View File

@ -0,0 +1,53 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.entities.brain.WardenBrain;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Unit;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BehaviorUtils;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
public class Roar extends Behavior<Warden> {
public Roar() {
super(ImmutableMap.of(WBMemoryModules.ROAR_TARGET.get(), MemoryStatus.VALUE_PRESENT, MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT, WBMemoryModules.ROAR_SOUND_COOLDOWN.get(), MemoryStatus.REGISTERED, WBMemoryModules.ROAR_SOUND_DELAY.get(), MemoryStatus.REGISTERED), WardenBrain.ROAR_DURATION);
}
@Override
protected void start(ServerLevel level, Warden warden, long time) {
warden.getBrain().setMemoryWithExpiry(WBMemoryModules.ROAR_SOUND_DELAY.get(), Unit.INSTANCE, 25L);
warden.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET);
LivingEntity entity = warden.getBrain().getMemory(WBMemoryModules.ROAR_TARGET.get()).get();
BehaviorUtils.lookAtEntity(warden, entity);
warden.setPose(Poses.ROARING.get());
warden.increaseAngerAt(entity, 20, false);
}
@Override
protected boolean canStillUse(ServerLevel level, Warden warden, long time) {
return true;
}
@Override
protected void tick(ServerLevel level, Warden warden, long time) {
if (!warden.getBrain().hasMemoryValue(WBMemoryModules.ROAR_SOUND_DELAY.get()) && !warden.getBrain().hasMemoryValue(WBMemoryModules.ROAR_SOUND_COOLDOWN.get())) {
warden.getBrain().setMemoryWithExpiry(WBMemoryModules.ROAR_SOUND_COOLDOWN.get(), Unit.INSTANCE, WardenBrain.ROAR_DURATION - 25);
warden.playSound(WBSoundEvents.WARDEN_ROAR, 3.0F, 1.0F);
}
}
@Override
protected void stop(ServerLevel level, Warden warden, long time) {
if (warden.hasPose(Poses.ROARING.get())) warden.setPose(Pose.STANDING);
warden.getBrain().getMemory(WBMemoryModules.ROAR_TARGET.get()).ifPresent(warden::updateAttackTarget);
warden.getBrain().eraseMemory(WBMemoryModules.ROAR_TARGET.get());
}
}

View File

@ -0,0 +1,35 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import java.util.Optional;
import java.util.function.Function;
public class SetRoarTarget<E extends Warden> extends Behavior<E> {
private final Function<E, Optional<? extends LivingEntity>> targetFinder;
public SetRoarTarget(Function<E, Optional<? extends LivingEntity>> target) {
super(ImmutableMap.of(WBMemoryModules.ROAR_TARGET.get(), MemoryStatus.VALUE_ABSENT, MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryStatus.REGISTERED));
this.targetFinder = target;
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, E entity) {
return this.targetFinder.apply(entity).filter(entity::isValidTarget).isPresent();
}
@Override
protected void start(ServerLevel level, E entity, long time) {
this.targetFinder.apply(entity).ifPresent(target -> {
entity.getBrain().setMemory(WBMemoryModules.ROAR_TARGET.get(), target);
entity.getBrain().eraseMemory(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE);
});
}
}

View File

@ -0,0 +1,31 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.behavior.BlockPosTracker;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
//<>
public class SetWardenLookTarget extends Behavior<Warden> {
public SetWardenLookTarget() {
super(ImmutableMap.of(WBMemoryModules.DISTURBANCE_LOCATION.get(), MemoryStatus.REGISTERED, WBMemoryModules.ROAR_TARGET.get(), MemoryStatus.REGISTERED, MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT));
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, Warden warden) {
return warden.getBrain().hasMemoryValue(WBMemoryModules.DISTURBANCE_LOCATION.get()) || warden.getBrain().hasMemoryValue(WBMemoryModules.ROAR_TARGET.get());
}
@Override
protected void start(ServerLevel level, Warden warden, long time) {
BlockPos pos = warden.getBrain().getMemory(WBMemoryModules.ROAR_TARGET.get()).map(Entity::blockPosition).or(() -> warden.getBrain().getMemory(WBMemoryModules.DISTURBANCE_LOCATION.get())).get();
warden.getBrain().setMemory(MemoryModuleType.LOOK_TARGET, new BlockPosTracker(pos));
}
}

View File

@ -0,0 +1,48 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.entities.brain.WardenBrain;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.utils.MobUtils;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
public class Sniffing<E extends Warden> extends Behavior<E> {
public Sniffing(int duration) {
super(ImmutableMap.of(WBMemoryModules.IS_SNIFFING.get(), MemoryStatus.VALUE_PRESENT, MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.LOOK_TARGET, MemoryStatus.REGISTERED, MemoryModuleType.NEAREST_ATTACKABLE, MemoryStatus.REGISTERED, WBMemoryModules.DISTURBANCE_LOCATION.get(), MemoryStatus.REGISTERED, WBMemoryModules.SNIFF_COOLDOWN.get(), MemoryStatus.REGISTERED), duration);
}
@Override
protected boolean canStillUse(ServerLevel level, E entity, long time) {
return true;
}
@Override
protected void start(ServerLevel level, E entity, long time) {
entity.playSound(WBSoundEvents.WARDEN_SNIFF, 5.0F, 1.0F);
}
@Override
protected void stop(ServerLevel level, E entity, long time) {
if (entity.hasPose(Poses.SNIFFING.get())) {
entity.setPose(Pose.STANDING);
}
entity.getBrain().eraseMemory(WBMemoryModules.IS_SNIFFING.get());
entity.getBrain().getMemory(MemoryModuleType.NEAREST_ATTACKABLE).filter(entity::isValidTarget).ifPresent(target -> {
if (MobUtils.closerThan(entity, target, 6.0D, 20.0D)) {
entity.increaseAngerAt(target);
}
if (!entity.getBrain().hasMemoryValue(WBMemoryModules.DISTURBANCE_LOCATION.get())) {
WardenBrain.setDisturbanceLocation(entity, target.blockPosition());
}
});
}
}

View File

@ -0,0 +1,87 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.client.registry.WBParticleTypes;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.cursedcauldron.wildbackport.common.utils.MobUtils;
import com.cursedcauldron.wildbackport.core.mixin.access.DamageSourceAccessor;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.Unit;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.damagesource.EntityDamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.phys.Vec3;
//<>
public class SonicBoom extends Behavior<Warden> {
private static final int TICKS_BEFORE_PLAYING_SOUND = Mth.ceil(34.0D);
private static final int DURATION = Mth.ceil(60.0F);
public SonicBoom() {
super(ImmutableMap.of(MemoryModuleType.ATTACK_TARGET, MemoryStatus.VALUE_PRESENT, WBMemoryModules.SONIC_BOOM_COOLDOWN.get(), MemoryStatus.VALUE_ABSENT, WBMemoryModules.SONIC_BOOM_SOUND_COOLDOWN.get(), MemoryStatus.REGISTERED, WBMemoryModules.SONIC_BOOM_SOUND_DELAY.get(), MemoryStatus.REGISTERED), DURATION);
}
@Override
protected boolean checkExtraStartConditions(ServerLevel level, Warden warden) {
return MobUtils.closerThan(warden, warden.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).get(), 15.0D, 20.0D);
}
@Override
protected boolean canStillUse(ServerLevel level, Warden warden, long time) {
return true;
}
@Override
protected void start(ServerLevel level, Warden warden, long time) {
warden.getBrain().setMemoryWithExpiry(MemoryModuleType.ATTACK_COOLING_DOWN, true, DURATION);
warden.getBrain().setMemoryWithExpiry(WBMemoryModules.SONIC_BOOM_SOUND_DELAY.get(), Unit.INSTANCE, TICKS_BEFORE_PLAYING_SOUND);
level.broadcastEntityEvent(warden, (byte)62);
warden.playSound(WBSoundEvents.WARDEN_SONIC_CHARGE, 3.0F, 1.0F);
}
@Override
protected void tick(ServerLevel level, Warden warden, long time) {
warden.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).ifPresent(target -> warden.getLookControl().setLookAt(target.position()));
if (!warden.getBrain().hasMemoryValue(WBMemoryModules.SONIC_BOOM_SOUND_DELAY.get()) && !warden.getBrain().hasMemoryValue(WBMemoryModules.SONIC_BOOM_SOUND_COOLDOWN.get())) {
warden.getBrain().setMemoryWithExpiry(WBMemoryModules.SONIC_BOOM_SOUND_COOLDOWN.get(), Unit.INSTANCE, DURATION - TICKS_BEFORE_PLAYING_SOUND);
warden.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).filter(warden::isValidTarget).filter(target -> MobUtils.closerThan(warden, target, 15.0D, 20.0D)).ifPresent(target -> {
Vec3 wardenPos = warden.position().add(0.0D, 1.6F, 0.0D);
Vec3 distance = target.getEyePosition().subtract(wardenPos);
Vec3 position = distance.normalize();
for (int i = 1; i < Mth.floor(distance.length()) + 7; i++) {
Vec3 rayCharge = wardenPos.add(position.scale(i));
level.sendParticles(WBParticleTypes.SONIC_BOOM.get(), rayCharge.x, rayCharge.y, rayCharge.z, 1, 0.0D, 0.0D, 0.0D, 0.0D);
}
warden.playSound(WBSoundEvents.WARDEN_SONIC_BOOM, 3.0F, 1.0F);
target.hurt(sonicBoom(warden), 10.0F);
double yForce = 0.5D * (1.0D - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
double xzForce = 2.5D * (1.0D - target.getAttributeValue(Attributes.KNOCKBACK_RESISTANCE));
target.push(position.x * xzForce, position.y * yForce, position.z * xzForce);
});
}
}
@Override
protected void stop(ServerLevel level, Warden warden, long time) {
setCooldown(warden, 40);
}
public static void setCooldown(LivingEntity entity, int cooldown) {
entity.getBrain().setMemoryWithExpiry(WBMemoryModules.SONIC_BOOM_COOLDOWN.get(), Unit.INSTANCE, cooldown);
}
public static DamageSource sonicBoom(Entity entity) {
return ((DamageSourceAccessor)new EntityDamageSource("sonic_boom", entity)).callBypassArmor().setMagic();
}
}

View File

@ -0,0 +1,29 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.common.entities.access.api.Poses;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.cursedcauldron.wildbackport.common.registry.entity.WBMemoryModules;
import com.google.common.collect.ImmutableMap;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Unit;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.entity.ai.behavior.Behavior;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
public class TryToSniff extends Behavior<Warden> {
private static final IntProvider SNIFF_COOLDOWN = UniformInt.of(100, 200);
public TryToSniff() {
super(ImmutableMap.of(WBMemoryModules.SNIFF_COOLDOWN.get(), MemoryStatus.VALUE_ABSENT, MemoryModuleType.NEAREST_ATTACKABLE, MemoryStatus.VALUE_PRESENT, WBMemoryModules.DISTURBANCE_LOCATION.get(), MemoryStatus.VALUE_ABSENT));
}
@Override
protected void start(ServerLevel level, Warden warden, long time) {
warden.getBrain().setMemory(WBMemoryModules.IS_SNIFFING.get(), Unit.INSTANCE);
warden.getBrain().setMemoryWithExpiry(WBMemoryModules.SNIFF_COOLDOWN.get(), Unit.INSTANCE, SNIFF_COOLDOWN.sample(level.getRandom()));
warden.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET);
warden.setPose(Poses.SNIFFING.get());
}
}

View File

@ -0,0 +1,52 @@
package com.cursedcauldron.wildbackport.common.entities.brain.warden;
import com.cursedcauldron.wildbackport.common.entities.Warden;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
public class WardenEntitySensor extends NearestLivingEntitySensor<Warden> {
@Override
public Set<MemoryModuleType<?>> requires() {
return ImmutableSet.copyOf(Iterables.concat(super.requires(), List.of(MemoryModuleType.NEAREST_ATTACKABLE)));
}
@Override
protected void doTick(ServerLevel level, Warden warden) {
super.doTick(level, warden);
getClosest(warden, target -> {
return target.getType() == EntityType.PLAYER;
}).or(() -> {
return getClosest(warden, target -> {
return target.getType() != EntityType.PLAYER;
});
}).ifPresentOrElse(target -> {
warden.getBrain().setMemory(MemoryModuleType.NEAREST_ATTACKABLE, target);
}, () -> {
warden.getBrain().eraseMemory(MemoryModuleType.NEAREST_ATTACKABLE);
});
}
private static Optional<LivingEntity> getClosest(Warden warden, Predicate<LivingEntity> targetFilter) {
return warden.getBrain().getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).stream().flatMap(Collection::stream).filter(warden::isValidTarget).filter(targetFilter).findFirst();
}
@Override
protected int radiusXZ() {
return 24;
}
@Override
protected int radiusY() {
return 24;
}
}

View File

@ -0,0 +1,52 @@
package com.cursedcauldron.wildbackport.common.entities.warden;
import com.cursedcauldron.wildbackport.client.registry.WBSoundEvents;
import net.minecraft.Util;
import net.minecraft.sounds.SoundEvent;
import java.util.Arrays;
//<>
public enum Angriness {
CALM(0, WBSoundEvents.WARDEN_AMBIENT, WBSoundEvents.WARDEN_LISTENING),
AGITATED(40, WBSoundEvents.WARDEN_AGITATED, WBSoundEvents.WARDEN_LISTENING_ANGRY),
ANGRY(80, WBSoundEvents.WARDEN_ANGRY, WBSoundEvents.WARDEN_LISTENING_ANGRY);
private static final Angriness[] VALUES = Util.make(Angriness.values(), values -> Arrays.sort(values, (a, b) -> Integer.compare(b.threshold, a.threshold)));
private final int threshold;
private final SoundEvent sound;
private final SoundEvent listeningSound;
Angriness(int threshold, SoundEvent sound, SoundEvent listeningSound) {
this.threshold = threshold;
this.sound = sound;
this.listeningSound = listeningSound;
}
public int getThreshold() {
return this.threshold;
}
public SoundEvent getSound() {
return this.sound;
}
public SoundEvent getListeningSound() {
return this.listeningSound;
}
public static Angriness getForAnger(int anger) {
for (Angriness angriness : VALUES) {
if (anger >= angriness.threshold) {
return angriness;
}
}
return CALM;
}
public boolean isAngry() {
return this == ANGRY;
}
}

View File

@ -0,0 +1,90 @@
package com.cursedcauldron.wildbackport.common.entities.warden;
import com.cursedcauldron.wildbackport.common.registry.WBPositionSources;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SerializableUUID;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.PositionSource;
import net.minecraft.world.level.gameevent.PositionSourceType;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
public class MobPositionSource implements PositionSource {
public static final Codec<MobPositionSource> CODEC = RecordCodecBuilder.create(instance -> instance.group((SerializableUUID.CODEC.fieldOf("source_entity")).forGetter(MobPositionSource::getUuid), (Codec.FLOAT.fieldOf("y_offset")).orElse(0.0f).forGetter(entityPositionSource -> entityPositionSource.yOffset)).apply(instance, (uUID, float_) -> new MobPositionSource(Either.right(Either.left(uUID)), float_.floatValue())));
private Either<Entity, Either<UUID, Integer>> source;
final float yOffset;
public MobPositionSource(Entity entity, float yOffset) {
this(Either.left(entity), yOffset);
}
public MobPositionSource(Either<Entity, Either<UUID, Integer>> sourceEntityId, float yOffset) {
this.source = sourceEntityId;
this.yOffset = yOffset;
}
@Override
public Optional<BlockPos> getPosition(Level world) {
if (this.source.left().isEmpty()) {
this.findEntityInWorld(world);
}
return this.source.left().map(entity -> entity.blockPosition().offset(0.0, this.yOffset, 0.0));
}
private void findEntityInWorld(Level world) {
this.source.map(Optional::of, either -> Optional.ofNullable(either.map(uuid -> {
Entity entity;
if (world instanceof ServerLevel serverLevel) {
entity = serverLevel.getEntity(uuid);
} else {
entity = null;
}
return entity;
}, world::getEntity))).ifPresent(entity -> {
this.source = Either.left(entity);
});
}
@Override
public PositionSourceType<?> getType() {
return WBPositionSources.MOB.get();
}
private UUID getUuid() {
return this.source.map(Entity::getUUID, either -> either.map(Function.identity(), integer -> {
throw new RuntimeException("Unable to get entityId from uuid");
}));
}
int getEntityId() {
return this.source.map(Entity::getId, either -> either.map(uUID -> {
throw new IllegalStateException("Unable to get entityId from uuid");
}, Function.identity()));
}
public static class Type implements PositionSourceType<MobPositionSource> {
@Override
public MobPositionSource read(FriendlyByteBuf buf) {
return new MobPositionSource(Either.right(Either.right(buf.readVarInt())), buf.readFloat());
}
@Override
public void write(FriendlyByteBuf buf, MobPositionSource source) {
buf.writeVarInt(source.getEntityId());
buf.writeFloat(source.yOffset);
}
@Override
public Codec<MobPositionSource> codec() {
return CODEC;
}
}
}

View File

@ -0,0 +1,226 @@
package com.cursedcauldron.wildbackport.common.entities.warden;
import com.cursedcauldron.wildbackport.client.registry.WBCriteriaTriggers;
import com.cursedcauldron.wildbackport.common.utils.PositionUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.core.SerializableUUID;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.GameEventTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.level.ClipBlockStateContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.gameevent.GameEventListener;
import net.minecraft.world.level.gameevent.PositionSource;
import net.minecraft.world.level.gameevent.vibrations.VibrationPath;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.UUID;
//<>
public class VibrationListenerSource implements GameEventListener {
protected final PositionSource source;
protected final int range;
protected final VibrationConfig config;
@Nullable protected Vibration event;
protected float distance;
protected int delay;
public static Codec<VibrationListenerSource> codec(VibrationConfig config) {
return RecordCodecBuilder.create(instance -> {
return instance.group(PositionSource.CODEC.fieldOf("source").forGetter(listener -> {
return listener.source;
}), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("range").forGetter(listener -> {
return listener.range;
}), Vibration.CODEC.optionalFieldOf("event").forGetter(listener -> {
return Optional.ofNullable(listener.event);
}), Codec.floatRange(0.0F, Float.MAX_VALUE).fieldOf("event_distance").orElse(0.0F).forGetter(listener -> {
return listener.distance;
}), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("event_delay").orElse(0).forGetter(listener -> {
return listener.delay;
})).apply(instance, (source, range, event, distance, delay) -> {
return new VibrationListenerSource(source, range, config, event.orElse(null), distance, delay);
});
});
}
public VibrationListenerSource(PositionSource source, int range, VibrationConfig config, @Nullable Vibration event, float distance, int delay) {
this.source = source;
this.range = range;
this.config = config;
this.event = event;
this.distance = distance;
this.delay = delay;
}
public void tick(Level level) {
if (level instanceof ServerLevel server) {
if (this.event != null) {
--this.delay;
if (this.delay <= 0) {
this.delay = 0;
this.config.onSignalReceive(server, this, new BlockPos(this.event.pos), this.event.event, this.event.getEntity(server).orElse(null), this.event.getProjectileOwner(server).orElse(null), this.distance);
this.event = null;
}
}
}
}
@Override
public PositionSource getListenerSource() {
return this.source;
}
@Override
public int getListenerRadius() {
return this.range;
}
@Override
public boolean handleGameEvent(Level level, GameEvent event, @Nullable Entity entity, BlockPos pos) {
if (this.event != null) {
return false;
} else {
Optional<BlockPos> optional = this.source.getPosition(level);
if (!this.config.isValidVibration(event, entity)) {
return false;
} else {
Vec3 source = PositionUtils.toVec(pos);
Vec3 target = PositionUtils.toVec(optional.get());
if (!this.config.shouldListen((ServerLevel)level, this, new BlockPos(source), event, entity)) {
return false;
} else if (isOccluded(level, source, target)) {
return false;
} else {
this.scheduleSignal(level, event, entity, source, target);
return true;
}
}
}
}
private void scheduleSignal(Level level, GameEvent event, @Nullable Entity entity, Vec3 source, Vec3 target) {
this.distance = (float)source.distanceTo(target);
this.event = new Vibration(event, this.distance, source, entity);
this.delay = Mth.floor(this.distance);
((ServerLevel)level).sendVibrationParticle(new VibrationPath(PositionUtils.toBlockPos(source), this.source, this.delay));
this.config.onSignalSchedule();
}
private static boolean isOccluded(Level level, Vec3 source, Vec3 target) {
Vec3 sourceVec = new Vec3((double)Mth.floor(source.x) + 0.5D, (double)Mth.floor(source.y) + 0.5D, (double)Mth.floor(source.z) + 0.5D);
Vec3 targetVec = new Vec3((double)Mth.floor(target.x) + 0.5D, (double)Mth.floor(target.y) + 0.5D, (double)Mth.floor(target.z) + 0.5D);
for (Direction direction : Direction.values()) {
Vec3 offsetVec = PositionUtils.relative(sourceVec, direction, 1.0E-5F);
if (level.isBlockInLine(new ClipBlockStateContext(offsetVec, targetVec, state -> {
return state.is(BlockTags.OCCLUDES_VIBRATION_SIGNALS);
})).getType() != HitResult.Type.BLOCK) {
return false;
}
}
return true;
}
public record Vibration(GameEvent event, float distance, Vec3 pos, @Nullable UUID source, @Nullable UUID projectileOwner, @Nullable Entity entity) {
public static final Codec<Vibration> CODEC = RecordCodecBuilder.create(instance -> {
return instance.group(Registry.GAME_EVENT.byNameCodec().fieldOf("game_event").forGetter(Vibration::event), Codec.floatRange(0.0F, Float.MAX_VALUE).fieldOf("distance").forGetter(Vibration::distance), PositionUtils.VEC_CODEC.fieldOf("pos").forGetter(Vibration::pos), SerializableUUID.CODEC.optionalFieldOf("source").forGetter(entity -> {
return Optional.ofNullable(entity.source());
}), SerializableUUID.CODEC.optionalFieldOf("projectile_owner").forGetter(entity -> {
return Optional.ofNullable(entity.projectileOwner());
})).apply(instance, (event, distance, pos, source, projectileOwner) -> {
return new Vibration(event, distance, pos, source.orElse(null), projectileOwner.orElse(null));
});
});
public Vibration(GameEvent event, float distance, Vec3 pos, @Nullable UUID source, @Nullable UUID projectileOwner) {
this(event, distance, pos, source, projectileOwner, null);
}
public Vibration(GameEvent event, float distance, Vec3 pos, @Nullable Entity entity) {
this(event, distance, pos, entity == null ? null : entity.getUUID(), getProjectileOwner(entity), entity);
}
@Nullable
private static UUID getProjectileOwner(@Nullable Entity entity) {
if (entity instanceof Projectile projectile) {
if (projectile.getOwner() != null) {
return projectile.getOwner().getUUID();
}
}
return null;
}
public Optional<Entity> getEntity(ServerLevel level) {
return Optional.ofNullable(this.entity).or(() -> {
return Optional.ofNullable(this.source).map(level::getEntity);
});
}
public Optional<Entity> getProjectileOwner(ServerLevel level) {
return this.getEntity(level).filter(entity -> {
return entity instanceof Projectile;
}).map(entity -> {
return (Projectile)entity;
}).map(Projectile::getOwner).or(() -> {
return Optional.ofNullable(this.projectileOwner).map(level::getEntity);
});
}
}
public interface VibrationConfig {
default TagKey<GameEvent> getListenableEvents() {
return GameEventTags.VIBRATIONS;
}
default boolean canTriggerAvoidVibration() {
return false;
}
default boolean isValidVibration(GameEvent event, @Nullable Entity entity) {
if (!event.is(this.getListenableEvents())) {
return false;
} else {
if (entity != null) {
if (entity.isSpectator()) {
return false;
}
if (entity.isSteppingCarefully() && event.is(GameEventTags.IGNORE_VIBRATIONS_SNEAKING)) {
if (this.canTriggerAvoidVibration() && entity instanceof ServerPlayer player) {
WBCriteriaTriggers.AVOID_VIBRATION.trigger(player);
}
return false;
}
return !entity.occludesVibrations();
}
return true;
}
}
boolean shouldListen(ServerLevel level, GameEventListener listener, BlockPos pos, GameEvent event, @Nullable Entity entity);
void onSignalReceive(ServerLevel level, GameEventListener listener, BlockPos pos, GameEvent event, @Nullable Entity entity, @Nullable Entity source, float distance);
default void onSignalSchedule() {}
}
}

View File

@ -0,0 +1,165 @@
package com.cursedcauldron.wildbackport.common.entities.warden;
import com.google.common.collect.Streams;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.core.SerializableUUID;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;
//<>
public class WardenAngerManager {
private int updateTimer = Mth.randomBetweenInclusive(new Random(System.nanoTime()), 0, 2);
private static final Codec<Pair<UUID, Integer>> SUSPECT_CODEC = RecordCodecBuilder.create(instance -> instance.group(SerializableUUID.CODEC.fieldOf("uuid").forGetter(Pair::getFirst), ExtraCodecs.NON_NEGATIVE_INT.fieldOf("anger").forGetter(Pair::getSecond)).apply(instance, Pair::of));
private final Predicate<Entity> validTarget;
protected final ArrayList<Entity> suspects;
private final SuspectComparator suspectComparator;
protected final Object2IntMap<Entity> suspectsToAngerLevel;
protected final Object2IntMap<UUID> suspectUuidsToAngerLevel;
public static Codec<WardenAngerManager> createCodec(Predicate<Entity> validTarget) {
return RecordCodecBuilder.create(instance -> instance.group(SUSPECT_CODEC.listOf().fieldOf("suspects").orElse(Collections.emptyList()).forGetter(WardenAngerManager::getSuspects)).apply(instance, suspects -> new WardenAngerManager(validTarget, suspects)));
}
public WardenAngerManager(Predicate<Entity> validTarget, List<Pair<UUID, Integer>> suspects) {
this.validTarget = validTarget;
this.suspects = new ArrayList<>();
this.suspectComparator = new SuspectComparator(this);
this.suspectsToAngerLevel = new Object2IntOpenHashMap<>();
this.suspectUuidsToAngerLevel = new Object2IntOpenHashMap<>(suspects.size());
suspects.forEach(pair -> this.suspectUuidsToAngerLevel.put(pair.getFirst(), pair.getSecond()));
}
private List<Pair<UUID, Integer>> getSuspects() {
return Streams.concat(this.suspects.stream().map(suspect -> Pair.of(suspect.getUUID(), this.suspectsToAngerLevel.getInt(suspect))), this.suspectUuidsToAngerLevel.object2IntEntrySet().stream().map(entry -> Pair.of(entry.getKey(), entry.getIntValue()))).collect(Collectors.toList());
}
public void tick(ServerLevel world, Predicate<Entity> suspectPredicate) {
--this.updateTimer;
if (this.updateTimer <= 0) {
this.updateSuspectsMap(world);
this.updateTimer = 2;
}
Iterator<Object2IntMap.Entry<UUID>> uuidAngerLevels = this.suspectUuidsToAngerLevel.object2IntEntrySet().iterator();
while (uuidAngerLevels.hasNext()) {
Object2IntMap.Entry<UUID> entry = uuidAngerLevels.next();
int level = entry.getIntValue();
if (level <= 1) {
uuidAngerLevels.remove();
} else {
entry.setValue(level - 1);
}
}
Iterator<Object2IntMap.Entry<Entity>> angerLevels = this.suspectsToAngerLevel.object2IntEntrySet().iterator();
while (angerLevels.hasNext()) {
Object2IntMap.Entry<Entity> entry = angerLevels.next();
int level = entry.getIntValue();
Entity entity = entry.getKey();
Entity.RemovalReason reason = entity.getRemovalReason();
if (level > 1 && suspectPredicate.test(entity) && reason == null) {
entry.setValue(level - 1);
} else {
this.suspects.remove(entity);
angerLevels.remove();
if (level > 1 && reason != null) {
switch (reason) {
case CHANGED_DIMENSION, UNLOADED_TO_CHUNK, UNLOADED_WITH_PLAYER -> this.suspectUuidsToAngerLevel.put(entity.getUUID(), level - 1);
}
}
}
}
this.suspects.sort(this.suspectComparator);
}
private void updateSuspectsMap(ServerLevel world) {
Iterator<Object2IntMap.Entry<UUID>> angerLevels = this.suspectUuidsToAngerLevel.object2IntEntrySet().iterator();
while (angerLevels.hasNext()) {
Object2IntMap.Entry<UUID> entry = angerLevels.next();
int level = entry.getIntValue();
Entity entity = world.getEntity(entry.getKey());
if (entity != null) {
this.suspectsToAngerLevel.put(entity, level);
this.suspects.add(entity);
angerLevels.remove();
}
}
this.suspects.sort(this.suspectComparator);
}
public int increaseAngerAt(Entity entity, int amount) {
boolean canAffectAnger = !this.suspectsToAngerLevel.containsKey(entity);
int angerValue = this.suspectsToAngerLevel.computeInt(entity, (suspect, anger) -> Math.min(150, (anger == null ? 0 : anger) + amount));
if (canAffectAnger) {
int anger = this.suspectUuidsToAngerLevel.removeInt(entity.getUUID());
this.suspectsToAngerLevel.put(entity, angerValue += anger);
this.suspects.add(entity);
}
this.suspects.sort(this.suspectComparator);
return angerValue;
}
public void removeSuspect(Entity entity) {
this.suspectsToAngerLevel.removeInt(entity);
this.suspects.remove(entity);
}
@Nullable
private Entity getSuspect() {
return this.suspects.stream().filter(this.validTarget).findFirst().orElse(null);
}
public int getPrimeSuspectAnger() {
return this.suspectsToAngerLevel.getInt(this.getSuspect());
}
public Optional<LivingEntity> getPrimeSuspect() {
return Optional.ofNullable(this.getSuspect()).filter(suspect -> suspect instanceof LivingEntity).map(suspect -> (LivingEntity)suspect);
}
protected record SuspectComparator(WardenAngerManager angerManagement) implements Comparator<Entity> {
@Override
public int compare(Entity first, Entity second) {
if (first.equals(second)) {
return 0;
} else {
int firstAngerLevel = this.angerManagement.suspectsToAngerLevel.getOrDefault(first, 0);
int secondAngerLevel = this.angerManagement.suspectsToAngerLevel.getOrDefault(second, 0);
boolean angryTowardsFirst = Angriness.getForAnger(firstAngerLevel).isAngry();
boolean angryTowardsSecond = Angriness.getForAnger(secondAngerLevel).isAngry();
if (angryTowardsFirst != angryTowardsSecond) {
return angryTowardsFirst ? -1 : 1;
} else if (angryTowardsFirst && first instanceof Player != second instanceof Player) {
return first instanceof Player ? -1 : 1;
} else {
return firstAngerLevel > secondAngerLevel ? -1 : 1;
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More