├── changelog-1.21.txt ├── src └── main │ ├── resources │ ├── data │ │ └── guardvillagers │ │ │ ├── loot_table │ │ │ └── entities │ │ │ │ ├── guard.json │ │ │ │ ├── guard_armor.json │ │ │ │ └── armor_sets │ │ │ │ └── armor.json │ │ │ ├── tags │ │ │ └── item │ │ │ │ └── convertible_guard_items.json │ │ │ └── advancement │ │ │ └── adventure │ │ │ └── recruit_guard.json │ ├── assets │ │ └── guardvillagers │ │ │ ├── models │ │ │ └── item │ │ │ │ ├── guard_spawn_egg.json │ │ │ │ └── illusioner_spawn_egg.json │ │ │ ├── textures │ │ │ ├── container │ │ │ │ └── inventory.png │ │ │ ├── entity │ │ │ │ └── guard │ │ │ │ │ ├── guard.png │ │ │ │ │ ├── guard_steve.png │ │ │ │ │ └── guard_variants │ │ │ │ │ ├── guard_desert.png │ │ │ │ │ ├── guard_jungle.png │ │ │ │ │ ├── guard_plains.png │ │ │ │ │ ├── guard_snow.png │ │ │ │ │ ├── guard_swamp.png │ │ │ │ │ ├── guard_taiga.png │ │ │ │ │ ├── guard_savanna.png │ │ │ │ │ ├── guard_steve_snow.png │ │ │ │ │ ├── guard_steve_swamp.png │ │ │ │ │ ├── guard_steve_taiga.png │ │ │ │ │ ├── guard_steve_desert.png │ │ │ │ │ ├── guard_steve_jungle.png │ │ │ │ │ ├── guard_steve_plains.png │ │ │ │ │ └── guard_steve_savanna.png │ │ │ ├── entity_icon │ │ │ │ └── guard │ │ │ │ │ └── guard.png │ │ │ └── gui │ │ │ │ └── sprites │ │ │ │ ├── following │ │ │ │ ├── following.png │ │ │ │ ├── not_following.png │ │ │ │ ├── following_highlighted.png │ │ │ │ └── not_following_highlighted.png │ │ │ │ └── patrolling │ │ │ │ ├── patrolling1.png │ │ │ │ ├── patrolling2.png │ │ │ │ ├── notpatrolling1.png │ │ │ │ └── notpatrolling2.png │ │ │ ├── lang │ │ │ ├── de_de.json │ │ │ ├── ko_kr.json │ │ │ ├── zh_tw.json │ │ │ ├── ja_jp.json │ │ │ ├── cs_cz.json │ │ │ ├── vi_vn.json │ │ │ ├── ro_ro.json │ │ │ ├── tr_tr.json │ │ │ ├── it_it.json │ │ │ ├── pl_pl.json │ │ │ ├── es_mx.json │ │ │ ├── fil_ph.json │ │ │ ├── uk_ua.json │ │ │ ├── es_es.json │ │ │ ├── zh_cn.json │ │ │ ├── ru_ru.json │ │ │ ├── pt_br.json │ │ │ ├── fr_fr.json │ │ │ └── en_us.json │ │ │ └── sounds.json │ ├── guard_villagers.png │ ├── pack.mcmeta │ ├── guardvillagers.mixins.json │ └── META-INF │ │ ├── accesstransformer.cfg │ │ └── neoforge.mods.toml │ └── java │ └── tallestegg │ └── guardvillagers │ ├── GuardVillagerTags.java │ ├── common │ └── entities │ │ ├── ai │ │ ├── goals │ │ │ ├── GolemFloatWaterGoal.java │ │ │ ├── AttackEntityDaytimeGoal.java │ │ │ └── GetOutOfWaterGoal.java │ │ └── tasks │ │ │ ├── VillagerHelp.java │ │ │ ├── ShareGossipWithGuard.java │ │ │ ├── RepairGolem.java │ │ │ ├── HealGuardAndHero.java │ │ │ └── RepairGuardEquipment.java │ │ └── GuardContainer.java │ ├── GuardStats.java │ ├── GuardEntityType.java │ ├── GuardItems.java │ ├── mixins │ ├── VillagerModelMixin.java │ ├── DefendVillageGoalGolemMixin.java │ ├── SinglePoolElementMixin.java │ └── VillagerGoalPackagesMixin.java │ ├── networking │ ├── GuardOpenInventoryPacket.java │ ├── GuardFollowPacket.java │ └── GuardSetPatrolPosPacket.java │ ├── GuardPacketHandler.java │ ├── client │ ├── GuardSounds.java │ ├── models │ │ ├── GuardArmorModel.java │ │ ├── GuardSteveModel.java │ │ └── GuardModel.java │ ├── GuardClientEvents.java │ ├── renderer │ │ └── GuardRenderer.java │ └── gui │ │ └── GuardInventoryScreen.java │ ├── loot_tables │ ├── functions │ │ └── ArmorSlotFunction.java │ └── GuardLootTables.java │ ├── GuardDataAttachments.java │ ├── GuardVillagers.java │ └── ModCompat.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitattributes ├── .gitignore ├── settings.gradle ├── .github └── workflows │ ├── license-update.yml │ ├── add-to-changelog.yaml │ ├── bump-to-major-or-minor.yaml │ └── build.yml ├── update.json ├── LICENSE ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /changelog-1.21.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/data/guardvillagers/loot_table/entities/guard.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "minecraft:entity" 3 | } -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/models/item/guard_spawn_egg.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "item/template_spawn_egg" 3 | } -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/models/item/illusioner_spawn_egg.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "item/template_spawn_egg" 3 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/guard_villagers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/guard_villagers.png -------------------------------------------------------------------------------- /src/main/resources/pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": { 4 | "text": "${mod_id} resources" 5 | }, 6 | "pack_format": ${pack_format_number} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/container/inventory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/container/inventory.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_steve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_steve.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity_icon/guard/guard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity_icon/guard/guard.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/gui/sprites/following/following.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/gui/sprites/following/following.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/gui/sprites/following/not_following.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/gui/sprites/following/not_following.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/gui/sprites/patrolling/patrolling1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/gui/sprites/patrolling/patrolling1.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/gui/sprites/patrolling/patrolling2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/gui/sprites/patrolling/patrolling2.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/gui/sprites/patrolling/notpatrolling1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/gui/sprites/patrolling/notpatrolling1.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/gui/sprites/patrolling/notpatrolling2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/gui/sprites/patrolling/notpatrolling2.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_desert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_desert.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_jungle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_jungle.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_plains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_plains.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_snow.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_swamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_swamp.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_taiga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_taiga.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_savanna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_savanna.png -------------------------------------------------------------------------------- /src/main/resources/data/guardvillagers/tags/item/convertible_guard_items.json: -------------------------------------------------------------------------------- 1 | { 2 | "replace": false, 3 | "values": [ 4 | { 5 | "id": "#c:tools/crossbow", 6 | "required": false 7 | }, 8 | "#minecraft:swords" 9 | ] 10 | } -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_snow.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_swamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_swamp.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_taiga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_taiga.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/gui/sprites/following/following_highlighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/gui/sprites/following/following_highlighted.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_desert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_desert.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_jungle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_jungle.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_plains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_plains.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_savanna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/entity/guard/guard_variants/guard_steve_savanna.png -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/textures/gui/sprites/following/not_following_highlighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seymourimadeit/guardvillagers/HEAD/src/main/resources/assets/guardvillagers/textures/gui/sprites/following/not_following_highlighted.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable autocrlf on generated files, they always generate with LF 2 | # Add any extra files or paths here to make git stop saying they 3 | # are changed when only line endings change. 4 | src/generated/**/.cache/cache text eol=lf 5 | src/generated/**/*.json text eol=lf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # eclipse 2 | bin 3 | *.launch 4 | .settings 5 | .metadata 6 | .classpath 7 | .project 8 | 9 | # idea 10 | out 11 | *.ipr 12 | *.iws 13 | *.iml 14 | .idea 15 | 16 | # gradle 17 | build 18 | .gradle 19 | 20 | # other 21 | eclipse 22 | run 23 | runs 24 | run-data 25 | 26 | repo -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | maven { url = 'https://maven.neoforged.net/releases' } 6 | maven { url = 'https://maven.parchmentmc.org' } 7 | } 8 | } 9 | 10 | plugins { 11 | id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/de_de.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Wache", 3 | "item.guardvillagers.guard_spawn_egg": "Wachen Spawn-Ei", 4 | "item.guardvillagers.illusioner_spawn_egg": "Illusionisten Spawn-Ei", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Eisen Golem Spawn-Ei", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Schnee Golem Spawn-Ei" 7 | } -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/ko_kr.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "보초병", 3 | "item.guardvillagers.guard_spawn_egg": "보초병 생성 알", 4 | "item.guardvillagers.illusioner_spawn_egg": "환술사 생성 알", 5 | "item.guardvillagers.iron_golem_spawn_egg": "철 골렘 생성 알", 6 | "item.guardvillagers.snow_golem_spawn_egg": "눈 골렘 생성 알", 7 | "guardinventory.health": "체력: %s", 8 | "guardinventory.armor": "방어력: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/zh_tw.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "護衛", 3 | "item.guardvillagers.guard_spawn_egg": "護衛 生怪蛋", 4 | "item.guardvillagers.illusioner_spawn_egg": "幻術師 生怪蛋", 5 | "item.guardvillagers.iron_golem_spawn_egg": "鐵巨人 生怪蛋", 6 | "item.guardvillagers.snow_golem_spawn_egg": "雪人 生怪蛋", 7 | "guardinventory.health": "生命值: %s", 8 | "guardinventory.armor": "護甲值: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/ja_jp.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "衛兵", 3 | "item.guardvillagers.guard_spawn_egg": "衛兵のスポーンエッグ", 4 | "item.guardvillagers.illusioner_spawn_egg": "イリュージョナーのスポーンエッグ", 5 | "item.guardvillagers.iron_golem_spawn_egg": "アイアンゴーレムのスポーンエッグ", 6 | "item.guardvillagers.snow_golem_spawn_egg": "スノーゴーレムのスポーンエッグ", 7 | "guardinventory.health": "体力: %s", 8 | "guardinventory.armor": "鎧: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/cs_cz.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Stráž", 3 | "item.guardvillagers.guard_spawn_egg": "Vajíčko se stráží", 4 | "item.guardvillagers.illusioner_spawn_egg": "Vajíčko s iluzionistou", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Vajíčko s železným golemem", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Vajíčko se sněhulákem", 7 | "guardinventory.health": "Životy: %s", 8 | "guardinventory.armor": "Brnění: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/vi_vn.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Lính canh", 3 | "item.guardvillagers.guard_spawn_egg": "Trứng sinh ra Lính canh", 4 | "item.guardvillagers.illusioner_spawn_egg": "Trứng sinh ra Kẻ Gây Ảo Giác", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Trứng sinh ra Người sắt", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Trứng sinh ra Người tuyết", 7 | "guardinventory.health": "Máu: %s", 8 | "guardinventory.armor": "Giáp: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/guardvillagers.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "tallestegg.guardvillagers.mixins", 4 | "compatibilityLevel": "JAVA_21", 5 | "refmap": "guardvillagers.refmap.json", 6 | "mixins": [ 7 | "VillagerGoalPackagesMixin", 8 | "DefendVillageGoalGolemMixin", 9 | "SinglePoolElementMixin" 10 | ], 11 | "client":[ 12 | "VillagerModelMixin" 13 | ], 14 | "injectors": { 15 | "defaultRequire": 1 16 | }, 17 | "minVersion": "0.8" 18 | } -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/ro_ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Paznic", 3 | "item.guardvillagers.guard_spawn_egg": "Ou generator de paznic", 4 | "item.guardvillagers.illusioner_spawn_egg": "Ou generator de iluzionist", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Ou generator de golem de fier", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Ou generator de golem de zăpadă", 7 | "guardinventory.health": "Sănătate: %s", 8 | "guardinventory.armor": "Armură: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/tr_tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Koruyucu", 3 | "item.guardvillagers.guard_spawn_egg": "Koruyucu Çağırma Yumurtası", 4 | "item.guardvillagers.illusioner_spawn_egg": "İllüzyoncu Çağırma Yumurtası", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Demir Golem Çağırma Yumurtası", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Kardan Adam Çağırma Yumurtası", 7 | "guardinventory.health": "Sağlık: %s", 8 | "guardinventory.armor": "Zırh: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/GuardVillagerTags.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers; 2 | 3 | import net.minecraft.core.registries.Registries; 4 | import net.minecraft.resources.ResourceLocation; 5 | import net.minecraft.tags.TagKey; 6 | import net.minecraft.world.item.Item; 7 | 8 | public class GuardVillagerTags { 9 | public static final TagKey GUARD_CONVERT = TagKey.create(Registries.ITEM, ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "convertible_guard_items")); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/it_it.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Guardia", 3 | "item.guardvillagers.guard_spawn_egg": "Uovo generatore di guardia", 4 | "item.guardvillagers.illusioner_spawn_egg": "Uovo generatore di illusionista", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Uovo generatore di golem di ferro", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Uovo generatore di pupazzo di neve", 7 | "guardinventory.health": "Salute: %s", 8 | "guardinventory.armor": "Armatura: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/pl_pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Strażnik", 3 | "item.guardvillagers.guard_spawn_egg": "Jajko Przyzywające Strażnika", 4 | "item.guardvillagers.illusioner_spawn_egg": "Jajko Przyzywające Iluzjonera", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Jajko Przyzywające Żelaznego Golema", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Jajko Przyzywające Śnieżnego Golema", 7 | "guardinventory.health": "Życie: %s", 8 | "guardinventory.armor": "Pancerz: %s" 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/es_mx.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Guardia", 3 | "item.guardvillagers.guard_spawn_egg": "Generar guardia", 4 | "item.guardvillagers.illusioner_spawn_egg": "Generar ilusionista", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Generar gólem de hierro", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Generar gólem de nieve", 7 | "guardinventory.health": "Salud: %s", 8 | "guardinventory.armor": "Armadura: %s", 9 | "subtitles.entity.guard.ambient": "Murmuro de guardia", 10 | "subtitles.entity.guard.hurt": "Guardia herido", 11 | "subtitles.entity.guard.death": "Guardia muerto" 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/ai/goals/GolemFloatWaterGoal.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities.ai.goals; 2 | 3 | import net.minecraft.tags.FluidTags; 4 | import net.minecraft.world.entity.Mob; 5 | import net.minecraft.world.entity.ai.goal.FloatGoal; 6 | 7 | public class GolemFloatWaterGoal extends FloatGoal { 8 | private final Mob mob; 9 | 10 | public GolemFloatWaterGoal(Mob pMob) { 11 | super(pMob); 12 | this.mob = pMob; 13 | } 14 | 15 | public boolean canUse() { 16 | return this.mob.isInWater() && this.mob.getFluidHeight(FluidTags.WATER) > this.mob.getFluidJumpThreshold(); 17 | } 18 | } -------------------------------------------------------------------------------- /.github/workflows/license-update.yml: -------------------------------------------------------------------------------- 1 | name: Update copyright year(s) in license file 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 3 1 1 *' # 03:00 AM on January 1 7 | 8 | jobs: 9 | update-license-year: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | fetch-depth: 0 15 | - uses: FantasticFiasco/action-update-license-year@v3 16 | with: 17 | token: ${{ secrets.GIT_TOKEN }} 18 | - name: Merge pull request 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GIT_TOKEN }} 21 | run: | 22 | gh pr merge --merge --delete-branch 23 | 24 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/fil_ph.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Guwardiya", 3 | "item.guardvillagers.guard_spawn_egg": "Panlikhaang Itlog ng Guwardiya", 4 | "item.guardvillagers.illusioner_spawn_egg": "Panlikhaang Itlog ng Illusioner", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Panlikhaang Itlog ng Bakal na Golem", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Panlikhaang Itlog ng snow golem", 7 | "guardinventory.health": "Kalusugan: %s", 8 | "guardinventory.armor": "Baluti: %s", 9 | "subtitles.entity.guard.ambient": "Umuungot ang Guwardiya", 10 | "subtitles.entity.guard.hurt": "Nasaktan ang Guwardiya", 11 | "subtitles.entity.guard.death": "Namatay ang Guwardiya" 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/GuardStats.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers; 2 | 3 | import net.minecraft.core.registries.Registries; 4 | import net.minecraft.resources.ResourceLocation; 5 | import net.minecraft.stats.Stat; 6 | import net.neoforged.neoforge.registries.DeferredHolder; 7 | import net.neoforged.neoforge.registries.DeferredRegister; 8 | 9 | public class GuardStats { 10 | public static final DeferredRegister STATS = DeferredRegister.create(Registries.CUSTOM_STAT, GuardVillagers.MODID); 11 | public static final DeferredHolder GUARDS_MADE = STATS.register("guards_made", () -> ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "guards_made")); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/ai/goals/AttackEntityDaytimeGoal.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities.ai.goals; 2 | 3 | import net.minecraft.world.entity.LivingEntity; 4 | import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; 5 | import net.minecraft.world.entity.monster.Spider; 6 | 7 | //The spiders goal was private, so this needed to be done. 8 | public class AttackEntityDaytimeGoal extends NearestAttackableTargetGoal { 9 | public AttackEntityDaytimeGoal(Spider spider, Class classTarget) { 10 | super(spider, classTarget, true); 11 | } 12 | 13 | @Override 14 | public boolean canUse() { 15 | float f = this.mob.getLightLevelDependentMagicValue(); 16 | return f >= 0.5F ? false : super.canUse(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/add-to-changelog.yaml: -------------------------------------------------------------------------------- 1 | name: Add to changelog 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | if: contains(github.event.head_commit.message, '[CHANGE]') 7 | steps: 8 | - name: Checkout repo 9 | uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 1000 12 | fetch-tags: true 13 | - name: Append changelog file 14 | uses: slegare/write-file-action@1.0.2 15 | with: 16 | path: changelog-1.21.txt 17 | write-mode: append 18 | empty-line-eof: true 19 | contents: | 20 | - ${{ github.event.head_commit.message }} 21 | - name: Commit & Push 22 | uses: Andro999b/push@v1.3 23 | with: 24 | github_token: ${{ secrets.GIT_TOKEN}} 25 | branch: ${{ github.ref }} 26 | force: true 27 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/uk_ua.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Вартовий", 3 | "item.guardvillagers.guard_spawn_egg": "Яйце виклику вартового", 4 | "item.guardvillagers.illusioner_spawn_egg": "Яйце виклику ілюзіоніста", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Яйце виклику залізного ґолема", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Яйце виклику снігового ґолема", 7 | "guardinventory.health": "Здоров’я: %s", 8 | "guardinventory.armor": "Броня: %s", 9 | "subtitles.entity.guard.ambient": "Вартовий бурмоче", 10 | "subtitles.entity.guard.hurt": "Вартового поранено", 11 | "subtitles.entity.guard.death": "Вартовий гине", 12 | "guardvillagers.advancements.adventure.recruit_guard.description": "Дайте меч безробітному селянину, щоб він допоміг захистити село", 13 | "guardvillagers.advancements.adventure.recruit_guard.title": "Ти нам потрібен, як новобранець!" 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/GuardEntityType.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers; 2 | 3 | import net.minecraft.core.registries.Registries; 4 | import net.minecraft.world.entity.EntityType; 5 | import net.minecraft.world.entity.MobCategory; 6 | import net.neoforged.neoforge.registries.DeferredHolder; 7 | import net.neoforged.neoforge.registries.DeferredRegister; 8 | import tallestegg.guardvillagers.common.entities.Guard; 9 | 10 | //@Mod.EventBusSubscriber(modid = GuardVillagers.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) 11 | public class GuardEntityType { 12 | public static final DeferredRegister> ENTITIES = DeferredRegister.create(Registries.ENTITY_TYPE, GuardVillagers.MODID); 13 | public static final DeferredHolder, EntityType> GUARD = ENTITIES.register("guard", () -> EntityType.Builder.of(Guard::new, MobCategory.MISC).sized(0.6F, 1.90F).ridingOffset(-0.7F).setShouldReceiveVelocityUpdates(true).build(GuardVillagers.MODID + "guard")); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/data/guardvillagers/advancement/adventure/recruit_guard.json: -------------------------------------------------------------------------------- 1 | { 2 | "parent": "minecraft:adventure/trade", 3 | "criteria": { 4 | "recruit_guard": { 5 | "conditions": { 6 | "entity": [ 7 | { 8 | "condition": "minecraft:entity_properties", 9 | "entity": "this", 10 | "predicate": { 11 | "type": "guardvillagers:guard" 12 | } 13 | } 14 | ] 15 | }, 16 | "trigger": "minecraft:summoned_entity" 17 | } 18 | }, 19 | "display": { 20 | "description": { 21 | "translate": "guardvillagers.advancements.adventure.recruit_guard.description" 22 | }, 23 | "frame": "goal", 24 | "icon": { 25 | "count": 1, 26 | "id": "minecraft:iron_sword" 27 | }, 28 | "title": { 29 | "translate": "guardvillagers.advancements.adventure.recruit_guard.title" 30 | } 31 | }, 32 | "requirements": [ 33 | [ 34 | "recruit_guard" 35 | ] 36 | ], 37 | "sends_telemetry_event": true 38 | } -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/sounds.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guard.ambient": { 3 | "sounds": [ 4 | "mob/villager/idle1", 5 | "mob/villager/idle2", 6 | "mob/villager/idle3" 7 | ], 8 | "subtitle": "subtitles.entity.guard.ambient" 9 | }, 10 | "entity.guard.hurt": { 11 | "sounds": [ 12 | "mob/villager/hit1", 13 | "mob/villager/hit2", 14 | "mob/villager/hit3", 15 | "mob/villager/hit4" 16 | ], 17 | "subtitle": "subtitles.entity.guard.hurt" 18 | }, 19 | "entity.guard.death": { 20 | "sounds": [ 21 | "mob/villager/death" 22 | ], 23 | "subtitle": "subtitles.entity.guard.death" 24 | }, 25 | "entity.guard.yes": { 26 | "sounds": [ 27 | "mob/villager/yes1", 28 | "mob/villager/yes2", 29 | "mob/villager/yes3" 30 | ], 31 | "subtitle": "subtitles.entity.guard.yes" 32 | }, 33 | "entity.guard.no": { 34 | "sounds": [ 35 | "mob/villager/no1", 36 | "mob/villager/no2", 37 | "mob/villager/no3" 38 | ], 39 | "subtitle": "subtitles.entity.guard.no" 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/GuardItems.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers; 2 | 3 | import net.minecraft.core.registries.Registries; 4 | import net.minecraft.world.entity.EntityType; 5 | import net.minecraft.world.item.Item; 6 | import net.minecraft.world.item.SpawnEggItem; 7 | import net.neoforged.neoforge.common.DeferredSpawnEggItem; 8 | import net.neoforged.neoforge.registries.DeferredHolder; 9 | import net.neoforged.neoforge.registries.DeferredRegister; 10 | 11 | public class GuardItems { 12 | public static final DeferredRegister ITEMS = DeferredRegister.create(Registries.ITEM, GuardVillagers.MODID); 13 | public static final DeferredHolder GUARD_SPAWN_EGG = ITEMS.register("guard_spawn_egg", () -> new DeferredSpawnEggItem(GuardEntityType.GUARD, 5651507, 9804699, new Item.Properties())); 14 | public static final DeferredHolder ILLUSIONER_SPAWN_EGG = ITEMS.register("illusioner_spawn_egg", () -> new SpawnEggItem(EntityType.ILLUSIONER, 9804699, 4547222, new Item.Properties())); 15 | } -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/ai/goals/GetOutOfWaterGoal.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities.ai.goals; 2 | 3 | import net.minecraft.world.entity.PathfinderMob; 4 | import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; 5 | import net.minecraft.world.entity.ai.util.LandRandomPos; 6 | import net.minecraft.world.phys.Vec3; 7 | 8 | import javax.annotation.Nullable; 9 | 10 | public class GetOutOfWaterGoal extends WaterAvoidingRandomStrollGoal { 11 | public GetOutOfWaterGoal(PathfinderMob p_25987_, double p_25988_) { 12 | super(p_25987_, p_25988_); 13 | } 14 | 15 | @Override 16 | public boolean canUse() { 17 | return this.mob.isInWaterOrBubble() && this.getPosition() != null; 18 | } 19 | 20 | @Nullable 21 | @Override 22 | protected Vec3 getPosition() { 23 | if (this.mob.isInWaterOrBubble()) { 24 | Vec3 vec3 = LandRandomPos.getPos(this.mob, 15, 7); 25 | return vec3 == null ? super.getPosition() : vec3; 26 | } 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/es_es.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Guardia", 3 | "item.guardvillagers.guard_spawn_egg": "Huevo generador de Guardia", 4 | "item.guardvillagers.illusioner_spawn_egg": "Huevo generador de Ilusionista", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Huevo generador de Golem de Hierro", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Huevo generador de Golem de las Nieves", 7 | "guardinventory.health": "Salud: %s", 8 | "guardinventory.armor": "Armadura: %s", 9 | "subtitles.entity.guard.ambient": "Murmuro de guardia", 10 | "subtitles.entity.guard.hurt": "Guardia herido", 11 | "subtitles.entity.guard.death": "Guardia muerto", 12 | "guardvillagers.advancements.adventure.recruit_guard.description": "Da una espada a una aldeano desempleado para que ayude a defender una aldea.", 13 | "guardvillagers.advancements.adventure.recruit_guard.title": "Te Quieren como Nuevo Recluta", 14 | "stat.guardvillagers.guards_made": "Guardias crearon", 15 | "subtitles.entity.guard.yes": "Guardia acepta", 16 | "subtitles.entity.guard.no": "Guardia rechaza" 17 | } 18 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/accesstransformer.cfg: -------------------------------------------------------------------------------- 1 | public net.minecraft.world.entity.Mob armorItems 2 | public net.minecraft.world.entity.Mob handItems 3 | public net.minecraft.world.entity.ai.goal.MeleeAttackGoal attacker 4 | public net.minecraft.world.entity.ai.goal.GoalSelector goals 5 | public net.minecraft.world.entity.ai.goal.WrappedGoal inner 6 | public net.minecraft.world.entity.Entity setPose(Lnet/minecraft/world/entity/Pose;)V 7 | public net.minecraft.world.entity.ai.goal.MeleeAttackGoal ticksUntilNextAttack 8 | public net.minecraft.world.entity.ai.goal.MeleeAttackGoal path 9 | public net.minecraft.world.entity.npc.Villager lastGossipTime 10 | public net.minecraft.server.level.ServerPlayer containerCounter # containerCounter 11 | public net.minecraft.server.level.ServerPlayer initMenu(Lnet/minecraft/world/inventory/AbstractContainerMenu;)V # initMenu 12 | public net.minecraft.server.level.ServerPlayer nextContainerCounter()V # nextContainerCounter 13 | public-f net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool rawTemplates # rawTemplates 14 | public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool templates # templates 15 | public net.minecraft.world.entity.npc.Villager MEMORY_TYPES -------------------------------------------------------------------------------- /update.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://www.curseforge.com/minecraft/mc-mods/guard-villagers/files", 3 | "promos": { 4 | "1.14.4-latest": "1.0.5", 5 | "1.14.4-recommended": "1.0.5", 6 | "1.15.2-latest": "1.0.8", 7 | "1.15.2-recommended": "1.0.8", 8 | "1.16.1-latest": "1.1.4", 9 | "1.16.1-recommended": "1.1.4", 10 | "1.16.3-latest": "1.1.9", 11 | "1.16.3-recommended": "1.1.9", 12 | "1.16.4-latest": "1.2.6", 13 | "1.16.4-recommended": "1.2.6", 14 | "1.16.5-latest": "1.2.6", 15 | "1.16.5-recommended": "1.2.6", 16 | "1.17.1-latest": "1.3.4", 17 | "1.17.1-recommended": "1.3.4", 18 | "1.18-recommended": "1.4.0", 19 | "1.18-latest": "1.4.0", 20 | "1.18.1-recommended": "1.4.0", 21 | "1.18.1-latest": "1.4.0", 22 | "1.18.2-recommended": "1.4.3", 23 | "1.18.2-latest": "1.4.3", 24 | "1.19-recommended": "1.5.1", 25 | "1.19-latest": "1.5.1", 26 | "1.19.1-recommended": "1.5.1", 27 | "1.19.1-latest": "1.5.1", 28 | "1.19.2-recommended": "1.19.2-1.5.7", 29 | "1.19.2-latest": "1.19.2-1.5.7", 30 | "1.19.3-recommended": "1.19.3-1.5.5", 31 | "1.19.3-latest": "1.19.3-1.5.5", 32 | "1.19.4-recommended": "1.19.4-1.5.8", 33 | "1.19.4-latest": "1.19.4-1.5.8", 34 | "1.20-latest": "1.20-1.6.1", 35 | "1.20.1-latest": "1.20-1.6.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/mixins/VillagerModelMixin.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.mixins; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import com.mojang.blaze3d.vertex.VertexConsumer; 5 | import net.minecraft.client.model.HeadedModel; 6 | import net.minecraft.client.model.HierarchicalModel; 7 | import net.minecraft.client.model.VillagerHeadModel; 8 | import net.minecraft.client.model.VillagerModel; 9 | import net.minecraft.client.model.geom.ModelPart; 10 | import net.minecraft.world.entity.Entity; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import tallestegg.guardvillagers.configuration.GuardConfig; 15 | 16 | @Mixin(VillagerModel.class) 17 | public abstract class VillagerModelMixin extends HierarchicalModel implements HeadedModel, VillagerHeadModel { 18 | @Shadow 19 | @Final 20 | private ModelPart head; 21 | 22 | @Override 23 | public void renderToBuffer(PoseStack p_170625_, VertexConsumer p_170626_, int p_170627_, int p_170628_, int p_350603_) { 24 | float scale = this.young && GuardConfig.CLIENT.bigHeadBabyVillager.get() ? 1.5F : 1.0F; 25 | this.head.xScale = scale; 26 | this.head.yScale = scale; 27 | this.head.zScale = scale; 28 | super.renderToBuffer(p_170625_, p_170626_, p_170627_, p_170628_, p_350603_); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/networking/GuardOpenInventoryPacket.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.networking; 2 | 3 | import net.minecraft.network.FriendlyByteBuf; 4 | import net.minecraft.network.codec.ByteBufCodecs; 5 | import net.minecraft.network.codec.StreamCodec; 6 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.neoforged.neoforge.network.handling.IPayloadContext; 9 | import tallestegg.guardvillagers.GuardPacketHandler; 10 | import tallestegg.guardvillagers.GuardVillagers; 11 | 12 | public record GuardOpenInventoryPacket(int id, int size, int entityId) implements CustomPacketPayload { 13 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "open_inventory")); 14 | 15 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 16 | ByteBufCodecs.INT, GuardOpenInventoryPacket::id, 17 | ByteBufCodecs.INT, GuardOpenInventoryPacket::size, 18 | ByteBufCodecs.INT, GuardOpenInventoryPacket::entityId, 19 | GuardOpenInventoryPacket::new 20 | ); 21 | 22 | public static void handle(GuardOpenInventoryPacket payload, IPayloadContext context) { 23 | context.enqueueWork(() -> { 24 | GuardPacketHandler.openGuardInventory(payload); 25 | }); 26 | } 27 | 28 | @Override 29 | public Type type() { 30 | return TYPE; 31 | } 32 | } -------------------------------------------------------------------------------- /.github/workflows/bump-to-major-or-minor.yaml: -------------------------------------------------------------------------------- 1 | name: Bump To Major or Minor 2 | 3 | on: [push] 4 | jobs: 5 | bump-to-major-or-minor: 6 | runs-on: ubuntu-latest 7 | if: contains(github.event.head_commit.message, '[MAJOR]') 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v4 11 | with: 12 | fetch-depth: 1000 13 | fetch-tags: true 14 | - uses: BrycensRanch/read-properties-action@v1 15 | id: version 16 | with: 17 | file: gradle.properties 18 | property: mod_version 19 | - name: Increment version variable 20 | id: bump_version 21 | uses: christian-draeger/increment-semantic-version@1.2.3 22 | with: 23 | current-version: ${{ steps.version.outputs.value }} 24 | version-fragment: 'feature' 25 | - name: Replace single file 26 | uses: richardrigutins/replace-in-files@v2 27 | with: 28 | files: 'gradle.properties' 29 | search-text: ${{ steps.version.outputs.value }} 30 | replacement-text: ${{ steps.bump_version.outputs.next-version }} 31 | - name: Append changelog file 32 | uses: slegare/write-file-action@1.0.2 33 | with: 34 | path: changelog-1.21.txt 35 | write-mode: append 36 | empty-line-eof: true 37 | contents: | 38 | - ${{ github.event.head_commit.message }} 39 | - name: Commit & Push 40 | uses: Andro999b/push@v1.3 41 | with: 42 | github_token: ${{ secrets.GIT_TOKEN}} 43 | branch: ${{ github.ref }} 44 | force: true 45 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/GuardPacketHandler.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.client.player.LocalPlayer; 5 | import net.minecraft.world.entity.Entity; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.neoforged.api.distmarker.Dist; 8 | import net.neoforged.api.distmarker.OnlyIn; 9 | import net.neoforged.fml.loading.FMLEnvironment; 10 | import tallestegg.guardvillagers.client.gui.GuardInventoryScreen; 11 | import tallestegg.guardvillagers.common.entities.Guard; 12 | import tallestegg.guardvillagers.common.entities.GuardContainer; 13 | import tallestegg.guardvillagers.networking.GuardOpenInventoryPacket; 14 | 15 | public class GuardPacketHandler { 16 | @SuppressWarnings("resource") 17 | public static void openGuardInventory(GuardOpenInventoryPacket packet) { 18 | if (FMLEnvironment.dist.isClient()) { 19 | Player player = Minecraft.getInstance().player; 20 | if (player != null) { 21 | Entity entity = player.level().getEntity(packet.entityId()); 22 | if (entity instanceof Guard guard) { 23 | LocalPlayer clientplayerentity = Minecraft.getInstance().player; 24 | GuardContainer container = new GuardContainer(packet.id(), player.getInventory(), guard.guardInventory, guard); 25 | clientplayerentity.containerMenu = container; 26 | Minecraft.getInstance().setScreen(new GuardInventoryScreen(container, player.getInventory(), guard)); 27 | } 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/resources/data/guardvillagers/loot_table/entities/guard_armor.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "guardvillagers:slot", 3 | "pools": [ 4 | { 5 | "rolls": 1, 6 | "entries": [ 7 | { 8 | "type": "minecraft:loot_table", 9 | "value": "guardvillagers:entities/armor_sets/armor" 10 | } 11 | ] 12 | }, 13 | { 14 | "rolls": 1, 15 | "entries": [ 16 | { 17 | "type": "minecraft:item", 18 | "name": "minecraft:iron_sword" 19 | }, 20 | { 21 | "type": "minecraft:item", 22 | "name": "minecraft:crossbow" 23 | } 24 | ], 25 | "functions": [ 26 | { 27 | "function": "guardvillagers:slot", 28 | "slot": "mainhand" 29 | } 30 | ] 31 | }, 32 | { 33 | "rolls": 1, 34 | "entries": [ 35 | { 36 | "type": "minecraft:item", 37 | "name": "minecraft:bread", 38 | "functions": [ 39 | { 40 | "function": "minecraft:set_count", 41 | "count": { 42 | "min": 1, 43 | "max": 8 44 | } 45 | } 46 | ], 47 | "conditions": [ 48 | { 49 | "condition": "minecraft:random_chance", 50 | "chance": 0.1 51 | } 52 | ] 53 | }, 54 | { 55 | "type": "minecraft:item", 56 | "name": "minecraft:shield", 57 | "conditions": [ 58 | { 59 | "condition": "minecraft:random_chance", 60 | "chance": 0.5 61 | } 62 | ] 63 | } 64 | ], 65 | "functions": [ 66 | { 67 | "function": "guardvillagers:slot", 68 | "slot": "offhand" 69 | } 70 | ] 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/client/GuardSounds.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.client; 2 | 3 | import net.minecraft.core.Holder; 4 | import net.minecraft.core.registries.Registries; 5 | import net.minecraft.resources.ResourceLocation; 6 | import net.minecraft.sounds.SoundEvent; 7 | import net.neoforged.fml.common.Mod; 8 | import net.neoforged.neoforge.common.NeoForgeMod; 9 | import net.neoforged.neoforge.registries.DeferredRegister; 10 | import net.neoforged.neoforge.registries.NeoForgeRegistries; 11 | import net.neoforged.neoforge.registries.NeoForgeRegistriesSetup; 12 | import tallestegg.guardvillagers.GuardVillagers; 13 | 14 | public class GuardSounds { 15 | public static final DeferredRegister SOUNDS = DeferredRegister.create(Registries.SOUND_EVENT, GuardVillagers.MODID); 16 | public static final Holder GUARD_AMBIENT = SOUNDS.register("entity.guard.ambient", () -> SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "entity.guard.ambient"))); 17 | public static final Holder GUARD_DEATH = SOUNDS.register("entity.guard.death", () -> SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "entity.guard.death"))); 18 | public static final Holder GUARD_HURT = SOUNDS.register("entity.guard.hurt", () -> SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "entity.guard.hurt"))); 19 | public static final Holder GUARD_YES = SOUNDS.register("entity.guard.yes", () -> SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "entity.guard.yes"))); 20 | public static final Holder GUARD_NO = SOUNDS.register("entity.guard.no", () -> SoundEvent.createVariableRangeEvent(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "entity.guard.no"))); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/client/models/GuardArmorModel.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.client.models; 2 | 3 | import net.minecraft.client.model.HumanoidModel; 4 | import net.minecraft.client.model.geom.ModelPart; 5 | import net.minecraft.client.model.geom.PartPose; 6 | import net.minecraft.client.model.geom.builders.CubeDeformation; 7 | import net.minecraft.client.model.geom.builders.CubeListBuilder; 8 | import net.minecraft.client.model.geom.builders.LayerDefinition; 9 | import net.minecraft.client.model.geom.builders.MeshDefinition; 10 | import net.minecraft.client.model.geom.builders.PartDefinition; 11 | import tallestegg.guardvillagers.common.entities.Guard; 12 | 13 | public class GuardArmorModel extends HumanoidModel { 14 | public GuardArmorModel(ModelPart part) { 15 | super(part); 16 | } 17 | 18 | public static LayerDefinition createOuterArmorLayer() { 19 | MeshDefinition meshdefinition = HumanoidModel.createMesh(new CubeDeformation(1.0F), 0.0F); 20 | PartDefinition partdefinition = meshdefinition.getRoot(); 21 | partdefinition.addOrReplaceChild("head", 22 | CubeListBuilder.create().texOffs(0, 0).addBox(-4.0F, -10.0F, -4.0F, 8.0F, 8.0F, 8.0F, new CubeDeformation(1.0F)), 23 | PartPose.offset(0.0F, 1.0F, 0.0F)); 24 | partdefinition.addOrReplaceChild( 25 | "hat", 26 | CubeListBuilder.create().texOffs(32, 0).addBox(-4.0F, -10.0F, -4.0F, 8.0F, 8.0F, 8.0F, new CubeDeformation(1.5F)), 27 | PartPose.offset(0.0F, 1.0F, 0.0F) 28 | ); 29 | return LayerDefinition.create(meshdefinition, 64, 32); 30 | } 31 | 32 | public static LayerDefinition createInnerArmorLayer() { 33 | MeshDefinition meshdefinition = HumanoidModel.createMesh(new CubeDeformation(0.5F), 0.0F); 34 | return LayerDefinition.create(meshdefinition, 64, 32); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/ai/tasks/VillagerHelp.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities.ai.tasks; 2 | 3 | import net.minecraft.server.level.ServerLevel; 4 | import net.minecraft.world.entity.LivingEntity; 5 | import net.minecraft.world.entity.ai.behavior.Behavior; 6 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 7 | import net.minecraft.world.entity.ai.memory.MemoryStatus; 8 | import net.minecraft.world.entity.npc.Villager; 9 | import net.minecraft.world.entity.schedule.Activity; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class VillagerHelp extends Behavior { 15 | private final List allowedProfessions; 16 | 17 | public VillagerHelp(Map, MemoryStatus> entryCondition, List allowedProfessions) { 18 | super(entryCondition); 19 | this.allowedProfessions = allowedProfessions; 20 | } 21 | 22 | @Override 23 | protected boolean checkExtraStartConditions(ServerLevel level, Villager owner) { 24 | Activity activity = owner.getBrain().getActiveNonCoreActivity().orElse(null); 25 | if (!checkIfDayHavePassedFromLastActivity(owner)) 26 | return false; 27 | else 28 | return this.allowedProfessions.contains(owner.getVillagerData().getProfession().name()) && !owner.isSleeping() && activity != Activity.AVOID && activity != Activity.HIDE && activity != Activity.PANIC; 29 | } 30 | 31 | protected boolean checkIfDayHavePassedFromLastActivity(LivingEntity owner) { 32 | long gameTime = owner.level().getDayTime(); 33 | if (timeToCheck(owner) > 0 && gameTime - timeToCheck(owner) < 24000L) 34 | return false; 35 | else if (timeToCheck(owner) <= 0) 36 | return true; 37 | else 38 | return true; 39 | } 40 | 41 | protected long timeToCheck(LivingEntity owner) { 42 | return 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/networking/GuardFollowPacket.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.networking; 2 | 3 | import net.minecraft.network.FriendlyByteBuf; 4 | import net.minecraft.network.codec.ByteBufCodecs; 5 | import net.minecraft.network.codec.StreamCodec; 6 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.server.level.ServerLevel; 9 | import net.minecraft.server.level.ServerPlayer; 10 | import net.minecraft.sounds.SoundEvents; 11 | import net.minecraft.world.entity.Entity; 12 | import net.neoforged.neoforge.network.handling.IPayloadContext; 13 | import tallestegg.guardvillagers.GuardVillagers; 14 | import tallestegg.guardvillagers.client.GuardSounds; 15 | import tallestegg.guardvillagers.common.entities.Guard; 16 | 17 | public record GuardFollowPacket(int entityId) implements CustomPacketPayload { 18 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "following")); 19 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 20 | ByteBufCodecs.INT, GuardFollowPacket::entityId, 21 | GuardFollowPacket::new 22 | ); 23 | 24 | public static void handle(GuardFollowPacket packet, IPayloadContext ctx) { 25 | ctx.enqueueWork(() -> { 26 | ServerPlayer player = (ServerPlayer) ctx.player(); 27 | if (player != null && player.level() instanceof ServerLevel) { 28 | Entity entity = player.level().getEntity(packet.entityId()); 29 | if (entity instanceof Guard) { 30 | Guard guard = (Guard) entity; 31 | guard.setFollowing(!guard.isFollowing()); 32 | guard.setOwnerId(player.getUUID()); 33 | guard.playSound(GuardSounds.GUARD_YES.value(), 1.0F, 1.0F); 34 | } 35 | } 36 | }); 37 | } 38 | 39 | @Override 40 | public Type type() { 41 | return TYPE; 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/loot_tables/functions/ArmorSlotFunction.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.loot_tables.functions; 2 | 3 | import com.mojang.serialization.MapCodec; 4 | import com.mojang.serialization.codecs.RecordCodecBuilder; 5 | import net.minecraft.world.entity.EquipmentSlot; 6 | import net.minecraft.world.entity.LivingEntity; 7 | import net.minecraft.world.item.ItemStack; 8 | import net.minecraft.world.level.storage.loot.LootContext; 9 | import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction; 10 | import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType; 11 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 12 | import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; 13 | 14 | import java.util.List; 15 | 16 | import static tallestegg.guardvillagers.loot_tables.GuardLootTables.ARMOR_SLOT; 17 | 18 | public class ArmorSlotFunction extends LootItemConditionalFunction { 19 | final EquipmentSlot slot; 20 | public static final MapCodec CODEC = RecordCodecBuilder.mapCodec( 21 | p_298087_ -> commonFields(p_298087_) 22 | .and(EquipmentSlot.CODEC.fieldOf("slot").forGetter(p_298086_ -> p_298086_.slot)) 23 | .apply(p_298087_, ArmorSlotFunction::new) 24 | ); 25 | 26 | ArmorSlotFunction(List pConditions, EquipmentSlot slot) { 27 | super(pConditions); 28 | this.slot = slot; 29 | } 30 | 31 | @Override 32 | protected ItemStack run(ItemStack pStack, LootContext pContext) { 33 | LivingEntity livingEntity = (LivingEntity) pContext.getParamOrNull(LootContextParams.THIS_ENTITY); 34 | livingEntity.setItemSlot(slot, pStack); 35 | return pStack; 36 | } 37 | 38 | @Override 39 | public LootItemFunctionType getType() { 40 | return ARMOR_SLOT.get(); 41 | } 42 | 43 | public static LootItemConditionalFunction.Builder armorSlotFunction(EquipmentSlot slot) { 44 | return simpleBuilder(conditions -> new ArmorSlotFunction(conditions, slot)); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/GuardDataAttachments.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers; 2 | 3 | import com.mojang.serialization.Codec; 4 | import net.minecraft.core.registries.Registries; 5 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 6 | import net.neoforged.neoforge.attachment.AttachmentType; 7 | import net.neoforged.neoforge.registries.DeferredHolder; 8 | import net.neoforged.neoforge.registries.DeferredRegister; 9 | import net.neoforged.neoforge.registries.NeoForgeRegistries; 10 | 11 | import java.util.Optional; 12 | import java.util.function.Supplier; 13 | 14 | public class GuardDataAttachments { 15 | public static final DeferredRegister> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, GuardVillagers.MODID); 16 | public static final Supplier> TIMES_THROWN_POTION = ATTACHMENT_TYPES.register( 17 | "times_thrown_potion",() -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build() 18 | ); 19 | public static final Supplier> TIMES_HEALED_GOLEM = ATTACHMENT_TYPES.register( 20 | "times_repaired_golem",() -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build() 21 | ); 22 | public static final Supplier> TIMES_REPAIRED_GUARD = ATTACHMENT_TYPES.register( 23 | "times_repaired_guard",() -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build() 24 | ); 25 | public static final Supplier> LAST_REPAIRED_GOLEM = ATTACHMENT_TYPES.register( 26 | "last_repaired_golem",() -> AttachmentType.builder(() -> 0L).serialize(Codec.LONG).build() 27 | ); 28 | public static final Supplier> LAST_THROWN_POTION = ATTACHMENT_TYPES.register( 29 | "last_thrown_potion",() -> AttachmentType.builder(() -> 0L).serialize(Codec.LONG).build() 30 | ); 31 | public static final Supplier> LAST_REPAIRED_GUARD = ATTACHMENT_TYPES.register( 32 | "last_repaired_guard",() -> AttachmentType.builder(() -> 0L).serialize(Codec.LONG).build() 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /src/main/resources/ 2 | 3 | The above written directories in this repository, including sub-directories, and all files inside them, are subject to the below license. 4 | 5 | All Rights Reserved 6 | 7 | Copyright (c) 2024-2025 seymourimadeit 8 | 9 | - These assets may be copied over to a separate repository only in use for a pull request to the mod or for a port 10 | - These assets may be used in a modpack as long as proper credit is given and said assets stay within the mod itself 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 18 | THE SOFTWARE. 19 | 20 | Any other files are subject to the below license. 21 | 22 | MIT License 23 | 24 | Copyright (c) 2023-2025 seymourimadeit 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining a copy 27 | of this software and associated documentation files (the "Software"), to deal 28 | in the Software without restriction, including without limitation the rights 29 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 30 | copies of the Software, and to permit persons to whom the Software is 31 | furnished to do so, subject to the following conditions: 32 | 33 | The above copyright notice and this permission notice shall be included in all 34 | copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 37 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 38 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 39 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 40 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 41 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 42 | SOFTWARE. 43 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/networking/GuardSetPatrolPosPacket.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.networking; 2 | 3 | import net.minecraft.core.BlockPos; 4 | import net.minecraft.network.FriendlyByteBuf; 5 | import net.minecraft.network.codec.ByteBufCodecs; 6 | import net.minecraft.network.codec.StreamCodec; 7 | import net.minecraft.network.protocol.common.custom.CustomPacketPayload; 8 | import net.minecraft.resources.ResourceLocation; 9 | import net.minecraft.server.level.ServerLevel; 10 | import net.minecraft.world.entity.Entity; 11 | import net.minecraft.world.entity.player.Player; 12 | import net.neoforged.neoforge.network.handling.IPayloadContext; 13 | import tallestegg.guardvillagers.GuardVillagers; 14 | import tallestegg.guardvillagers.client.GuardSounds; 15 | import tallestegg.guardvillagers.common.entities.Guard; 16 | 17 | public record GuardSetPatrolPosPacket(int entityId, boolean pressed) implements CustomPacketPayload { 18 | public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "set_patrol")); 19 | public static final StreamCodec STREAM_CODEC = StreamCodec.composite( 20 | ByteBufCodecs.INT, GuardSetPatrolPosPacket::entityId, 21 | ByteBufCodecs.BOOL, GuardSetPatrolPosPacket::pressed, 22 | GuardSetPatrolPosPacket::new 23 | ); 24 | 25 | public static void setPatrolPosition(GuardSetPatrolPosPacket packet, IPayloadContext context) { 26 | Player player = context.player(); 27 | if (player != null && player.level() instanceof ServerLevel) { 28 | Entity entity = player.level().getEntity(packet.entityId()); 29 | if (entity instanceof Guard) { 30 | Guard guard = (Guard) entity; 31 | BlockPos pos = packet.pressed() ? null : guard.blockPosition(); 32 | if (guard.blockPosition() != null) 33 | guard.setPatrolPos(pos); 34 | guard.setPatrolling(!packet.pressed()); 35 | guard.playSound(GuardSounds.GUARD_YES.value()); 36 | } 37 | } 38 | } 39 | 40 | @Override 41 | public Type type() { 42 | return TYPE; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/neoforge.mods.toml: -------------------------------------------------------------------------------- 1 | 2 | modLoader="javafml" #mandatory 3 | loaderVersion="${loader_version_range}" #mandatory 4 | license="${mod_license}" 5 | issueTrackerURL="https://github.com/seymourimadeit/guardvillagers/issues" #optional 6 | [[mods]] #mandatory 7 | modId="${mod_id}" #mandatory 8 | version="${mod_version}" #mandatory 9 | displayName="${mod_name}" #mandatory 10 | authors="${mod_authors}" #optional 11 | displayURL="https://www.curseforge.com/minecraft/mc-mods/guard-villagers" #optional 12 | updateJSONURL = "https://api.modrinth.com/updates/H1sntfo8/forge_updates.json" 13 | logoFile="guard_villagers.png" #optional 14 | # The description text for the mod (multi line!) (#mandatory) 15 | description='''${mod_description}''' 16 | # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. 17 | [[dependencies.${mod_id}]] #optional 18 | # the modid of the dependency 19 | modId="neoforge" #mandatory 20 | # Does this dependency have to exist - if not, ordering below must be specified 21 | type='required' #mandatory 22 | # The version range of the dependency 23 | versionRange="${neo_version_range}" #mandatory 24 | # An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory 25 | # BEFORE - This mod is loaded BEFORE the dependency 26 | # AFTER - This mod is loaded AFTER the dependency 27 | ordering="NONE" 28 | # Side this dependency is applied on - BOTH, CLIENT, or SERVER 29 | side="BOTH" 30 | # Here's another dependency 31 | [[dependencies.${mod_id}]] 32 | modId="minecraft" 33 | type='required' 34 | # This version range declares a minimum of the current minecraft version up to but not including the next major version 35 | versionRange="${minecraft_version_range}" 36 | ordering="NONE" 37 | side="BOTH" 38 | [[mixins]] 39 | config="guardvillagers.mixins.json" 40 | 41 | # Features are specific properties of the game environment, that you may want to declare you require. This example declares 42 | # that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't 43 | # stop your mod loading on the server for example. 44 | #[features.${mod_id}] 45 | #openGLVersion="[3.2,)" 46 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties. 2 | # This is required to provide enough memory for the Minecraft decompilation process. 3 | org.gradle.jvmargs=-Xmx3G 4 | # Sets default memory used for gradle commands. Can be overridden by user or command line properties. 5 | #org.gradle.jvmargs= 6 | org.gradle.daemon=false 7 | org.gradle.debug=false 8 | 9 | ## Environment Properties 10 | # You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge 11 | # The Minecraft version must agree with the Neo version to get a valid artifact 12 | minecraft_version=1.21.1 13 | # The Minecraft version range can use any release version of Minecraft as bounds. 14 | # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly 15 | # as they do not follow standard versioning conventions. 16 | minecraft_version_range=[1.21.1, 1.22) 17 | # The Neo version must agree with the Minecraft version to get a valid artifact 18 | neo_version=21.1.215 19 | # The Neo version range can use any version of Neo as bounds 20 | neo_version_range=[20.4,) 21 | # The loader version range can only use the major version of FML as bounds 22 | loader_version_range=[2,) 23 | 24 | neogradle.subsystems.parchment.minecraftVersion=1.21 25 | neogradle.subsystems.parchment.mappingsVersion=2024.06.23 26 | 27 | 28 | ## Mod Properties 29 | 30 | # The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} 31 | # Must match the String constant located in the main mod class annotated with @Mod. 32 | mod_id=guardvillagers 33 | # The human-readable display name for the mod. 34 | mod_name=Guard Villagers 35 | # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. 36 | mod_license=MIT for code, assets are ARR 37 | # The mod version. See https://semver.org/ 38 | mod_version=2.4.6 39 | # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. 40 | # This should match the base package used for the mod sources. 41 | # See https://maven.apache.org/guides/mini/guide-naming-conventions.html 42 | mod_group_id=tallestegg.guardvillagers 43 | # The authors of the mod. This is a simple text string that is used for display purposes in the mod list. 44 | mod_authors=TallestEgg, HadeZ/SadNya69 for the textures. 45 | mod_description=Need some help with pest control? The Guards are ready to help! 46 | pack_format_number=18 -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [ workflow_dispatch ] 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout repository 9 | uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 1000 12 | fetch-tags: true 13 | - name: Setup JDK 21 14 | uses: actions/setup-java@v2 15 | with: 16 | java-version: '21' 17 | distribution: 'temurin' 18 | - name: Build with Gradle 19 | uses: gradle/actions/setup-gradle@v3 20 | with: 21 | arguments: build 22 | gradle-version: 8.9 23 | - uses: BrycensRanch/read-properties-action@v1 24 | id: version 25 | with: 26 | file: gradle.properties 27 | property: mod_version 28 | - name: mc-publish 29 | uses: Kir-Antipov/mc-publish@v3.3.0 30 | with: 31 | modrinth-id: H1sntfo8 32 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 33 | curseforge-id: 360203 34 | curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} 35 | github-token: ${{ secrets.GIT_TOKEN }} 36 | files-primary: build/libs/!(*source*|*dev*|*javadoc*|*slim*).jar 37 | version-type: release 38 | loaders: neoforge 39 | game-versions: 1.21.1 40 | version: ${{ steps.version.outputs.value }} 41 | java: 21 42 | name: "" 43 | changelog-file: changelog-1.21.* 44 | - name: Nuke Changelog dot txt 45 | uses: JesseTG/rm@v1.0.3 46 | with: 47 | path: changelog-1.21.txt 48 | - uses: finnp/create-file-action@master 49 | env: 50 | FILE_NAME: "changelog-1.21.txt" 51 | FILE_DATA: "" 52 | - name: Increment version variable 53 | id: bump_version 54 | uses: christian-draeger/increment-semantic-version@1.2.3 55 | with: 56 | current-version: ${{ steps.version.outputs.value }} 57 | version-fragment: 'hotfix' 58 | - name: Replace single file 59 | uses: richardrigutins/replace-in-files@v2 60 | with: 61 | files: 'gradle.properties' 62 | search-text: ${{ steps.version.outputs.value }} 63 | replacement-text: ${{ steps.bump_version.outputs.next-version }} 64 | - name: Commit & Push 65 | uses: Andro999b/push@v1.3 66 | with: 67 | github_token: ${{ secrets.GIT_TOKEN}} 68 | branch: ${{ github.ref }} 69 | force: true 70 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/mixins/DefendVillageGoalGolemMixin.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.mixins; 2 | 3 | import net.minecraft.world.entity.LivingEntity; 4 | import net.minecraft.world.entity.ai.goal.target.DefendVillageTargetGoal; 5 | import net.minecraft.world.entity.ai.targeting.TargetingConditions; 6 | import net.minecraft.world.entity.animal.IronGolem; 7 | import net.minecraft.world.entity.npc.Villager; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.phys.AABB; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 17 | import tallestegg.guardvillagers.configuration.GuardConfig; 18 | 19 | import java.util.List; 20 | 21 | @Mixin(DefendVillageTargetGoal.class) 22 | public abstract class DefendVillageGoalGolemMixin { 23 | @Final 24 | @Shadow 25 | private IronGolem golem; 26 | @Final 27 | @Shadow 28 | private TargetingConditions attackTargeting; 29 | 30 | @Shadow 31 | private LivingEntity potentialTarget; 32 | 33 | @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getNearbyPlayers(Lnet/minecraft/world/entity/ai/targeting/TargetingConditions;Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/phys/AABB;)Ljava/util/List;"), method = "canUse", cancellable = true) 34 | public void modifyRep(CallbackInfoReturnable cir) { 35 | cir.cancel(); 36 | AABB aabb = this.golem.getBoundingBox().inflate(10.0, 8.0, 10.0); 37 | List list = this.golem.level().getNearbyEntities(Villager.class, this.attackTargeting, this.golem, aabb); 38 | List list1 = this.golem.level().getNearbyPlayers(this.attackTargeting, this.golem, aabb); 39 | 40 | for (LivingEntity livingentity : list) { 41 | Villager villager = (Villager) livingentity; 42 | 43 | for (Player player : list1) { 44 | int i = villager.getPlayerReputation(player); 45 | if (i <= GuardConfig.COMMON.reputationRequirementToBeAttacked.get()) { 46 | this.potentialTarget = player; 47 | } 48 | } 49 | } 50 | if (this.potentialTarget == null) { 51 | cir.setReturnValue(false); 52 | } else { 53 | cir.setReturnValue(!(this.potentialTarget instanceof Player) || !this.potentialTarget.isSpectator() && !((Player) this.potentialTarget).isCreative()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/loot_tables/GuardLootTables.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.loot_tables; 2 | 3 | import com.google.common.collect.BiMap; 4 | import com.google.common.collect.HashBiMap; 5 | import net.minecraft.core.registries.Registries; 6 | import net.minecraft.resources.ResourceKey; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.world.entity.EntityType; 9 | import net.minecraft.world.level.storage.loot.LootTable; 10 | import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType; 11 | import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet; 12 | import net.minecraft.world.level.storage.loot.parameters.LootContextParams; 13 | import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType; 14 | import net.neoforged.neoforge.registries.DeferredHolder; 15 | import net.neoforged.neoforge.registries.DeferredRegister; 16 | import tallestegg.guardvillagers.GuardVillagers; 17 | import tallestegg.guardvillagers.loot_tables.functions.ArmorSlotFunction; 18 | 19 | import java.util.function.Consumer; 20 | 21 | public class GuardLootTables { 22 | public static final BiMap REGISTRY = HashBiMap.create(); 23 | public static final LootContextParamSet SLOT = register("slot", (table) -> { 24 | table.required(LootContextParams.THIS_ENTITY); 25 | }); 26 | 27 | public static final DeferredRegister> LOOT_ITEM_FUNCTION_TYPES = DeferredRegister.create(Registries.LOOT_FUNCTION_TYPE, GuardVillagers.MODID); 28 | public static final DeferredRegister LOOT_ITEM_CONDITION_TYPES = DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, GuardVillagers.MODID); 29 | public static final DeferredHolder, LootItemFunctionType> ARMOR_SLOT = LOOT_ITEM_FUNCTION_TYPES.register("slot", () -> new LootItemFunctionType<>(ArmorSlotFunction.CODEC)); 30 | 31 | public static LootContextParamSet register(String p_81429_, Consumer p_81430_) { 32 | LootContextParamSet.Builder lootcontextparamset$builder = new LootContextParamSet.Builder(); 33 | p_81430_.accept(lootcontextparamset$builder); 34 | LootContextParamSet lootcontextparamset = lootcontextparamset$builder.build(); 35 | ResourceLocation resourcelocation = ResourceLocation.parse(GuardVillagers.MODID + p_81429_); 36 | LootContextParamSet lootcontextparamset1 = REGISTRY.put(resourcelocation, lootcontextparamset); 37 | if (lootcontextparamset1 != null) { 38 | throw new IllegalStateException("Loot table parameter set " + resourcelocation + " is already registered"); 39 | } else { 40 | return lootcontextparamset; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/mixins/SinglePoolElementMixin.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.mixins; 2 | 3 | import com.mojang.datafixers.util.Either; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.network.chat.Component; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.minecraft.util.RandomSource; 9 | import net.minecraft.world.entity.MobSpawnType; 10 | import net.minecraft.world.level.StructureManager; 11 | import net.minecraft.world.level.WorldGenLevel; 12 | import net.minecraft.world.level.block.Rotation; 13 | import net.minecraft.world.level.chunk.ChunkGenerator; 14 | import net.minecraft.world.level.levelgen.structure.BoundingBox; 15 | import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement; 16 | import net.minecraft.world.level.levelgen.structure.templatesystem.LiquidSettings; 17 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; 18 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; 19 | import org.spongepowered.asm.mixin.Final; 20 | import org.spongepowered.asm.mixin.Mixin; 21 | import org.spongepowered.asm.mixin.Shadow; 22 | import org.spongepowered.asm.mixin.injection.At; 23 | import org.spongepowered.asm.mixin.injection.Inject; 24 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 25 | import tallestegg.guardvillagers.GuardEntityType; 26 | import tallestegg.guardvillagers.common.entities.Guard; 27 | import tallestegg.guardvillagers.configuration.GuardConfig; 28 | 29 | @Mixin(SinglePoolElement.class) 30 | public abstract class SinglePoolElementMixin { 31 | @Shadow 32 | @Final 33 | protected Either template; 34 | 35 | @Inject(at = @At(value = "RETURN"), method = "place", cancellable = true) 36 | public void place(StructureTemplateManager structureTemplateManager, WorldGenLevel level, StructureManager structureManager, ChunkGenerator generator, BlockPos offset, BlockPos pos, Rotation rotation, BoundingBox box, RandomSource random, LiquidSettings liquidSettings, boolean keepJigsaws, CallbackInfoReturnable cir) { 37 | this.template.left().ifPresent(resourceLocation -> { 38 | if (GuardConfig.COMMON.structuresThatSpawnGuards.get().contains(resourceLocation.toString())) { 39 | for (int guardCount = 0; guardCount < GuardConfig.COMMON.guardSpawnInVillage.getAsInt(); guardCount++) { 40 | Guard guard = GuardEntityType.GUARD.get().create(level.getLevel()); 41 | guard.moveTo(offset, 0, 0); 42 | guard.finalizeSpawn(level, level.getCurrentDifficultyAt(offset), MobSpawnType.STRUCTURE, null); 43 | level.addFreshEntityWithPassengers(guard); 44 | } 45 | } 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/client/GuardClientEvents.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.client; 2 | 3 | import net.minecraft.client.model.HumanoidArmorModel; 4 | import net.minecraft.client.model.geom.ModelLayerLocation; 5 | import net.minecraft.client.model.geom.builders.CubeDeformation; 6 | import net.minecraft.client.model.geom.builders.LayerDefinition; 7 | import net.minecraft.resources.ResourceLocation; 8 | import net.neoforged.api.distmarker.Dist; 9 | import net.neoforged.bus.api.SubscribeEvent; 10 | import net.neoforged.fml.common.EventBusSubscriber; 11 | import net.neoforged.fml.common.Mod; 12 | import net.neoforged.neoforge.client.event.EntityRenderersEvent; 13 | import tallestegg.guardvillagers.GuardEntityType; 14 | import tallestegg.guardvillagers.GuardVillagers; 15 | import tallestegg.guardvillagers.client.models.GuardArmorModel; 16 | import tallestegg.guardvillagers.client.models.GuardModel; 17 | import tallestegg.guardvillagers.client.models.GuardSteveModel; 18 | import tallestegg.guardvillagers.client.renderer.GuardRenderer; 19 | 20 | @EventBusSubscriber(value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) 21 | public class GuardClientEvents { 22 | public static ModelLayerLocation GUARD = new ModelLayerLocation( 23 | ResourceLocation.withDefaultNamespace("modded/" + GuardVillagers.MODID + "/" + "guard"), "main"); 24 | public static ModelLayerLocation GUARD_STEVE = new ModelLayerLocation( 25 | ResourceLocation.withDefaultNamespace("modded/" + GuardVillagers.MODID + "/" + "guard_steve"), "main"); 26 | public static ModelLayerLocation GUARD_ARMOR_OUTER = new ModelLayerLocation( 27 | ResourceLocation.withDefaultNamespace("modded/" + GuardVillagers.MODID + "/" + "guard"), "armor_outer"); 28 | public static ModelLayerLocation GUARD_ARMOR_INNER = new ModelLayerLocation( 29 | ResourceLocation.withDefaultNamespace("modded/" + GuardVillagers.MODID + "/" + "guard"), "armor_inner"); 30 | public static ModelLayerLocation GUARD_PLAYER_ARMOR_OUTER = new ModelLayerLocation( 31 | ResourceLocation.withDefaultNamespace("modded/" + GuardVillagers.MODID + "/" + "guard_steve"), "armor_outer"); 32 | public static ModelLayerLocation GUARD_PLAYER_ARMOR_INNER = new ModelLayerLocation( 33 | ResourceLocation.withDefaultNamespace("modded/" + GuardVillagers.MODID + "/"+ "guard_steve"), "armor_inner"); 34 | 35 | @SubscribeEvent 36 | public static void layerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) { 37 | event.registerLayerDefinition(GuardClientEvents.GUARD, GuardModel::createBodyLayer); 38 | event.registerLayerDefinition(GuardClientEvents.GUARD_STEVE, GuardSteveModel::createMesh); 39 | event.registerLayerDefinition(GuardClientEvents.GUARD_ARMOR_OUTER, GuardArmorModel::createOuterArmorLayer); 40 | event.registerLayerDefinition(GuardClientEvents.GUARD_ARMOR_INNER, GuardArmorModel::createInnerArmorLayer); 41 | event.registerLayerDefinition(GuardClientEvents.GUARD_PLAYER_ARMOR_INNER, () -> LayerDefinition.create(HumanoidArmorModel.createBodyLayer(new CubeDeformation(0.5F)), 64, 32)); 42 | event.registerLayerDefinition(GuardClientEvents.GUARD_PLAYER_ARMOR_OUTER, () -> LayerDefinition.create(HumanoidArmorModel.createBodyLayer(new CubeDeformation(1.0F)), 64, 32)); 43 | } 44 | 45 | @SubscribeEvent 46 | public static void entityRenderers(EntityRenderersEvent.RegisterRenderers event) { 47 | event.registerEntityRenderer(GuardEntityType.GUARD.get(), GuardRenderer::new); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/client/models/GuardSteveModel.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.client.models; 2 | 3 | import net.minecraft.client.model.AnimationUtils; 4 | import net.minecraft.client.model.PlayerModel; 5 | import net.minecraft.client.model.geom.ModelPart; 6 | import net.minecraft.client.model.geom.builders.CubeDeformation; 7 | import net.minecraft.client.model.geom.builders.LayerDefinition; 8 | import net.minecraft.client.model.geom.builders.MeshDefinition; 9 | import net.minecraft.util.Mth; 10 | import net.minecraft.world.InteractionHand; 11 | import net.minecraft.world.entity.HumanoidArm; 12 | import net.minecraft.world.item.ItemStack; 13 | import net.minecraft.world.item.ProjectileWeaponItem; 14 | import net.minecraft.world.item.UseAnim; 15 | import tallestegg.guardvillagers.common.entities.Guard; 16 | 17 | public class GuardSteveModel extends PlayerModel { 18 | public GuardSteveModel(ModelPart part) { 19 | super(part, false); 20 | } 21 | 22 | @Override 23 | public void setupAnim(Guard entityIn, float limbSwing, float limbSwingAmount, float ageInTicks, 24 | float netbipedHeadYaw, float bipedHeadPitch) { 25 | super.setupAnim(entityIn, limbSwing, limbSwingAmount, ageInTicks, netbipedHeadYaw, bipedHeadPitch); 26 | if (entityIn.getKickTicks() > 0) { 27 | float f1 = 1.0F - (float) Mth.abs(10 - 2 * entityIn.getKickTicks()) / 10.0F; 28 | this.rightLeg.xRot = Mth.lerp(f1, this.rightLeg.xRot, -1.40F); 29 | } 30 | ItemStack itemstack = entityIn.getItemInHand(InteractionHand.MAIN_HAND); 31 | boolean isHoldingShootable = itemstack.getItem() instanceof ProjectileWeaponItem; 32 | double speed = 0.005D; 33 | if (this.attackTime == 0.0F && entityIn.isAggressive() && !isHoldingShootable && entityIn.getDeltaMovement().horizontalDistanceSqr() > speed && !entityIn.getMainHandItem().isEmpty() && !entityIn.isBlocking()) { 34 | AnimationUtils.swingWeaponDown(this.rightArm, this.leftArm, entityIn, this.attackTime, ageInTicks); 35 | } 36 | if (entityIn.getMainArm() == HumanoidArm.RIGHT) { 37 | this.eatingAnimationRightHand(InteractionHand.MAIN_HAND, entityIn, ageInTicks); 38 | this.eatingAnimationLeftHand(InteractionHand.OFF_HAND, entityIn, ageInTicks); 39 | } else { 40 | this.eatingAnimationRightHand(InteractionHand.OFF_HAND, entityIn, ageInTicks); 41 | this.eatingAnimationLeftHand(InteractionHand.MAIN_HAND, entityIn, ageInTicks); 42 | } 43 | } 44 | 45 | public static LayerDefinition createMesh() { 46 | MeshDefinition meshdefinition = PlayerModel.createMesh(CubeDeformation.NONE, false); 47 | return LayerDefinition.create(meshdefinition, 64, 64); 48 | } 49 | 50 | public void eatingAnimationRightHand(InteractionHand hand, Guard entity, float ageInTicks) { 51 | ItemStack itemstack = entity.getItemInHand(hand); 52 | boolean drinkingoreating = itemstack.getUseAnimation() == UseAnim.EAT 53 | || itemstack.getUseAnimation() == UseAnim.DRINK; 54 | if (entity.isEating() && drinkingoreating) { 55 | this.rightArm.yRot = -0.5F; 56 | this.rightArm.xRot = -1.3F; 57 | this.rightArm.zRot = Mth.cos(ageInTicks) * 0.1F; 58 | this.head.xRot = Mth.cos(ageInTicks) * 0.2F; 59 | this.head.yRot = 0.0F; 60 | this.hat.copyFrom(head); 61 | } 62 | } 63 | 64 | public void eatingAnimationLeftHand(InteractionHand hand, Guard entity, float ageInTicks) { 65 | ItemStack itemstack = entity.getItemInHand(hand); 66 | boolean drinkingoreating = itemstack.getUseAnimation() == UseAnim.EAT 67 | || itemstack.getUseAnimation() == UseAnim.DRINK; 68 | if (entity.isEating() && drinkingoreating) { 69 | this.leftArm.yRot = 0.5F; 70 | this.leftArm.xRot = -1.3F; 71 | this.leftArm.zRot = Mth.cos(ageInTicks) * 0.1F; 72 | this.head.xRot = Mth.cos(ageInTicks) * 0.2F; 73 | this.head.yRot = 0.0F; 74 | this.hat.copyFrom(head); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/mixins/VillagerGoalPackagesMixin.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.mixins; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.google.common.collect.ImmutableSet; 6 | import com.mojang.datafixers.util.Pair; 7 | import net.minecraft.world.entity.EntityType; 8 | import net.minecraft.world.entity.ai.behavior.*; 9 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 10 | import net.minecraft.world.entity.npc.Villager; 11 | import net.minecraft.world.entity.npc.VillagerProfession; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 16 | import tallestegg.guardvillagers.GuardEntityType; 17 | import tallestegg.guardvillagers.common.entities.ai.tasks.HealGuardAndHero; 18 | import tallestegg.guardvillagers.common.entities.ai.tasks.RepairGolem; 19 | import tallestegg.guardvillagers.common.entities.ai.tasks.RepairGuardEquipment; 20 | import tallestegg.guardvillagers.common.entities.ai.tasks.ShareGossipWithGuard; 21 | import tallestegg.guardvillagers.configuration.GuardConfig; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | @Mixin(VillagerGoalPackages.class) 27 | public abstract class VillagerGoalPackagesMixin { 28 | @Inject(method = "getCorePackage", cancellable = true, at = @At("RETURN")) 29 | private static void getCorePackage(VillagerProfession pProfession, float pSpeedModifier, CallbackInfoReturnable>>> cir) { 30 | List>> villagerList = new ArrayList<>(cir.getReturnValue()); 31 | if (GuardConfig.COMMON.BlacksmithHealing.get()) 32 | villagerList.add(Pair.of(10, new RepairGolem())); 33 | if (GuardConfig.COMMON.ClericHealing.get()) 34 | villagerList.add(Pair.of(10, new HealGuardAndHero())); 35 | if (GuardConfig.COMMON.armorersRepairGuardArmor.get()) 36 | villagerList.add(Pair.of(10, new RepairGuardEquipment())); 37 | cir.setReturnValue(ImmutableList.copyOf(villagerList)); 38 | } 39 | 40 | @Inject(method = "getMeetPackage", cancellable = true, at = @At("RETURN")) 41 | private static void getMeetPackage(VillagerProfession pProfession, float pSpeedModifier, CallbackInfoReturnable>>> cir) { 42 | List>> villagerList = new ArrayList<>(cir.getReturnValue()); 43 | villagerList.add(Pair.of(2, new GateBehavior<>(ImmutableMap.of(), ImmutableSet.of(MemoryModuleType.INTERACTION_TARGET), GateBehavior.OrderPolicy.ORDERED, GateBehavior.RunningPolicy.RUN_ONE, ImmutableList.of(Pair.of(new ShareGossipWithGuard(), 1), Pair.of(new TradeWithVillager(), 1))))); 44 | cir.setReturnValue(ImmutableList.copyOf(villagerList)); 45 | } 46 | 47 | @Inject(method = "getIdlePackage", cancellable = true, at = @At("RETURN")) 48 | private static void getIdlePackage(VillagerProfession pProfession, float pSpeedModifier, CallbackInfoReturnable>>> cir) { 49 | List>> villagerList = new ArrayList<>(cir.getReturnValue()); 50 | villagerList.add(Pair.of(2, new RunOne<>(ImmutableList.of(Pair.of(InteractWith.of(GuardEntityType.GUARD.get(), 8, MemoryModuleType.INTERACTION_TARGET, pSpeedModifier, 2), 3), Pair.of(InteractWith.of(EntityType.VILLAGER, 8, MemoryModuleType.INTERACTION_TARGET, pSpeedModifier, 2), 3), Pair.of(new DoNothing(30, 60), 1))))); 51 | villagerList.add(Pair.of(2, new GateBehavior<>(ImmutableMap.of(), ImmutableSet.of(MemoryModuleType.INTERACTION_TARGET), GateBehavior.OrderPolicy.ORDERED, GateBehavior.RunningPolicy.RUN_ONE, ImmutableList.of(Pair.of(new ShareGossipWithGuard(), 1), Pair.of(new TradeWithVillager(), 1))))); 52 | cir.setReturnValue(ImmutableList.copyOf(villagerList)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/ai/tasks/ShareGossipWithGuard.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities.ai.tasks; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.world.SimpleContainer; 6 | import net.minecraft.world.entity.EquipmentSlot; 7 | import net.minecraft.world.entity.LivingEntity; 8 | import net.minecraft.world.entity.ai.behavior.Behavior; 9 | import net.minecraft.world.entity.ai.behavior.BehaviorUtils; 10 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 11 | import net.minecraft.world.entity.ai.memory.MemoryStatus; 12 | import net.minecraft.world.entity.npc.Villager; 13 | import net.minecraft.world.item.Item; 14 | import net.minecraft.world.item.ItemStack; 15 | import tallestegg.guardvillagers.GuardEntityType; 16 | import tallestegg.guardvillagers.common.entities.Guard; 17 | 18 | import java.util.Set; 19 | 20 | public class ShareGossipWithGuard extends Behavior { 21 | public ShareGossipWithGuard() { 22 | super(ImmutableMap.of(MemoryModuleType.INTERACTION_TARGET, MemoryStatus.VALUE_PRESENT, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryStatus.VALUE_PRESENT)); 23 | } 24 | 25 | @Override 26 | protected boolean checkExtraStartConditions(ServerLevel pLevel, Villager pOwner) { 27 | return BehaviorUtils.targetIsValid(pOwner.getBrain(), MemoryModuleType.INTERACTION_TARGET, GuardEntityType.GUARD.get()); 28 | } 29 | 30 | @Override 31 | protected boolean canStillUse(ServerLevel pLevel, Villager pEntity, long pGameTime) { 32 | return this.checkExtraStartConditions(pLevel, pEntity); 33 | } 34 | 35 | @Override 36 | protected void start(ServerLevel pLevel, Villager pEntity, long pGameTime) { 37 | Guard guard = (Guard) pEntity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).get(); 38 | BehaviorUtils.lockGazeAndWalkToEachOther(pEntity, guard, 0.5F, 2); 39 | } 40 | 41 | @Override 42 | protected void tick(ServerLevel pLevel, Villager pOwner, long pGameTime) { 43 | Guard guard = (Guard) pOwner.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).get(); 44 | if (pOwner.distanceToSqr(guard) < 5.0D) { 45 | BehaviorUtils.lockGazeAndWalkToEachOther(pOwner, guard, 0.5F, 2); 46 | guard.gossip(pOwner, pGameTime); 47 | } 48 | if (pOwner.hasExcessFood() && guard.getOffhandItem().isEmpty()) { 49 | throwHalfStack(pOwner, Villager.FOOD_POINTS.keySet(), guard); 50 | } 51 | } 52 | 53 | @Override 54 | protected void stop(ServerLevel pLevel, Villager pEntity, long pGameTime) { 55 | pEntity.getBrain().eraseMemory(MemoryModuleType.INTERACTION_TARGET); 56 | } 57 | 58 | // From the TradeWithVillager class 59 | private static void throwHalfStack(Villager pVillager, Set pStack, LivingEntity pEntity) { 60 | SimpleContainer simplecontainer = pVillager.getInventory(); 61 | ItemStack itemstack = ItemStack.EMPTY; 62 | int i = 0; 63 | 64 | while(i < simplecontainer.getContainerSize()) { 65 | ItemStack itemstack1; 66 | Item item; 67 | int j; 68 | label28: { 69 | itemstack1 = simplecontainer.getItem(i); 70 | if (!itemstack1.isEmpty()) { 71 | item = itemstack1.getItem(); 72 | if (pStack.contains(item)) { 73 | if (itemstack1.getCount() > itemstack1.getMaxStackSize() / 2) { 74 | j = itemstack1.getCount() / 2; 75 | break label28; 76 | } 77 | 78 | if (itemstack1.getCount() > 24) { 79 | j = itemstack1.getCount() - 24; 80 | break label28; 81 | } 82 | } 83 | } 84 | 85 | ++i; 86 | continue; 87 | } 88 | 89 | itemstack1.shrink(j); 90 | itemstack = new ItemStack(item, j); 91 | break; 92 | } 93 | 94 | if (!itemstack.isEmpty()) { 95 | pEntity.setItemSlot(EquipmentSlot.OFFHAND, itemstack); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/ai/tasks/RepairGolem.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities.ai.tasks; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.sounds.SoundEvents; 6 | import net.minecraft.world.InteractionHand; 7 | import net.minecraft.world.entity.EntityType; 8 | import net.minecraft.world.entity.EquipmentSlot; 9 | import net.minecraft.world.entity.LivingEntity; 10 | import net.minecraft.world.entity.ai.behavior.BehaviorUtils; 11 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 12 | import net.minecraft.world.entity.ai.memory.MemoryStatus; 13 | import net.minecraft.world.entity.npc.Villager; 14 | import net.minecraft.world.item.ItemStack; 15 | import net.minecraft.world.item.Items; 16 | import tallestegg.guardvillagers.GuardDataAttachments; 17 | import tallestegg.guardvillagers.configuration.GuardConfig; 18 | 19 | import java.util.List; 20 | import java.util.Optional; 21 | 22 | public class RepairGolem extends VillagerHelp { 23 | private LivingEntity golem; 24 | 25 | public RepairGolem() { 26 | super(ImmutableMap.of(MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryStatus.VALUE_PRESENT), GuardConfig.COMMON.professionsThatRepairGolems.get()); 27 | } 28 | 29 | @Override 30 | protected boolean checkExtraStartConditions(ServerLevel worldIn, Villager owner) { 31 | List list = owner.getBrain().getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).get(); 32 | if (!list.isEmpty()) { 33 | for (LivingEntity golem : list) { 34 | if (!golem.isInvisible() && golem.isAlive() && golem.getType() == EntityType.IRON_GOLEM) { // Check only for iron golems and if a day has passed since the last time a golem was healed 35 | if (golem.getHealth() <= (golem.getMaxHealth() * 0.75F)) { 36 | this.golem = golem; 37 | return super.checkExtraStartConditions(worldIn, owner); 38 | } 39 | } 40 | } 41 | } 42 | return false; 43 | } 44 | 45 | @Override 46 | protected long timeToCheck(LivingEntity owner) { 47 | return owner.getData(GuardDataAttachments.LAST_REPAIRED_GOLEM); 48 | } 49 | 50 | @Override 51 | protected boolean canStillUse(ServerLevel level, Villager entity, long gameTime) { 52 | return entity.getData(GuardDataAttachments.TIMES_HEALED_GOLEM) < GuardConfig.COMMON.maxGolemRepair.get() && this.golem.getHealth() <= this.golem.getMaxHealth(); 53 | } 54 | 55 | @Override 56 | protected void stop(ServerLevel worldIn, Villager entityIn, long gameTimeIn) { 57 | if (entityIn.getData(GuardDataAttachments.TIMES_HEALED_GOLEM) >= GuardConfig.COMMON.maxGolemRepair.get()) { 58 | entityIn.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); 59 | entityIn.setData(GuardDataAttachments.LAST_REPAIRED_GOLEM.get(), worldIn.getDayTime()); 60 | entityIn.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET); 61 | entityIn.getBrain().eraseMemory(MemoryModuleType.LOOK_TARGET); 62 | entityIn.setData(GuardDataAttachments.TIMES_HEALED_GOLEM.get(), 0); 63 | } 64 | } 65 | 66 | @Override 67 | protected void start(ServerLevel worldIn, Villager entityIn, long gameTimeIn) { 68 | if (golem == null) return; 69 | } 70 | 71 | @Override 72 | protected void tick(ServerLevel worldIn, Villager entityIn, long gameTimeIn) { 73 | this.healGolem(entityIn); 74 | } 75 | 76 | public void healGolem(Villager healer) { 77 | BehaviorUtils.setWalkAndLookTargetMemories(healer, golem, 0.5F, 0); 78 | if (healer.distanceTo(golem) <= 2.0D) { 79 | healer.setData(GuardDataAttachments.TIMES_HEALED_GOLEM.get(), healer.getData(GuardDataAttachments.TIMES_HEALED_GOLEM.get()) + 1); 80 | healer.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_INGOT)); 81 | healer.swing(InteractionHand.MAIN_HAND); 82 | golem.heal(15.0F); 83 | float pitch = 1.0F + (golem.getRandom().nextFloat() - golem.getRandom().nextFloat()) * 0.2F; 84 | golem.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, pitch); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Are you tired of your village dying off because its golem is too lazy? 2 | 3 | Are you tired of essentially defending it all by yourself? 4 | 5 | This mod adds Guards as a solution, as well as new villager-related AI changes. 6 | 7 | An * means a feature is configurable 8 | 9 |
10 | Guard information 11 | 12 | They spawn in villages in groups of six, equipped with either an iron sword or a crossbow. They're prepared to fight any threat to the village (possibly including you!). To make new guards, crouch and right click on a nitwit or unemployed villager with a sword or crossbow. 13 | 14 | Guards can 'gossip' with villagers and will trust the player to open their inventory if they have a high enough reputation, have hero of the village, or was converted by the player. Right clicking will open up the guard inventory, allowing you to give them armor, and swap out their weapons! For their offhand slot, you can either give them a shield, or some food. If they have a shield, they will use it to block attacks. Crossbow guards with a shield won't be able to kick enemies if they're blocking. If they have food (or a potion), they will start eating or drinking if they are low on health. 15 | If you have hero of the village, they will gain the ability to follow and fight for you when you click the follow button. 16 | 17 | ![Guard](https://cdn-raw.modrinth.com/data/H1sntfo8/images/0b0df1486eb77f265e6aef5b619288a83b548bb0.png) 18 | 19 | ![Guard inventory](https://i.imgur.com/vpfqbl2.png) 20 | 21 | Players are able to make guards stand on a specific area by clicking on the 'patrol' button seen on the right. If the player has hero of the village, the 'follow' button, seen on the left, will be unlocked and as the name implies will allow guards to follow the player and fight for the player. Ringing a village bell with the hero of the village effect will also allow multiple guards in the bell's area to follow the player. 22 |
23 | 24 | 25 | 26 |
27 | Misc 28 | 29 | Baby villagers now have big heads, just like in bedrock! 30 | ![baby](https://cdn.modrinth.com/data/cached_images/e77c6d00423049cd2a406e0960fd681dc3ccdbac.png) 31 | Witches attack villagers. *true by default  32 | ![witch attack villager](https://cdn.modrinth.com/data/cached_images/d94b4da456d814d37e4a8a8d3c01631dd5fcaede.jpeg) 33 | In raids, illagers kill any animal they see. *false by default  34 | ![Rueben is dead](https://cdn.modrinth.com/data/cached_images/fd6177c51af3adb92085f0c31fbb9509caa5e729.png) 35 | Villagers and Illagers are scared of polar bears. *true by default  36 | Clerics heal low-health guards, villagers, and players with Hero of the Village using regeneration potions, and smiths repair iron golems with ingots. 37 | ![Cleric heals](https://cdn.modrinth.com/data/H1sntfo8/images/4978a7c4f63fcf8f921621c7ca099ea2083eabea.png) 38 | Villagers that have excess food in their internal inventory will give food to the guards.  39 | 40 | Golems will now float on water in order to avoid being stuck in rivers and ponds. *true on default 41 | 42 |
43 | 44 | 45 |
46 | Frequently Asked Questions 47 | 48 | Can you port to an older version/fabric? 49 | 50 | No. Maintaining one version is hard enough already, and I've never learned fabric. However, the mod's license permits you to make ports yourself. A fabric version of this mod also currently exists. 51 | 52 | Can you port to Windows 10 Edition/Xbox/Mobile? 53 | 54 | No. Mods for the Bedrock ediiton use a completely different framework that I've also never learned. The mod's license allows you to try yourself, but since the modding API on Bedrock is less capable than forge you probably won't be able to replicate all of it. 55 | 56 | Why don't guards have a workstation like other villager professions? 57 | 58 | I haven't thought of a suitable block that would let you have a large number of guards without being inconvenient to store. 59 | 60 | How would you make guards spawn with modded equipment? 61 | 62 | You can make them spawn with modded equipment by making a datapack that overrides the guard_chestplate, guard_helmet, guard_legs, guard feet, guard_main_hand or guard_off_hand json files.  63 | 64 | Is this mod compatible with X? 65 | 66 | Try and see! 67 | 68 | 69 |
70 | 71 | 72 |   73 | 74 |  Special thanks to those in the MMD discord for helping me with some of the mods contents. 75 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "entity.guardvillagers.guard": "警卫", 4 | "item.guardvillagers.guard_spawn_egg": "警卫刷怪蛋", 5 | "item.guardvillagers.illusioner_spawn_egg": "幻术师刷怪蛋", 6 | "item.guardvillagers.iron_golem_spawn_egg": "铁傀儡刷怪蛋", 7 | "item.guardvillagers.snow_golem_spawn_egg": "雪傀儡刷怪蛋", 8 | "guardinventory.health": "生命值: %s", 9 | "guardinventory.armor": "护甲值: %s", 10 | "subtitles.entity.guard.ambient": "警卫:喃喃自语", 11 | "subtitles.entity.guard.hurt": "警卫:受伤", 12 | "subtitles.entity.guard.death": "警卫:死亡", 13 | "guardvillagers.advancements.adventure.recruit_guard.description": "Give a sword to an unemployed villager to help defend the village", 14 | "guardvillagers.advancements.adventure.recruit_guard.title": "We Want You, as a New Recruit!", 15 | "guardvillagers.configuration.raids and illagers": "Raids and Illagers", 16 | "guardvillagers.config.cleric": "牧师会治疗玩家和警卫吗?", 17 | "guardvillagers.configuration.Have baby villagers have big heads like in bedrock?": "婴儿村民会拥有像基岩一样大的脑袋吗", 18 | "guardvillagers.configuration.Guard follow range": "警卫跟随范围", 19 | "guardvillagers.configuration.villager stuff": "村民相关", 20 | "guardvillagers.config.range": "This is the range in which the guards will be aggroed to mobs that are attacking villagers.", 21 | "guardvillagers.config.RaidAnimals": "Allow raiders to attack farm animals during raids?", 22 | "guardvillagers.config.IllagersRunFromPolarBears": "Allow Illagers to run from polar bears", 23 | "guardvillagers.config.armorvillager": "Allow armorers and weaponsmiths repair guard items when down below half durability?", 24 | "guardvillagers.config.steveModel": "启用警卫使用Steve模型", 25 | "guardvillagers.config.hotvPatrolPoint": "Require hero of the village for guards to patrol areas", 26 | "guardvillagers.config.hotv": "只会拥有“村庄英雄”效果的玩家才能将村民转化为警卫", 27 | "guardvillagers.config.blacksmith": "Allow blacksmiths to repair Iron Golems below 80 health", 28 | "guardvillagers.configuration.Chance to drop equipment": "Chance to drop equipment", 29 | "guardvillagers.config.GuardFormation": "启用拥有盾牌的警卫在战斗中会聚集", 30 | "guardvillagers.configuration.Allow guards to naturally patrol villages? This feature can cause lag if a lot of guards are spawned": ".Allow guards to naturally patrol villages? This feature can cause lag if a lot of guards are spawned", 31 | "guardvillagers.config.guardArrows": "Allow guard arrows to hit villager-like mobs?", 32 | "guardvillagers.config.AttackAllMobs": "Allow guards to attack all mobs", 33 | "guardvillagers.config.VillagersRunFromPolarBears": "Allow villagers to run from polar bears?", 34 | "guardvillagers.configuration.Allow guards to teleport if following the player": "Allow guards to teleport if following the player", 35 | "guardvillagers.configuration.Have guards randomly spawn with biome variants?": "Have guards randomly spawn with biome variants?", 36 | "guardvillagers.config.FriendlyFire": "警卫会试图避免向友好生物开火吗?", 37 | "guardvillagers.config.amountofHealthRegenerated": "Amount of hearts regenerated", 38 | "guardvillagers.configuration.How low of a reputation of a player should have to be instantly aggroed upon by guards and golems?": "How low of a reputation of a player should have to be instantly aggroed upon by guards and golems?", 39 | "guardvillagers.configuration.guard stuff": "警卫相关", 40 | "guardvillagers.config.WitchesVillager": "女巫会攻击村民", 41 | "guardvillagers.config.GuardsOpenDoors": "警卫会开门", 42 | "guardvillagers.configuration.mob ai in general": "常规生物AI", 43 | "guardvillagers.configuration.Guard speed": "警卫速度", 44 | "guardvillagers.configuration.Mob Whitelist": "警卫攻击生物的白名单", 45 | "guardvillagers.configuration.Have guards only follow the player if they have hero of the village?": "Have guards only follow the player if they have hero of the village?", 46 | "guardvillagers.configuration.Guard health": "警卫生命值", 47 | "guardvillagers.configuration.Minimum reputation requirement for guards to give you access to their inventories": "Minimum reputation requirement for guards to give you access to their inventories", 48 | "guardvillagers.configuration.All mobs attack guards": "All mobs attack guards", 49 | "guardvillagers.configuration.Mob Blacklist": "生物黑名单", 50 | "guardvillagers.config.GuardRaiseShield": "警卫会一直举起他们的盾牌吗?", 51 | "guardvillagers.configuration.Allow Iron Golems to float on water?": "铁傀儡会漂浮在水面上吗?", 52 | "guardvillagers.configuration.golem stuff": "傀儡相关", 53 | "guardvillagers.config.hotvArmor": "村民只会给予拥有“村庄英雄”效果的玩家礼物吗?", 54 | "guardvillagers.config.multifollow": "是否允许玩家右键钟以命令守卫跟随他们?", 55 | "stat.guardvillagers.guards_made": "Guards created", 56 | "subtitles.entity.guard.yes": "警卫赞同", 57 | "subtitles.entity.guard.no": "警卫反对", 58 | "guardvillagers.configuration.Chance for guards to lose durability": "Chance for guards to lose durability", 59 | "guardvillagers.configuration.Profession Whitelist for guard conversion": "Profession Whitelist for guard conversion" 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/ai/tasks/HealGuardAndHero.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities.ai.tasks; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import net.minecraft.core.Holder; 5 | import net.minecraft.server.level.ServerLevel; 6 | import net.minecraft.sounds.SoundEvents; 7 | import net.minecraft.world.effect.MobEffects; 8 | import net.minecraft.world.entity.LivingEntity; 9 | import net.minecraft.world.entity.ai.behavior.BehaviorUtils; 10 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 11 | import net.minecraft.world.entity.ai.memory.MemoryStatus; 12 | import net.minecraft.world.entity.npc.Villager; 13 | import net.minecraft.world.entity.projectile.ThrownPotion; 14 | import net.minecraft.world.item.Items; 15 | import net.minecraft.world.item.alchemy.Potion; 16 | import net.minecraft.world.item.alchemy.PotionContents; 17 | import net.minecraft.world.item.alchemy.Potions; 18 | import tallestegg.guardvillagers.GuardDataAttachments; 19 | import tallestegg.guardvillagers.common.entities.Guard; 20 | import tallestegg.guardvillagers.configuration.GuardConfig; 21 | 22 | import java.util.List; 23 | import java.util.Optional; 24 | 25 | public class HealGuardAndHero extends VillagerHelp { 26 | private LivingEntity targetToHeal; 27 | private int waitUntilInSightTicks = 0; 28 | 29 | public HealGuardAndHero() { 30 | super(ImmutableMap.of(MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryStatus.VALUE_PRESENT), GuardConfig.COMMON.professionsThatHeal.get()); 31 | } 32 | 33 | @Override 34 | protected boolean checkExtraStartConditions(ServerLevel level, Villager owner) { 35 | List list = owner.getBrain().getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).get(); 36 | if (!list.isEmpty()) { 37 | for (LivingEntity searchedForHeal : list) { 38 | if (searchedForHeal instanceof Guard || searchedForHeal.hasEffect(MobEffects.HERO_OF_THE_VILLAGE) || searchedForHeal instanceof Villager) { 39 | if (searchedForHeal.getHealth() < searchedForHeal.getMaxHealth() && searchedForHeal.distanceTo(owner) <= 4.0D) { 40 | this.targetToHeal = searchedForHeal; 41 | return super.checkExtraStartConditions(level, owner); 42 | } 43 | } 44 | } 45 | } 46 | return false; 47 | } 48 | 49 | @Override 50 | protected long timeToCheck(LivingEntity owner) { 51 | return owner.getData(GuardDataAttachments.LAST_THROWN_POTION.get()); 52 | } 53 | 54 | @Override 55 | protected boolean canStillUse(ServerLevel level, Villager entity, long gameTime) { 56 | return targetToHeal != null && checkIfDayHavePassedFromLastActivity(entity) && entity.getData(GuardDataAttachments.TIMES_THROWN_POTION.get()) < GuardConfig.COMMON.maxClericHeal.get(); 57 | } 58 | 59 | 60 | @Override 61 | protected void tick(ServerLevel level, Villager owner, long gameTime) { 62 | super.tick(level, owner, gameTime); 63 | if (this.targetToHeal == null) 64 | return; 65 | BehaviorUtils.lookAtEntity(owner, targetToHeal); 66 | owner.lookAt(this.targetToHeal, 30.0F, 30.0F); 67 | owner.getLookControl().setLookAt(this.targetToHeal); 68 | if (!owner.hasLineOfSight(this.targetToHeal)) { 69 | this.waitUntilInSightTicks += 5; 70 | } else if (waitUntilInSightTicks > 0) this.waitUntilInSightTicks--; 71 | if (waitUntilInSightTicks == 0) { 72 | this.throwPotion(owner); 73 | this.waitUntilInSightTicks = 40; 74 | } 75 | } 76 | 77 | @Override 78 | protected void stop(ServerLevel level, Villager entity, long gameTime) { 79 | super.stop(level, entity, gameTime); 80 | this.waitUntilInSightTicks = 40; 81 | if (entity.getData(GuardDataAttachments.TIMES_THROWN_POTION.get()) >= GuardConfig.COMMON.maxClericHeal.get()) { 82 | entity.setData(GuardDataAttachments.LAST_THROWN_POTION.get(), level.getDayTime()); 83 | entity.setData(GuardDataAttachments.TIMES_THROWN_POTION.get(), 0); 84 | } 85 | this.targetToHeal = null; 86 | } 87 | 88 | @Override 89 | protected void start(ServerLevel level, Villager entity, long gameTime) { 90 | this.waitUntilInSightTicks = 40; 91 | } 92 | 93 | public void throwPotion(LivingEntity healer) { 94 | healer.setData(GuardDataAttachments.TIMES_THROWN_POTION.get(), healer.getData(GuardDataAttachments.TIMES_THROWN_POTION.get()) + 1); 95 | Holder potion = targetToHeal.getHealth() > 4.0F ? Potions.REGENERATION : Potions.HEALING; 96 | ThrownPotion potionentity = new ThrownPotion(healer.level(), healer); 97 | potionentity.setItem(PotionContents.createItemStack(Items.SPLASH_POTION, potion)); 98 | potionentity.shootFromRotation(healer, healer.getViewXRot(1.0F), healer.getYHeadRot(), -20.0F, 0.5F, 0.0F); 99 | healer.level().playSound(null, healer.getX(), healer.getY(), healer.getZ(), SoundEvents.SPLASH_POTION_THROW, healer.getSoundSource(), 1.0F, 0.8F + healer.getRandom().nextFloat() * 0.4F); 100 | healer.level().addFreshEntity(potionentity); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/ru_ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Стражник", 3 | "guardinventory.armor": "Броня: %s", 4 | "guardinventory.health": "Здоровье: %s", 5 | "guardvillagers.advancements.adventure.recruit_guard.description": "Дайте меч безработному крестьянину, чтобы помочь защитить деревню", 6 | "guardvillagers.advancements.adventure.recruit_guard.title": "Новобранец", 7 | "guardvillagers.config.amountofHealthRegenerated": "Количество регенерированных сердец", 8 | "guardvillagers.config.armorvillager": "Разрешить бронникам и оружейникам ремонтировать предметы на стражниках, прочность которых снизилась наполовину?", 9 | "guardvillagers.config.AttackAllMobs": "Разрешить стражникам атаковать всех мобов", 10 | "guardvillagers.config.blacksmith": "Разрешить кузнецам ремонтировать железных големов с запасом здоровья ниже 80", 11 | "guardvillagers.config.cleric": "Разрешить священикам лечить игроков и крестьян?", 12 | "guardvillagers.config.FriendlyFire": "Заставлять ли стражников избегать стрельбы по своим товарищам?", 13 | "guardvillagers.config.guardArrows": "Разрешить стрелам стражников поражать мобов, похожих на деревенских жителей?", 14 | "guardvillagers.config.GuardFormation": "Дайте возможность стражникам с щитами держаться ближе друг к другу в бою", 15 | "guardvillagers.config.GuardRaiseShield": "Разрешить стражникам постоянно поднимать свои щиты?", 16 | "guardvillagers.config.GuardsOpenDoors": "Разрешить стражникам открывать двери", 17 | "guardvillagers.config.hotv": "Это приведет к тому, что жители деревни смогут быть превращены в стражников только в том случае, если у игрока есть эффект героя деревни", 18 | "guardvillagers.config.hotvArmor": "Разрешить игрокам раздавать стражникам предметы только в том случае, если у них есть эффект героя деревни?", 19 | "guardvillagers.config.hotvPatrolPoint": "Требовать героя деревни", 20 | "guardvillagers.config.IllagersRunFromPolarBears": "Разрешить разбойникам убегать от белых медведей?", 21 | "guardvillagers.config.multifollow": "Разрешить игроку, щёлкнув правой кнопкой мыши по колоколу, вызвать большое количество стражников, которые последуют за ним?", 22 | "guardvillagers.config.RaidAnimals": "Разрешить рейдерам атаковать сельскохозяйственных животных во время рейдов?", 23 | "guardvillagers.config.range": "Это расстояние, на котором стражники будут атаковать мобов, атакующих крестьян.", 24 | "guardvillagers.config.steveModel": "Включите модель Стива для стражников", 25 | "guardvillagers.config.VillagersRunFromPolarBears": "Разрешить крестьянам убегать от белых медведей", 26 | "guardvillagers.config.WitchesVillager": "Разрешить ведьмам атаковать крестьян", 27 | "guardvillagers.configuration.All mobs attack guards": "Все мобы атакуют стражников", 28 | "guardvillagers.configuration.Allow guards to naturally patrol villages? This feature can cause lag if a lot of guards are spawned": "Разрешить стражам самостоятельно патрулировать деревни? Эта функция может вызвать задержки сервера, если будет загружено множество стражников", 29 | "guardvillagers.configuration.Allow guards to teleport if following the player": "Разрешить стражникам телепортироваться, если они следуют за игроком", 30 | "guardvillagers.configuration.Allow Iron Golems to float on water?": "Разрешить железным големам плавать на воде?", 31 | "guardvillagers.configuration.Chance to drop equipment": "Шанс сбросить снаряжение", 32 | "guardvillagers.configuration.golem stuff": "Поведение големов", 33 | "guardvillagers.configuration.Guard follow range": "Дальность преследования патруля", 34 | "guardvillagers.configuration.Guard health": "Здоровье стражника", 35 | "guardvillagers.configuration.Guard speed": "Скорость стражника", 36 | "guardvillagers.configuration.guard stuff": "Поведение стражников", 37 | "guardvillagers.configuration.Have baby villagers have big heads like in bedrock?": "Дать детям крестьян большие головы как в Minecraft Bedrock?", 38 | "guardvillagers.configuration.Have guards only follow the player if they have hero of the village?": "Должны ли стражники следовать за игроком только в том случае, если у него есть эффект героя деревни?", 39 | "guardvillagers.configuration.Have guards randomly spawn with biome variants?": "Будут ли стражники случайным образом появляться с различными вариантами биомов?", 40 | "guardvillagers.configuration.How low of a reputation of a player should have to be instantly aggroed upon by guards and golems?": "Насколько низкой должна быть репутация игрока, чтобы его мгновенно атаковали стражники и големы?", 41 | "guardvillagers.configuration.Minimum reputation requirement for guards to give you access to their inventories": "Минимальное требование к репутации игрока, позволяющее получить доступ к инвентарю стражника", 42 | "guardvillagers.configuration.mob ai in general": "Общий ИИ мобов", 43 | "guardvillagers.configuration.Mob Blacklist": "Чёрный список мобов", 44 | "guardvillagers.configuration.Mob Whitelist": "Белый список мобов, которых могут атаковать стражники", 45 | "guardvillagers.configuration.raids and illagers": "Рейды и разбойники", 46 | "guardvillagers.configuration.villager stuff": "Поведение крестьян", 47 | "item.guardvillagers.guard_spawn_egg": "Яйцо призыва стражника", 48 | "item.guardvillagers.illusioner_spawn_egg": "Яйцо призыва иллюзора", 49 | "item.guardvillagers.iron_golem_spawn_egg": "Яйцо призыва железного голема", 50 | "item.guardvillagers.snow_golem_spawn_egg": "Яйцо призыва снеговика", 51 | "stat.guardvillagers.guards_made": "Стражник создан", 52 | "subtitles.entity.guard.ambient": "Стражник бормочет", 53 | "subtitles.entity.guard.death": "Стражник умирает", 54 | "subtitles.entity.guard.hurt": "Стражник ранен", 55 | "subtitles.entity.guard.no": "Стражник не согласен", 56 | "subtitles.entity.guard.yes": "Стражник согласен" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/pt_br.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Guarda", 3 | "item.guardvillagers.guard_spawn_egg": "Ovo Gerador de Guarda", 4 | "item.guardvillagers.illusioner_spawn_egg": "Ovo Gerador de Ilusionista", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Ovo Gerador de Golem de Ferro", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Ovo Gerador de Golem de Neve", 7 | "guardinventory.health": "Vida: %s", 8 | "guardinventory.armor": "Armadura: %s", 9 | "subtitles.entity.guard.ambient": "Guarda murmura", 10 | "subtitles.entity.guard.hurt": "Guarda se machuca", 11 | "subtitles.entity.guard.death": "Guarda morre", 12 | "guardvillagers.advancements.adventure.recruit_guard.description": "Dê uma espada a um aldeão desempregado para ajudar a defender a vila", 13 | "guardvillagers.advancements.adventure.recruit_guard.title": "Queremos Você, Como Novo Recruta!", 14 | "guardvillagers.configuration.raids and illagers": "Invasões e Illagers", 15 | "guardvillagers.config.cleric": "Permitir que clérigos curem jogadores e aldeões?", 16 | "guardvillagers.configuration.Have baby villagers have big heads like in bedrock?": "Fazer com que aldeões bebês tenham cabeças grandes como no Bedrock?", 17 | "guardvillagers.configuration.Guard follow range": "Alcance de seguimento dos guardas", 18 | "guardvillagers.configuration.villager stuff": "Coisas de aldeões", 19 | "guardvillagers.config.range": "Este é o alcance em que os guardas ficarão agressivos contra mobs que estão atacando aldeões.", 20 | "guardvillagers.config.RaidAnimals": "Permitir que invasores ataquem animais de fazenda durante invasões?", 21 | "guardvillagers.config.IllagersRunFromPolarBears": "Permitir que Illagers fujam de ursos polares?", 22 | "guardvillagers.config.armorvillager": "Permitir que armeiros e ferreiros reparem itens de guardas quando estiverem abaixo de metade da durabilidade?", 23 | "guardvillagers.config.steveModel": "Habilitar modelo Steve para guardas", 24 | "guardvillagers.config.hotvPatrolPoint": "Exigir Herói da Vila para que guardas patrulhem áreas", 25 | "guardvillagers.config.hotv": "Isso fará com que aldeões só sejam convertidos em guardas se o jogador tiver o efeito Herói da Vila", 26 | "guardvillagers.config.blacksmith": "Permitir que ferreiros reparem Golems de Ferro com menos de 80 de saúde", 27 | "guardvillagers.configuration.Chance to drop equipment": "Chance de dropar equipamento", 28 | "guardvillagers.config.G BUDDYardFormation": "Habilitar guardas com escudo para ficarem próximos uns dos outros em combate", 29 | "guardvillagers.configuration.Allow guards to naturally patrol villages? This feature can cause lag if a lot of guards are spawned": "Permitir que guardas patrulhem vilas naturalmente? Este recurso pode causar lag se muitos guardas forem gerados", 30 | "guardvillagers.config.guardArrows": "Permitir que flechas de guardas acertem mobs semelhantes a aldeões?", 31 | "guardvillagers.config.AttackAllMobs": "Permitir que guardas ataquem todos os mobs", 32 | "guardvillagers.config.VillagersRunFromPolarBears": "Permitir que aldeões fujam de ursos polares?", 33 | "guardvillagers.configuration.Allow guards to teleport if following the player": "Permitir que guardas se teletransportem se estiverem seguindo o jogador", 34 | "guardvillagers.configuration.Have guards randomly spawn with biome variants?": "Fazer com que guardas apareçam aleatoriamente com variantes de bioma?", 35 | "guardvillagers.config.FriendlyFire": "Fazer com que guardas tentem evitar atirar em outros mobs amigáveis?", 36 | "guardvillagers.config.amountofHealthRegenerated": "Quantidade de corações regenerados", 37 | "guardvillagers.configuration.How low of a reputation of a player should have to be instantly aggroed upon by guards and golems?": "Quão baixa deve ser a reputação de um jogador para ser instantaneamente atacado por guardas e golems?", 38 | "guardvillagers.configuration.guard stuff": "Coisas de guardas", 39 | "guardvillagers.config.WitchesVillager": "Permitir que bruxas ataquem aldeões", 40 | "guardvillagers.config.GuardsOpenDoors": "Permitir que guardas abram portas", 41 | "guardvillagers.configuration.mob ai in general": "IA de mobs em geral", 42 | "guardvillagers.configuration.Guard speed": "Velocidade dos guardas", 43 | "guardvillagers.configuration.Mob Whitelist": "Lista branca de mobs para guardas atacarem", 44 | "guardvillagers.configuration.Have guards only follow the player if they have hero of the village?": "Fazer com que guardas sigam o jogador apenas se ele tiver o efeito Herói da Vila?", 45 | "guardvillagers.configuration.Guard health": "Vida dos guardas", 46 | "guardvillagers.configuration.Minimum reputation requirement for guards to give you access to their inventories": "Requisito mínimo de reputação para guardas darem acesso aos seus inventários", 47 | "guardvillagers.configuration.All mobs attack guards": "Todos os mobs atacam guardas", 48 | "guardvillagers.configuration.Mob Blacklist": "Lista negra de mobs", 49 | "guardvillagers.config.GuardRaiseShield": "Permitir que guardas mantenham seus escudos levantados permanentemente?", 50 | "guardvillagers.configuration.Allow Iron Golems to float on water?": "Permitir que Golems de Ferro flutuem na água?", 51 | "guardvillagers.configuration.golem stuff": "Coisas de golems", 52 | "guardvillagers.config.hotvArmor": "Permitir que jogadores deem itens aos guardas apenas se tiverem o efeito Herói da Vila?", 53 | "guardvillagers.config.multifollow": "Permitir que o jogador clique com o botão direito em sinos para ordenar que vários guardas o sigam?", 54 | "stat.guardvillagers.guards_made": "Guardas criados", 55 | "subtitles.entity.guard.yes": "Guarda concorda", 56 | "subtitles.entity.guard.no": "Guarda discorda", 57 | "guardvillagers.configuration.Chance for guards to lose durability": "Chance de guardas perderem durabilidade", 58 | "guardvillagers.configuration.Profession Whitelist for guard conversion": "Lista branca de profissões para conversão em guardas" 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/GuardVillagers.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers; 2 | 3 | import net.minecraft.core.Registry; 4 | import net.minecraft.core.registries.Registries; 5 | import net.minecraft.world.effect.MobEffects; 6 | import net.minecraft.world.entity.player.Player; 7 | import net.minecraft.world.item.CreativeModeTabs; 8 | import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; 9 | import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList; 10 | import net.neoforged.api.distmarker.Dist; 11 | import net.neoforged.bus.api.IEventBus; 12 | import net.neoforged.bus.api.SubscribeEvent; 13 | import net.neoforged.fml.ModContainer; 14 | import net.neoforged.fml.common.Mod; 15 | import net.neoforged.fml.config.ModConfig; 16 | import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; 17 | import net.neoforged.neoforge.client.gui.ConfigurationScreen; 18 | import net.neoforged.neoforge.client.gui.IConfigScreenFactory; 19 | import net.neoforged.neoforge.common.NeoForge; 20 | import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; 21 | import net.neoforged.neoforge.event.entity.EntityAttributeCreationEvent; 22 | import net.neoforged.neoforge.event.server.ServerAboutToStartEvent; 23 | import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; 24 | import net.neoforged.neoforge.network.registration.PayloadRegistrar; 25 | import tallestegg.guardvillagers.client.GuardSounds; 26 | import tallestegg.guardvillagers.common.entities.Guard; 27 | import tallestegg.guardvillagers.configuration.GuardConfig; 28 | import tallestegg.guardvillagers.loot_tables.GuardLootTables; 29 | import tallestegg.guardvillagers.networking.GuardFollowPacket; 30 | import tallestegg.guardvillagers.networking.GuardOpenInventoryPacket; 31 | import tallestegg.guardvillagers.networking.GuardSetPatrolPosPacket; 32 | 33 | @Mod(GuardVillagers.MODID) 34 | public class GuardVillagers { 35 | public static final String MODID = "guardvillagers"; 36 | 37 | public GuardVillagers(ModContainer container, IEventBus modEventBus) { 38 | container.registerConfig(ModConfig.Type.COMMON, GuardConfig.COMMON_SPEC); 39 | container.registerConfig(ModConfig.Type.CLIENT, GuardConfig.CLIENT_SPEC); 40 | container.registerConfig(ModConfig.Type.STARTUP, GuardConfig.STARTUP_SPEC); 41 | modEventBus.addListener(this::setup); 42 | NeoForge.EVENT_BUS.register(HandlerEvents.class); 43 | GuardEntityType.ENTITIES.register(modEventBus); 44 | GuardItems.ITEMS.register(modEventBus); 45 | GuardSounds.SOUNDS.register(modEventBus); 46 | GuardLootTables.LOOT_ITEM_CONDITION_TYPES.register(modEventBus); 47 | GuardLootTables.LOOT_ITEM_FUNCTION_TYPES.register(modEventBus); 48 | GuardStats.STATS.register(modEventBus); 49 | GuardDataAttachments.ATTACHMENT_TYPES.register(modEventBus); 50 | NeoForge.EVENT_BUS.addListener(this::serverStart); 51 | modEventBus.addListener(this::addAttributes); 52 | modEventBus.addListener(this::addCreativeTabs); 53 | modEventBus.addListener(this::register); 54 | } 55 | 56 | 57 | private void register(final RegisterPayloadHandlersEvent event) { 58 | PayloadRegistrar reg = event.registrar(MODID).versioned("2.0.2"); 59 | reg.playToServer(GuardSetPatrolPosPacket.TYPE, GuardSetPatrolPosPacket.STREAM_CODEC, GuardSetPatrolPosPacket::setPatrolPosition); 60 | reg.playToClient(GuardOpenInventoryPacket.TYPE, GuardOpenInventoryPacket.STREAM_CODEC, GuardOpenInventoryPacket::handle); 61 | reg.playToServer(GuardFollowPacket.TYPE, GuardFollowPacket.STREAM_CODEC, GuardFollowPacket::handle); 62 | } 63 | 64 | public static boolean hotvChecker(Player player, Guard guard) { 65 | return player.hasEffect(MobEffects.HERO_OF_THE_VILLAGE) && GuardConfig.COMMON.giveGuardStuffHOTV.get() 66 | || !GuardConfig.COMMON.giveGuardStuffHOTV.get() || guard.getPlayerReputation(player) > GuardConfig.COMMON.reputationRequirement.get() && !player.level().isClientSide(); 67 | } 68 | 69 | public static boolean canFollow(Player player) { 70 | return GuardConfig.COMMON.followHero.get() && player.hasEffect(MobEffects.HERO_OF_THE_VILLAGE) || !GuardConfig.COMMON.followHero.get(); 71 | } 72 | 73 | @SubscribeEvent 74 | private void addCreativeTabs(final BuildCreativeModeTabContentsEvent event) { 75 | if (event.getTabKey() == CreativeModeTabs.SPAWN_EGGS) { 76 | event.accept(GuardItems.GUARD_SPAWN_EGG.get()); 77 | event.accept(GuardItems.ILLUSIONER_SPAWN_EGG.get()); 78 | } 79 | } 80 | 81 | @SubscribeEvent 82 | private void setup(final FMLCommonSetupEvent event) { 83 | } 84 | 85 | @SubscribeEvent 86 | private void addAttributes(final EntityAttributeCreationEvent event) { 87 | event.put(GuardEntityType.GUARD.get(), Guard.createAttributes().build()); 88 | } 89 | 90 | // Turns something like modid:john to simply john 91 | public static String removeModIdFromVillagerType(String stringWithModId) { 92 | String[] parts = stringWithModId.split(":"); 93 | if (parts.length <= 1) 94 | return parts[0]; 95 | else 96 | return parts[1]; 97 | } 98 | 99 | private void serverStart(final ServerAboutToStartEvent event) { 100 | Registry templatePoolRegistry = event.getServer().registryAccess().registry(Registries.TEMPLATE_POOL).orElseThrow(); 101 | Registry processorListRegistry = event.getServer().registryAccess().registry(Registries.PROCESSOR_LIST).orElseThrow(); 102 | } 103 | 104 | 105 | @Mod(value = GuardVillagers.MODID, dist = Dist.CLIENT) 106 | public static class GuardVillagersClient { 107 | public GuardVillagersClient(ModContainer container, IEventBus modEventBus) { 108 | container.registerExtensionPoint(IConfigScreenFactory.class, ConfigurationScreen::new); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/ai/tasks/RepairGuardEquipment.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities.ai.tasks; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import net.minecraft.server.level.ServerLevel; 5 | import net.minecraft.sounds.SoundEvents; 6 | import net.minecraft.world.entity.LivingEntity; 7 | import net.minecraft.world.entity.ai.behavior.BehaviorUtils; 8 | import net.minecraft.world.entity.ai.memory.MemoryModuleType; 9 | import net.minecraft.world.entity.ai.memory.MemoryStatus; 10 | import net.minecraft.world.entity.npc.Villager; 11 | import net.minecraft.world.entity.npc.VillagerProfession; 12 | import net.minecraft.world.item.ArmorItem; 13 | import net.minecraft.world.item.ItemStack; 14 | import tallestegg.guardvillagers.GuardDataAttachments; 15 | import tallestegg.guardvillagers.common.entities.Guard; 16 | import tallestegg.guardvillagers.configuration.GuardConfig; 17 | 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | public class RepairGuardEquipment extends VillagerHelp { 22 | private Guard guard; 23 | 24 | public RepairGuardEquipment() { 25 | super(ImmutableMap.of(MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryStatus.VALUE_PRESENT), GuardConfig.COMMON.professionsThatRepairGuards.get()); 26 | } 27 | 28 | @Override 29 | protected boolean checkExtraStartConditions(ServerLevel worldIn, Villager owner) { 30 | List list = owner.getBrain().getMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES).get(); 31 | if (!list.isEmpty()) { 32 | for (LivingEntity livingEntity : list) { 33 | if (!livingEntity.isInvisible() && livingEntity.isAlive() && livingEntity instanceof Guard guard) { // Check only for iron golems and if a day has passed since the last time a golem was healed 34 | if (owner.getVillagerData().getProfession() == VillagerProfession.ARMORER) { 35 | for (int i = 0; i < guard.guardInventory.getContainerSize() - 2; ++i) { 36 | ItemStack itemstack = guard.guardInventory.getItem(i); 37 | if (itemstack.isDamaged() && itemstack.getItem() instanceof ArmorItem && itemstack.getDamageValue() >= (itemstack.getMaxDamage() / 2)) { 38 | this.guard = guard; 39 | return super.checkExtraStartConditions(worldIn, owner); 40 | } 41 | } 42 | } else { 43 | for (int i = 4; i < 6; ++i) { 44 | ItemStack itemstack = guard.guardInventory.getItem(i); 45 | if (itemstack.isDamaged() && itemstack.getDamageValue() >= (itemstack.getMaxDamage() / 2)) { 46 | this.guard = guard; 47 | return super.checkExtraStartConditions(worldIn, owner); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | @Override 58 | protected long timeToCheck(LivingEntity owner) { 59 | Long timeLastRepairedGuardEquipment = owner.getData(GuardDataAttachments.LAST_REPAIRED_GUARD); 60 | return timeLastRepairedGuardEquipment; 61 | } 62 | 63 | @Override 64 | protected boolean canStillUse(ServerLevel level, Villager entity, long gameTime) { 65 | return entity.getData(GuardDataAttachments.TIMES_REPAIRED_GUARD) < GuardConfig.COMMON.maxVillageRepair.get(); 66 | } 67 | 68 | @Override 69 | protected void stop(ServerLevel worldIn, Villager entityIn, long gameTimeIn) { 70 | if (entityIn.getData(GuardDataAttachments.TIMES_REPAIRED_GUARD) >= GuardConfig.COMMON.maxVillageRepair.get()) { 71 | entityIn.setData(GuardDataAttachments.LAST_REPAIRED_GUARD, worldIn.getDayTime()); 72 | entityIn.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET); 73 | entityIn.getBrain().eraseMemory(MemoryModuleType.LOOK_TARGET); 74 | entityIn.setData(GuardDataAttachments.TIMES_REPAIRED_GUARD, 0); 75 | float pitch = 1.0F + (guard.getRandom().nextFloat() - guard.getRandom().nextFloat()) * 0.2F; 76 | guard.playSound(SoundEvents.ANVIL_USE, 1.0F, pitch); 77 | } 78 | } 79 | 80 | @Override 81 | protected void start(ServerLevel worldIn, Villager entityIn, long gameTimeIn) { 82 | } 83 | 84 | @Override 85 | protected void tick(ServerLevel worldIn, Villager entityIn, long gameTimeIn) { 86 | this.repairGuardEquipment(entityIn); 87 | } 88 | 89 | public void repairGuardEquipment(Villager healer) { 90 | BehaviorUtils.setWalkAndLookTargetMemories(healer, guard, 0.5F, 0); 91 | if (healer.distanceTo(guard) <= 2.0D) { 92 | healer.setData(GuardDataAttachments.TIMES_REPAIRED_GUARD, healer.getData(GuardDataAttachments.TIMES_REPAIRED_GUARD) + 1); 93 | VillagerProfession profession = healer.getVillagerData().getProfession(); 94 | if (profession == VillagerProfession.ARMORER) { 95 | for (int i = 0; i < guard.guardInventory.getContainerSize() - 2; ++i) { 96 | ItemStack itemstack = guard.guardInventory.getItem(i); 97 | if (itemstack.isDamaged() && itemstack.getItem() instanceof ArmorItem && itemstack.getDamageValue() >= (itemstack.getMaxDamage() / 2) + guard.getRandom().nextInt(5)) { 98 | itemstack.setDamageValue(itemstack.getDamageValue() - guard.getRandom().nextInt(5)); 99 | } 100 | } 101 | } else { 102 | for (int i = 4; i < 6; ++i) { 103 | ItemStack itemstack = guard.guardInventory.getItem(i); 104 | if (itemstack.isDamaged() && itemstack.getDamageValue() >= (itemstack.getMaxDamage() / 2) + guard.getRandom().nextInt(5)) { 105 | itemstack.setDamageValue(itemstack.getDamageValue() - guard.getRandom().nextInt(5)); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/ModCompat.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers; 2 | 3 | import ewewukek.musketmod.GunItem; 4 | import net.minecraft.client.model.HumanoidModel; 5 | import net.minecraft.world.InteractionHand; 6 | import net.minecraft.world.entity.EquipmentSlot; 7 | import net.minecraft.world.entity.LivingEntity; 8 | import net.minecraft.world.entity.PathfinderMob; 9 | import net.minecraft.world.entity.ai.goal.Goal; 10 | import net.minecraft.world.entity.ai.util.LandRandomPos; 11 | import net.minecraft.world.entity.monster.RangedAttackMob; 12 | import net.minecraft.world.entity.projectile.ProjectileUtil; 13 | import net.minecraft.world.item.ItemStack; 14 | import net.minecraft.world.level.pathfinder.Path; 15 | import net.minecraft.world.phys.Vec3; 16 | import tallestegg.guardvillagers.common.entities.Guard; 17 | 18 | import javax.annotation.Nullable; 19 | import java.util.EnumSet; 20 | 21 | public class ModCompat { 22 | public static HumanoidModel.ArmPose reloadMusketAnim(ItemStack stack, InteractionHand handIn, Guard guard, HumanoidModel.ArmPose bipedmodel$armpose) { 23 | if (stack.getItem() instanceof GunItem && !GunItem.isLoaded(stack)) { 24 | if (handIn == guard.getUsedItemHand()) { 25 | return HumanoidModel.ArmPose.CROSSBOW_CHARGE; 26 | } 27 | } 28 | return bipedmodel$armpose; 29 | } 30 | 31 | public static boolean isHoldingMusket(ItemStack stack) { 32 | return stack.getItem() instanceof GunItem; 33 | } 34 | 35 | public static HumanoidModel.ArmPose holdMusketAnim(ItemStack stack, Guard guard) { 36 | if (stack.getItem() instanceof GunItem && GunItem.isLoaded(stack) && guard.isAggressive()) 37 | return HumanoidModel.ArmPose.CROSSBOW_HOLD; 38 | return HumanoidModel.ArmPose.ITEM; 39 | } 40 | 41 | public static void shootGun(Guard guard) { 42 | if (guard.getMainHandItem().getItem() instanceof GunItem musketItem) { 43 | Vec3 front = Vec3.directionFromRotation(guard.getXRot(), guard.getYRot()); 44 | musketItem.fire(guard, front); 45 | GunItem.setLoaded(guard.getMainHandItem(), false); 46 | guard.playSound(musketItem.fireSound(), 3.5F, 1); 47 | guard.damageGuardItem(1, EquipmentSlot.MAINHAND, guard.getMainHandItem()); 48 | } 49 | } 50 | 51 | public static class UseMusketGoal extends Goal { 52 | private final float attackRadiusSqr; 53 | private final T mob; 54 | private int attackIntervalMin; 55 | private Path path; 56 | private int attackTime = -1; 57 | private int seeTime; 58 | private int timeUntilShoot = 20; 59 | 60 | public UseMusketGoal(T pMob, int pAttackIntervalMin, float pAttackRadius) { 61 | this.mob = pMob; 62 | this.attackIntervalMin = pAttackIntervalMin; 63 | this.attackRadiusSqr = pAttackRadius * pAttackRadius; 64 | this.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); 65 | } 66 | 67 | @Override 68 | public boolean canUse() { 69 | LivingEntity target = mob.getTarget(); 70 | return target != null && mob.getMainHandItem().getItem() instanceof GunItem; 71 | } 72 | 73 | @Override 74 | public boolean canContinueToUse() { 75 | return this.canUse(); 76 | } 77 | 78 | @Override 79 | public void start() { 80 | this.mob.setAggressive(true); 81 | } 82 | 83 | @Override 84 | public boolean requiresUpdateEveryTick() { 85 | return true; 86 | } 87 | 88 | @Override 89 | public void tick() { 90 | LivingEntity target = mob.getTarget(); 91 | if (target != null) { 92 | double distanceSquared = mob.distanceToSqr(target); 93 | boolean canSee = mob.getSensing().hasLineOfSight(target); 94 | boolean seeTimeGreaterThanZero = this.seeTime > 0; 95 | this.mob.getLookControl().setLookAt(target); 96 | this.mob.lookAt(target, 30.0F, 30.0F); 97 | if (!canSee && this.seeTime < -60) 98 | mob.stopUsingItem(); 99 | if (GunItem.isLoaded(this.mob.getMainHandItem())) { 100 | this.mob.stopUsingItem(); 101 | if (canSee) { 102 | this.timeUntilShoot--; 103 | if (timeUntilShoot <= 0) { 104 | this.mob.performRangedAttack(target, ((GunItem) this.mob.getMainHandItem().getItem()).bulletSpeed()); 105 | this.attackTime = this.attackIntervalMin; 106 | } 107 | } 108 | } else if (--this.attackTime <= 0 && this.seeTime >= -60 && !GunItem.isLoaded(this.mob.getMainHandItem())) { 109 | this.mob.startUsingItem(ProjectileUtil.getWeaponHoldingHand(mob, item -> item instanceof GunItem)); 110 | this.timeUntilShoot = 20; 111 | } 112 | if (canSee != seeTimeGreaterThanZero) 113 | this.seeTime = 0; 114 | if (canSee) { 115 | ++this.seeTime; 116 | } else { 117 | --this.seeTime; 118 | } 119 | if (distanceSquared <= 6.0D) { 120 | this.mob.getMoveControl().strafe(-0.5F, 0.0F); 121 | } 122 | if ((distanceSquared > (double) this.attackRadiusSqr) || this.seeTime < 5) { 123 | this.mob.getNavigation().moveTo(target, 1.0D); 124 | } else if (distanceSquared < (double) this.attackRadiusSqr) { 125 | this.mob.getNavigation().stop(); 126 | } 127 | if (Guard.RangedCrossbowAttackPassiveGoal.friendlyInLineOfSight(this.mob)) { 128 | Vec3 vec3 = this.getPosition(this.mob); 129 | if (distanceSquared <= this.attackRadiusSqr) { 130 | if (vec3 != null && mob.getNavigation().isDone()) { 131 | this.path = mob.getNavigation().createPath(vec3.x, vec3.y, vec3.z, 0); 132 | this.mob.getLookControl().setLookAt(vec3.x, mob.getEyeY(), vec3.z); 133 | if (this.path != null && this.path.canReach()) { 134 | this.mob.getNavigation().moveTo(this.path, 0.9D); 135 | this.attackTime = -1; 136 | this.mob.stopUsingItem(); 137 | } 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | @Override 145 | public void stop() { 146 | mob.setAggressive(false); 147 | this.seeTime = 0; 148 | this.attackTime = -1; 149 | mob.stopUsingItem(); 150 | this.timeUntilShoot = 20; 151 | } 152 | 153 | @Nullable 154 | protected Vec3 getPosition(T mob) { 155 | if (mob.getTarget() != null) 156 | return LandRandomPos.getPosAway(mob, 5, 7, mob.getTarget().position()); 157 | else 158 | return LandRandomPos.getPos(mob, 5, 7); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/fr_fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Garde", 3 | "item.guardvillagers.guard_spawn_egg": "Œuf d’apparition de garde", 4 | "item.guardvillagers.illusioner_spawn_egg": "Œuf d’apparition d’illusionniste", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Œuf d’apparition de golem de fer", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Œuf d’apparition de golem de neige", 7 | "guardinventory.health": "Santé : %s", 8 | "guardinventory.armor": "Armure : %s", 9 | "subtitles.entity.guard.ambient": "Le garde marmonne", 10 | "subtitles.entity.guard.hurt": "Le garde est blessé", 11 | "subtitles.entity.guard.death": "Le garde meurt", 12 | "guardvillagers.advancements.adventure.recruit_guard.description": "Donnez une épée à un villageois sans emploi pour aider à défendre le village", 13 | "guardvillagers.advancements.adventure.recruit_guard.title": "Nous voulons vous recruter !", 14 | "guardvillagers.configuration.raids and illagers": "Raids et Illageois", 15 | "guardvillagers.config.cleric": "Autoriser les prêtres à soigner les joueurs et les villageois ?", 16 | "guardvillagers.configuration.Have baby villagers have big heads like in bedrock?": "Donner aux bébés villageois de grosses têtes comme sur Bedrock ?", 17 | "guardvillagers.configuration.Guard follow range": "Portée de suivi du garde", 18 | "guardvillagers.configuration.villager stuff": "Options des villageois", 19 | "guardvillagers.config.range": "Portée à laquelle les gardes deviendront agressifs envers les mobs qui attaquent les villageois.", 20 | "guardvillagers.config.RaidAnimals": "Autoriser les pillards à attaquer les animaux de ferme durant les raids ?", 21 | "guardvillagers.config.IllagersRunFromPolarBears": "Autoriser les Illageois à fuir les ours polaires ?", 22 | "guardvillagers.config.armorvillager": "Autoriser les armuriers et les forgerons à réparer l’équipement des gardes quand il est à moins de la moitié de sa durabilité ?", 23 | "guardvillagers.config.steveModel": "Activer le modèle de Steve pour les gardes", 24 | "guardvillagers.config.hotvPatrolPoint": "Exiger Héros du village pour que les gardes patrouillent des zones", 25 | "guardvillagers.config.hotv": "Les villageois ne pourront devenir gardes que si le joueur a Héros du village", 26 | "guardvillagers.config.blacksmith": "Autoriser les forgerons à réparer les golems de fer en dessous de 80 PV", 27 | "guardvillagers.configuration.Chance to drop equipment": "Chance de laisser tomber l’équipement", 28 | "guardvillagers.config.GuardFormation": "Permettre aux gardes avec bouclier de rester groupés en combat", 29 | "guardvillagers.configuration.Allow guards to naturally patrol villages? This feature can cause lag if a lot of guards are spawned": "Autoriser les gardes à patrouiller naturellement les villages ? (Peut provoquer du lag si beaucoup de gardes apparaissent.)", 30 | "guardvillagers.config.guardArrows": "Autoriser les flèches des gardes à toucher les mobs de type villageois ?", 31 | "guardvillagers.config.AttackAllMobs": "Autoriser les gardes à attaquer tous les mobs", 32 | "guardvillagers.config.VillagersRunFromPolarBears": "Autoriser les villageois à fuir les ours polaires ?", 33 | "guardvillagers.configuration.Allow guards to teleport if following the player": "Autoriser les gardes à se téléporter lorsqu’ils suivent le joueur", 34 | "guardvillagers.configuration.Have guards randomly spawn with biome variants?": "Les gardes apparaissent-ils aléatoirement avec des variantes selon le biome ?", 35 | "guardvillagers.config.FriendlyFire": "Faire en sorte que les gardes évitent de tirer sur les alliés ?", 36 | "guardvillagers.config.amountofHealthRegenerated": "Quantité de cœurs régénérés", 37 | "guardvillagers.configuration.How low of a reputation of a player should have to be instantly aggroed upon by guards and golems?": "À quel niveau de réputation un joueur doit-il être immédiatement attaqué par les gardes et les golems ?", 38 | "guardvillagers.configuration.guard stuff": "Options des gardes", 39 | "guardvillagers.config.WitchesVillager": "Autoriser les sorcières à attaquer les villageois ?", 40 | "guardvillagers.config.GuardsOpenDoors": "Autoriser les gardes à ouvrir les portes", 41 | "guardvillagers.configuration.mob ai in general": "IA générale des mobs", 42 | "guardvillagers.configuration.Guard speed": "Vitesse des gardes", 43 | "guardvillagers.configuration.Mob Whitelist": "Liste blanche des mobs que les gardes peuvent attaquer", 44 | "guardvillagers.configuration.Have guards only follow the player if they have hero of the village?": "Les gardes ne suivent le joueur que s’il a Héros du village ?", 45 | "guardvillagers.configuration.Guard health": "Santé des gardes", 46 | "guardvillagers.configuration.Minimum reputation requirement for guards to give you access to their inventories": "Réputation minimale nécessaire pour accéder à l’inventaire des gardes", 47 | "guardvillagers.configuration.All mobs attack guards": "Tous les mobs attaquent les gardes", 48 | "guardvillagers.configuration.Mob Blacklist": "Liste noire des mobs", 49 | "guardvillagers.config.GuardRaiseShield": "Autoriser les gardes à garder leur bouclier levé en permanence ?", 50 | "guardvillagers.configuration.Allow Iron Golems to float on water?": "Autoriser les golems de fer à flotter sur l’eau ?", 51 | "guardvillagers.configuration.golem stuff": "Options des golems", 52 | "guardvillagers.config.hotvArmor": "Autoriser les joueurs à donner de l’équipement aux gardes uniquement avec Héros du village ?", 53 | "guardvillagers.config.multifollow": "Autoriser le joueur à faire suivre les gardes en cliquant sur une cloche ?", 54 | "guardvillagers.configuration.Structure pieces that spawn guards": "Éléments de structure faisant apparaître des gardes", 55 | "guardvillagers.configuration.How many guards should spawn in a village?": "Combien de gardes doivent apparaître dans un village ?", 56 | "guardvillagers.configuration.Allow guards to convert to zombie villagers upon being killed by zombies?": "Autoriser les gardes à se transformer en villageois zombies lorsqu’ils sont tués par des zombies ?", 57 | "guardvillagers.configuration.Guard crossbow attack radius": "Rayon d’attaque de l’arbalète des gardes", 58 | "stat.guardvillagers.guards_made": "Gardes créés", 59 | "subtitles.entity.guard.yes": "Le garde approuve", 60 | "subtitles.entity.guard.no": "Le garde désapprouve", 61 | "guardvillagers.configuration.Chance for guards to lose durability": "Chance pour les gardes de perdre de la durabilité", 62 | "guardvillagers.configuration.Profession Whitelist for guard conversion": "Liste blanche des professions pour la conversion en garde", 63 | "guardvillagers.configuration.How many times a villager can heal a guard's equipment in one day": "Nombre de réparations d’équipement de garde qu’un villageois peut faire par jour", 64 | "guardvillagers.configuration.How many times a cleric can heal a guard in one day": "Nombre de soins qu’un prêtre peut apporter à un garde par jour", 65 | "guardvillagers.configuration.Profession Whitelist for guard weaponry repair ai": "Liste blanche des professions pour l’IA de réparation d’armes des gardes", 66 | "guardvillagers.configuration.How many times a smith villager can heal a golem in one day": "Nombre de soins qu’un forgeron peut apporter à un golem par jour", 67 | "guardvillagers.configuration.Profession Whitelist for healing ai for clerics": "Liste blanche des professions pour l’IA de soin des prêtres", 68 | "guardvillagers.configuration.Profession Whitelist for golem repair ai": "Liste blanche des professions pour l’IA de réparation des golems" 69 | } 70 | -------------------------------------------------------------------------------- /src/main/resources/assets/guardvillagers/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "entity.guardvillagers.guard": "Guard", 3 | "item.guardvillagers.guard_spawn_egg": "Guard Spawn Egg", 4 | "item.guardvillagers.illusioner_spawn_egg": "Illusioner Spawn Egg", 5 | "item.guardvillagers.iron_golem_spawn_egg": "Iron Golem Spawn Egg", 6 | "item.guardvillagers.snow_golem_spawn_egg": "Snow Golem Spawn Egg", 7 | "guardinventory.health": "Health: %s", 8 | "guardinventory.armor": "Armor: %s", 9 | "subtitles.entity.guard.ambient": "Guard mumbles", 10 | "subtitles.entity.guard.hurt": "Guard hurts", 11 | "subtitles.entity.guard.death": "Guard dies", 12 | "guardvillagers.advancements.adventure.recruit_guard.description": "Give a sword to an unemployed villager to help defend the village", 13 | "guardvillagers.advancements.adventure.recruit_guard.title": "We Want You, as a New Recruit!", 14 | "guardvillagers.configuration.raids and illagers": "Raids and Illagers", 15 | "guardvillagers.config.cleric": "Allow clerics to heal players and villagers?", 16 | "guardvillagers.configuration.Have baby villagers have big heads like in bedrock?": "Have baby villagers have big heads like in bedrock?", 17 | "guardvillagers.configuration.Guard follow range": "Guard follow range", 18 | "guardvillagers.configuration.villager stuff": "villager stuff", 19 | "guardvillagers.config.range": "This is the range in which the guards will be aggroed to mobs that are attacking villagers.", 20 | "guardvillagers.config.RaidAnimals": "Allow raiders to attack farm animals during raids?", 21 | "guardvillagers.config.IllagersRunFromPolarBears": "Allow Illagers to run from polar bears", 22 | "guardvillagers.config.armorvillager": "Allow armorers and weaponsmiths repair guard items when down below half durability?", 23 | "guardvillagers.config.steveModel": "Enable steve model for Guards", 24 | "guardvillagers.config.hotvPatrolPoint": "Require hero of the village for guards to patrol areas", 25 | "guardvillagers.config.hotv": "This will make it so villagers will only be converted into guards if the player has hero of the village", 26 | "guardvillagers.config.blacksmith": "Allow blacksmiths to repair Iron Golems below 80 health", 27 | "guardvillagers.configuration.Chance to drop equipment": "Chance to drop equipment", 28 | "guardvillagers.config.GuardFormation": "Enable shield guards to stay close together in combat", 29 | "guardvillagers.configuration.Allow guards to naturally patrol villages? This feature can cause lag if a lot of guards are spawned": ".Allow guards to naturally patrol villages? This feature can cause lag if a lot of guards are spawned", 30 | "guardvillagers.config.guardArrows": "Allow guard arrows to hit villager-like mobs?", 31 | "guardvillagers.config.AttackAllMobs": "Allow guards to attack all mobs", 32 | "guardvillagers.config.VillagersRunFromPolarBears": "Allow villagers to run from polar bears?", 33 | "guardvillagers.configuration.Allow guards to teleport if following the player": "Allow guards to teleport if following the player", 34 | "guardvillagers.configuration.Have guards randomly spawn with biome variants?": "Have guards randomly spawn with biome variants?", 35 | "guardvillagers.config.FriendlyFire": "Have guards attempt to avoid firing into other friendly mobs?", 36 | "guardvillagers.config.amountofHealthRegenerated": "Amount of hearts regenerated", 37 | "guardvillagers.configuration.How low of a reputation of a player should have to be instantly aggroed upon by guards and golems?": "How low of a reputation of a player should have to be instantly aggroed upon by guards and golems?", 38 | "guardvillagers.configuration.guard stuff": "guard stuff", 39 | "guardvillagers.config.WitchesVillager": "Allow witches to attack villagers", 40 | "guardvillagers.config.GuardsOpenDoors": "Allow guards to open doors", 41 | "guardvillagers.configuration.mob ai in general": "mob ai in general", 42 | "guardvillagers.configuration.Guard speed": "Guard speed", 43 | "guardvillagers.configuration.Mob Whitelist": "Whitelist of mobs for guards to attack", 44 | "guardvillagers.configuration.Have guards only follow the player if they have hero of the village?": "Have guards only follow the player if they have hero of the village?", 45 | "guardvillagers.configuration.Guard health": "Guard health", 46 | "guardvillagers.configuration.Minimum reputation requirement for guards to give you access to their inventories": "Minimum reputation requirement for guards to give you access to their inventories", 47 | "guardvillagers.configuration.All mobs attack guards": "All mobs attack guards", 48 | "guardvillagers.configuration.Mob Blacklist": "Mob Blacklist", 49 | "guardvillagers.config.GuardRaiseShield": "Allow guards to permanently raise their shields?", 50 | "guardvillagers.configuration.Allow Iron Golems to float on water?": "Allow Iron Golems to float on water?", 51 | "guardvillagers.configuration.golem stuff": "golem stuff", 52 | "guardvillagers.config.hotvArmor": "Allow players to give guards stuff only if they have the hero of the village effect?", 53 | "guardvillagers.config.multifollow": "Allow the player to right click on bells to mass order guards to follow them?", 54 | "guardvillagers.configuration.Structure pieces that spawn guards": "Structure pieces that spawn guards", 55 | "guardvillagers.configuration.How many guards should spawn in a village?": "How many guards should spawn in a village?", 56 | "guardvillagers.configuration.Allow guards to convert to zombie villagers upon being killed by zombies?": "Allow guards to convert to zombie villagers upon being killed by zombies?", 57 | "guardvillagers.configuration.Guard crossbow attack radius": "Guard crossbow attack radius", 58 | "stat.guardvillagers.guards_made": "Guards created", 59 | "subtitles.entity.guard.yes": "Guard agrees", 60 | "subtitles.entity.guard.no": "Guard disagrees", 61 | "guardvillagers.configuration.Chance for guards to lose durability": "Chance for guards to lose durability", 62 | "guardvillagers.configuration.Profession Whitelist for guard conversion": "Profession Whitelist for guard conversion", 63 | "guardvillagers.configuration.How many times a villager can heal a guard's equipment in one day": "How many times a villager can heal a guard's equipment in one day", 64 | "guardvillagers.configuration.How many times a cleric can heal a guard in one day": "How many times a cleric can heal a guard in one day", 65 | "guardvillagers.configuration.Profession Whitelist for guard weaponry repair ai": "Profession Whitelist for guard weaponry repair ai", 66 | "guardvillagers.configuration.How many times a smith villager can heal a golem in one day": "How many times a smith villager can heal a golem in one day", 67 | "guardvillagers.configuration.Profession Whitelist for healing ai for clerics": "Profession Whitelist for healing ai for clerics", 68 | "guardvillagers.configuration.Profession Whitelist for golem repair ai": "Profession Whitelist for golem repair ai", 69 | "guardvillagers.configuration.Display guard health in icons": "Display guard health in icons", 70 | "guardvillagers.configuration.Mobs that guards actively protect when they get hurt": "Mobs that guards actively protect when they get hurt", 71 | "guardvillagers.configuration.Angle of how ranged guards determine if a friendly mob is infront of them before firing": "Angle of how ranged guards determine if a friendly mob is infront of them before firing", 72 | "guardvillagers.configuration.Depth value for guards fighting underwater mobs": "Depth value for guards fighting underwater mobs", 73 | "guardvillagers.configuration.Allow guards to sink temporarily to fight mobs that are under water?": "Allow guards to sink temporarily to fight mobs that are under water?", 74 | "guardvillagers.configuration.Mobs that guards actively protect when they get targeted": "Mobs that guards actively protect when they get targeted" 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/client/models/GuardModel.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.client.models; 2 | 3 | import net.minecraft.client.model.AnimationUtils; 4 | import net.minecraft.client.model.HumanoidModel; 5 | import net.minecraft.client.model.geom.ModelPart; 6 | import net.minecraft.client.model.geom.PartPose; 7 | import net.minecraft.client.model.geom.builders.CubeDeformation; 8 | import net.minecraft.client.model.geom.builders.CubeListBuilder; 9 | import net.minecraft.client.model.geom.builders.LayerDefinition; 10 | import net.minecraft.client.model.geom.builders.MeshDefinition; 11 | import net.minecraft.client.model.geom.builders.PartDefinition; 12 | import net.minecraft.util.Mth; 13 | import net.minecraft.world.InteractionHand; 14 | import net.minecraft.world.entity.EquipmentSlot; 15 | import net.minecraft.world.entity.HumanoidArm; 16 | import net.minecraft.world.item.ArmorItem; 17 | import net.minecraft.world.item.ItemStack; 18 | import net.minecraft.world.item.ProjectileWeaponItem; 19 | import net.minecraft.world.item.UseAnim; 20 | import net.neoforged.fml.ModList; 21 | import tallestegg.guardvillagers.ModCompat; 22 | import tallestegg.guardvillagers.common.entities.Guard; 23 | 24 | public class GuardModel extends HumanoidModel { 25 | public ModelPart Nose = this.head.getChild("nose"); 26 | public ModelPart quiver = this.body.getChild("quiver"); 27 | public ModelPart ArmLShoulderPad = this.rightArm.getChild("shoulderPad_left"); 28 | public ModelPart ArmRShoulderPad = this.leftArm.getChild("shoulderPad_right"); 29 | 30 | public GuardModel(ModelPart part) { 31 | super(part); 32 | } 33 | 34 | public static LayerDefinition createBodyLayer() { 35 | MeshDefinition meshdefinition = HumanoidModel.createMesh(CubeDeformation.NONE, 0.0F); 36 | PartDefinition partdefinition = meshdefinition.getRoot(); 37 | PartDefinition torso = partdefinition.addOrReplaceChild("body", CubeListBuilder.create().texOffs(52, 50) 38 | .addBox(-4.0F, 0.0F, -2.0F, 8, 12, 4, new CubeDeformation(0.25F)), PartPose.offset(0.0F, 0.0F, 0.0F)); 39 | PartDefinition head = partdefinition.addOrReplaceChild("head", CubeListBuilder.create().texOffs(49, 99) 40 | .addBox(-4.0F, -10.0F, -4.0F, 8, 10, 8, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 1.0F, 0.0F)); 41 | PartDefinition rightArm = partdefinition.addOrReplaceChild("right_arm", CubeListBuilder.create().texOffs(32, 75) 42 | .mirror().addBox(-3.0F, -2.0F, -2.0F, 4, 12, 4, new CubeDeformation(0.0F)), 43 | PartPose.offset(-5.0F, 2.0F, 0.0F)); 44 | PartDefinition leftArm = partdefinition.addOrReplaceChild("left_arm", CubeListBuilder.create().texOffs(33, 48) 45 | .addBox(-1.0F, -2.0F, -2.0F, 4, 12, 4, new CubeDeformation(0.0F)), PartPose.offset(5.0F, 2.0F, 0.0F)); 46 | torso.addOrReplaceChild("quiver", CubeListBuilder.create().texOffs(100, 0).addBox(-2.5F, -2.0F, 0.0F, 5, 10, 5, 47 | new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.5F, 3.0F, 2.3F, 0.0F, 0.0F, 0.2617993877991494F)); 48 | head.addOrReplaceChild("nose", 49 | CubeListBuilder.create().texOffs(54, 0).addBox(-1.0F, 0.0F, -2.0F, 2, 4, 2, new CubeDeformation(0.0F)), 50 | PartPose.offset(0.0F, -3.0F, -4.0F)); 51 | partdefinition.addOrReplaceChild("right_leg", CubeListBuilder.create().texOffs(16, 48).mirror().addBox(-2.0F, 52 | 0.0F, -2.0F, 4, 12, 4, new CubeDeformation(0.0F)), PartPose.offset(-1.9F, 12.0F, 0.0F)); 53 | partdefinition.addOrReplaceChild("left_leg", CubeListBuilder.create().texOffs(16, 28).addBox(-2.0F, 0.0F, -2.0F, 54 | 4, 12, 4, new CubeDeformation(0.0F)), PartPose.offset(1.9F, 12.0F, 0.0F)); 55 | leftArm.addOrReplaceChild("shoulderPad_right", 56 | CubeListBuilder.create().texOffs(72, 33).mirror().addBox(0.0F, 0.0F, -3.0F, 5, 3, 6, new CubeDeformation(0.0F)), 57 | PartPose.offsetAndRotation(-0.5F, -3.5F, 0.0F, 0.0F, 0.0F, 0.3490658503988659F)); 58 | rightArm.addOrReplaceChild("shoulderPad_left", 59 | CubeListBuilder.create().texOffs(72, 33).addBox(-5.0F, 0.0F, -3.0F, 5, 3, 6, new CubeDeformation(0.0F)), 60 | PartPose.offsetAndRotation(0.5F, -3.5F, 0.0F, 0.0F, 0.0F, -0.3490658503988659F)); 61 | partdefinition.addOrReplaceChild("hat", CubeListBuilder.create().texOffs(0, 0).addBox(-4.5F, -11.0F, -4.5F, 9, 62 | 11, 9, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F)); 63 | return LayerDefinition.create(meshdefinition, 128, 128); 64 | } 65 | 66 | @Override 67 | public void setupAnim(Guard entityIn, float limbSwing, float limbSwingAmount, float ageInTicks, 68 | float netbipedHeadYaw, float bipedHeadPitch) { 69 | super.setupAnim(entityIn, limbSwing, limbSwingAmount, ageInTicks, netbipedHeadYaw, bipedHeadPitch); 70 | ItemStack itemstack = entityIn.getItemInHand(InteractionHand.MAIN_HAND); 71 | boolean isHoldingShootable = itemstack.getItem() instanceof ProjectileWeaponItem || (ModList.get().isLoaded("musketmod") && ModCompat.isHoldingMusket(itemstack)); 72 | this.quiver.visible = isHoldingShootable; 73 | boolean hasChestplate = entityIn.getItemBySlot(EquipmentSlot.CHEST).getItem() instanceof ArmorItem; 74 | this.ArmLShoulderPad.visible = !hasChestplate; 75 | this.ArmRShoulderPad.visible = !hasChestplate; 76 | if (entityIn.getKickTicks() > 0) { 77 | float f1 = 1.0F - (float) Mth.abs(10 - 2 * entityIn.getKickTicks()) / 10.0F; 78 | this.rightLeg.xRot = Mth.lerp(f1, this.rightLeg.xRot, -1.40F); 79 | } 80 | double speed = 0.005D; 81 | if (this.attackTime == 0.0F && entityIn.isAggressive() && !isHoldingShootable && entityIn.getDeltaMovement().horizontalDistanceSqr() > speed && !entityIn.getMainHandItem().isEmpty() && !entityIn.isBlocking()) { 82 | this.holdWeaponHigh(entityIn); 83 | } 84 | if (entityIn.getMainArm() == HumanoidArm.RIGHT) { 85 | this.eatingAnimationRightHand(InteractionHand.MAIN_HAND, entityIn, ageInTicks); 86 | this.eatingAnimationLeftHand(InteractionHand.OFF_HAND, entityIn, ageInTicks); 87 | } else { 88 | this.eatingAnimationRightHand(InteractionHand.OFF_HAND, entityIn, ageInTicks); 89 | this.eatingAnimationLeftHand(InteractionHand.MAIN_HAND, entityIn, ageInTicks); 90 | } 91 | } 92 | 93 | public void eatingAnimationRightHand(InteractionHand hand, Guard entity, float ageInTicks) { 94 | ItemStack itemstack = entity.getItemInHand(hand); 95 | boolean drinkingoreating = itemstack.getUseAnimation() == UseAnim.EAT 96 | || itemstack.getUseAnimation() == UseAnim.DRINK; 97 | if (entity.isEating() && drinkingoreating) { 98 | this.rightArm.yRot = -0.5F; 99 | this.rightArm.xRot = -1.3F; 100 | this.rightArm.zRot = Mth.cos(ageInTicks) * 0.1F; 101 | this.head.xRot = Mth.cos(ageInTicks) * 0.2F; 102 | this.head.yRot = 0.0F; 103 | this.hat.copyFrom(head); 104 | } 105 | } 106 | 107 | public void eatingAnimationLeftHand(InteractionHand hand, Guard entity, float ageInTicks) { 108 | ItemStack itemstack = entity.getItemInHand(hand); 109 | boolean drinkingoreating = itemstack.getUseAnimation() == UseAnim.EAT 110 | || itemstack.getUseAnimation() == UseAnim.DRINK; 111 | if (entity.isEating() && drinkingoreating) { 112 | this.leftArm.yRot = 0.5F; 113 | this.leftArm.xRot = -1.3F; 114 | this.leftArm.zRot = Mth.cos(ageInTicks) * 0.1F; 115 | this.head.xRot = Mth.cos(ageInTicks) * 0.2F; 116 | this.head.yRot = 0.0F; 117 | this.hat.copyFrom(head); 118 | } 119 | } 120 | 121 | private void holdWeaponHigh(Guard pMob) { 122 | if (pMob.isLeftHanded()) { 123 | this.leftArm.xRot = -1.8F; 124 | } else { 125 | this.rightArm.xRot = -1.8F; 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/client/renderer/GuardRenderer.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.client.renderer; 2 | 3 | import com.mojang.blaze3d.vertex.PoseStack; 4 | import net.minecraft.ResourceLocationException; 5 | import net.minecraft.client.Minecraft; 6 | import net.minecraft.client.model.EntityModel; 7 | import net.minecraft.client.model.HumanoidArmorModel; 8 | import net.minecraft.client.model.HumanoidModel; 9 | import net.minecraft.client.renderer.MultiBufferSource; 10 | import net.minecraft.client.renderer.entity.EntityRendererProvider; 11 | import net.minecraft.client.renderer.entity.HumanoidMobRenderer; 12 | import net.minecraft.client.renderer.entity.RenderLayerParent; 13 | import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer; 14 | import net.minecraft.client.renderer.entity.layers.RenderLayer; 15 | import net.minecraft.client.renderer.texture.AbstractTexture; 16 | import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; 17 | import net.minecraft.resources.ResourceLocation; 18 | import net.minecraft.world.InteractionHand; 19 | import net.minecraft.world.entity.HumanoidArm; 20 | import net.minecraft.world.item.CrossbowItem; 21 | import net.minecraft.world.item.ItemStack; 22 | import net.minecraft.world.item.UseAnim; 23 | import net.neoforged.fml.ModList; 24 | import tallestegg.guardvillagers.GuardVillagers; 25 | import tallestegg.guardvillagers.ModCompat; 26 | import tallestegg.guardvillagers.client.GuardClientEvents; 27 | import tallestegg.guardvillagers.client.models.GuardArmorModel; 28 | import tallestegg.guardvillagers.client.models.GuardModel; 29 | import tallestegg.guardvillagers.client.models.GuardSteveModel; 30 | import tallestegg.guardvillagers.common.entities.Guard; 31 | import tallestegg.guardvillagers.configuration.GuardConfig; 32 | 33 | import javax.annotation.Nullable; 34 | 35 | public class GuardRenderer extends HumanoidMobRenderer> { 36 | private final HumanoidModel steve; 37 | private final HumanoidModel normal = this.getModel(); 38 | private static final ResourceLocation guardTextures = ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, 39 | "textures/entity/guard/guard" + (GuardConfig.CLIENT.GuardSteve.get() ? "_steve" : "") + ".png"); 40 | 41 | public GuardRenderer(EntityRendererProvider.Context context) { 42 | super(context, new GuardModel(context.bakeLayer(GuardClientEvents.GUARD)), 0.5F); 43 | this.steve = new GuardSteveModel(context.bakeLayer(GuardClientEvents.GUARD_STEVE)); 44 | if (GuardConfig.CLIENT.GuardSteve.get()) 45 | this.model = steve; 46 | else 47 | this.model = normal; 48 | this.addLayer(new GuardVariantLayer(this)); 49 | this.addLayer(new HumanoidArmorLayer(this, !GuardConfig.CLIENT.GuardSteve.get() ? new GuardArmorModel(context.bakeLayer(GuardClientEvents.GUARD_ARMOR_INNER)) : new HumanoidArmorModel<>(context.bakeLayer(GuardClientEvents.GUARD_PLAYER_ARMOR_INNER)), 50 | !GuardConfig.CLIENT.GuardSteve.get() ? new GuardArmorModel(context.bakeLayer(GuardClientEvents.GUARD_ARMOR_OUTER)) : new HumanoidArmorModel<>(context.bakeLayer(GuardClientEvents.GUARD_PLAYER_ARMOR_OUTER)), context.getModelManager())); 51 | } 52 | 53 | @Override 54 | public void render(Guard entityIn, float entityYaw, float partialTicks, PoseStack matrixStackIn, MultiBufferSource bufferIn, int packedLightIn) { 55 | this.setModelVisibilities(entityIn); 56 | super.render(entityIn, entityYaw, partialTicks, matrixStackIn, bufferIn, packedLightIn); 57 | } 58 | 59 | private void setModelVisibilities(Guard entityIn) { 60 | HumanoidModel guardmodel = this.getModel(); 61 | ItemStack itemstack = entityIn.getMainHandItem(); 62 | ItemStack itemstack1 = entityIn.getOffhandItem(); 63 | guardmodel.setAllVisible(true); 64 | HumanoidModel.ArmPose bipedmodel$armpose = this.getArmPose(entityIn, itemstack, itemstack1, 65 | InteractionHand.MAIN_HAND); 66 | HumanoidModel.ArmPose bipedmodel$armpose1 = this.getArmPose(entityIn, itemstack, itemstack1, 67 | InteractionHand.OFF_HAND); 68 | guardmodel.crouching = entityIn.isCrouching(); 69 | if (entityIn.getMainArm() == HumanoidArm.RIGHT) { 70 | guardmodel.rightArmPose = bipedmodel$armpose; 71 | guardmodel.leftArmPose = bipedmodel$armpose1; 72 | } else { 73 | guardmodel.rightArmPose = bipedmodel$armpose1; 74 | guardmodel.leftArmPose = bipedmodel$armpose; 75 | } 76 | } 77 | 78 | private HumanoidModel.ArmPose getArmPose(Guard entityIn, ItemStack itemStackMain, ItemStack itemStackOff, 79 | InteractionHand handIn) { 80 | HumanoidModel.ArmPose bipedmodel$armpose = HumanoidModel.ArmPose.EMPTY; 81 | ItemStack itemstack = handIn == InteractionHand.MAIN_HAND ? itemStackMain : itemStackOff; 82 | if (!itemstack.isEmpty()) { 83 | bipedmodel$armpose = HumanoidModel.ArmPose.ITEM; 84 | if (entityIn.getUseItemRemainingTicks() > 0) { 85 | UseAnim useaction = itemstack.getUseAnimation(); 86 | switch (useaction) { 87 | case BLOCK: 88 | bipedmodel$armpose = HumanoidModel.ArmPose.BLOCK; 89 | break; 90 | case BOW: 91 | bipedmodel$armpose = HumanoidModel.ArmPose.BOW_AND_ARROW; 92 | break; 93 | case SPEAR: 94 | bipedmodel$armpose = HumanoidModel.ArmPose.THROW_SPEAR; 95 | break; 96 | case CROSSBOW: 97 | if (handIn == entityIn.getUsedItemHand()) { 98 | bipedmodel$armpose = HumanoidModel.ArmPose.CROSSBOW_CHARGE; 99 | } 100 | break; 101 | default: 102 | bipedmodel$armpose = HumanoidModel.ArmPose.EMPTY; 103 | break; 104 | } 105 | if (ModList.get().isLoaded("musketmod")) 106 | bipedmodel$armpose = ModCompat.reloadMusketAnim(itemstack, handIn, entityIn, bipedmodel$armpose); 107 | } else { 108 | if (ModList.get().isLoaded("musketmod")) 109 | bipedmodel$armpose = ModCompat.holdMusketAnim(itemstack, entityIn); 110 | boolean flag1 = itemStackMain.getItem() instanceof CrossbowItem; 111 | boolean flag2 = itemStackOff.getItem() instanceof CrossbowItem; 112 | if (flag1 && entityIn.isAggressive()) { 113 | bipedmodel$armpose = HumanoidModel.ArmPose.CROSSBOW_HOLD; 114 | } 115 | 116 | if (flag2 && itemStackMain.getItem().getUseAnimation(itemStackMain) == UseAnim.NONE 117 | && entityIn.isAggressive()) { 118 | bipedmodel$armpose = HumanoidModel.ArmPose.CROSSBOW_HOLD; 119 | } 120 | } 121 | } 122 | return bipedmodel$armpose; 123 | } 124 | 125 | @Override 126 | protected void scale(Guard entitylivingbaseIn, PoseStack matrixStackIn, float partialTickTime) { 127 | matrixStackIn.scale(0.9375F, 0.9375F, 0.9375F); 128 | } 129 | 130 | @Nullable 131 | @Override 132 | public ResourceLocation getTextureLocation(Guard entity) { 133 | return guardTextures; 134 | } 135 | 136 | public static class GuardVariantLayer extends RenderLayer> { 137 | public GuardVariantLayer(RenderLayerParent renderer) { 138 | super(renderer); 139 | } 140 | 141 | @Override 142 | public void render(PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, Guard livingEntity, float limbSwing, float limbSwingAmount, float partialTick, float ageInTicks, float netHeadYaw, float headPitch) { 143 | if (!livingEntity.isInvisible()) { 144 | EntityModel m = this.getParentModel(); 145 | String guardSteve = GuardConfig.CLIENT.GuardSteve.get() ? "_steve" : ""; 146 | ResourceLocation resourcelocation = ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "textures/entity/guard/guard_variants/guard" + guardSteve + "_" + livingEntity.getVariant() + ".png"); 147 | AbstractTexture abstracttexture = Minecraft.getInstance().getTextureManager().getTexture(resourcelocation); 148 | if (abstracttexture == MissingTextureAtlasSprite.getTexture()) 149 | resourcelocation = ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "textures/entity/guard/guard_variants/guard" + guardSteve + "_plains.png"); 150 | renderColoredCutoutModel(m, resourcelocation, poseStack, bufferSource, packedLight, livingEntity, -1); 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/client/gui/GuardInventoryScreen.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.client.gui; 2 | 3 | import com.mojang.blaze3d.systems.RenderSystem; 4 | import net.minecraft.client.gui.Gui; 5 | import net.minecraft.client.gui.GuiGraphics; 6 | import net.minecraft.client.gui.components.ImageButton; 7 | import net.minecraft.client.gui.components.WidgetSprites; 8 | import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; 9 | import net.minecraft.client.gui.screens.inventory.InventoryScreen; 10 | import net.minecraft.client.renderer.GameRenderer; 11 | import net.minecraft.network.chat.Component; 12 | import net.minecraft.resources.ResourceLocation; 13 | import net.minecraft.util.Mth; 14 | import net.minecraft.world.effect.MobEffects; 15 | import net.minecraft.world.entity.player.Inventory; 16 | import net.minecraft.world.entity.player.Player; 17 | import net.neoforged.neoforge.network.PacketDistributor; 18 | import tallestegg.guardvillagers.GuardVillagers; 19 | import tallestegg.guardvillagers.configuration.GuardConfig; 20 | import tallestegg.guardvillagers.common.entities.Guard; 21 | import tallestegg.guardvillagers.common.entities.GuardContainer; 22 | import tallestegg.guardvillagers.networking.GuardFollowPacket; 23 | import tallestegg.guardvillagers.networking.GuardSetPatrolPosPacket; 24 | 25 | public class GuardInventoryScreen extends AbstractContainerScreen { 26 | private static final ResourceLocation GUARD_GUI_TEXTURES = ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "textures/container/inventory.png"); 27 | private static final WidgetSprites GUARD_FOLLOWING_ICONS = new WidgetSprites(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "following/following"), ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "following/following_highlighted")); 28 | private static final WidgetSprites GUARD_NOT_FOLLOWING_ICONS = new WidgetSprites(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "following/not_following"), ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "following/not_following_highlighted")); 29 | private static final WidgetSprites GUARD_PATROLLING_ICONS = new WidgetSprites(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "patrolling/patrolling1"), ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "patrolling/patrolling2")); 30 | private static final WidgetSprites GUARD_NOT_PATROLLING_ICONS = new WidgetSprites(ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "patrolling/notpatrolling1"), ResourceLocation.fromNamespaceAndPath(GuardVillagers.MODID, "patrolling/notpatrolling2")); 31 | private static final ResourceLocation ARMOR_EMPTY_SPRITE = ResourceLocation.withDefaultNamespace("hud/armor_empty"); 32 | private static final ResourceLocation ARMOR_HALF_SPRITE = ResourceLocation.withDefaultNamespace("hud/armor_half"); 33 | private static final ResourceLocation ARMOR_FULL_SPRITE = ResourceLocation.withDefaultNamespace("hud/armor_full"); 34 | 35 | private final Guard guard; 36 | private Player player; 37 | private float mousePosX; 38 | private float mousePosY; 39 | private boolean buttonPressed; 40 | 41 | public GuardInventoryScreen(GuardContainer container, Inventory playerInventory, Guard guard) { 42 | super(container, playerInventory, guard.getDisplayName()); 43 | this.guard = guard; 44 | this.titleLabelX = 80; 45 | this.inventoryLabelX = 100; 46 | this.player = playerInventory.player; 47 | } 48 | 49 | @Override 50 | public void init() { 51 | super.init(); 52 | if (GuardConfig.COMMON.followHero.get() && player.hasEffect(MobEffects.HERO_OF_THE_VILLAGE) || !GuardConfig.COMMON.followHero.get()) { 53 | this.addRenderableWidget(new GuardGuiButton(this.leftPos + 100, this.height / 2 - 40, 20, 18, GUARD_FOLLOWING_ICONS, GUARD_NOT_FOLLOWING_ICONS, true, (p_214086_1_) -> { 54 | PacketDistributor.sendToServer(new GuardFollowPacket(guard.getId())); 55 | })); 56 | } 57 | if (GuardConfig.COMMON.setGuardPatrolHotv.get() && player.hasEffect(MobEffects.HERO_OF_THE_VILLAGE) || !GuardConfig.COMMON.setGuardPatrolHotv.get()) { 58 | this.addRenderableWidget(new GuardGuiButton(this.leftPos + 120, this.height / 2 - 40, 20, 18, GUARD_PATROLLING_ICONS, GUARD_NOT_PATROLLING_ICONS, false, (p_214086_1_) -> { 59 | buttonPressed = !buttonPressed; 60 | PacketDistributor.sendToServer(new GuardSetPatrolPosPacket(guard.getId(), buttonPressed)); 61 | })); 62 | } 63 | } 64 | 65 | @Override 66 | protected void renderBg(GuiGraphics graphics, float partialTicks, int x, int y) { 67 | RenderSystem.setShader(GameRenderer::getPositionTexShader); 68 | RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); 69 | RenderSystem.setShaderTexture(0, GUARD_GUI_TEXTURES); 70 | int i = (this.width - this.imageWidth) / 2; 71 | int j = (this.height - this.imageHeight) / 2; 72 | graphics.blit(GUARD_GUI_TEXTURES, i, j, 0, 0, this.imageWidth, this.imageHeight); 73 | InventoryScreen.renderEntityInInventoryFollowsMouse(graphics, i + 26, j + 8, i + 75, j + 78, 30, 0.0625F, this.mousePosX, this.mousePosY, this.guard); 74 | } 75 | 76 | @Override 77 | protected void renderLabels(GuiGraphics graphics, int x, int y) { 78 | super.renderLabels(graphics, x, y); 79 | int health = Mth.ceil(guard.getHealth()); 80 | int armor = guard.getArmorValue(); 81 | Component guardHealthText = Component.translatable("guardinventory.health", health); 82 | Component guardArmorText = Component.translatable("guardinventory.armor", armor); 83 | int yValueWithOrWithoutArmor = armor <= 0 ? 20 : 30; 84 | if (!GuardConfig.CLIENT.guardInventoryNumbers.get() || guard.getMaxHealth() > 20) { 85 | graphics.drawString(font, guardHealthText, 80, 30, 4210752, false); 86 | } else if (guard.getMaxHealth() <= 20) { 87 | for (int i = 0; i < (guard.getMaxHealth() * 0.5); i++) { 88 | int heartXValue = i * 8 + 80; 89 | this.renderHeart(graphics, Gui.HeartType.CONTAINER, heartXValue, yValueWithOrWithoutArmor, false); 90 | } 91 | for (int i = 0; i < health / 2; i++) { 92 | int heartXValue = i * 8 + 80; 93 | if (health % 2 != 0 && health / 2 == i + 1) { 94 | this.renderHeart(graphics, Gui.HeartType.NORMAL, heartXValue, yValueWithOrWithoutArmor, true); 95 | } else { 96 | this.renderHeart(graphics, Gui.HeartType.NORMAL, heartXValue, yValueWithOrWithoutArmor, false); 97 | } 98 | } 99 | } 100 | if (!GuardConfig.CLIENT.guardInventoryNumbers.get()) { 101 | graphics.drawString(font, guardArmorText, 80, 20, 4210752, false); 102 | } else { 103 | if (armor > 0) { 104 | RenderSystem.enableBlend(); 105 | for (int k = 0; k < 10; k++) { 106 | int l = k * 8 + 80; 107 | if (k * 2 + 1 < armor) { 108 | graphics.blitSprite(ARMOR_FULL_SPRITE, l, 20, 9, 9); 109 | } 110 | 111 | if (k * 2 + 1 == armor) { 112 | graphics.blitSprite(ARMOR_HALF_SPRITE, l, 20, 9, 9); 113 | } 114 | 115 | if (k * 2 + 1 > armor) { 116 | graphics.blitSprite(ARMOR_EMPTY_SPRITE, l, 20, 9, 9); 117 | } 118 | } 119 | RenderSystem.disableBlend(); 120 | } 121 | } 122 | } 123 | 124 | private void renderHeart(GuiGraphics guiGraphics, Gui.HeartType heartType, int x, int y, boolean halfHeart) { 125 | RenderSystem.enableBlend(); 126 | guiGraphics.blitSprite(heartType.getSprite(false, halfHeart, false), x, y, 9, 9); 127 | RenderSystem.disableBlend(); 128 | } 129 | 130 | 131 | @Override 132 | public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { 133 | this.renderBackground(graphics, mouseX, mouseX, partialTicks); 134 | this.mousePosX = (float) mouseX; 135 | this.mousePosY = (float) mouseY; 136 | super.render(graphics, mouseX, mouseY, partialTicks); 137 | this.renderTooltip(graphics, mouseX, mouseY); 138 | } 139 | 140 | class GuardGuiButton extends ImageButton { 141 | private WidgetSprites texture; 142 | private WidgetSprites newTexture; 143 | private boolean isFollowButton; 144 | 145 | public GuardGuiButton(int xIn, int yIn, int widthIn, int heightIn, WidgetSprites resourceLocationIn, WidgetSprites newTexture, boolean isFollowButton, OnPress onPressIn) { 146 | super(xIn, yIn, widthIn, heightIn, resourceLocationIn, onPressIn); 147 | this.texture = resourceLocationIn; 148 | this.newTexture = newTexture; 149 | this.isFollowButton = isFollowButton; 150 | } 151 | 152 | public boolean requirementsForTexture() { 153 | boolean following = GuardInventoryScreen.this.guard.isFollowing(); 154 | boolean patrol = GuardInventoryScreen.this.guard.isPatrolling(); 155 | return this.isFollowButton ? following : patrol; 156 | } 157 | 158 | @Override 159 | public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) { 160 | WidgetSprites icon = this.requirementsForTexture() ? this.texture : this.newTexture; 161 | ResourceLocation resourcelocation = icon.get(this.isActive(), this.isHoveredOrFocused()); 162 | graphics.blitSprite(resourcelocation, this.getX(), this.getY(), this.width, this.height); 163 | } 164 | } 165 | 166 | } -------------------------------------------------------------------------------- /src/main/java/tallestegg/guardvillagers/common/entities/GuardContainer.java: -------------------------------------------------------------------------------- 1 | package tallestegg.guardvillagers.common.entities; 2 | 3 | import com.mojang.datafixers.util.Pair; 4 | 5 | import net.minecraft.world.entity.player.Player; 6 | import net.minecraft.world.entity.player.Inventory; 7 | import net.minecraft.world.entity.EquipmentSlot; 8 | import net.minecraft.world.Container; 9 | import net.minecraft.world.inventory.AbstractContainerMenu; 10 | import net.minecraft.world.inventory.InventoryMenu; 11 | import net.minecraft.world.inventory.Slot; 12 | import net.minecraft.world.item.ItemStack; 13 | import net.minecraft.resources.ResourceLocation; 14 | import tallestegg.guardvillagers.GuardVillagers; 15 | 16 | public class GuardContainer extends AbstractContainerMenu { 17 | private final Container guardInventory; 18 | private final Guard guard; 19 | 20 | public GuardContainer(int id, Inventory playerInventory, Container guardInventory, final Guard guard) { 21 | super(null, id); 22 | this.guardInventory = guardInventory; 23 | this.guard = guard; 24 | guardInventory.startOpen(playerInventory.player); 25 | this.addSlot(new Slot(guardInventory, 0, 8, 8) { 26 | @Override 27 | public boolean mayPlace(ItemStack stack) { 28 | return stack.canEquip(EquipmentSlot.HEAD, guard) && GuardVillagers.hotvChecker(playerInventory.player, guard); 29 | } 30 | 31 | @Override 32 | public int getMaxStackSize() { 33 | return 1; 34 | } 35 | 36 | @Override 37 | public void set(ItemStack stack) { 38 | super.set(stack); 39 | guard.setItemSlot(EquipmentSlot.HEAD, stack); 40 | } 41 | 42 | @Override 43 | public boolean mayPickup(Player playerIn) { 44 | return GuardVillagers.hotvChecker(playerInventory.player, guard); 45 | } 46 | 47 | @Override 48 | public Pair getNoItemIcon() { 49 | return Pair.of(InventoryMenu.BLOCK_ATLAS, InventoryMenu.EMPTY_ARMOR_SLOT_HELMET); 50 | } 51 | }); 52 | this.addSlot(new Slot(guardInventory, 1, 8, 26) { 53 | @Override 54 | public boolean mayPlace(ItemStack stack) { 55 | return stack.canEquip(EquipmentSlot.CHEST, guard) && GuardVillagers.hotvChecker(playerInventory.player, guard); 56 | } 57 | 58 | @Override 59 | public int getMaxStackSize() { 60 | return 1; 61 | } 62 | 63 | @Override 64 | public void set(ItemStack stack) { 65 | super.set(stack); 66 | guard.setItemSlot(EquipmentSlot.CHEST, stack); 67 | } 68 | 69 | @Override 70 | public boolean mayPickup(Player playerIn) { 71 | return GuardVillagers.hotvChecker(playerInventory.player, guard); 72 | } 73 | 74 | @Override 75 | public Pair getNoItemIcon() { 76 | return Pair.of(InventoryMenu.BLOCK_ATLAS, InventoryMenu.EMPTY_ARMOR_SLOT_CHESTPLATE); 77 | } 78 | }); 79 | this.addSlot(new Slot(guardInventory, 2, 8, 44) { 80 | @Override 81 | public boolean mayPlace(ItemStack stack) { 82 | return stack.canEquip(EquipmentSlot.LEGS, guard) && GuardVillagers.hotvChecker(playerInventory.player, guard); 83 | } 84 | 85 | @Override 86 | public int getMaxStackSize() { 87 | return 1; 88 | } 89 | 90 | @Override 91 | public void set(ItemStack stack) { 92 | super.set(stack); 93 | guard.setItemSlot(EquipmentSlot.LEGS, stack); 94 | } 95 | 96 | @Override 97 | public boolean mayPickup(Player playerIn) { 98 | return GuardVillagers.hotvChecker(playerInventory.player, guard); 99 | } 100 | 101 | @Override 102 | public Pair getNoItemIcon() { 103 | return Pair.of(InventoryMenu.BLOCK_ATLAS, InventoryMenu.EMPTY_ARMOR_SLOT_LEGGINGS); 104 | } 105 | }); 106 | this.addSlot(new Slot(guardInventory, 3, 8, 62) { 107 | @Override 108 | public boolean mayPlace(ItemStack stack) { 109 | return stack.canEquip(EquipmentSlot.FEET, guard) && GuardVillagers.hotvChecker(playerInventory.player, guard); 110 | } 111 | 112 | @Override 113 | public int getMaxStackSize() { 114 | return 1; 115 | } 116 | 117 | @Override 118 | public void set(ItemStack stack) { 119 | super.set(stack); 120 | guard.setItemSlot(EquipmentSlot.FEET, stack); 121 | } 122 | 123 | @Override 124 | public boolean mayPickup(Player playerIn) { 125 | return GuardVillagers.hotvChecker(playerInventory.player, guard); 126 | } 127 | 128 | @Override 129 | public Pair getNoItemIcon() { 130 | return Pair.of(InventoryMenu.BLOCK_ATLAS, InventoryMenu.EMPTY_ARMOR_SLOT_BOOTS); 131 | } 132 | }); 133 | this.addSlot(new Slot(guardInventory, 4, 77, 62) { 134 | @Override 135 | public boolean mayPlace(ItemStack stack) { 136 | return GuardVillagers.hotvChecker(playerInventory.player, guard); 137 | } 138 | 139 | @Override 140 | public void set(ItemStack stack) { 141 | super.set(stack); 142 | guard.setItemSlot(EquipmentSlot.OFFHAND, stack); 143 | } 144 | 145 | @Override 146 | public boolean mayPickup(Player playerIn) { 147 | return GuardVillagers.hotvChecker(playerInventory.player, guard); 148 | } 149 | 150 | @Override 151 | public Pair getNoItemIcon() { 152 | return Pair.of(InventoryMenu.BLOCK_ATLAS, InventoryMenu.EMPTY_ARMOR_SLOT_SHIELD); 153 | } 154 | }); 155 | 156 | this.addSlot(new Slot(guardInventory, 5, 77, 44) { 157 | @Override 158 | public boolean mayPlace(ItemStack stack) { 159 | return GuardVillagers.hotvChecker(playerInventory.player, guard); 160 | } 161 | 162 | @Override 163 | public boolean mayPickup(Player playerIn) { 164 | return GuardVillagers.hotvChecker(playerIn, guard); 165 | } 166 | 167 | @Override 168 | public void set(ItemStack stack) { 169 | super.set(stack); 170 | guard.setItemSlot(EquipmentSlot.MAINHAND, stack); 171 | } 172 | }); 173 | for (int l = 0; l < 3; ++l) { 174 | for (int j1 = 0; j1 < 9; ++j1) { 175 | this.addSlot(new Slot(playerInventory, j1 + (l + 1) * 9, 8 + j1 * 18, 84 + l * 18)); 176 | } 177 | } 178 | 179 | for (int i1 = 0; i1 < 9; ++i1) { 180 | this.addSlot(new Slot(playerInventory, i1, 8 + i1 * 18, 142)); 181 | } 182 | } 183 | 184 | @Override 185 | public boolean stillValid(Player playerIn) { 186 | return this.guardInventory.stillValid(playerIn) && this.guard.isAlive() && this.guard.distanceTo(playerIn) < 8.0F; 187 | } 188 | 189 | @Override 190 | public ItemStack quickMoveStack(Player playerIn, int index) { 191 | ItemStack itemstack = ItemStack.EMPTY; 192 | Slot slot = this.slots.get(index); 193 | if (slot != null && slot.hasItem()) { 194 | ItemStack itemstack1 = slot.getItem(); 195 | itemstack = itemstack1.copy(); 196 | int i = this.guardInventory.getContainerSize(); 197 | if (index < i) { 198 | if (!this.moveItemStackTo(itemstack1, i, this.slots.size(), true)) { 199 | return ItemStack.EMPTY; 200 | } 201 | } else if (this.getSlot(1).mayPlace(itemstack1) && !this.getSlot(1).hasItem()) { 202 | if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { 203 | return ItemStack.EMPTY; 204 | } 205 | } else if (this.getSlot(0).mayPlace(itemstack1)) { 206 | if (!this.moveItemStackTo(itemstack1, 0, 1, false)) { 207 | return ItemStack.EMPTY; 208 | } 209 | } else if (i <= 2 || !this.moveItemStackTo(itemstack1, 2, i, false)) { 210 | int j = i + 27; 211 | int k = j + 9; 212 | if (index >= j && index < k) { 213 | if (!this.moveItemStackTo(itemstack1, i, j, false)) { 214 | return ItemStack.EMPTY; 215 | } 216 | } else if (index >= i && index < j) { 217 | if (!this.moveItemStackTo(itemstack1, j, k, false)) { 218 | return ItemStack.EMPTY; 219 | } 220 | } else if (!this.moveItemStackTo(itemstack1, j, j, false)) { 221 | return ItemStack.EMPTY; 222 | } 223 | 224 | return ItemStack.EMPTY; 225 | } 226 | 227 | if (itemstack1.isEmpty()) { 228 | slot.set(ItemStack.EMPTY); 229 | } else { 230 | slot.setChanged(); 231 | } 232 | } 233 | 234 | return itemstack; 235 | } 236 | 237 | @Override 238 | public void removed(Player playerIn) { 239 | super.removed(playerIn); 240 | this.guardInventory.stopOpen(playerIn); 241 | this.guard.interacting = false; 242 | } 243 | } -------------------------------------------------------------------------------- /src/main/resources/data/guardvillagers/loot_table/entities/armor_sets/armor.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "guardvillagers:slot", 3 | "pools": [ 4 | { 5 | "rolls": 1, 6 | "entries": [ 7 | { 8 | "type": "minecraft:item", 9 | "name": "minecraft:chainmail_helmet" 10 | } 11 | ], 12 | "functions": [ 13 | { 14 | "function": "guardvillagers:slot", 15 | "slot": "head" 16 | } 17 | ], 18 | "conditions": [ 19 | { 20 | "condition": "minecraft:random_chance", 21 | "chance": 0.012 22 | } 23 | ] 24 | }, 25 | { 26 | "rolls": 1, 27 | "entries": [ 28 | { 29 | "type": "minecraft:item", 30 | "name": "minecraft:chainmail_chestplate" 31 | } 32 | ], 33 | "functions": [ 34 | { 35 | "function": "guardvillagers:slot", 36 | "slot": "chest" 37 | } 38 | ], 39 | "conditions": [ 40 | { 41 | "condition": "minecraft:random_chance", 42 | "chance": 0.012 43 | } 44 | ] 45 | }, 46 | { 47 | "rolls": 1, 48 | "entries": [ 49 | { 50 | "type": "minecraft:item", 51 | "name": "minecraft:chainmail_leggings" 52 | } 53 | ], 54 | "functions": [ 55 | { 56 | "function": "guardvillagers:slot", 57 | "slot": "legs" 58 | } 59 | ], 60 | "conditions": [ 61 | { 62 | "condition": "minecraft:random_chance", 63 | "chance": 0.012 64 | } 65 | ] 66 | }, 67 | { 68 | "rolls": 1, 69 | "entries": [ 70 | { 71 | "type": "minecraft:item", 72 | "name": "minecraft:chainmail_boots" 73 | } 74 | ], 75 | "functions": [ 76 | { 77 | "function": "guardvillagers:slot", 78 | "slot": "feet" 79 | } 80 | ], 81 | "conditions": [ 82 | { 83 | "condition": "minecraft:random_chance", 84 | "chance": 0.012 85 | } 86 | ] 87 | }, 88 | { 89 | "rolls": 1, 90 | "entries": [ 91 | { 92 | "type": "minecraft:item", 93 | "name": "minecraft:diamond_helmet" 94 | } 95 | ], 96 | "functions": [ 97 | { 98 | "function": "guardvillagers:slot", 99 | "slot": "head" 100 | } 101 | ], 102 | "conditions": [ 103 | { 104 | "condition": "minecraft:random_chance", 105 | "chance": 0.0004 106 | } 107 | ] 108 | }, 109 | { 110 | "rolls": 1, 111 | "entries": [ 112 | { 113 | "type": "minecraft:item", 114 | "name": "minecraft:diamond_chestplate" 115 | } 116 | ], 117 | "functions": [ 118 | { 119 | "function": "guardvillagers:slot", 120 | "slot": "chest" 121 | } 122 | ], 123 | "conditions": [ 124 | { 125 | "condition": "minecraft:random_chance", 126 | "chance": 0.0004 127 | } 128 | ] 129 | }, 130 | { 131 | "rolls": 1, 132 | "entries": [ 133 | { 134 | "type": "minecraft:item", 135 | "name": "minecraft:diamond_leggings" 136 | } 137 | ], 138 | "functions": [ 139 | { 140 | "function": "guardvillagers:slot", 141 | "slot": "legs" 142 | } 143 | ], 144 | "conditions": [ 145 | { 146 | "condition": "minecraft:random_chance", 147 | "chance": 0.0004 148 | } 149 | ] 150 | }, 151 | { 152 | "rolls": 1, 153 | "entries": [ 154 | { 155 | "type": "minecraft:item", 156 | "name": "minecraft:diamond_boots" 157 | } 158 | ], 159 | "functions": [ 160 | { 161 | "function": "guardvillagers:slot", 162 | "slot": "feet" 163 | } 164 | ], 165 | "conditions": [ 166 | { 167 | "condition": "minecraft:random_chance", 168 | "chance": 0.0004 169 | } 170 | ] 171 | }, 172 | { 173 | "rolls": 1, 174 | "entries": [ 175 | { 176 | "type": "minecraft:item", 177 | "name": "minecraft:golden_helmet" 178 | } 179 | ], 180 | "functions": [ 181 | { 182 | "function": "guardvillagers:slot", 183 | "slot": "head" 184 | } 185 | ], 186 | "conditions": [ 187 | { 188 | "condition": "minecraft:random_chance", 189 | "chance": 0.033 190 | } 191 | ] 192 | }, 193 | { 194 | "rolls": 1, 195 | "entries": [ 196 | { 197 | "type": "minecraft:item", 198 | "name": "minecraft:golden_chestplate" 199 | } 200 | ], 201 | "functions": [ 202 | { 203 | "function": "guardvillagers:slot", 204 | "slot": "chest" 205 | } 206 | ], 207 | "conditions": [ 208 | { 209 | "condition": "minecraft:random_chance", 210 | "chance": 0.033 211 | } 212 | ] 213 | }, 214 | { 215 | "rolls": 1, 216 | "entries": [ 217 | { 218 | "type": "minecraft:item", 219 | "name": "minecraft:golden_leggings" 220 | } 221 | ], 222 | "functions": [ 223 | { 224 | "function": "guardvillagers:slot", 225 | "slot": "legs" 226 | } 227 | ], 228 | "conditions": [ 229 | { 230 | "condition": "minecraft:random_chance", 231 | "chance": 0.033 232 | } 233 | ] 234 | }, 235 | { 236 | "rolls": 1, 237 | "entries": [ 238 | { 239 | "type": "minecraft:item", 240 | "name": "minecraft:golden_boots" 241 | } 242 | ], 243 | "functions": [ 244 | { 245 | "function": "guardvillagers:slot", 246 | "slot": "feet" 247 | } 248 | ], 249 | "conditions": [ 250 | { 251 | "condition": "minecraft:random_chance", 252 | "chance": 0.033 253 | } 254 | ] 255 | }, 256 | { 257 | "rolls": 1, 258 | "entries": [ 259 | { 260 | "type": "minecraft:item", 261 | "name": "minecraft:iron_helmet" 262 | } 263 | ], 264 | "functions": [ 265 | { 266 | "function": "guardvillagers:slot", 267 | "slot": "head" 268 | } 269 | ], 270 | "conditions": [ 271 | { 272 | "condition": "minecraft:random_chance", 273 | "chance": 0.013 274 | } 275 | ] 276 | }, 277 | { 278 | "rolls": 1, 279 | "entries": [ 280 | { 281 | "type": "minecraft:item", 282 | "name": "minecraft:iron_chestplate" 283 | } 284 | ], 285 | "functions": [ 286 | { 287 | "function": "guardvillagers:slot", 288 | "slot": "chest" 289 | } 290 | ], 291 | "conditions": [ 292 | { 293 | "condition": "minecraft:random_chance", 294 | "chance": 0.013 295 | } 296 | ] 297 | }, 298 | { 299 | "rolls": 1, 300 | "entries": [ 301 | { 302 | "type": "minecraft:item", 303 | "name": "minecraft:iron_leggings" 304 | } 305 | ], 306 | "functions": [ 307 | { 308 | "function": "guardvillagers:slot", 309 | "slot": "legs" 310 | } 311 | ], 312 | "conditions": [ 313 | { 314 | "condition": "minecraft:random_chance", 315 | "chance": 0.013 316 | } 317 | ] 318 | }, 319 | { 320 | "rolls": 1, 321 | "entries": [ 322 | { 323 | "type": "minecraft:item", 324 | "name": "minecraft:iron_boots" 325 | } 326 | ], 327 | "functions": [ 328 | { 329 | "function": "guardvillagers:slot", 330 | "slot": "feet" 331 | } 332 | ], 333 | "conditions": [ 334 | { 335 | "condition": "minecraft:random_chance", 336 | "chance": 0.013 337 | } 338 | ] 339 | }, 340 | { 341 | "rolls": 1, 342 | "entries": [ 343 | { 344 | "type": "minecraft:item", 345 | "name": "minecraft:leather_helmet" 346 | } 347 | ], 348 | "functions": [ 349 | { 350 | "function": "guardvillagers:slot", 351 | "slot": "head" 352 | } 353 | ], 354 | "conditions": [ 355 | { 356 | "condition": "minecraft:random_chance", 357 | "chance": 0.037 358 | } 359 | ] 360 | }, 361 | { 362 | "rolls": 1, 363 | "entries": [ 364 | { 365 | "type": "minecraft:item", 366 | "name": "minecraft:leather_chestplate" 367 | } 368 | ], 369 | "functions": [ 370 | { 371 | "function": "guardvillagers:slot", 372 | "slot": "chest" 373 | } 374 | ], 375 | "conditions": [ 376 | { 377 | "condition": "minecraft:random_chance", 378 | "chance": 0.037 379 | } 380 | ] 381 | }, 382 | { 383 | "rolls": 1, 384 | "entries": [ 385 | { 386 | "type": "minecraft:item", 387 | "name": "minecraft:leather_leggings" 388 | } 389 | ], 390 | "functions": [ 391 | { 392 | "function": "guardvillagers:slot", 393 | "slot": "legs" 394 | } 395 | ], 396 | "conditions": [ 397 | { 398 | "condition": "minecraft:random_chance", 399 | "chance": 0.037 400 | } 401 | ] 402 | }, 403 | { 404 | "rolls": 1, 405 | "entries": [ 406 | { 407 | "type": "minecraft:item", 408 | "name": "minecraft:leather_boots" 409 | } 410 | ], 411 | "functions": [ 412 | { 413 | "function": "guardvillagers:slot", 414 | "slot": "feet" 415 | } 416 | ], 417 | "conditions": [ 418 | { 419 | "condition": "minecraft:random_chance", 420 | "chance": 0.037 421 | } 422 | ] 423 | } 424 | ] 425 | } 426 | 427 | --------------------------------------------------------------------------------