Initial commit
This commit is contained in:
commit
e3e26d810a
|
@ -0,0 +1,2 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
|
@ -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
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.cursedcauldron.wildbackport.client.render.model;
|
||||
|
||||
public interface Drawable {
|
||||
boolean skipDraw();
|
||||
|
||||
void setSkipDraw(boolean set);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.cursedcauldron.wildbackport.common.entities.access;
|
||||
|
||||
public interface EntityExperience {
|
||||
void disableExpDrop();
|
||||
|
||||
boolean isExpDropDisabled();
|
||||
|
||||
int getExpToDrop();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue