diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abe54ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.gradle +/.idea +/build +/run \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5d31b99 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2025 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..241b56a --- /dev/null +++ b/build.gradle @@ -0,0 +1,98 @@ +plugins { + id 'fabric-loom' version '1.11-SNAPSHOT' + id 'maven-publish' +} + +version = project.mod_version +group = project.maven_group + +base { + archivesName = project.archives_base_name +} + +loom { + splitEnvironmentSourceSets() + + mods { + "hungryfrog" { + sourceSet sourceSets.main + } + } +} + +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. +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" +} + +processResources { + inputs.property "version", project.version + inputs.property "minecraft_version", project.minecraft_version + inputs.property "loader_version", project.loader_version + filteringCharset "UTF-8" + + filesMatching("fabric.mod.json") { + expand "version": project.version, + "minecraft_version": project.minecraft_version, + "loader_version": project.loader_version + } +} + +def targetJavaVersion = 21 +tasks.withType(JavaCompile).configureEach { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + it.options.encoding = "UTF-8" + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + it.options.release.set(targetJavaVersion) + } +} + +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +jar { + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}" } + } +} + +// configure the maven publication +publishing { + publications { + create("mavenJava", MavenPublication) { + artifactId = project.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. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..21c4303 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,14 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +# Fabric Properties +# check these on https://modmuss50.me/fabric.html +minecraft_version=1.21.5 +yarn_mappings=1.21.5+build.1 +loader_version=0.16.14 +# Mod Properties +mod_version=1.0 +maven_group=com.kasetoatz +archives_base_name=hungryfrog +# Dependencies +# check this on https://modmuss50.me/fabric.html +fabric_version=0.128.1+1.21.5 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f9b829e --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..f91a4fe --- /dev/null +++ b/settings.gradle @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/HungryFrog.java b/src/main/java/com/kasetoatz/hungryfrog/HungryFrog.java new file mode 100644 index 0000000..47d0d11 --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/HungryFrog.java @@ -0,0 +1,46 @@ +package com.kasetoatz.hungryfrog; + +import com.kasetoatz.hungryfrog.item.FrogInfestationItem; +import com.kasetoatz.hungryfrog.sensor.NearestBlockSensor; +import com.mojang.serialization.codecs.ListCodec; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; +import net.minecraft.entity.ai.brain.MemoryModuleType; +import net.minecraft.entity.ai.brain.sensor.SensorType; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroups; +import net.minecraft.item.Items; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +public class HungryFrog implements ModInitializer +{ + public static final MemoryModuleType BLOCK_TO_EAT = new MemoryModuleType<>(Optional.of(BlockPos.CODEC)); + public static final MemoryModuleType> UNREACHABLE_BLOCK_TARGETS = new MemoryModuleType<>(Optional.of(new ListCodec<>(BlockPos.CODEC, 0, 5))); + public static final SensorType NEAREST_BLOCK_SENSOR = new SensorType<>(NearestBlockSensor::new); + public static final Item FROG_INFESTATION = register("frog_infestation", FrogInfestationItem::new, new Item.Settings().maxCount(1)); + + public static Item register(String path, Function factory, Item.Settings settings) + { + final RegistryKey registryKey = RegistryKey.of(RegistryKeys.ITEM, Identifier.of("hungryfrog", path)); + return Items.register(registryKey, factory, settings); + } + + @Override + public void onInitialize() + { + Registry.register(Registries.MEMORY_MODULE_TYPE, Identifier.of("hungryfrog", "block_to_eat"), BLOCK_TO_EAT); + Registry.register(Registries.MEMORY_MODULE_TYPE, Identifier.of("hungryfrog", "unreachable_block_targets"), UNREACHABLE_BLOCK_TARGETS); + Registry.register(Registries.SENSOR_TYPE, Identifier.of("hungryfrog", "nearest_block_sensor"), NEAREST_BLOCK_SENSOR); + ItemGroupEvents.modifyEntriesEvent(ItemGroups.TOOLS).register(entries -> { + entries.add(FROG_INFESTATION); + }); + } +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/item/FrogInfestationItem.java b/src/main/java/com/kasetoatz/hungryfrog/item/FrogInfestationItem.java new file mode 100644 index 0000000..85b7d87 --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/item/FrogInfestationItem.java @@ -0,0 +1,37 @@ +package com.kasetoatz.hungryfrog.item; + +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.passive.FrogEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class FrogInfestationItem extends Item { + public FrogInfestationItem(Settings settings) { + super(settings); + } + + public ActionResult use(World world, PlayerEntity user, Hand hand) + { + HitResult hit = user.raycast(1000, 1.F, true); + if (hit.getType() == HitResult.Type.BLOCK) + { + Vec3d pos = hit.getPos().add(0.F, 1.F, 0.F); + for (int i = 0; i < 100; i++) + { + FrogEntity frog = EntityType.FROG.create(world, SpawnReason.MOB_SUMMONED); + if (frog != null) + { + frog.setPos(pos.getX(), pos.getY(), pos.getZ()); + world.spawnEntity(frog); + } + } + } + return ActionResult.SUCCESS; + } +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/mixin/BrainAccessor.java b/src/main/java/com/kasetoatz/hungryfrog/mixin/BrainAccessor.java new file mode 100644 index 0000000..1374597 --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/mixin/BrainAccessor.java @@ -0,0 +1,22 @@ +package com.kasetoatz.hungryfrog.mixin; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.brain.Activity; +import net.minecraft.entity.ai.brain.Brain; +import net.minecraft.entity.ai.brain.sensor.Sensor; +import net.minecraft.entity.ai.brain.sensor.SensorType; +import net.minecraft.entity.ai.brain.task.Task; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import java.util.Map; +import java.util.Set; + +@Mixin(Brain.class) +public interface BrainAccessor +{ + @Accessor("sensors") + Map>, Sensor> getSensors(); + + @Accessor("tasks") + Map>>> getTasks(); +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/mixin/FrogBrainMixin.java b/src/main/java/com/kasetoatz/hungryfrog/mixin/FrogBrainMixin.java new file mode 100644 index 0000000..1a1eca8 --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/mixin/FrogBrainMixin.java @@ -0,0 +1,30 @@ +package com.kasetoatz.hungryfrog.mixin; + +import com.kasetoatz.hungryfrog.HungryFrog; +import com.kasetoatz.hungryfrog.sensor.NearestBlockSensor; +import com.kasetoatz.hungryfrog.task.FrogEatBlockTask; +import net.minecraft.entity.ai.brain.Activity; +import net.minecraft.entity.ai.brain.Brain; +import net.minecraft.entity.passive.FrogBrain; +import net.minecraft.entity.passive.FrogEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.HashMap; +import java.util.LinkedHashSet; + +@Mixin(FrogBrain.class) +public class FrogBrainMixin +{ + @Inject(method = "create", at = @At("RETURN"), cancellable = true) + private static void create(Brain brain, CallbackInfoReturnable> cir) + { + Brain frog = cir.getReturnValue(); + @SuppressWarnings("unchecked") + BrainAccessor accessor = (BrainAccessor) frog; + accessor.getSensors().put(HungryFrog.NEAREST_BLOCK_SENSOR, new NearestBlockSensor()); + accessor.getTasks().computeIfAbsent(1, p -> new HashMap<>()).computeIfAbsent(Activity.IDLE, a -> new LinkedHashSet<>()).add(new FrogEatBlockTask()); + cir.setReturnValue(frog); + } +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/mixin/FrogEntityMixin.java b/src/main/java/com/kasetoatz/hungryfrog/mixin/FrogEntityMixin.java new file mode 100644 index 0000000..d8c4da4 --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/mixin/FrogEntityMixin.java @@ -0,0 +1,50 @@ +package com.kasetoatz.hungryfrog.mixin; + +import com.kasetoatz.hungryfrog.HungryFrog; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.brain.Brain; +import net.minecraft.entity.ai.brain.MemoryModuleType; +import net.minecraft.entity.ai.brain.sensor.Sensor; +import net.minecraft.entity.ai.brain.sensor.SensorType; +import net.minecraft.entity.attribute.DefaultAttributeContainer; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.passive.FrogEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.ArrayList; + +@Mixin(FrogEntity.class) +public class FrogEntityMixin +{ + @Inject(method = "isValidFrogFood", at = @At("HEAD"), cancellable = true) + private static void isValidFrogFood(LivingEntity entity, CallbackInfoReturnable cir) + { + cir.setReturnValue(!(entity instanceof FrogEntity)); + } + + @Inject(method = "createFrogAttributes", at = @At("RETURN"), cancellable = true) + private static void createFrogAttributes(CallbackInfoReturnable cir) + { + DefaultAttributeContainer.Builder attr = cir.getReturnValue(); + attr.add(EntityAttributes.ATTACK_DAMAGE, Double.MAX_VALUE); + cir.setReturnValue(attr); + } + + @Inject(method = "createBrainProfile", at = @At("RETURN"), cancellable = true) + private void createBrainProfile(CallbackInfoReturnable> cir) + { + @SuppressWarnings("unchecked") + ProfileAccessor profile = (ProfileAccessor)(Object)cir.getReturnValue(); + if (profile != null) + { + ArrayList> memories = new ArrayList<>(profile.getMemoryModules()); + ArrayList>> sensors = new ArrayList<>(profile.getSensors()); + memories.add(HungryFrog.BLOCK_TO_EAT); + memories.add(HungryFrog.UNREACHABLE_BLOCK_TARGETS); + sensors.add(HungryFrog.NEAREST_BLOCK_SENSOR); + cir.setReturnValue(Brain.createProfile(memories, sensors)); + } + } +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/mixin/LivingEntityMixin.java b/src/main/java/com/kasetoatz/hungryfrog/mixin/LivingEntityMixin.java new file mode 100644 index 0000000..9e4eff8 --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/mixin/LivingEntityMixin.java @@ -0,0 +1,22 @@ +package com.kasetoatz.hungryfrog.mixin; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.passive.FrogEntity; +import net.minecraft.registry.tag.DamageTypeTags; +import net.minecraft.server.world.ServerWorld; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(LivingEntity.class) +public class LivingEntityMixin { + @Inject(method = "damage", at = @At("HEAD"), cancellable = true) + private void damage(ServerWorld world, DamageSource source, float amount, CallbackInfoReturnable cir) + { + if ((LivingEntity)(Object)this instanceof FrogEntity && source.isIn(DamageTypeTags.IS_FIRE)) { + cir.setReturnValue(false); + } + } +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/mixin/ProfileAccessor.java b/src/main/java/com/kasetoatz/hungryfrog/mixin/ProfileAccessor.java new file mode 100644 index 0000000..0934e7b --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/mixin/ProfileAccessor.java @@ -0,0 +1,20 @@ +package com.kasetoatz.hungryfrog.mixin; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.brain.Brain; +import net.minecraft.entity.ai.brain.MemoryModuleType; +import net.minecraft.entity.ai.brain.sensor.Sensor; +import net.minecraft.entity.ai.brain.sensor.SensorType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import java.util.Collection; + +@Mixin(Brain.Profile.class) +public interface ProfileAccessor +{ + @Accessor("memoryModules") + Collection> getMemoryModules(); + + @Accessor("sensors") + Collection>> getSensors(); +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/sensor/NearestBlockSensor.java b/src/main/java/com/kasetoatz/hungryfrog/sensor/NearestBlockSensor.java new file mode 100644 index 0000000..89583e4 --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/sensor/NearestBlockSensor.java @@ -0,0 +1,59 @@ +package com.kasetoatz.hungryfrog.sensor; + +import com.google.common.collect.ImmutableSet; +import com.kasetoatz.hungryfrog.HungryFrog; +import net.minecraft.block.Blocks; +import net.minecraft.entity.ai.brain.Brain; +import net.minecraft.entity.ai.brain.MemoryModuleType; +import net.minecraft.entity.ai.brain.sensor.Sensor; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.passive.FrogEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import java.util.*; + +public class NearestBlockSensor extends Sensor +{ + protected void sense(ServerWorld world, FrogEntity frog) + { + int range = (int) frog.getAttributeValue(EntityAttributes.FOLLOW_RANGE); + Set unreachable = new HashSet<>(frog.getBrain().getOptionalRegisteredMemory(HungryFrog.UNREACHABLE_BLOCK_TARGETS).orElse(new ArrayList<>())); + BlockPos entityPos = frog.getBlockPos(); + BlockPos.Mutable pos = new BlockPos.Mutable(); + BlockPos closest = null; + double closestDistance = Double.MAX_VALUE; + for (int dx = -range; dx < range; dx++) + { + for (int dy = -range; dy < range; dy++) + { + for (int dz = -range; dz < range; dz++) + { + pos.set(entityPos.getX() + dx, entityPos.getY() + dy, entityPos.getZ() + dz); + if (!unreachable.contains(pos) && !world.getBlockState(pos).isAir()) + { + double distance = entityPos.getSquaredDistance(pos); + if (distance < closestDistance) + { + closestDistance = distance; + closest = pos.toImmutable(); + } + } + } + } + } + Brain brain = frog.getBrain(); + if (closest != null) + { + brain.remember(HungryFrog.BLOCK_TO_EAT, closest); + } + else + { + brain.forget(HungryFrog.BLOCK_TO_EAT); + } + } + + public Set> getOutputMemoryModules() + { + return ImmutableSet.of(HungryFrog.BLOCK_TO_EAT); + } +} diff --git a/src/main/java/com/kasetoatz/hungryfrog/task/FrogEatBlockTask.java b/src/main/java/com/kasetoatz/hungryfrog/task/FrogEatBlockTask.java new file mode 100644 index 0000000..d3e8461 --- /dev/null +++ b/src/main/java/com/kasetoatz/hungryfrog/task/FrogEatBlockTask.java @@ -0,0 +1,235 @@ +package com.kasetoatz.hungryfrog.task; + +import com.google.common.collect.ImmutableMap; +import com.kasetoatz.hungryfrog.HungryFrog; +import net.minecraft.block.*; +import net.minecraft.command.argument.EntityAnchorArgumentType; +import net.minecraft.entity.EntityPose; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.ai.brain.MemoryModuleState; +import net.minecraft.entity.ai.brain.MemoryModuleType; +import net.minecraft.entity.ai.brain.WalkTarget; +import net.minecraft.entity.ai.brain.task.MultiTickTask; +import net.minecraft.entity.ai.pathing.Path; +import net.minecraft.entity.passive.FrogEntity; +import net.minecraft.fluid.FluidState; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.tag.FluidTags; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class FrogEatBlockTask extends MultiTickTask +{ + public static final SoundEvent TONGUE_SOUND = SoundEvents.ENTITY_FROG_TONGUE; + public static final SoundEvent EAT_SOUND = SoundEvents.ENTITY_FROG_EAT; + private int eatTick; + private int moveToTargetTick; + private Phase phase; + + public FrogEatBlockTask() + { + super(ImmutableMap.of(HungryFrog.BLOCK_TO_EAT, MemoryModuleState.VALUE_PRESENT), 100); + this.phase = Phase.DONE; + } + + protected boolean shouldRun(ServerWorld world, FrogEntity frog) + { + Optional memory = frog.getBrain().getOptionalRegisteredMemory(HungryFrog.BLOCK_TO_EAT); + if (memory.isEmpty()) + { + return false; + } + BlockPos pos = memory.get(); + boolean reachable = this.isBlockReachable(frog, pos); + if (!reachable) + { + frog.getBrain().forget(HungryFrog.BLOCK_TO_EAT); + this.markTargetAsUnreachable(frog, pos); + } + return reachable && frog.getPose() != EntityPose.CROAKING && frog.getBrain().getOptionalRegisteredMemory(MemoryModuleType.ATTACK_TARGET).isEmpty(); + } + + protected boolean shouldKeepRunning(ServerWorld world, FrogEntity frog, long time) + { + return frog.getBrain().hasMemoryModule(HungryFrog.BLOCK_TO_EAT) && this.phase != Phase.DONE && !frog.getBrain().hasMemoryModule(MemoryModuleType.IS_PANICKING); + } + + protected void run(ServerWorld world, FrogEntity frog, long time) + { + Optional value = frog.getBrain().getOptionalRegisteredMemory(HungryFrog.BLOCK_TO_EAT); + if (value.isEmpty()) + { + return; + } + BlockPos pos = value.get(); + frog.lookAt(EntityAnchorArgumentType.EntityAnchor.EYES, Vec3d.of(pos)); + frog.getBrain().remember(MemoryModuleType.WALK_TARGET, new WalkTarget(pos, 2.F, 0)); + this.moveToTargetTick = 10; + this.phase = Phase.MOVE_TO_TARGET; + } + + protected void finishRunning(ServerWorld world, FrogEntity frog, long time) + { + frog.getBrain().forget(HungryFrog.BLOCK_TO_EAT); + frog.setPose(EntityPose.STANDING); + } + + private void absorb(ServerWorld world, BlockPos pos) + { + BlockPos.iterateRecursively(pos, 6, 65, (currentPos, queuer) -> + { + for (Direction direction : Direction.values()) + { + queuer.accept(currentPos.offset(direction)); + } + }, currentPos -> + { + if (currentPos.equals(pos)) + { + return BlockPos.IterationState.ACCEPT; + } + else + { + BlockState blockState = world.getBlockState(currentPos); + FluidState fluidState = world.getFluidState(currentPos); + if (!fluidState.isIn(FluidTags.WATER) && !fluidState.isIn(FluidTags.LAVA)) + { + return BlockPos.IterationState.SKIP; + } + else if (blockState.getBlock() instanceof FluidDrainable fluidDrainable && !fluidDrainable.tryDrainFluid(null, world, currentPos, blockState).isEmpty()) + { + return BlockPos.IterationState.ACCEPT; + } + else + { + if (blockState.getBlock() instanceof FluidBlock) + { + world.setBlockState(currentPos, Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL); + } + else + { + if (!blockState.isOf(Blocks.KELP) && !blockState.isOf(Blocks.KELP_PLANT) && !blockState.isOf(Blocks.SEAGRASS) && !blockState.isOf(Blocks.TALL_SEAGRASS)) + { + return BlockPos.IterationState.SKIP; + } + world.setBlockState(currentPos, Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL); + } + return BlockPos.IterationState.ACCEPT; + } + } + }); + } + + private void eat(ServerWorld world, FrogEntity frog, BlockPos pos) + { + world.playSoundFromEntity(null, frog, EAT_SOUND, SoundCategory.NEUTRAL, 2.0F, 1.0F); + BlockState state = world.getBlockState(pos); + Item item = Item.BLOCK_ITEMS.get(state.getBlock()); + if (state.isOf(Blocks.WATER) || state.isOf(Blocks.LAVA)) + { + world.setBlockState(pos, Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL); + this.absorb(world, pos); + } + else + { + world.removeBlock(pos, false); + } + if (item == null) + { + return; + } + ItemStack stack = new ItemStack(item); + ItemEntity entity = new ItemEntity(world, frog.getX(), frog.getY(), frog.getZ(), stack); + world.spawnEntity(entity); + } + + protected void keepRunning(ServerWorld world, FrogEntity frog, long time) + { + Optional memory = frog.getBrain().getOptionalRegisteredMemory(HungryFrog.BLOCK_TO_EAT); + if (memory.isEmpty()) + { + return; + } + BlockPos pos = memory.get(); + switch (this.phase) + { + case MOVE_TO_TARGET: + if (frog.getPos().distanceTo(Vec3d.of(pos)) < 1.75F) + { + world.playSoundFromEntity(null, frog, TONGUE_SOUND, SoundCategory.NEUTRAL, 2.0F, 1.0F); + frog.setPose(EntityPose.USING_TONGUE); + this.eatTick = 0; + this.phase = Phase.CATCH_ANIMATION; + } + else if (this.moveToTargetTick <= 0) + { + frog.getBrain().remember(MemoryModuleType.WALK_TARGET, new WalkTarget(pos, 2.F, 0)); + this.moveToTargetTick = 10; + } + else + { + this.moveToTargetTick--; + } + break; + case CATCH_ANIMATION: + if (this.eatTick++ >= 6) + { + this.phase = Phase.EAT_ANIMATION; + this.eat(world, frog, pos); + } + break; + case EAT_ANIMATION: + if (this.eatTick >= 10) + { + this.phase = Phase.DONE; + } + else + { + this.eatTick++; + } + } + } + + private boolean isBlockReachable(FrogEntity frog, BlockPos pos) + { + Path path = frog.getNavigation().findPathTo(pos, 0); + return path != null && path.getManhattanDistanceFromTarget() < 1.75F; + } + + private void markTargetAsUnreachable(FrogEntity frog, BlockPos pos) { + try + { + List list = frog.getBrain().getOptionalRegisteredMemory(HungryFrog.UNREACHABLE_BLOCK_TARGETS).orElse(new ArrayList<>()); + boolean contains = !list.contains(pos); + if (list.size() == 5 && contains) { + list.removeFirst(); + } + if (contains) { + + list.add(pos); + } + frog.getBrain().remember(HungryFrog.UNREACHABLE_BLOCK_TARGETS, list, 100L); + } + catch (Exception e) + { + frog.getBrain().forget(HungryFrog.UNREACHABLE_BLOCK_TARGETS); + } + } + + private enum Phase + { + MOVE_TO_TARGET, + CATCH_ANIMATION, + EAT_ANIMATION, + DONE + } +} diff --git a/src/main/resources/assets/hungryfrog/items/frog_infestation.json b/src/main/resources/assets/hungryfrog/items/frog_infestation.json new file mode 100644 index 0000000..721e6d2 --- /dev/null +++ b/src/main/resources/assets/hungryfrog/items/frog_infestation.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "model", + "model": "hungryfrog:item/frog_infestation" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/hungryfrog/lang/en_us.json b/src/main/resources/assets/hungryfrog/lang/en_us.json new file mode 100644 index 0000000..81cf744 --- /dev/null +++ b/src/main/resources/assets/hungryfrog/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "item.hungryfrog.frog_infestation": "Frog Infestation" +} \ No newline at end of file diff --git a/src/main/resources/assets/hungryfrog/models/item/frog_infestation.json b/src/main/resources/assets/hungryfrog/models/item/frog_infestation.json new file mode 100644 index 0000000..d890e94 --- /dev/null +++ b/src/main/resources/assets/hungryfrog/models/item/frog_infestation.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "minecraft:item/stick" + } +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..8a82497 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,24 @@ +{ + "schemaVersion": 1, + "id": "hungryfrog", + "version": "${version}", + "name": "HungryFrog", + "description": "", + "authors": [], + "contact": {}, + "license": "MIT", + "environment": "*", + "entrypoints": { + "main": [ + "com.kasetoatz.hungryfrog.HungryFrog" + ] + }, + "mixins": [ + "hungryfrog.mixins.json" + ], + "depends": { + "fabricloader": ">=${loader_version}", + "fabric": "*", + "minecraft": "${minecraft_version}" + } +} diff --git a/src/main/resources/hungryfrog.mixins.json b/src/main/resources/hungryfrog.mixins.json new file mode 100644 index 0000000..ba67dd2 --- /dev/null +++ b/src/main/resources/hungryfrog.mixins.json @@ -0,0 +1,19 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.kasetoatz.hungryfrog.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "BrainAccessor", + "FrogBrainMixin", + "FrogEntityMixin", + "LivingEntityMixin", + "ProfileAccessor" + ], + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "requireAnnotations": true + } +}