diff --git a/build.gradle b/build.gradle index 59db348..7a73dea 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.7-SNAPSHOT' + id 'fabric-loom' version '1.9-SNAPSHOT' id 'maven-publish' } diff --git a/gradle.properties b/gradle.properties index 88f4296..8a48e5f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,9 +4,9 @@ org.gradle.parallel=true # Fabric Properties # check these on https://fabricmc.net/develop -minecraft_version=1.21 -yarn_mappings=1.21+build.9 -loader_version=0.15.11 +minecraft_version=1.21.3 +yarn_mappings=1.21.3+build.2 +loader_version=0.16.9 # Mod Properties mod_version = 1.0.1-beta-1.21 @@ -14,5 +14,5 @@ maven_group = com.virus5600 archives_base_name = defensive-measures # Dependencies -fabric_version=0.100.6+1.21 -geckolib_version=4.5.5 +fabric_version=0.110.0+1.21.3 +geckolib_version=4.7.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a441313..94113f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/remappedSrc/com/virus5600/defensive_measures/DefensiveMeasures.java b/remappedSrc/com/virus5600/defensive_measures/DefensiveMeasures.java new file mode 100644 index 0000000..ccb69de --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/DefensiveMeasures.java @@ -0,0 +1,53 @@ +package com.virus5600.defensive_measures; + +import com.virus5600.defensive_measures.advancement.criterion.ModCriterion; +import com.virus5600.defensive_measures.entity.ModEntities; +import com.virus5600.defensive_measures.item.ModItemGroups; +import com.virus5600.defensive_measures.item.ModItems; +import com.virus5600.defensive_measures.particle.ModParticles; +import com.virus5600.defensive_measures.sound.ModSoundEvents; +import net.fabricmc.api.ModInitializer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Main entry point of the mod. + * + * This class holds the Mod ID, name, and Logger instances, along with common initialization processes and + * utility methods that can be used throughout the mod's development. + */ +public class DefensiveMeasures implements ModInitializer { + /** + * Defines the mod's unique identifier. + */ + public static final String MOD_ID = "dm"; + /** + * Defines the mod's name. + */ + public static final String MOD_NAME = "DefensiveMeasures"; + /** + * Defines the mod's logger instance. + */ + public static final Logger LOGGER = LoggerFactory.getLogger(DefensiveMeasures.MOD_NAME); + + /** + * Main entry point of the mod. Initializes all server side logic. + */ + @Override + public void onInitialize() { + LOGGER.info("INITIALIZING MAIN ENTRY POINT FOR {}...", MOD_NAME); + + // Modded Stuff's Registration + ModItemGroups.registerModItemGroups(); + ModItems.registerModItems(); + ModSoundEvents.registerSoundEvents(); + ModCriterion.registerModCriterion(); + ModParticles.registerParticles(); + ModEntities.registerModEntityAttributes(); + + // Networking part + + LOGGER.info("{} MAIN ENTRY POINT INITIALIZED.", MOD_NAME); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/advancement/criterion/ModCriterion.java b/remappedSrc/com/virus5600/defensive_measures/advancement/criterion/ModCriterion.java new file mode 100644 index 0000000..95d8067 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/advancement/criterion/ModCriterion.java @@ -0,0 +1,14 @@ +package com.virus5600.defensive_measures.advancement.criterion; + +import com.virus5600.defensive_measures.DefensiveMeasures; + +import net.minecraft.advancement.criterion.Criteria; + +public class ModCriterion { + + public static final TurretItemRetrievedCriterion TURRET_ITEM_RETRIEVED_CRITERION = Criteria.register("turret_item_retrieved", new TurretItemRetrievedCriterion()); + + public static void registerModCriterion() { + DefensiveMeasures.LOGGER.info("REGISTERING CRITERION FOR {}...", DefensiveMeasures.MOD_NAME); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/advancement/criterion/TurretItemRetrievedCriterion.java b/remappedSrc/com/virus5600/defensive_measures/advancement/criterion/TurretItemRetrievedCriterion.java new file mode 100644 index 0000000..a88345c --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/advancement/criterion/TurretItemRetrievedCriterion.java @@ -0,0 +1,54 @@ +package com.virus5600.defensive_measures.advancement.criterion; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.virus5600.defensive_measures.DefensiveMeasures; + +import net.minecraft.advancement.AdvancementCriterion; +import net.minecraft.advancement.criterion.AbstractCriterion; +import net.minecraft.item.ItemStack; +import net.minecraft.predicate.entity.EntityPredicate; +import net.minecraft.predicate.entity.LootContextPredicate; +import net.minecraft.predicate.item.ItemPredicate; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.Optional; + +public class TurretItemRetrievedCriterion extends AbstractCriterion { + @Override + public Codec getConditionsCodec() { + return TurretItemRetrievedCriterion.Conditions.CODEC; + } + + public void trigger(ServerPlayerEntity player, ItemStack stack) { + this.trigger(player, conditions -> conditions.matches(stack)); + } + + public static record Conditions(Optional player, Optional item) implements AbstractCriterion.Conditions { + public static final Codec CODEC = RecordCodecBuilder.create( + instance -> instance.group( + EntityPredicate.LOOT_CONTEXT_PREDICATE_CODEC + .optionalFieldOf("player") + .forGetter(TurretItemRetrievedCriterion.Conditions::player), + ItemPredicate.CODEC + .optionalFieldOf("item") + .forGetter(TurretItemRetrievedCriterion.Conditions::item) + ) + .apply(instance, TurretItemRetrievedCriterion.Conditions::new) + ); + + public static AdvancementCriterion create(ItemPredicate.Builder item) { + return ModCriterion.TURRET_ITEM_RETRIEVED_CRITERION + .create( + new TurretItemRetrievedCriterion.Conditions( + Optional.empty(), + Optional.of(item.build()) + ) + ); + } + + public boolean matches(ItemStack stack) { + return this.item.isEmpty() || this.item.get().test(stack); + } + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/entity/ModEntities.java b/remappedSrc/com/virus5600/defensive_measures/entity/ModEntities.java new file mode 100644 index 0000000..11b9628 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/entity/ModEntities.java @@ -0,0 +1,86 @@ +package com.virus5600.defensive_measures.entity; + +import com.virus5600.defensive_measures.DefensiveMeasures; + +import com.virus5600.defensive_measures.entity.turrets.CannonTurretEntity; + +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry; + +import net.minecraft.entity.EntityType; +import net.minecraft.entity.EntityType.Builder; +import net.minecraft.entity.SpawnGroup; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public class ModEntities { + // TURRETS // + // v1.0.0 + public static final EntityType CANNON_TURRET = Registry.register( + Registries.ENTITY_TYPE, + Identifier.of(DefensiveMeasures.MOD_ID, "cannon_turret"), + Builder + .create(CannonTurretEntity::new, SpawnGroup.MISC) + .dimensions(1F, 1F) + .eyeHeight(0.51F) + .build() + ); + +// public static final EntityType BALLISTA = Registry.register( +// Registries.ENTITY_TYPE, +// Identifier.of(DefensiveMeasures.MOD_ID, "ballista"), +// Builder +// .create(BallistaTurretEntity::new, SpawnGroup.MISC) +// .dimensions(1F, 1F) +// .build() +// ); +// +// public static final EntityType MG_TURRET = Registry.register( +// Registries.ENTITY_TYPE, +// Identifier.of(DefensiveMeasures.MOD_ID, "mg_turret"), +// Builder +// .create(MGTurretEntity::new, SpawnGroup.MISC) +// .dimensions(1F, 1F) +// .build() +// ); + + // PROJECTILES // + // v1.0.0 +// public static final EntityType CANNONBALL = Registry.register( +// Registries.ENTITY_TYPE, +// Identifier.of(DefensiveMeasures.MOD_ID, "cannonball"), +// Builder +// .create(CannonballEntity::new, SpawnGroup.MISC) +// .dimensions(EntityDimensions.fixed(0.125f, 0.125f)) +// .build() +// ); + +// public static final EntityType BALLISTA_ARROW = Registry.register( +// Registries.ENTITY_TYPE, +// Identifier.of(DefensiveMeasures.MOD_ID, "ballista_arrow"), +// Builder +// .create(BallistaArrowEntity::new, SpawnGroup.MISC) +// .dimensions(EntityDimensions.fixed(0.125f, 0.125f)) +// .build() +// ); + +// public static final EntityType MG_BULLET = Registry.register( +// Registries.ENTITY_TYPE, +// Identifier.of(DefensiveMeasures.MOD_ID, "mg_bullet"), +// Builder +// .create(MGBulletEntity::new, SpawnGroup.MISC) +// .dimensions(EntityDimensions.fixed(0.125f, 0.125f)) +// .build() +// ); + + // REGISTRY // + public static void registerModEntityAttributes() { + DefensiveMeasures.LOGGER.info("REGISTERING ENTITY ATTRIBUTES FOR {}...", DefensiveMeasures.MOD_NAME); + + // TURRETS // + // v1.0.0 + FabricDefaultAttributeRegistry.register(ModEntities.CANNON_TURRET, CannonTurretEntity.setAttributes()); +// FabricDefaultAttributeRegistry.register(ModEntities.BALLISTA, BallistaTurretEntity.setAttributes()); +// FabricDefaultAttributeRegistry.register(ModEntities.MG_TURRET, MGTurretEntity.setAttributes()); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/entity/TurretMaterial.java b/remappedSrc/com/virus5600/defensive_measures/entity/TurretMaterial.java new file mode 100644 index 0000000..4e3934c --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/entity/TurretMaterial.java @@ -0,0 +1,6 @@ +package com.virus5600.defensive_measures.entity; + +public enum TurretMaterial { + METAL, + WOOD +} diff --git a/remappedSrc/com/virus5600/defensive_measures/entity/ai/goal/TargetOtherTeamGoal.java b/remappedSrc/com/virus5600/defensive_measures/entity/ai/goal/TargetOtherTeamGoal.java new file mode 100644 index 0000000..38c81cf --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/entity/ai/goal/TargetOtherTeamGoal.java @@ -0,0 +1,34 @@ +package com.virus5600.defensive_measures.entity.ai.goal; + +import com.virus5600.defensive_measures.entity.turrets.TurretEntity; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.ActiveTargetGoal; +import net.minecraft.entity.mob.Monster; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; + +public class TargetOtherTeamGoal extends ActiveTargetGoal { + public TargetOtherTeamGoal(TurretEntity turret) { + super(turret, LivingEntity.class, 10, true, false, (entity) -> { + return entity instanceof Monster; + }); + } + + public boolean canStart() { + return this.mob.getScoreboardTeam() != null && super.canStart(); + } + + protected Box getSearchBox(double distance) { + Direction dir = Direction.DOWN; + + if (dir.getAxis() == Direction.Axis.X) { + return this.mob.getBoundingBox().expand(4.0, distance, distance); + } + + return dir.getAxis() == Direction.Axis.Z + ? this.mob.getBoundingBox().expand(distance, distance, 4.0) + : this.mob.getBoundingBox().expand(distance, 4.0, distance); + + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/entity/ai/goal/TargetPlayerGoal.java b/remappedSrc/com/virus5600/defensive_measures/entity/ai/goal/TargetPlayerGoal.java new file mode 100644 index 0000000..84c5310 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/entity/ai/goal/TargetPlayerGoal.java @@ -0,0 +1,32 @@ +package com.virus5600.defensive_measures.entity.ai.goal; + +import net.minecraft.entity.ai.goal.ActiveTargetGoal; +import net.minecraft.entity.player.PlayerEntity; + +import com.virus5600.defensive_measures.entity.turrets.TurretEntity; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; + +public class TargetPlayerGoal extends ActiveTargetGoal { + public TargetPlayerGoal(TurretEntity turret) { + super(turret, PlayerEntity.class, true); + } + + @Override + public boolean canStart() { + return this.mob.getScoreboardTeam() != null && super.canStart(); + } + + @Override + protected Box getSearchBox(double distance) { + Direction dir = Direction.DOWN; + + if (dir.getAxis() == Direction.Axis.X) { + return this.mob.getBoundingBox().expand(4.0, distance, distance); + } + + return dir.getAxis() == Direction.Axis.Z + ? this.mob.getBoundingBox().expand(distance, distance, 4.0) + : this.mob.getBoundingBox().expand(distance, 4.0, distance); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/entity/turrets/CannonTurretEntity.java b/remappedSrc/com/virus5600/defensive_measures/entity/turrets/CannonTurretEntity.java new file mode 100644 index 0000000..39ce443 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/entity/turrets/CannonTurretEntity.java @@ -0,0 +1,344 @@ +package com.virus5600.defensive_measures.entity.turrets; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.virus5600.defensive_measures.item.ModItems; +import com.virus5600.defensive_measures.particle.ModParticles; +import com.virus5600.defensive_measures.sound.ModSoundEvents; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.RangedAttackMob; +import net.minecraft.entity.ai.goal.*; +import net.minecraft.entity.attribute.DefaultAttributeContainer; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.data.DataTracker.Builder; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.mob.Monster; +import net.minecraft.entity.projectile.ArrowEntity; +import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import org.jetbrains.annotations.Nullable; + +import com.virus5600.defensive_measures.DefensiveMeasures; +import com.virus5600.defensive_measures.entity.TurretMaterial; +import com.virus5600.defensive_measures.entity.ai.goal.TargetOtherTeamGoal; + +import software.bernie.geckolib.animatable.GeoEntity; +import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache; +import software.bernie.geckolib.animation.AnimatableManager.ControllerRegistrar; +import software.bernie.geckolib.animation.AnimationController; +import software.bernie.geckolib.animation.AnimationState; +import software.bernie.geckolib.animation.PlayState; +import software.bernie.geckolib.animation.RawAnimation; +import software.bernie.geckolib.util.GeckoLibUtil; + +public class CannonTurretEntity extends TurretEntity implements GeoEntity, RangedAttackMob, Itemable { + /** + * Defines how many seconds the cannon should wait before shooting again. + * The time is calculated in ticks and by default, it's 5 seconds (20 ticks times 5 seconds). + */ + private static final int totalAttCooldown = 20 * 5; + private static final TrackedData FUSE_LIT; + /** + * Contains all the items that can heal this entity. + */ + protected static final Map healables; + /** + * Contains all the items that can give effect to this entity + */ + protected static final Map> effectSource; + + private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this); + private double attCooldown = totalAttCooldown; + private boolean animPlayed = false; + + protected ProjectileAttackGoal attackGoal; + + ////////////////// + // CONSTRUCTORS // + ////////////////// + public CannonTurretEntity(EntityType entityType, World world) { + super(entityType, world, TurretMaterial.METAL, ArrowEntity.class, ModItems.CANNON_TURRET); + this.setShootSound(ModSoundEvents.TURRET_CANNON_SHOOT); + this.setHealSound(ModSoundEvents.TURRET_REPAIR_METAL); + this.addHealables(healables); + this.addEffectSource(effectSource); + } + + ////////////////// + // INITIALIZERS // + ////////////////// + @Override + protected void initGoals() { + this.attackGoal = new ProjectileAttackGoal(this, 0, totalAttCooldown, 16.625F); + // Goals + this.goalSelector.add(1, attackGoal); + this.goalSelector.add(2, new LookAtEntityGoal(this, MobEntity.class, 8.0F, 0.02F, true)); + this.goalSelector.add(8, new LookAroundGoal(this)); + + // Targets + this.targetSelector.add(1, new ActiveTargetGoal(this, MobEntity.class, 10, true, false, (entity) -> { + return entity instanceof Monster; + })); + this.targetSelector.add(3, new TargetOtherTeamGoal(this)); + } + + @Override + protected void initDataTracker(Builder builder) { + super.initDataTracker(builder); + + builder.add(FUSE_LIT, false); + } + + public static DefaultAttributeContainer.Builder setAttributes() { + return TurretEntity.createMobAttributes() + .add(EntityAttributes.GENERIC_FOLLOW_RANGE, 16) + .add(EntityAttributes.GENERIC_MAX_HEALTH, 50) + .add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0) + .add(EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE, 1.0); + } + + ///////////////////// + // PROCESS METHODS // + ///////////////////// + + @Override + public void shootAt(LivingEntity target, float pullProgress) { + if (target == null) { + if (this.isShooting()) + this.setShooting(false); + return; + } + + this.setShooting(true); + + try { + double vx = (target.getX() - this.getX()) * 1.0625; + double vy = target.getBodyY((double) 2 / 3) - this.getY() + 0.25; + double vz = (target.getZ() - this.getZ()) * 1.0625; + double variance = Math.sqrt(vx * vx + vz * vz); + float divergence = this.getWorld().getDifficulty().getId() * 2; +// ProjectileEntity projectile = (ProjectileEntity) new CannonballEntity(ModEntities.CANNONBALL, this, vx, vy, vz, this.getWorld()); + ProjectileEntity projectile = (ProjectileEntity) new ArrowEntity(EntityType.ARROW, this.getWorld()); + + projectile.setVelocity(vx, vy + variance * 0.1f, vz, 1.5f, divergence); + projectile.setPos(this.getX(), this.getY() + 0.5, this.getZ()); + + this.playSound(this.getShootSound(), 1.0f, 1.0f / (this.getRandom().nextFloat() * 0.4f + 0.8f)); + this.getWorld().spawnEntity(projectile); + this.triggerAnim("Firing Sequence", "Shoot"); + } catch (IllegalArgumentException | SecurityException e) { + e.printStackTrace(System.out); + + DefensiveMeasures.LOGGER.error(""); + DefensiveMeasures.LOGGER.error(" {} ERROR OCCURRED ", DefensiveMeasures.MOD_ID.toUpperCase()); + DefensiveMeasures.LOGGER.error("===== ERROR MSG START ====="); + DefensiveMeasures.LOGGER.error("LOCALIZED ERROR MESSAGE:"); + DefensiveMeasures.LOGGER.error(e.getLocalizedMessage()); + DefensiveMeasures.LOGGER.error(""); + DefensiveMeasures.LOGGER.error("ERROR MESSAGE:"); + DefensiveMeasures.LOGGER.error(e.getMessage()); + DefensiveMeasures.LOGGER.error("===== ERROR MSG END ====="); + DefensiveMeasures.LOGGER.error(""); + } + } + + @Override + public void tick() { + super.tick(); + + this.setYaw(0); + this.setBodyYaw(0); + + if (this.isShooting() && this.hasTarget()) { + if (this.attackGoal != null) { + if (this.attackGoal.shouldContinue()) { + if (--this.attCooldown <= 0) { + this.setShootingFXDone(false); + this.setFuseLit(false); + this.attCooldown = totalAttCooldown; + } + else { + this.setShootingFXDone(true); + this.setFuseLit(true); + this.triggerAnim("Firing Sequence", "Charge"); + } + } + } + } + } + + ////////////////////////////////////// + // QUESTION METHODS (True or False) // + ////////////////////////////////////// + + ///////////////////////// + // GETTERS AND SETTERS // + ///////////////////////// + + @Nullable + @Override + protected SoundEvent getHurtSound(DamageSource source) { + return ModSoundEvents.TURRET_CANNON_HURT; + } + + @Nullable + @Override + protected SoundEvent getDeathSound() { + return ModSoundEvents.TURRET_CANNON_DESTROYED; + } + + protected boolean isFuseLit() { + return this.dataTracker.get(FUSE_LIT); + } + protected void setFuseLit(boolean lit) { + this.dataTracker.set(FUSE_LIT, lit); + } + + @Override + public ItemStack getEntityItem() { + return new ItemStack(ModItems.CANNON_TURRET); + } + + /////////////////////////// + // ANIMATION CONTROLLERS // + /////////////////////////// + + private PlayState deathController(final AnimationState event) { + if (!this.isAlive() && !animPlayed) { + animPlayed = true; + event.setAnimation( + RawAnimation + .begin() + .thenPlayAndHold("animation.cannon_turret.death") + ); + return PlayState.STOP; + } + return PlayState.CONTINUE; + } + + private PlayState idleController(final AnimationState event) { + return event + .setAndContinue( + RawAnimation + .begin() + .thenLoop("animation.cannon_turret.look_at_target") + .thenLoop("animation.cannon_turret.setup") + ); + } + + // TODO: Fix particle spawning + private PlayState firingSequenceController(final AnimationState event) { + Vec3d fusePos = this.getRelativePos(0, 1, 0), + barrelPos = this.getRelativePos(0, 0, 0); + + // Shooting sequence + event.getController() + .setParticleKeyframeHandler((state) -> { + String locator = state.getKeyframeData().getLocator(), + effectName = state.getKeyframeData().getEffect(), + currentState = "fuse"; + + if (this.hasTarget() && this.isShooting()) { + if (this.isFuseLit()) currentState = "fuse"; + else currentState = "shoot"; + } + + System.out.println("Locator: " + locator + " | Effect: " + effectName + " | State: " + currentState); + + if (currentState.equals("fuse")) { + System.out.println("Fuse Position: " + fusePos.toString()); + this.getWorld().addParticle( + ModParticles.CANNON_FUSE, + fusePos.getX(), fusePos.getY(), fusePos.getZ(), + 0,0,0 + ); + } + else { + System.out.println("Barrel Position: " + barrelPos.toString()); + this.getWorld().addParticle( + ModParticles.CANNON_FLASH, + barrelPos.getX(), barrelPos.getY(), barrelPos.getZ(), + 0,0,0 + ); + } + + state.getController() + .setAnimation( + RawAnimation + .begin() + .thenPlay("animation.cannon_turret." + currentState) + ); + }); + + return PlayState.CONTINUE; + } + + /////////////////////////////// + // INTERFACE IMPLEMENTATIONS // + /////////////////////////////// + + // GeoEntity // + @Override + public void registerControllers(final ControllerRegistrar controllers) { + controllers + .add( + new AnimationController<>(this, "Death", this::deathController), + new AnimationController<>(this, "Idle", this::idleController), + new AnimationController<>(this, "Firing Sequence", this::firingSequenceController) + .triggerableAnim("Charge", RawAnimation.begin().thenPlay("animation.cannon_turret.fuse")) + .triggerableAnim("Shoot", RawAnimation.begin().thenPlay("animation.cannon_turret.shoot")) + ); + } + + @Override + public AnimatableInstanceCache getAnimatableInstanceCache() { + return this.geoCache; + } + + /////////////////// + // LOCAL CLASSES // + /////////////////// + + /////////////////////// + // STATIC INITIALIZE // + /////////////////////// + + static { + FUSE_LIT = DataTracker.registerData(CannonTurretEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + + healables = new HashMap() { + { + for (Item item : TurretEntity.PLANKS) + put(item, 1.0f); + put(Items.IRON_NUGGET, 1f); + put(Items.IRON_INGOT, 10f); + put(Items.IRON_BLOCK, 100f); + } + }; + + effectSource = new HashMap>() { + { + put(Items.IRON_BLOCK, new ArrayList() { + { + add(new Object[] {StatusEffects.ABSORPTION, 60, 2}); + } + }); + } + }; + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/entity/turrets/Itemable.java b/remappedSrc/com/virus5600/defensive_measures/entity/turrets/Itemable.java new file mode 100644 index 0000000..dc79877 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/entity/turrets/Itemable.java @@ -0,0 +1,154 @@ +package com.virus5600.defensive_measures.entity.turrets; + +import com.virus5600.defensive_measures.advancement.criterion.ModCriterion; +import com.virus5600.defensive_measures.entity.TurretMaterial; +import com.virus5600.defensive_measures.sound.ModSoundEvents; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.NbtComponent; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.MathHelper; +import net.minecraft.world.World; + +import java.util.Optional; +import java.util.Random; + +/** + * An interface that serves as a base for all entities that can be stored as an items. + * + * @author Virus5600 + * @version 1.0.0 + */ +public interface Itemable { + /** + * Determines whether this entity is from an item. + * @return {@link byte} + */ + public byte isFromItem(); + + /** + * Sets the value that identifies whether this entity is from an item. + * @param fromItem {@link byte} A `1` or `0` value. + */ + public void setFromItem(byte fromItem); + + /** + * Copies the data to the given stack. + * @param stack {@link ItemStack} The stack to copy the data to. + */ + public void copyDataToStack(ItemStack stack); + + /** + * Copies the data from the given NBT to this entity. + * @param nbt {@link NbtCompound} The NBT to copy the data from. + */ + public void copyDataFromNbt(NbtCompound nbt); + + /** + * Retrieves the item this turret is from. + * @return {@link ItemStack} + */ + public ItemStack getEntityItem(); + + /** + * Retrieves the sound that plays when the turret is removed. + * @return {@link SoundEvent} + */ + public SoundEvent getEntityRemoveSound(); + + /** + * Copies the data from this entity to the given stack. + * + * @param entity {@link MobEntity} The entity to copy the data from. + * @param stack {@link ItemStack} The stack to copy the data to. + */ + public static void copyDataToStack(MobEntity entity, ItemStack stack) { + // Sets the name + stack.set(DataComponentTypes.CUSTOM_NAME, entity.hasCustomName() ? entity.getCustomName() : stack.getName()); + + NbtCompound nbtCompound = new NbtCompound(); + + // Sets the NBT + nbtCompound.putBoolean("NoAI", entity.isAiDisabled()); + nbtCompound.putBoolean("Silent", entity.isSilent()); + nbtCompound.putBoolean("NoGravity", entity.hasNoGravity()); + nbtCompound.putBoolean("Glowing", entity.isGlowing()); + nbtCompound.putBoolean("Invulnerable", entity.isInvulnerable()); + nbtCompound.putFloat("Health", entity.getHealth()); + nbtCompound.putUuid("UUID", entity.getUuid()); + + stack.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.of(nbtCompound)); + } + + /** + * Copies the data from the given NBT to this entity. + * + * @param entity {@link MobEntity} The entity to copy the data to. + * @param nbt {@link NbtCompound} The NBT to copy the data from. + */ + public static void copyDataFromNbt(MobEntity entity, NbtCompound nbt) { + NbtComponent.of(nbt).applyToEntity(entity); + } + + /** + * Attempts to retrieve the {@link Itemable Itemable Entity} as an item. + * @param player The player that is attempting to retrieve the item. + * @param hand The hand the player is using to retrieve the item. + * @param entity The entity to retrieve the item from. + * @param tool The tool the player is using to retrieve the item. + * @param modItem The item to retrieve. + * @return {@link Optional} The result of the action. + * @param The type of entity. + */ + public static Optional tryItem(PlayerEntity player, Hand hand, T entity, Item tool, Item modItem) { + ItemStack itemStack = player.getStackInHand(hand); + if (itemStack.getItem() == tool && entity.isAlive()) { + World world = entity.getWorld(); + + if (!world.isClient) { + if (((TurretEntity) entity).getTurretMaterial() == TurretMaterial.METAL) { + entity.playSound(ModSoundEvents.TURRET_REMOVED_METAL, 1.0f, new Random().nextFloat(0.75f, 1.25f)); + } + else if (((TurretEntity) entity).getTurretMaterial() == TurretMaterial.WOOD) { + entity.playSound(ModSoundEvents.TURRET_REMOVED_WOOD, 1.0f, new Random().nextFloat(0.75f, 1.25f)); + } + } + + if (player.isCreative() && !player.isSneaking()) { + entity.discard(); + return Optional.of(ActionResult.success(true)); + } + + ItemStack stack = new ItemStack(modItem); + ((Itemable) entity).copyDataToStack(stack); + + float x = (float) entity.getPos().x + 0.5f; + float y = (float) entity.getPos().y + 0.5f; + float z = (float) entity.getPos().z + 0.5f; + double vx = MathHelper.nextDouble(world.random, -0.1, 0.1); + double vy = MathHelper.nextDouble(world.random, 0.0, 0.1); + double vz = MathHelper.nextDouble(world.random, -0.1, 0.1); + + entity.discard(); + ItemEntity itemStackEntity = new ItemEntity(world, x, y, z, stack, vx, vy, vz); + world.spawnEntity(itemStackEntity); + + if (!world.isClient) { + ModCriterion.TURRET_ITEM_RETRIEVED_CRITERION.trigger((ServerPlayerEntity) player, stack); + } + + entity.discard(); + return Optional.of(ActionResult.success(world.isClient)); + } + return Optional.empty(); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/entity/turrets/TurretEntity.java b/remappedSrc/com/virus5600/defensive_measures/entity/turrets/TurretEntity.java new file mode 100644 index 0000000..8cd46ce --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/entity/turrets/TurretEntity.java @@ -0,0 +1,1100 @@ +package com.virus5600.defensive_measures.entity.turrets; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.*; +import net.minecraft.entity.ai.RangedAttackMob; +import net.minecraft.entity.ai.control.BodyControl; +import net.minecraft.entity.ai.control.LookControl; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.ArrowEntity; +import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.item.Equipment; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.*; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.LocalDifficulty; +import net.minecraft.world.ServerWorldAccess; +import net.minecraft.world.World; + +import com.virus5600.defensive_measures.DefensiveMeasures; +import com.virus5600.defensive_measures.entity.TurretMaterial; +import com.virus5600.defensive_measures.item.ModItems; +import com.virus5600.defensive_measures.item.turrets.TurretItem; + +import net.minecraft.world.event.GameEvent; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +/** + * The mob base for all the Turrets that will be added into this mod. The + * {@code TurretEntity} extends to the base code of {@code MobEntity}, the same + * superclass used by various mobs that has the same trait as this. The custom + * interface {@code Itemable} and {@code RangedAttackMob} interfaces are also + * implemented to define this entity as something that can do range attacks + * and can be converted to an item.
+ *
+ * New custom {@code NBT}s are also added such as {@code LEVEL} + * and {@code FROM_ITEM} to identify their level variation and whether + * this entity is spawned from an instance of already spawned turret entity. + * + * @author Virus5600 + * @since 1.0.0 + * + * @see MobEntity + * @see Itemable + * @see RangedAttackMob + * + */ +public class TurretEntity extends MobEntity implements Itemable, RangedAttackMob{ + /** + * Tracks the level (stage) of this turret entity. + */ + private static final TrackedData LEVEL; + /** + * Tracks whether this entity is spawned from an item. + */ + private static final TrackedData FROM_ITEM; + /** + * Tracks whether this entity is shooting. + */ + private static final TrackedData SHOOTING; + /** + * Tracks whether the shooting effect is done. + */ + private static final TrackedData SHOOTING_FX_DONE; + /** + * Tracks whether this turret has a target. + */ + private static final TrackedData HAS_TARGET; + + /** + * Tracks the direction where this turret is attached to. + */ + protected static final TrackedData ATTACHED_FACE; + /** + * Tracks the X position of this turret. + */ + protected static final TrackedData X; + /** + * Tracks the Y position of this turret. + */ + protected static final TrackedData Y; + /** + * Tracks the Z position of this turret. + */ + protected static final TrackedData Z; + /** + * Tracks the yaw of this turret. + */ + protected static final TrackedData YAW; + /** + * Tracks the pitch of this turret. + */ + protected static final TrackedData PITCH; + /** + * Tracks the X position of this turret's target. + */ + protected static final TrackedData TARGET_POS_X; + /** + * Tracks the Y position of this turret's target. + */ + protected static final TrackedData TARGET_POS_Y; + /** + * Tracks the Z position of this turret's target. + */ + protected static final TrackedData TARGET_POS_Z; + + //////////////////////// + // INSTANCE VARIABLES // + //////////////////////// + /** + * Acts as a storage for the turret's world position, allowing the turret to snap in place. + */ + private BlockPos prevAttachedBlock; + /** + * Contains all the items that can heal this entity. + */ + private Map healables; + /** + * Contains all the items that can give effect to this entity, and must be stored in a 2D array. + * An item can have multiple effects and is structured as this:
+ *

+	 * [
+	 * 	[StatusEffect, duration, amplifier]
+	 * 	[StatusEffect, duration, amplifier]
+	 * 	[StatusEffect, duration, amplifier]
+	 * ]
+	 * 
+ */ + private Map> effectSource; + + /** + * The sound the turret makes when it is being healed. + */ + protected SoundEvent healSound = SoundEvents.ENTITY_IRON_GOLEM_REPAIR; + /** + * The sound the turret makes when it is shooting. + */ + protected SoundEvent shootSound = SoundEvents.ENTITY_ARROW_SHOOT; + /** + * Defines what kind of projectile this turret will shoot. + */ + protected Class projectile; + /** + * Defines the level of this turret. The higher the level, the stronger + * the turret. + */ + protected int level; + /** + * The material of this turret. + */ + protected TurretMaterial material; + /** + * The item counterpart of this item. Refer to {@link ModItems} list + * to see the list of items that can be converted to this entity. + * The items should be a subclass of {@link TurretItem}. + */ + protected Item itemable; + /** + * A randomizer for this turret's instance. + */ + protected final Random random; + + /** + * List of plank items in the game. This allows easy insertion of all the items when needed by iterating through the List. + */ + public static final List PLANKS = Arrays.asList(new Item[] { + Items.ACACIA_PLANKS, + Items.BAMBOO_PLANKS, + Items.BIRCH_PLANKS, + Items.CHERRY_PLANKS, + Items.CRIMSON_PLANKS, + Items.DARK_OAK_PLANKS, + Items.JUNGLE_PLANKS, + Items.MANGROVE_PLANKS, + Items.OAK_PLANKS, + Items.SPRUCE_PLANKS, + Items.WARPED_PLANKS + }); + /** + * List of log items in the game. This allows easy insertion of all the items when needed by iterating through the List. + */ + public static final List LOGS = Arrays.asList(new Item[]{ + Items.ACACIA_LOG, + Items.BAMBOO, + Items.BIRCH_LOG, + Items.CHERRY_LOG, + Items.CRIMSON_STEM, + Items.DARK_OAK_LOG, + Items.JUNGLE_LOG, + Items.MANGROVE_LOG, + Items.OAK_LOG, + Items.SPRUCE_LOG, + Items.WARPED_HYPHAE, + Items.WARPED_STEM, + Items.STRIPPED_ACACIA_LOG, + Items.STRIPPED_BIRCH_LOG, + Items.STRIPPED_CHERRY_LOG, + Items.STRIPPED_CRIMSON_STEM, + Items.STRIPPED_DARK_OAK_LOG, + Items.STRIPPED_JUNGLE_LOG, + Items.STRIPPED_MANGROVE_LOG, + Items.STRIPPED_OAK_LOG, + Items.STRIPPED_SPRUCE_LOG, + Items.STRIPPED_WARPED_HYPHAE, + Items.STRIPPED_WARPED_STEM + }); + + ////////////////// + // CONSTRUCTORS // + ////////////////// + + /** + * Constructs a new {@code TurretEntity} with the given {@code EntityType}, {@code World}, + * {@code TurretMaterial}, {@code Class} of the projectile, and {@code Item}. + * + * @param entityType The type of this entity. + * @param world The world this entity is in. + * @param material The material of this turret. + * @param projectile The class of the projectile this turret will shoot. + * @param itemable The itemable counterpart of this entity. + * + * @see #itemable + */ + public TurretEntity(EntityType entityType, World world, TurretMaterial material, Class projectile, Item itemable) { + this(entityType, world, material, itemable); + this.projectile = projectile; + } + + /** + * Constructs a new {@code TurretEntity} with the given {@code EntityType}, {@code World}, + * {@code TurretMaterial}, and {@code Item}. If the {@code projectile} is not yet defined, the default + * projectile will be used, which is an {@code ArrowEntity}. + * + * @param entityType The type of this entity. + * @param world The world this entity is in. + * @param material The material of this turret. + * @param itemable The itemable counterpart of this entity. + * + * @see #itemable + */ + public TurretEntity(EntityType entityType, World world, TurretMaterial material, Item itemable) { + super(entityType, world); + DefensiveMeasures.LOGGER.debug("Creating a new TurretEntity called {}", entityType.getName()); + + this.material = material; + this.itemable = itemable; + this.random = Random.create(); + this.lookControl = new TurretEntity.TurretLookControl(this); + + if (this.projectile == null) { + this.projectile = ArrowEntity.class; + } + } + + ////////////////// + // INITIALIZERS // + ////////////////// + + @Override + protected void initDataTracker(DataTracker.Builder builder) { + // Entity related tracking + builder.add(LEVEL, this.level) + .add(FROM_ITEM, (byte) 1) + .add(SHOOTING, false) + .add(SHOOTING_FX_DONE, true) + .add(HAS_TARGET, false) + + // Position related tracking + .add(ATTACHED_FACE, Direction.DOWN) + .add(X, 0f) + .add(Y, 0f) + .add(Z, 0f) + .add(YAW, 0f) + .add(PITCH, 0f) + .add(TARGET_POS_X, 0f) + .add(TARGET_POS_Y, 0f) + .add(TARGET_POS_Z, 0f); + super.initDataTracker(builder); + } + + @Override + protected BodyControl createBodyControl() { + return new TurretBodyControl(this); + } + + @Nullable + @Override + public EntityData initialize(ServerWorldAccess world, LocalDifficulty difficulty, SpawnReason spawnReason, @Nullable EntityData entityData) { + this.headYaw = this.prevHeadYaw; + this.prevBodyYaw = 0.0f; + this.resetPosition(); + + if (spawnReason == SpawnReason.SPAWN_EGG) { + return entityData; + } + + return super.initialize(world, difficulty, spawnReason, entityData); + } + + ///////////////////// + // PROCESS METHODS // + ///////////////////// + + /** + * Attempts to attach this entity to a block or fall if there is no block to attach to + * at the current position. If the entity is attached to a block, it will not fall but + * will instead snap to the block's position. However, if the entity is not attached to + * a block, it will look for a block to attach to around it and if there is none, it will + * fall. + */ + protected void tryAttachOrFall() { + Direction dir = this.findAttachableSide(this.getBlockPos()); + if (dir != null) + this.setAttachedFace(dir); + else + this.tryFall(); + } + + /** + * Attempts to let this turret fall regardless of whether it is attached to a block or not. + * + * @return {@code boolean} Returns {@code true} if the turret is falling, otherwise {@code false}. + */ + protected boolean tryFall() { + if (this.isAiDisabled() || !this.isAlive()) + return false; + + BlockPos blockPos = this.getBlockPos().add(new Vec3i(0, -1, 0)); + if (this.isValidFallingPosition(blockPos)) { + this.setVelocity(super.getVelocity().multiply(0.98)); + return true; + } + return false; + } + + /** + * Retrieves the currently attached side of this turret. + * @param pos The current position of this turret. + * @return {@code Direction} The direction where this turret is attached to. + */ + @Nullable + protected Direction findAttachableSide(BlockPos pos) { + for (Direction dir : Direction.values()) { + if (!this.canStay(pos, dir)) + continue; + return dir; + } + return null; + } + + @Override + protected ActionResult interactMob(PlayerEntity player, Hand hand) { + ItemStack item = player.getStackInHand(hand); + Equipment equipment = Equipment.fromStack(item); + boolean isSurvival = !player.isCreative(); + boolean isSuccess = false; + + // Turret Remover Interaction + if (item.getItem() == ModItems.TURRET_REMOVER) { + if (equipment != null && isSurvival) + item.damage(1, player, equipment.getSlotType()); + + isSuccess = true; + } + + // Healables + else if (this.isHealableItem(item.getItem())) { + // Heals the turret + this.heal(this.getHealAmt(item.getItem())); + + // Indicates a repair was done + float pitch = 1F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F; + this.playSound(this.getHealSound(), 1F, pitch); + + isSuccess = true; + } + + // Effect Source + if (this.isEffectSource(item.getItem())) { + for (Object[] args : this.getMobEffect(item.getItem())) { + this.addStatusEffect( + new StatusEffectInstance( + (RegistryEntry) args[0], + (int) ((float) ((Integer) args[1]) * 20), + (Integer) args[2] + ) + ); + } + + if (item.isDamageable()) { + if (equipment != null) + item.damage(1, player, equipment.getSlotType()); + } + else { + item.decrementUnlessCreative(1, player); + } + + isSuccess = true; + } + + if (isSuccess) + return ActionResult.SUCCESS; + + if (item.getItem() == ModItems.TURRET_REMOVER) { + return Itemable.tryItem(player, hand, this, ModItems.TURRET_REMOVER, this.itemable) + .orElse(super.interactMob(player, hand)); + } + return super.interactMob(player, hand); + } + + @Override + public void readCustomDataFromNbt(NbtCompound nbt) { + super.readCustomDataFromNbt(nbt); + this.setLevel(nbt.getInt("Level")); + this.setFromItem(nbt.getByte("FromItem")); + } + + @Override + public boolean startRiding(Entity entity, boolean force) { + if (this.getWorld().isClient()) { + this.prevAttachedBlock = null; + } + + this.setAttachedFace(Direction.DOWN); + return super.startRiding(entity, force); + } + + @Override + public void stopRiding() { + super.stopRiding(); + + if (this.getWorld().isClient) { + this.prevAttachedBlock = this.getBlockPos(); + } + + this.prevBodyYaw = 0.0f; + this.bodyYaw = 0.0f; + } + + @Override + @Nullable + public ItemStack getPickBlockStack() { + TurretItem turretItem = TurretItem.forEntity(this.getType()); + + if (turretItem == null) + return null; + + return new ItemStack(turretItem); + } + + @Override + public void tick() { + super.tick(); + + if (!this.getWorld().isClient()) { + this.setHasTarget(this.getTarget() != null); + } else { + // SNAPPING THE TURRET BACK IN PLACE + if (this.getVelocity().x == 0 && this.getVelocity().z == 0 && !this.hasVehicle()) { + Vec3d newPos = new Vec3d( + (double) MathHelper.floor(this.getX()) + 0.5, + this.getY(), + (double) MathHelper.floor(this.getZ()) + 0.5 + ); + + this.tryAttachOrFall(); + super.setPosition(newPos); + this.getWorld().emitGameEvent(this, GameEvent.TELEPORT, newPos); + + if (this.getVelocity() == Vec3d.ZERO && this.getWorld().isClient() && !this.hasVehicle()) { + this.lastRenderX = this.getX(); + this.lastRenderY = this.getY(); + this.lastRenderZ = this.getZ(); + } + } + } + } + + @Override + public void tickMovement() { + super.tickMovement(); + + if (!(this.getWorld().isClient() + || this.hasVehicle() + || this.canStay(this.getBlockPos(), this.getAttachedFace()))) { + this.tryAttachOrFall(); + } + } + + ////////////////////////////////////// + // QUESTION METHODS (True or False) // + ////////////////////////////////////// + + /** + * Determines whether this turret can stay on the given position or not. + * @param pos The position to check. + * @param dir The direction to check. + * @return {@code boolean} Returns {@code true} if the turret can stay, otherwise {@code false}. + */ + protected boolean canStay(BlockPos pos, Direction dir) { + if (!this.isValidFallingPosition(pos)) + return false; + + dir = dir == null ? Direction.DOWN : dir; + Direction opposite = dir.getOpposite(); + + if (!this.getWorld().isDirectionSolid(pos.offset(dir), this, opposite)) + return false; + + Box box = this.calculateBoundingBox().offset(pos).contract(1.0E-6); + return this.getWorld().isSpaceEmpty(this, box); + } + + /** + * Identifies whether the position is a valid falling position for this turret. + * Blocks that are considered valid falling blocks are air, water, and bubble columns. + * @param pos The position to check. + * @return {@code boolean} Returns {@code true} if the position is valid, otherwise {@code false}. + */ + protected boolean isValidFallingPosition(BlockPos pos) { + BlockState bState = this.getWorld().getBlockState(pos); + + if (bState.isAir() + || (bState.isOf(Blocks.BUBBLE_COLUMN) && pos.equals(this.getBlockPos())) + || (bState.isOf(Blocks.WATER) && pos.equals(this.getBlockPos())) + ) + return true; + + return !(bState.isOf(Blocks.MOVING_PISTON) && pos.equals(this.getBlockPos())); + } + + /** + * Identifies whether the submitted item is part of the healable map. + * @param item The item in question + * @return boolean + */ + public boolean isHealableItem(Item item) { + return this.healables.containsKey(item); + } + + /** + * Identifies whether the submitted item is part of the effect source map. + * @param item The item in question + * + * @return boolean + * + * @see StatusEffect + */ + public boolean isEffectSource(Item item) { + return this.effectSource.containsKey(item); + } + /** + * Identifies whether this item already have the effect in its list or not. This iterates through all the entries + * + * @param item The item in question + * @param effect The effect to check + * @return boolean + */ + public boolean effectSourceHasEffect(Item item, StatusEffect effect) { + List mobEffects = this.getMobEffect(item); + if (mobEffects!= null) + for (Object[] registeredEffect : mobEffects) + if ((StatusEffect) registeredEffect[0] == effect) + return true; + return false; + } + + @Override + public boolean canBeLeashed() { + return false; + } + + @Override + public boolean isCollidable() { + return this.isAlive(); + } + + @Override + public boolean isPushable() { + return false; + } + + ///////////////////////// + // GETTERS AND SETTERS // + ///////////////////////// + + @Override + protected MoveEffect getMoveEffect() { + return MoveEffect.NONE; + } + + protected final Vec3d getHeadRotationVector() { + float f = this.getPitch() * ((float)Math.PI / 180); + float g = -this.getHeadYaw()* ((float)Math.PI / 180); + float h = MathHelper.cos(g); + float i = MathHelper.sin(g); + float j = MathHelper.cos(f); + float k = MathHelper.sin(f); + return new Vec3d(i * j, -k, h * j); + } + + /** + * Sets the direction where this will be attached to. + * @param dir The direction to attach to. + */ + protected void setAttachedFace(Direction dir) { + this.dataTracker.set(ATTACHED_FACE, dir); + } + /** + * Retrieves the direction where this turret is attached to. + * @return {@code Direction} The direction where this turret is attached to. + */ + protected Direction getAttachedFace() { + return this.dataTracker.get(ATTACHED_FACE); + } + + /** + * Identifies the position of a point relative to this turret rotation and position. + * For reference: + *
    + *
  • X-Axis == Pitch: Identifies the elevation rotation (Horizontal line axis)
  • + *
  • Y-Axis == Yaw: Identifies where you are looking (Vertical line axis)
  • + *
  • Z-Axis == Roll: It's the one facing you (The 3D line)
  • + *
+ * + * @param xOffset The offset of the point at the local X-Axis of this turret. + * @param yOffset The offset of the point at the local Y-Axis of this turret. + * @param zOffset The offset of the point at the local Z-Axis of this turret. + * + * @return Vec3d the relative position of this point, assuming that the origin is at [0, 0, 0] + */ + public Vec3d getRelativePos(double xOffset, double yOffset, double zOffset) { + return this.getRotationVecClient().add(this.getPos()) + .add(xOffset, yOffset, zOffset); + } + + @Override + public int getMaxLookPitchChange() { + return 30; + } + + @Override + public int getMaxHeadRotation() { + return 360; + } + + @Override + public boolean handleFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { + return false; + } + + @Override + public int getMinAmbientSoundDelay() { + return 120; + } + + public int getLevel() { + return this.dataTracker.get(LEVEL); + } + public void setLevel(int level) { + this.dataTracker.set(LEVEL, level); + } + + public SoundEvent getShootSound() { + return this.shootSound; + } + public void setShootSound(SoundEvent sound) { + this.shootSound = sound; + } + + public SoundEvent getHealSound() { + return this.healSound; + } + public void setHealSound(SoundEvent sound) { + this.healSound = sound; + } + + public TurretMaterial getTurretMaterial() { + return this.material; + } + + /** + * Adds an item to the map of items that can heal this turret. + *

NOTE: If the item is already added, the old heal amount would be updated to the specified value + * + * @param item Item to be added + * @param amount Amount of health that will be healed when used + * @return TurretEntity + * + * @see Item + * @see Items + * @see TurretEntity + */ + public TurretEntity addHealable(Item item, float amount) { + if (this.healables == null) + this.healables = new HashMap<>(); + this.healables.put(item, amount); + return this; + } + /** + * Adds the list of items to the map of items that can heal this turret. + *

NOTE: If the item is already added, the old heal amount would be updated to the specified value + * + * @param group A {@link List list} of items that will be added to the {@code healable} map + * @param amount Amount of health that will be healed when used + * @return TurretEntity + * + * @see Item + * @see Items + * @see TurretEntity + */ + public TurretEntity addHealable(List group, float amount) { + if (this.healables == null) + this.healables = new HashMap<>(); + + for (Item item : group) + this.healables.put(item, amount); + + return this; + } + /** + * Adds all the items to the list of items that can heal this turret. + * @param healables A key-value pair made from a {@code Map}, The map uses the {@code Item} as the key, and the {@code Float} as the amount of health healed + * @return TurretEntity + * + * @see Item + * @see Items + * @see Map + * @see TurretEntity + */ + public TurretEntity addHealables(Map healables) { + if (this.healables == null || this.healables.isEmpty()) + this.healables = healables; + else + this.healables.putAll(healables); + return this; + } + /** + * Retrieves the item and identifies the heal amount of this item, otherwise, return {@code null}. + * @param item The item to get the heal amount from. + * @return float Amount of healing this item will give to the entity; otherwise, {@code 0} + */ + public float getHealAmt(Item item) { + return this.isHealableItem(item) ? this.healables.get(item) : 0; + } + + /** + * Adds an item to the map of items that can give mob effect to this turret. + *

NOTE: If the item is already added, the old values will be updated + * + * @param item Item to be added + * @param effect The mob effect that will be added + * @param duration How long this effect will last (in seconds) + * @param amplifier Level of severity (or just basically level) with 0 being the lowest. + * @return TurretEntity + * + * @see Item + * @see Items + * @see StatusEffect + * @see TurretEntity + */ + public TurretEntity addEffectSource(Item item, StatusEffect effect, float duration, int amplifier) { + if (this.effectSource == null) + this.effectSource = new HashMap<>(); + this.effectSource.put(item, new ArrayList() {{add(new Object[] {effect, duration, amplifier});}}); + + return this; + } + /** + * Adds the list of items to the map of items that can give this turret a mob effect. + *

NOTE: If the item is already added, the old heal amount would be updated to the specified value + * + * @param group A {@link List list} of items that will be added to the {@code healable} map + * @param effect The mob effect that will be added + * @param duration How long this effect will last (in seconds) + * @param amplifier Level of severity (or just basically level) with 0 being the lowest. + * @return TurretEntity + * + * @see Item + * @see Items + * @see StatusEffect + * @see TurretEntity + */ + public TurretEntity addEffectSource(List group, StatusEffect effect, float duration, int amplifier) { + if (this.effectSource == null) + this.effectSource = new HashMap<>(); + + List args = new ArrayList<>() {{add(new Object[] {effect, duration, amplifier});}}; + for (Item item : group) + this.effectSource.put(item, args); + + return this; + } + + /** + * Adds all the items to the list of items that can give this turret a mob effect. + * @param effectSource A key-value pair made from a {@code Map}, The map uses the {@code Item} as the key, and a {@code List} of {@code Object} array for the effect. The array should only have three items in order: {@code StatusEffect}, {@code duration}, and {@code amplifier}. + * @return TurretEntity + * + * @see #effectSource + * @see Item + * @see Items + * @see Map + * @see StatusEffect + * @see TurretEntity + */ + public TurretEntity addEffectSource(Map> effectSource) { + if (this.effectSource == null) + this.effectSource = effectSource; + else + this.effectSource.putAll(effectSource); + return this; + } + + /** + * Updates a single entry in the list of effect the item has. If the effect isn't in the list yet, it will be added with default values of 10 seconds duration and amplifier of 0. + * Many effects can be updated at the same time. + * + * @param item The item in question + * @param args An Object array that consists of {@code StatusEffect,} {@code duration} (in seconds), and {@code amplifier} level + * + * @return TurretEntity + * + * @see #effectSource + * @see Item + * @see Items + * @see StatusEffect + * @see TurretEntity + */ + public TurretEntity updateEffectSource(Item item, Object[]... args) { + List currentArgs = this.getMobEffect(item); + + if (args.length == 0) + return this; + + for (Object[] arg : args) { + if (arg.length == 0) + continue; + + Object[] toPass = new Object[3]; + + if (arg[0] instanceof StatusEffect) { + toPass[0] = arg[0]; + toPass[1] = 10; + toPass[2] = 0; + + if (arg.length == 2) { + if (arg[1] instanceof Float) + toPass[1] = arg[1]; + else if (arg[1] instanceof Integer) + toPass[2] = arg[1]; + } + else if (arg.length == 3) { + toPass[1] = arg[1]; + toPass[2] = arg[2]; + } + + if (currentArgs != null) + currentArgs.add(toPass); + else + currentArgs = Arrays.asList(new Object[][] {toPass}); + } + else { + if (arg.length == 3) + DefensiveMeasures.LOGGER.warn("Effect source at {} was not {} due to given array not matching the correct order of items in the array, having [{}, {}, {}] instead of [StatusEffect, Float, Integer]", this.getName().getString(), this.effectSourceHasEffect(item, (StatusEffect) arg[0]) ? "updated" : "registered", arg[0].getClass().getName(), arg[1].getClass().getName(), arg[2].getClass().getName()); + else if (arg.length == 2) + DefensiveMeasures.LOGGER.warn("Effect source at {} was not {} due to given array not matching the correct order of items in the array, having [{}, {}] instead of [StatusEffect, Float] OR [StatusEffect, Integer]", this.getName().getString(), this.effectSourceHasEffect(item, (StatusEffect) arg[0]) ? "updated" : "registered", arg[0].getClass().getName(), arg[1].getClass().getName()); + else if (arg.length == 1) + DefensiveMeasures.LOGGER.warn("Effect source at {} was not {} due to given array not matching the correct order of items in the array, having [{}] instead of [StatusEffect]", this.getName().getString(), this.effectSourceHasEffect(item, (StatusEffect) arg[0]) ? "updated" : "registered", arg[0].getClass().getName()); + } + + } + + this.effectSource.put(item, currentArgs); + + return this; + } + + /** + * Retrieves the item and identifies the mob effects this should apply to this turret, otherwise, returns an empty {@code List}. + * @param item The item to get the mob effects from. + * + * @return {@code List} A list of information consisting an array of the effect data. The data are set in this order: + *
    + *
  1. {@code StatusEffect}
  2. + *
  3. {@code duration}
  4. + *
  5. {@code amplifier}
  6. + *
+ */ + public List getMobEffect(Item item) { + return this.isEffectSource(item) ? this.effectSource.get(item) : List.of(); + } + + public boolean isShooting() { + return this.dataTracker.get(SHOOTING); + } + + public void setShooting(boolean shooting) { + this.dataTracker.set(SHOOTING, shooting); + } + + public boolean getShootingFXDone() { + return this.dataTracker.get(SHOOTING_FX_DONE); + } + + public void setShootingFXDone(boolean status) { + this.dataTracker.set(SHOOTING_FX_DONE, status); + } + + public boolean hasTarget() { + return this.dataTracker.get(HAS_TARGET); + } + + public void setHasTarget(boolean hasTarget) { + this.dataTracker.set(HAS_TARGET, hasTarget); + } + + public void setPos(TrackedData axis, double value) { + this.dataTracker.set(axis, (float) value); + } + + public double getPos(TrackedData axis) { + return this.dataTracker.get(axis); + } + + public void setTrackedYaw(double value) { + this.dataTracker.set(YAW, (float) value); + } + + public double getTrackedYaw() { + return (double) this.dataTracker.get(YAW); + } + + public void setTrackedPitch(double value) { + this.dataTracker.set(PITCH, (float) value); + } + + public double getTrackedPitch() { + return (double) this.dataTracker.get(PITCH); + } + + @Override + public Vec3d getVelocity() { + if (this.getWorld().getBlockState(this.getVelocityAffectingPos()).isOf(Blocks.BUBBLE_COLUMN) + || this.getWorld().getBlockState(this.getVelocityAffectingPos().add(0, 1, 0)).isOf(Blocks.BUBBLE_COLUMN) + || this.getWorld().getBlockState(this.getVelocityAffectingPos()).isOf(Blocks.WATER) + || this.getWorld().getBlockState(this.getVelocityAffectingPos().add(0, 1, 0)).isOf(Blocks.WATER) + || this.getAttachedFace() != Direction.DOWN) + return new Vec3d(0, -Math.abs(super.getVelocity().getY()), 0); + + return super.getVelocity(); + } + + /////////////////////////////// + // INTERFACE IMPLEMENTATIONS // + /////////////////////////////// + + @Override + public byte isFromItem() { + return this.getDataTracker().get(FROM_ITEM); + } + + @Override + public void setFromItem(byte fromItem) { + this.getDataTracker().set(FROM_ITEM, fromItem); + } + + @Override + public void copyDataToStack(ItemStack stack) { + Itemable.copyDataToStack(this, stack); + } + + @Override + public void copyDataFromNbt(NbtCompound nbt) { + Itemable.copyDataFromNbt(this, nbt); + } + + public ItemStack getEntityItem() { + return null; + } + + public SoundEvent getEntityRemoveSound() { + return null; + }; + + @Override + public void shootAt(LivingEntity target, float pullProgress) { + if (!this.isShooting()) + this.setShooting(target != null); + + try { + String targetName = target != null ? target.getName().getString() : "(nothing)"; + System.out.println("Shooting at " + targetName + " with a pull progress of " + pullProgress); + this.setHasTarget(target != null); + + if (target != null) { + ProjectileEntity projectile = (ProjectileEntity) this.projectile + .getConstructor(World.class, LivingEntity.class) + .newInstance(this.getWorld(), this); + + double[] velocity = new double[] { + target.getX() - this.getX(), + target.getBodyY((double) 1 / 2) - projectile.getX(), + target.getZ() - this.getZ(), + }; + double variance = Math.sqrt(velocity[0] * velocity[0] + velocity[2] + velocity[2]); + float divergence = this.getWorld().getDifficulty().getId() * 2; + + projectile.setVelocity(velocity[0], velocity[1] + variance * 0.125F, velocity[2], 2.5F, divergence); + this.getWorld().spawnEntity(projectile); + this.playSound(this.getShootSound(), 1F, 1F / (this.random.nextFloat() * 0.4F + 0.8F)); + } + + } catch (InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException + | SecurityException + | NoSuchMethodException e) + { + e.printStackTrace(System.out); + + DefensiveMeasures.LOGGER.error(""); + DefensiveMeasures.LOGGER.error("\t {} ERROR OCCURRED\t ", DefensiveMeasures.MOD_ID.toUpperCase()); + DefensiveMeasures.LOGGER.error("===== ERROR MSG START ====="); + DefensiveMeasures.LOGGER.error("LOCALIZED ERROR MESSAGE:"); + DefensiveMeasures.LOGGER.error(e.getLocalizedMessage()); + DefensiveMeasures.LOGGER.error(""); + DefensiveMeasures.LOGGER.error("ERROR MESSAGE:"); + DefensiveMeasures.LOGGER.error(e.getMessage()); + DefensiveMeasures.LOGGER.error("===== ERROR MSG END ====="); + DefensiveMeasures.LOGGER.error(""); + } + } + + /////////////////// + // LOCAL CLASSES // + /////////////////// + + static class TurretBodyControl extends BodyControl { + public TurretBodyControl(MobEntity entity) { + super(entity); + } + + @Override + public void tick() { + } + } + + static class TurretLookControl extends LookControl { + public TurretLookControl(MobEntity entity) { + super(entity); + } + + @Override + protected void clampHeadYaw() { + } + } + + /////////////////////// + // STATIC INITIALIZE // + /////////////////////// + + static { + LEVEL = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.INTEGER); + FROM_ITEM = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.BYTE); + SHOOTING = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + SHOOTING_FX_DONE = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + HAS_TARGET = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.BOOLEAN); + + ATTACHED_FACE = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FACING); + X = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FLOAT); + Y = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FLOAT); + Z = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FLOAT); + YAW = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FLOAT); + PITCH = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FLOAT); + + TARGET_POS_X = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FLOAT); + TARGET_POS_Y = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FLOAT); + TARGET_POS_Z = DataTracker.registerData(TurretEntity.class, TrackedDataHandlerRegistry.FLOAT); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/ModItemGroups.java b/remappedSrc/com/virus5600/defensive_measures/item/ModItemGroups.java new file mode 100644 index 0000000..1fffa22 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/ModItemGroups.java @@ -0,0 +1,50 @@ +package com.virus5600.defensive_measures.item; + +import com.virus5600.defensive_measures.DefensiveMeasures; + +import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; +import net.minecraft.item.ItemGroup; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class ModItemGroups { + public static final RegistryKey DMI_KEY = RegistryKey.of(Registries.ITEM_GROUP.getKey(), Identifier.of(DefensiveMeasures.MOD_ID, "defensive_measures_items")); + public static final ItemGroup DEFENSIVE_MEASURES_ITEMS = FabricItemGroup.builder() + .displayName(Text.translatable("itemGroup.dm.defensive_measures.items")) + .icon(ModItems.CANNON_HEAD::getDefaultStack) + .build(); + + public static final RegistryKey DME_KEY = RegistryKey.of(Registries.ITEM_GROUP.getKey(), Identifier.of(DefensiveMeasures.MOD_ID, "defensive_measures_equipments")); + public static final ItemGroup DEFENSIVE_MEASURES_EQUIPMENTS = FabricItemGroup.builder() + .displayName(Text.translatable("itemGroup.dm.defensive_measures.equipments")) + .icon(ModItems.TURRET_REMOVER::getDefaultStack) + .build(); + +// public static final RegistryKey DMTR_KEY = RegistryKey.of(Registries.ITEM_GROUP.getKey(), Identifier.of(DefensiveMeasures.MOD_ID, "defensive_measures_traps")); +// public static final ItemGroup DEFENSIVE_MEASURES_TRAPS = FabricItemGroup.builder() +// .displayName(Text.translatable("itemGroup.dm.defensive_measures.traps")) +// .icon(() -> ModBlocks.ARROWHEAD.asItem().getDefaultStack()) +// .build(); + + public static final RegistryKey DMTT_KEY = RegistryKey.of(Registries.ITEM_GROUP.getKey(), Identifier.of(DefensiveMeasures.MOD_ID, "defensive_measures_turrets")); + public static final ItemGroup DEFENSIVE_MEASURES_TURRETS = FabricItemGroup.builder() + .displayName(Text.translatable("itemGroup.dm.defensive_measures.turrets")) + .icon(ModItems.CANNON_TURRET::getDefaultStack) + .build(); + + //////////////////// + // REGISTER ITEMS // + //////////////////// + + public static void registerModItemGroups() { + DefensiveMeasures.LOGGER.info("REGISTERING ITEM GROUPS FOR {}...", DefensiveMeasures.MOD_NAME); + + Registry.register(Registries.ITEM_GROUP, DMI_KEY, DEFENSIVE_MEASURES_ITEMS); + Registry.register(Registries.ITEM_GROUP, DME_KEY, DEFENSIVE_MEASURES_EQUIPMENTS); +// Registry.register(Registries.ITEM_GROUP, DMTR_KEY, DEFENSIVE_MEASURES_TRAPS); + Registry.register(Registries.ITEM_GROUP, DMTT_KEY, DEFENSIVE_MEASURES_TURRETS); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/ModItems.java b/remappedSrc/com/virus5600/defensive_measures/item/ModItems.java new file mode 100644 index 0000000..5f0ac32 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/ModItems.java @@ -0,0 +1,137 @@ +package com.virus5600.defensive_measures.item; + +import com.virus5600.defensive_measures.DefensiveMeasures; + +import com.virus5600.defensive_measures.entity.ModEntities; +import com.virus5600.defensive_measures.item.equipments.TurretRemoverItem; +import com.virus5600.defensive_measures.item.turrets.cannon.*; + +import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; +import net.minecraft.item.Item; +import net.minecraft.item.Item.Settings; +import net.minecraft.item.ToolItem; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +import java.util.Arrays; + +public class ModItems { + public final static Item[] DM_ITEMS; + public final static Item[] DM_EQUIPMENTS; + public final static Item[] DM_TRAPS; + public final static Item[] DM_TURRETS; + + ///////////// + // TURRETS // + ///////////// + + // CANNON + public final static Item CANNON_TURRET = registerItem("cannon_turret", new CannonTurretItem(ModEntities.CANNON_TURRET, itemSettings())); + public final static Item CANNON_BASE = registerItem("cannon_base", new CannonBaseItem(itemSettings())); + public final static Item CANNON_HEAD = registerItem("cannon_head", new CannonHeadItem(itemSettings())); + public final static Item CANNON_STAND = registerItem("cannon_stand", new CannonStandItem(itemSettings())); + public final static Item UNFINISHED_CANNON_HEAD = registerItem("unfinished_cannon_head", new UnfinishedCannonHeadItem(itemSettings())); + + // BALLISTA +// public final static Item BALLISTA = new BallistaTurretItem(ModEntities.BALLISTA, SETTING_DMT); +// public final static Item BALLISTA_ARROW = new BallistaArrowItem(SETTING_DMI); +// public final static Item BALLISTA_BASE = new BallistaBaseItem(SETTING_DMI); +// public final static Item BALLISTA_BASE_WITH_STAND = new BallistaBaseWithStandItem(SETTING_DMI); +// public final static Item BALLISTA_BOW = new BallistaBowItem(SETTING_DMI); + + // MACHINE GUN +// public final static Item MG_TURRET = new MachineGunTurretItem(ModEntities.MG_TURRET, SETTING_DMT); +// public final static Item AMMO_CASE = new AmmoCaseItem(SETTING_DMI); +// public final static Item AMMO_ROUNDS = new AmmoRoundsItem(SETTING_DMI); +// public final static Item MACHINE_GUN_BASE = new MachineGunBaseItem(SETTING_DMI); +// public final static Item MACHINE_GUN_HEAD = new MachineGunHeadItem(SETTING_DMI); +// public final static Item MACHINE_GUN_STAND = new MachineGunStandItem(SETTING_DMI); + + //////////////// + // EQUIPMENTS // + //////////////// + + // TURRET REMOVER + public final static ToolItem TURRET_REMOVER = (ToolItem) registerItem("turret_remover", new TurretRemoverItem(ModToolMaterials.TURRET_REMOVER, itemSettings())); + + ////////////////////////////// + // REGISTRY RELATED METHODS // + ////////////////////////////// + + /** + * Creates an instance of {@link Settings} for items. + * @return an instance of {@link Settings} + */ + private static Settings itemSettings() { + return new Settings(); + }; + + /** + * Registers an item to the game. + * @param name the name of the item. + * @param item the item to register. + */ + private static Item registerItem(String name, Item item) { + return Registry.register(Registries.ITEM, Identifier.of(DefensiveMeasures.MOD_ID, name), item); + } + + public static void registerModItems() { + DefensiveMeasures.LOGGER.info("REGISTERING ITEMS TO ITEM GROUPS..."); + + Arrays.stream(DM_ITEMS).iterator().forEachRemaining((item) -> { + ItemGroupEvents.modifyEntriesEvent(ModItemGroups.DMI_KEY).register((content) -> { + content.add(item); + }); + }); + + Arrays.stream(DM_EQUIPMENTS).iterator().forEachRemaining((item) -> { + ItemGroupEvents.modifyEntriesEvent(ModItemGroups.DME_KEY).register((content) -> { + content.add(item); + }); + }); + +// Arrays.stream(DM_TRAPS).iterator().forEachRemaining((item) -> { +// ItemGroupEvents.modifyEntriesEvent(ModItemGroups.DMTR_KEY).register((content) -> { +// content.add(item); +// }); +// }); + + Arrays.stream(DM_TURRETS).iterator().forEachRemaining((item) -> { + ItemGroupEvents.modifyEntriesEvent(ModItemGroups.DMTT_KEY).register((content) -> { + content.add(item); + }); + }); + } + + static { + DM_ITEMS = new Item[]{ + CANNON_BASE, + CANNON_HEAD, + CANNON_STAND, + UNFINISHED_CANNON_HEAD, +// BALLISTA_ARROW, +// BALLISTA_BASE, +// BALLISTA_BASE_WITH_STAND, +// BALLISTA_BOW, +// AMMO_CASE, +// AMMO_ROUNDS, +// MACHINE_GUN_BASE, +// MACHINE_GUN_HEAD, +// MACHINE_GUN_STAND + }; + + DM_EQUIPMENTS = new Item[]{ + TURRET_REMOVER + }; + + DM_TRAPS = new Item[]{ + }; + + DM_TURRETS = new Item[]{ + CANNON_TURRET, +// BALLISTA, +// MG_TURRET + }; + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/ModToolMaterials.java b/remappedSrc/com/virus5600/defensive_measures/item/ModToolMaterials.java new file mode 100644 index 0000000..955e80d --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/ModToolMaterials.java @@ -0,0 +1,62 @@ +package com.virus5600.defensive_measures.item; + +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import net.minecraft.item.ToolMaterial; +import net.minecraft.recipe.Ingredient; +import net.minecraft.registry.tag.TagKey; + +public enum ModToolMaterials implements ToolMaterial { + TURRET_REMOVER(100, 1.0F, 0.0F, null, 15, new Item[] { + Items.GOLD_NUGGET, + Items.IRON_NUGGET, + Items.REDSTONE + }); + + private final int itemDurability; + private final float miningSpeed; + private final float attackDamage; + private final TagKey inverseTags; + private final int enchantability; + private final Item[] repairIngredients; + + private ModToolMaterials(int itemDurability, float miningSpeed, float attackDamage, TagKey inverseTags, int enchantability, Item[] repairIngredients) { + this.itemDurability = itemDurability; + this.miningSpeed = miningSpeed; + this.attackDamage = attackDamage; + this.inverseTags = inverseTags; + this.enchantability = enchantability; + this.repairIngredients = repairIngredients; + } + + @Override + public int getDurability() { + return this.itemDurability; + } + + @Override + public float getMiningSpeedMultiplier() { + return this.miningSpeed; + } + + @Override + public float getAttackDamage() { + return this.attackDamage; + } + + @Override + public TagKey getInverseTag() { + return this.inverseTags; + } + + @Override + public int getEnchantability() { + return this.enchantability; + } + + @Override + public Ingredient getRepairIngredient() { + return Ingredient.ofItems(this.repairIngredients); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/equipments/TurretRemoverItem.java b/remappedSrc/com/virus5600/defensive_measures/item/equipments/TurretRemoverItem.java new file mode 100644 index 0000000..d8ee7ec --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/equipments/TurretRemoverItem.java @@ -0,0 +1,31 @@ +package com.virus5600.defensive_measures.item.equipments; + +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ToolItem; +import net.minecraft.item.ToolMaterial; +import net.minecraft.util.Rarity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class TurretRemoverItem extends ToolItem { + + public TurretRemoverItem(ToolMaterial material, Settings settings) { + super( + material, + // RARITY + settings.rarity(Rarity.COMMON) + ); + } + + @Override + public boolean canMine(BlockState state, World world, BlockPos pos, PlayerEntity miner) { + // CAN'T DESTROY IN CREATIVE + return !miner.isCreative(); + } + + @Override + public int getEnchantability() { + return this.getMaterial().getEnchantability(); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/turrets/TurretItem.java b/remappedSrc/com/virus5600/defensive_measures/item/turrets/TurretItem.java new file mode 100644 index 0000000..e449b4d --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/turrets/TurretItem.java @@ -0,0 +1,152 @@ +package com.virus5600.defensive_measures.item.turrets; + +import java.util.Map; +import java.util.Objects; + +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.NbtComponent; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import com.virus5600.defensive_measures.entity.turrets.TurretEntity; + +import net.minecraft.block.BlockState; +import net.minecraft.block.FluidBlock; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.item.SpawnEggItem; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.stat.Stats; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.RaycastContext; +import net.minecraft.world.World; +import net.minecraft.world.event.GameEvent; + +public class TurretItem extends Item { + private static final Map, TurretItem> TURRETS = Maps.newIdentityHashMap(); + private final EntityType type; + + public TurretItem(EntityType type, net.minecraft.item.Item.Settings settings) { + super(settings); + this.type = type; + } + + public ActionResult useOnBlock(ItemUsageContext context) { + World world = context.getWorld(); + + // If the world is not the Server world + if (!(world instanceof ServerWorld)) { + return ActionResult.SUCCESS; + } else { + ItemStack itemStack = context.getStack(); + BlockPos blockPos = context.getBlockPos(); + Direction direction = context.getSide(); + BlockState blockState = world.getBlockState(blockPos); + BlockPos blockPos2; + + if (blockState.getCollisionShape(world, blockPos).isEmpty()) { + blockPos2 = blockPos; + } else { + blockPos2 = blockPos.offset(direction); + } + + NbtComponent nbtComponent = itemStack.get(DataComponentTypes.CUSTOM_DATA); + NbtCompound nbt = nbtComponent != null ? nbtComponent.copyNbt() : NbtComponent.DEFAULT.copyNbt(); + EntityType entityType2 = this.getEntityType(nbt); + Entity entity = entityType2.spawnFromItemStack( + (ServerWorld) world, + itemStack, + context.getPlayer(), + blockPos2, + SpawnReason.SPAWN_EGG, + true, + !Objects.equals(blockPos, blockPos2) && direction == Direction.UP + ); + + if (entity != null) { + itemStack.decrement(1); + world.emitGameEvent(context.getPlayer(), GameEvent.ENTITY_PLACE, blockPos); + } + + return ActionResult.CONSUME; + } + } + + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + ItemStack itemStack = user.getStackInHand(hand); + BlockHitResult hitResult = SpawnEggItem.raycast(world, user, RaycastContext.FluidHandling.SOURCE_ONLY); + + if (((HitResult) hitResult).getType() != HitResult.Type.BLOCK) { + return TypedActionResult.pass(itemStack); + } + if (!(world instanceof ServerWorld)) { + return TypedActionResult.success(itemStack); + } + + BlockPos blockPos = hitResult.getBlockPos(); + + if (!(world.getBlockState(blockPos).getBlock() instanceof FluidBlock)) { + return TypedActionResult.pass(itemStack); + } + if (!world.canPlayerModifyAt(user, blockPos) || !user.canPlaceOn(blockPos, hitResult.getSide(), itemStack)) { + return TypedActionResult.fail(itemStack); + } + + NbtComponent nbtComponent = itemStack.get(DataComponentTypes.CUSTOM_DATA); + NbtCompound nbt = nbtComponent != null ? nbtComponent.copyNbt() : NbtComponent.DEFAULT.copyNbt(); + EntityType entityType = this.getEntityType(nbt); + Entity entity = entityType.spawnFromItemStack((ServerWorld)world, itemStack, user, blockPos, SpawnReason.SPAWN_EGG, false, false); + if (entity == null) { + return TypedActionResult.pass(itemStack); + } + if (!user.getAbilities().creativeMode) { + itemStack.decrement(1); + } + + user.incrementStat(Stats.USED.getOrCreateStat(this)); + world.emitGameEvent((Entity)user, GameEvent.ENTITY_PLACE, entity.getPos()); + + return TypedActionResult.consume(itemStack); + } + + public boolean isOfSameEntityType(@Nullable NbtCompound nbt, EntityType type) { + return Objects.equals(this.getEntityType(nbt), type); + } + + @Nullable + public static TurretItem forEntity(@Nullable EntityType type) { + return (TurretItem)TURRETS.get(type); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Iterable getAll() { + return Iterables.unmodifiableIterable((Iterable)TURRETS.values()); + } + + public EntityType getEntityType(@Nullable NbtCompound nbt) { + if (nbt != null && nbt.contains("EntityTag", NbtElement.COMPOUND_TYPE)) { + NbtCompound nbtCompound = nbt.getCompound("EntityTag"); + if (nbtCompound.contains("id", NbtElement.STRING_TYPE)) { + return EntityType.get(nbtCompound.getString("id")).orElse(this.type); + } + } + + return this.type; + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonBaseItem.java b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonBaseItem.java new file mode 100644 index 0000000..a545f57 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonBaseItem.java @@ -0,0 +1,21 @@ +package com.virus5600.defensive_measures.item.turrets.cannon; + +import net.fabricmc.fabric.api.registry.FuelRegistry; +import net.minecraft.item.Item; +import net.minecraft.util.Rarity; + +/** + * The class for the Cannon Base {@link Item item}. + */ +public class CannonBaseItem extends Item { + public CannonBaseItem(net.minecraft.item.Item.Settings settings) { + super( + settings + .maxCount(16) // MAX STACK SIZE + .rarity(Rarity.COMMON) // RARITY + ); + + // FUEL + FuelRegistry.INSTANCE.add(this, 300); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonHeadItem.java b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonHeadItem.java new file mode 100644 index 0000000..5cb0b00 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonHeadItem.java @@ -0,0 +1,17 @@ +package com.virus5600.defensive_measures.item.turrets.cannon; + +import net.minecraft.item.Item; +import net.minecraft.util.Rarity; + +/** + * The class for the Cannon Head {@link Item item}. + */ +public class CannonHeadItem extends Item { + public CannonHeadItem(net.minecraft.item.Item.Settings settings) { + super( + settings + .maxCount(1) // MAX STACK SIZE + .rarity(Rarity.COMMON) // RARITY + ); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonStandItem.java b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonStandItem.java new file mode 100644 index 0000000..9486762 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonStandItem.java @@ -0,0 +1,21 @@ +package com.virus5600.defensive_measures.item.turrets.cannon; + +import net.fabricmc.fabric.api.registry.FuelRegistry; +import net.minecraft.item.Item; +import net.minecraft.util.Rarity; + +/** + * The class for the Cannon Stand {@link Item item}. + */ +public class CannonStandItem extends Item { + public CannonStandItem(net.minecraft.item.Item.Settings settings) { + super( + settings + .maxCount(16) // MAX STACK SIZE + .rarity(Rarity.COMMON) // RARITY + ); + + // FUEL + FuelRegistry.INSTANCE.add(this, 600); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonTurretItem.java b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonTurretItem.java new file mode 100644 index 0000000..0878d54 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/CannonTurretItem.java @@ -0,0 +1,18 @@ +package com.virus5600.defensive_measures.item.turrets.cannon; + +import com.virus5600.defensive_measures.item.turrets.TurretItem; + +import net.minecraft.entity.EntityType; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.util.Rarity; + +public class CannonTurretItem extends TurretItem { + public CannonTurretItem(EntityType type, net.minecraft.item.Item.Settings settings) { + super( + type, + settings + .maxCount(16) // MAX STACK SIZE + .rarity(Rarity.RARE) // RARITY + ); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/UnfinishedCannonHeadItem.java b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/UnfinishedCannonHeadItem.java new file mode 100644 index 0000000..c6c14b6 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/item/turrets/cannon/UnfinishedCannonHeadItem.java @@ -0,0 +1,14 @@ +package com.virus5600.defensive_measures.item.turrets.cannon; + +import net.minecraft.item.Item; +import net.minecraft.util.Rarity; + +public class UnfinishedCannonHeadItem extends Item { + public UnfinishedCannonHeadItem(net.minecraft.item.Item.Settings settings) { + super( + settings + .maxCount(1) // MAX STACK SIZE + .rarity(Rarity.COMMON) // RARITY + ); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/network/ModPackets.java b/remappedSrc/com/virus5600/defensive_measures/network/ModPackets.java new file mode 100644 index 0000000..3bd8f83 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/network/ModPackets.java @@ -0,0 +1,8 @@ +package com.virus5600.defensive_measures.network; + +import com.virus5600.defensive_measures.DefensiveMeasures; +import net.minecraft.util.Identifier; + +public class ModPackets { + public static final Identifier SPAWN_TURRET = Identifier.of(DefensiveMeasures.MOD_ID, "spawn_turret"); +} diff --git a/remappedSrc/com/virus5600/defensive_measures/particle/ModParticles.java b/remappedSrc/com/virus5600/defensive_measures/particle/ModParticles.java new file mode 100644 index 0000000..7ceffda --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/particle/ModParticles.java @@ -0,0 +1,26 @@ +package com.virus5600.defensive_measures.particle; + +import com.virus5600.defensive_measures.DefensiveMeasures; + +import net.fabricmc.fabric.api.particle.v1.FabricParticleTypes; +import net.minecraft.particle.SimpleParticleType; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.util.Identifier; + +public class ModParticles { + public static final SimpleParticleType CANNON_FUSE = register("cannon_fuse", true); + public static final SimpleParticleType CANNON_FLASH = register("cannon_flash", false); + + private static SimpleParticleType register(String identifier, boolean shouldAlwaysSpawn) { + return Registry.register( + Registries.PARTICLE_TYPE, + Identifier.of(DefensiveMeasures.MOD_ID, identifier), + FabricParticleTypes.simple(shouldAlwaysSpawn) + ); + } + + public static void registerParticles() { + DefensiveMeasures.LOGGER.info("REGISTERING PARTICLES FOR {}...", DefensiveMeasures.MOD_NAME); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/sound/ModSoundEvents.java b/remappedSrc/com/virus5600/defensive_measures/sound/ModSoundEvents.java new file mode 100644 index 0000000..507e31d --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/sound/ModSoundEvents.java @@ -0,0 +1,59 @@ +package com.virus5600.defensive_measures.sound; + +import com.virus5600.defensive_measures.DefensiveMeasures; + +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.Identifier; + +public class ModSoundEvents { + + // V1.0 + public final static SoundEvent TURRET_REMOVED_METAL = ModSoundEvents.registerSoundEvent("turret.remove.metal"); + public final static SoundEvent TURRET_REMOVED_WOOD = ModSoundEvents.registerSoundEvent("turret.remove.wood"); + public final static SoundEvent TURRET_REPAIR_METAL = ModSoundEvents.registerSoundEvent("turret.repair.iron"); + public final static SoundEvent TURRET_REPAIR_WOOD = ModSoundEvents.registerSoundEvent("turret.repair.wood"); + public final static SoundEvent TURRET_REPAIR_BOW = ModSoundEvents.registerSoundEvent("turret.repair.bow"); + + // CANNON + public final static SoundEvent TURRET_CANNON_HURT = ModSoundEvents.registerSoundEvent("turret.cannon.hurt"); + public final static SoundEvent TURRET_CANNON_DESTROYED = ModSoundEvents.registerSoundEvent("turret.cannon.destroyed"); + public final static SoundEvent TURRET_CANNON_SHOOT = ModSoundEvents.registerSoundEvent("turret.cannon.shoot"); + // BALLISTA + public final static SoundEvent TURRET_BALLISTA_HURT = ModSoundEvents.registerSoundEvent("turret.ballista.hurt"); + public final static SoundEvent TURRET_BALLISTA_DESTROYED = ModSoundEvents.registerSoundEvent("turret.ballista.destroyed"); + public final static SoundEvent TURRET_BALLISTA_SHOOT = ModSoundEvents.registerSoundEvent("turret.ballista.shoot"); + // MG TURRET + public final static SoundEvent TURRET_MG_HURT = ModSoundEvents.registerSoundEvent("turret.mg_turret.hurt"); + public final static SoundEvent TURRET_MG_DESTROYED = ModSoundEvents.registerSoundEvent("turret.mg_turret.destroyed"); + public final static SoundEvent TURRET_MG_SHOOT = ModSoundEvents.registerSoundEvent("turret.mg_turret.shoot"); + + // v1.1.0-beta + public static final SoundEvent BULLET_IMPACT_DIRT = ModSoundEvents.registerSoundEvent("generic.impact.bullet.dirt"); + public static final SoundEvent BULLET_IMPACT_FLESH = ModSoundEvents.registerSoundEvent("generic.impact.bullet.flesh"); + public static final SoundEvent BULLET_IMPACT_GLASS = ModSoundEvents.registerSoundEvent("generic.impact.bullet.glass"); + public static final SoundEvent BULLET_IMPACT_METAL = ModSoundEvents.registerSoundEvent("generic.impact.bullet.metal"); + public static final SoundEvent BULLET_IMPACT_STONE = ModSoundEvents.registerSoundEvent("generic.impact.bullet.stone"); + public static final SoundEvent BULLET_IMPACT_WOOD = ModSoundEvents.registerSoundEvent("generic.impact.bullet.wood"); + + // ANTI-AIR TURRET + public static final SoundEvent TURRET_ANTI_AIR_HURT = ModSoundEvents.registerSoundEvent("turret.anti_air_turret.hurt"); + public static final SoundEvent TURRET_ANTI_AIR_DESTROYED = ModSoundEvents.registerSoundEvent("turret.anti_air_turret.destroyed"); + public static final SoundEvent TURRET_ANTI_AIR_BEGIN_SHOOT = ModSoundEvents.registerSoundEvent("turret.anti_air_turret.shoot.begin"); + public static final SoundEvent TURRET_ANTI_AIR_SHOOT = ModSoundEvents.registerSoundEvent("turret.anti_air_turret.shoot"); + public static final SoundEvent TURRET_ANTI_AIR_END_SHOOT = ModSoundEvents.registerSoundEvent("turret.anti_air_turret.shoot.end"); + + private static SoundEvent registerSoundEvent(final String soundID) { + Identifier identifier = Identifier.of(DefensiveMeasures.MOD_ID, soundID); + return Registry.register( + Registries.SOUND_EVENT, + identifier, + SoundEvent.of(identifier) + ); + } + + public static void registerSoundEvents() { + DefensiveMeasures.LOGGER.info("REGISTERING SOUND EVENTS FOR {}...", DefensiveMeasures.MOD_NAME); + } +} diff --git a/remappedSrc/com/virus5600/defensive_measures/util/ItemUtil.java b/remappedSrc/com/virus5600/defensive_measures/util/ItemUtil.java new file mode 100644 index 0000000..1fcdda4 --- /dev/null +++ b/remappedSrc/com/virus5600/defensive_measures/util/ItemUtil.java @@ -0,0 +1,59 @@ +package com.virus5600.defensive_measures.util; + +import net.minecraft.item.Item; +import net.minecraft.item.ToolItem; + +/** + * Contains all utility methods that can be used for the {@link net.minecraft.item.Item Item class} such as identifying if it is a {@link net.minecraft.item.ToolItem ToolItem} instance or other. + * This is to supplement some shortcomings of the primary vanilla {@code Item} class and create flexibility towards applying features and modifications. + * @author Virus5600 + * @since 1.0.0 + * @see net.minecraft.item.Item Item + * @see net.minecraft.item.ToolItem ToolItem + * @see net.minecraft.item.SwordItem SwordItem + */ +public class ItemUtil { + /** + * Identifies whether the provided {@code item} is a subclass of the provided {@code class} + * @param type Target subclass + * @param item Item in question + * @return boolean + */ + public static boolean isTypeMatch(Class type, Item item) { + return type.isAssignableFrom(item.getClass()); + } + + /** + * Identifies whether the provided {@code item} is a subclass of {@link ToolItem} + * @param item Item in question + * @return boolean + * @see ToolItem + */ + public static boolean isToolItem(Item item) { + return ToolItem.class.isAssignableFrom(item.getClass()); + } + + /** + * Retrieves the Class instance provided by the {@code type} of this item. + * @param type Target subclass + * @param item Item in question + * @return An instance provided on the parameter {@code type} + */ + public static T getObjectInstance(Class type, Item item) { + if (ItemUtil.isTypeMatch(type, item)) + return type.cast(item); + return null; + } + + /** + * Retrieves the {@link ToolItem} instance of this item. + * @param item Item in question + * @return ToolItem + * @see ToolItem + */ + public static ToolItem getToolItem(Item item) { + if (ItemUtil.isToolItem(item)) + return (ToolItem) item; + return null; + } +}