diff --git a/gradle.properties b/gradle.properties index ce5b9c4..816dd0d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.20.1 yarn_mappings=1.20.1+build.10 loader_version=0.18.3 # Mod Properties -mod_version=1.0.10 +mod_version=1.0.11 maven_group=dev.tggamesyt archives_base_name=szar # Dependencies diff --git a/src/main/java/dev/tggamesyt/szar/Arrestable.java b/src/main/java/dev/tggamesyt/szar/Arrestable.java new file mode 100644 index 0000000..8ae4880 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/Arrestable.java @@ -0,0 +1,6 @@ +package dev.tggamesyt.szar; + +public interface Arrestable { + boolean isArrestable(); +} + diff --git a/src/main/java/dev/tggamesyt/szar/ArrestedEffect.java b/src/main/java/dev/tggamesyt/szar/ArrestedEffect.java index 73b9719..388b651 100644 --- a/src/main/java/dev/tggamesyt/szar/ArrestedEffect.java +++ b/src/main/java/dev/tggamesyt/szar/ArrestedEffect.java @@ -2,50 +2,65 @@ package dev.tggamesyt.szar; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.attribute.AttributeContainer; +import net.minecraft.entity.attribute.EntityAttributeModifier; import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.effect.StatusEffect; import net.minecraft.entity.effect.StatusEffectCategory; import net.minecraft.entity.mob.MobEntity; +import java.util.UUID; + public class ArrestedEffect extends StatusEffect { - private static double originalspeed; + private static final UUID SPEED_MODIFIER_ID = + UUID.fromString("b2c7c2b8-0e55-4c9f-9e8d-8c5fdd7b9c11"); + public ArrestedEffect() { - // BENEFICIAL = false because it's like a debuff - super(StatusEffectCategory.HARMFUL, 0xA0A0A0); // gray color + super(StatusEffectCategory.HARMFUL, 0xA0A0A0); } @Override public void applyUpdateEffect(LivingEntity entity, int amplifier) { - // This method is called every tick while the effect is active if (!entity.getWorld().isClient) { - // Stop movement completely - entity.setVelocity(0, entity.getVelocity().y, 0); // keep Y velocity (falling) if you want + // Freeze horizontal movement + entity.setVelocity(0, entity.getVelocity().y, 0); entity.velocityModified = true; - // Stop AI for mobs + // Stop mob AI if (entity instanceof MobEntity mob) { mob.getNavigation().stop(); mob.setTarget(null); } - - // Optional: reset movement speed to 0 - originalspeed = entity.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED).getValue(); - entity.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED) - .setBaseValue(0.0D); } } @Override public boolean canApplyUpdateEffect(int duration, int amplifier) { - // Run every tick return true; } + @Override + public void onApplied(LivingEntity entity, AttributeContainer attributes, int amplifier) { + var speed = entity.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED); + if (speed == null) return; + + // Check by UUID manually (1.20.1-safe) + if (speed.getModifier(SPEED_MODIFIER_ID) == null) { + speed.addPersistentModifier(new EntityAttributeModifier( + SPEED_MODIFIER_ID, + "Arrested speed reduction", + -1.0D, + EntityAttributeModifier.Operation.MULTIPLY_TOTAL + )); + } + } + + @Override public void onRemoved(LivingEntity entity, AttributeContainer attributes, int amplifier) { - // Reset movement speed when effect ends - entity.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED) - .setBaseValue(originalspeed); // default walking speed for mobs + var speed = entity.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED); + if (speed != null) { + speed.removeModifier(SPEED_MODIFIER_ID); + } } } diff --git a/src/main/java/dev/tggamesyt/szar/GypsyEntity.java b/src/main/java/dev/tggamesyt/szar/GypsyEntity.java index ed9c9ce..3588668 100644 --- a/src/main/java/dev/tggamesyt/szar/GypsyEntity.java +++ b/src/main/java/dev/tggamesyt/szar/GypsyEntity.java @@ -18,7 +18,7 @@ import net.minecraft.world.World; import java.util.*; -public class GypsyEntity extends PathAwareEntity { +public class GypsyEntity extends PathAwareEntity implements Arrestable{ public static boolean arrestable = false; @@ -285,4 +285,8 @@ public class GypsyEntity extends PathAwareEntity { return best; } } + @Override + public boolean isArrestable() { + return arrestable; + } } diff --git a/src/main/java/dev/tggamesyt/szar/HandcuffItem.java b/src/main/java/dev/tggamesyt/szar/HandcuffItem.java new file mode 100644 index 0000000..01bc56c --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/HandcuffItem.java @@ -0,0 +1,26 @@ +package dev.tggamesyt.szar; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; + +public class HandcuffItem extends Item { + + public HandcuffItem(Item.Settings settings) { + super(settings); + } + + /** + * Called when the player right-clicks an entity with this item. + */ + @Override + public ActionResult useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity, Hand hand) { + entity.addStatusEffect(new StatusEffectInstance(Szar.ARRESTED, 2400, 1)); + stack.decrement(1); + return ActionResult.SUCCESS; + } +} diff --git a/src/main/java/dev/tggamesyt/szar/IslamTerrorist.java b/src/main/java/dev/tggamesyt/szar/IslamTerrorist.java index 66d8c4f..fafbd08 100644 --- a/src/main/java/dev/tggamesyt/szar/IslamTerrorist.java +++ b/src/main/java/dev/tggamesyt/szar/IslamTerrorist.java @@ -20,7 +20,7 @@ import net.minecraft.world.World; import java.util.*; -public class IslamTerrorist extends PathAwareEntity { +public class IslamTerrorist extends PathAwareEntity implements Arrestable{ public static boolean arrestable = false; private int BlowUpCooldown = 0; @@ -223,4 +223,8 @@ public class IslamTerrorist extends PathAwareEntity { return best; } } + @Override + public boolean isArrestable() { + return arrestable; + } } diff --git a/src/main/java/dev/tggamesyt/szar/KeyItem.java b/src/main/java/dev/tggamesyt/szar/KeyItem.java new file mode 100644 index 0000000..42d7f0d --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/KeyItem.java @@ -0,0 +1,79 @@ +package dev.tggamesyt.szar; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.world.World; + +public class KeyItem extends Item { + + public KeyItem(Settings settings) { + super(settings.maxDamage(3)); // max durability 3 + } + + // Right-click on entity → lose 1 durability + @Override + public ActionResult useOnEntity(ItemStack stack, PlayerEntity player, LivingEntity target, Hand hand) { + if (!player.getWorld().isClient) { + if (target.hasStatusEffect(Szar.ARRESTED)) { + target.removeStatusEffect(Szar.ARRESTED); + + // Play sound to indicate effect removed + player.getWorld().playSound(null, target.getBlockPos(), + SoundEvents.BLOCK_ANVIL_USE, SoundCategory.PLAYERS, 1.0F, 1.0F); + + // Use 1 durability + stack.damage(1, player, p -> p.sendToolBreakStatus(hand)); + } + } + return ActionResult.SUCCESS; + } + + // Hold right-click to use on self → lose 3 durability + @Override + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + ItemStack stack = user.getStackInHand(hand); + + if (!world.isClient) { + if (user.hasStatusEffect(Szar.ARRESTED)) { + user.removeStatusEffect(Szar.ARRESTED); + + world.playSound(null, user.getBlockPos(), + SoundEvents.BLOCK_ANVIL_USE, SoundCategory.PLAYERS, 1.0F, 1.0F); + + // Use 3 durability + stack.damage(3, user, p -> p.sendToolBreakStatus(hand)); + } + } + + // Start hold animation + return TypedActionResult.consume(stack); + } + + // Make it holdable like a bow + @Override + public int getMaxUseTime(ItemStack stack) { + return 72000; + } + + // Continuous usage tick + @Override + public void usageTick(World world, LivingEntity user, ItemStack stack, int remainingUseTicks) { + if (!world.isClient && user instanceof PlayerEntity player) { + if (player.hasStatusEffect(Szar.ARRESTED)) { + player.removeStatusEffect(Szar.ARRESTED); + world.playSound(null, player.getBlockPos(), + SoundEvents.BLOCK_ANVIL_USE, SoundCategory.PLAYERS, 1.0F, 1.0F); + + // Remove full 3 durability if held + stack.damage(3, player, p -> p.sendToolBreakStatus(player.getActiveHand())); + } + } + } +} diff --git a/src/main/java/dev/tggamesyt/szar/NiggerEntity.java b/src/main/java/dev/tggamesyt/szar/NiggerEntity.java index dfc812f..b907137 100644 --- a/src/main/java/dev/tggamesyt/szar/NiggerEntity.java +++ b/src/main/java/dev/tggamesyt/szar/NiggerEntity.java @@ -16,7 +16,7 @@ import net.minecraft.world.World; import java.util.List; -public class NiggerEntity extends PathAwareEntity { +public class NiggerEntity extends PathAwareEntity implements Arrestable{ public static boolean arrestable = true; @@ -45,4 +45,9 @@ public class NiggerEntity extends PathAwareEntity { protected void dropLoot(DamageSource source, boolean causedByPlayer) { this.dropItem(Szar.NIGGERITE_INGOT); } + + @Override + public boolean isArrestable() { + return arrestable; + } } diff --git a/src/main/java/dev/tggamesyt/szar/PoliceArrestGoal.java b/src/main/java/dev/tggamesyt/szar/PoliceArrestGoal.java index 2b65baa..e6b6ef0 100644 --- a/src/main/java/dev/tggamesyt/szar/PoliceArrestGoal.java +++ b/src/main/java/dev/tggamesyt/szar/PoliceArrestGoal.java @@ -25,35 +25,55 @@ public class PoliceArrestGoal extends Goal { @Override public boolean canStart() { - // 1️⃣ Find nearest arrestable mob - List nearby = police.getWorld().getEntitiesByClass( + target = null; + + // 1️⃣ Find nearest arrestable mob (NOT police) + List arrestables = police.getWorld().getEntitiesByClass( MobEntity.class, police.getBoundingBox().expand(16), - mob -> isArrestable(mob) + mob -> mob != police && isArrestable(mob) ); - if (!nearby.isEmpty()) { - target = nearby.get(0); - return true; + if (!arrestables.isEmpty()) { + target = police.getWorld().getClosestEntity( + arrestables, + TargetPredicate.DEFAULT, + police, + police.getX(), + police.getY(), + police.getZ() + ); + return target != null; } - // 2️⃣ Find entities attacking villagers or police - List possibleTargets = police.getWorld().getEntitiesByClass( + // 2️⃣ Find actual criminals (players or mobs attacking protected entities) + List criminals = police.getWorld().getEntitiesByClass( LivingEntity.class, police.getBoundingBox().expand(16), - entity -> isAttackingProtected(entity) + entity -> entity != police && isAttackingProtected(entity) ); - if (!possibleTargets.isEmpty()) { - target = possibleTargets.get(0); - return true; + if (!criminals.isEmpty()) { + target = police.getWorld().getClosestEntity( + criminals, + TargetPredicate.DEFAULT, + police, + police.getX(), + police.getY(), + police.getZ() + ); + return target != null; } return false; } + @Override public void start() { + if (target == null || !target.isAlive()) { + return; + } police.getNavigation().startMovingTo(target, 1.2D); } @@ -93,28 +113,25 @@ public class PoliceArrestGoal extends Goal { } private boolean isArrestable(MobEntity mob) { - try { - return mob.getClass().getField("arrestable").getBoolean(null); - } catch (NoSuchFieldException | IllegalAccessException e) { - return false; - } + if (mob == police) return false; + return mob instanceof Arrestable arrestable && arrestable.isArrestable(); } + private boolean isAttackingProtected(LivingEntity entity) { - // Check if entity is currently attacking a villager or police - if (entity instanceof MobEntity mob) { - LivingEntity targetEntity = mob.getTarget(); - if (targetEntity instanceof PlayerEntity player) { - return false; // optional: ignore if player attacking non-protected - } - return targetEntity instanceof MobEntity protectedEntity && - (protectedEntity instanceof PathAwareEntity p && p.getClass() == police.getClass() - || protectedEntity.getType().getSpawnGroup().isPeaceful()); - } else if (entity instanceof PlayerEntity) { - // Check if player recently attacked villager or police - // You may need to track recent attacks in an event listener - return false; // placeholder, can be implemented via attack events + if (entity == police) return false; + if (entity instanceof PlayerEntity player) { + long lastCrime = player.getDataTracker().get(Szar.LAST_CRIME_TICK); + return police.getWorld().getTime() - lastCrime < 200; // 10 sec window } + + if (entity instanceof MobEntity mob) { + LivingEntity target = mob.getTarget(); + return target instanceof PoliceEntity + || target != null && target.getType().getSpawnGroup().isPeaceful(); + } + return false; } + } diff --git a/src/main/java/dev/tggamesyt/szar/PoliceEntity.java b/src/main/java/dev/tggamesyt/szar/PoliceEntity.java index d058d68..5a3ae95 100644 --- a/src/main/java/dev/tggamesyt/szar/PoliceEntity.java +++ b/src/main/java/dev/tggamesyt/szar/PoliceEntity.java @@ -49,6 +49,11 @@ public class PoliceEntity extends PathAwareEntity { @Override protected void dropLoot(DamageSource source, boolean causedByPlayer) { - this.dropItem(Items.DEBUG_STICK); + this.dropItem(Szar.KEY_ITEM); + int randomnum = this.random.nextInt(20); + if (randomnum == 6 || randomnum == 7) { // SIX OR SEVEENNN (1 in 10) + this.dropItem(Items.DEBUG_STICK); + } } + } diff --git a/src/main/java/dev/tggamesyt/szar/Szar.java b/src/main/java/dev/tggamesyt/szar/Szar.java index 368587a..593ca33 100644 --- a/src/main/java/dev/tggamesyt/szar/Szar.java +++ b/src/main/java/dev/tggamesyt/szar/Szar.java @@ -5,6 +5,7 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.biome.v1.BiomeModifications; import net.fabricmc.fabric.api.biome.v1.BiomeSelectors; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.player.AttackEntityCallback; import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback; import net.fabricmc.fabric.api.item.v1.FabricItemSettings; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; @@ -16,17 +17,20 @@ import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper; import net.fabricmc.fabric.api.object.builder.v1.world.poi.PointOfInterestHelper; import net.minecraft.advancement.Advancement; import net.minecraft.block.*; -import net.minecraft.entity.EntityDimensions; -import net.minecraft.entity.EntityType; -import net.minecraft.entity.SpawnGroup; -import net.minecraft.entity.SpawnRestriction; +import net.minecraft.entity.*; +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.passive.VillagerEntity; +import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.*; import net.minecraft.registry.*; import net.minecraft.registry.tag.BiomeTags; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.collection.DataPool; @@ -59,6 +63,8 @@ public class Szar implements ModInitializer { public static final Logger LOGGER = LogManager.getLogger(MOD_ID); public static final Block SZAR_BLOCK = new SzarBlock(); + public static final TrackedData LAST_CRIME_TICK = + DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.LONG); public static final Block NIGGERITEBLOCK = new Block(AbstractBlock.Settings.copy(Blocks.NETHERITE_BLOCK)); public static final Block FASZ_BLOCK = @@ -133,6 +139,8 @@ public class Szar implements ModInitializer { entries.add(Szar.GYPSY_SPAWNEGG); entries.add(Szar.TERRORIST_SPAWNEGG); entries.add(Szar.POLICE_SPAWNEGG); + entries.add(Szar.KEY_ITEM); + entries.add(Szar.HANDCUFF_ITEM); entries.add(Szar.CANNABIS_ITEM); entries.add(Szar.WEED_ITEM); entries.add(Szar.WEED_JOINT_ITEM); @@ -355,6 +363,20 @@ public class Szar implements ModInitializer { new Identifier("szar", "cannabis_patch") ) ); + AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> { + if (!world.isClient && entity instanceof LivingEntity victim) { + + // Villagers or police are protected + if (victim instanceof PoliceEntity || victim instanceof VillagerEntity) { + + player.getDataTracker().set( + Szar.LAST_CRIME_TICK, + world.getTime() + ); + } + } + return ActionResult.PASS; + }); } public static final Feature CANNABIS_PATCH = @@ -399,6 +421,16 @@ public class Szar implements ModInitializer { new Item.Settings() ) ); + public static final Item KEY_ITEM = Registry.register( + Registries.ITEM, + new Identifier(MOD_ID, "police_key"), + new KeyItem(new Item.Settings()) + ); + public static final Item HANDCUFF_ITEM = Registry.register( + Registries.ITEM, + new Identifier(MOD_ID, "police_handcuff"), + new HandcuffItem(new Item.Settings()) + ); public static final Item NIGGERITE_INGOT = Registry.register( Registries.ITEM, new Identifier(MOD_ID, "niggerite_ingot"), diff --git a/src/main/java/dev/tggamesyt/szar/mixin/PlayerEntityMixin.java b/src/main/java/dev/tggamesyt/szar/mixin/PlayerEntityMixin.java new file mode 100644 index 0000000..6a5f5c5 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/mixin/PlayerEntityMixin.java @@ -0,0 +1,18 @@ +package dev.tggamesyt.szar.mixin; + +import dev.tggamesyt.szar.Szar; +import net.minecraft.entity.player.PlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerEntity.class) +public abstract class PlayerEntityMixin { + + @Inject(method = "initDataTracker", at = @At("TAIL")) + private void szar$initTracker(CallbackInfo ci) { + PlayerEntity player = (PlayerEntity) (Object) this; + player.getDataTracker().startTracking(Szar.LAST_CRIME_TICK, 0L); + } +} diff --git a/src/main/resources/assets/szar/lang/en_us.json b/src/main/resources/assets/szar/lang/en_us.json index 410c1e4..59839e1 100644 --- a/src/main/resources/assets/szar/lang/en_us.json +++ b/src/main/resources/assets/szar/lang/en_us.json @@ -29,5 +29,8 @@ "item.szar.terrorist_spawn_egg": "Islam Terrorist Spawn Egg", "entity.szar.police": "Police Man", "item.szar.police_spawn_egg": "Police Man Spawn Egg", - "effect.szar.arrested": "Arrested" + "effect.szar.arrested": "Arrested", + "item.szar.police_key": "Police Key", + "item.szar.police_handcuff": "Police Handcuff", + "entity.szar.gypsy": "Cigány" } diff --git a/src/main/resources/assets/szar/models/item/police_handcuff.json b/src/main/resources/assets/szar/models/item/police_handcuff.json new file mode 100644 index 0000000..6fcd128 --- /dev/null +++ b/src/main/resources/assets/szar/models/item/police_handcuff.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "szar:item/police_handcuff" + } +} diff --git a/src/main/resources/assets/szar/models/item/police_key.json b/src/main/resources/assets/szar/models/item/police_key.json new file mode 100644 index 0000000..8047735 --- /dev/null +++ b/src/main/resources/assets/szar/models/item/police_key.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "szar:item/police_key" + } +} diff --git a/src/main/resources/assets/szar/textures/item/handcuffs.png b/src/main/resources/assets/szar/textures/item/handcuffs.png new file mode 100644 index 0000000..c8110a5 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/handcuffs.png differ diff --git a/src/main/resources/assets/szar/textures/item/police_handcuff.png b/src/main/resources/assets/szar/textures/item/police_handcuff.png new file mode 100644 index 0000000..aaf50d4 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/police_handcuff.png differ diff --git a/src/main/resources/assets/szar/textures/item/police_key.png b/src/main/resources/assets/szar/textures/item/police_key.png new file mode 100644 index 0000000..4743f1c Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/police_key.png differ diff --git a/src/main/resources/assets/szar/textures/item/police_key_og.png b/src/main/resources/assets/szar/textures/item/police_key_og.png new file mode 100644 index 0000000..b6d6717 Binary files /dev/null and b/src/main/resources/assets/szar/textures/item/police_key_og.png differ diff --git a/src/main/resources/assets/szar/textures/mob_effect/arrested.png b/src/main/resources/assets/szar/textures/mob_effect/arrested.png index 1976f31..aaf50d4 100644 Binary files a/src/main/resources/assets/szar/textures/mob_effect/arrested.png and b/src/main/resources/assets/szar/textures/mob_effect/arrested.png differ diff --git a/src/main/resources/data/szar/recipes/police_key.json b/src/main/resources/data/szar/recipes/police_key.json new file mode 100644 index 0000000..3daf55b --- /dev/null +++ b/src/main/resources/data/szar/recipes/police_key.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "BBB", + "NNB" + ], + "key": { + "B": { + "item": "minecraft:iron_ingot" + }, + "N": { + "item": "minecraft:iron_nugget" + } + }, + "result": { + "item": "szar:police_key", + "count": 1 + } +} diff --git a/src/main/resources/szar.mixins.json b/src/main/resources/szar.mixins.json index 5061bcc..7dac352 100644 --- a/src/main/resources/szar.mixins.json +++ b/src/main/resources/szar.mixins.json @@ -4,6 +4,7 @@ "package": "dev.tggamesyt.szar.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ + "PlayerEntityMixin" ], "injectors": { "defaultRequire": 1