From 9a3ed83cf246972834bf86a9dec73b6ad46b6d51 Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Tue, 20 Jan 2026 09:16:31 +0000 Subject: [PATCH 01/21] Add example boss, ore, and incursion content Introduces ExampleBossMob, ExampleOreItem, ExampleBaseRockObject, and ExampleOreRockObject, along with their textures and localization. Refactors example incursion classes into a dedicated incursion package, updates incursion logic to use new example objects and items, and registers new mobs, items, and objects in ExampleMod. Adds ExampleAI for mob behavior and updates localization for new content. --- src/main/java/examplemod/ExampleMod.java | 22 +++ .../java/examplemod/examples/ExampleAI.java | 68 +++++++++ .../{ => incursion}/ExampleBiome.java | 2 +- .../ExampleIncursionBiome.java | 6 +- .../ExampleIncursionLevel.java | 6 +- .../examples/items/ExampleOreItem.java | 14 ++ .../examples/mobs/ExampleBossMob.java | 134 ++++++++++++++++++ .../examples/{ => mobs}/ExampleMob.java | 2 +- .../objects/ExampleBaseRockObject.java | 14 ++ .../examples/{ => objects}/ExampleObject.java | 2 +- .../objects/ExampleOreRockObject.java | 27 ++++ src/main/resources/items/examplebaserock.png | Bin 0 -> 547 bytes src/main/resources/items/exampleore.png | Bin 0 -> 494 bytes src/main/resources/items/exampleorerock.png | Bin 0 -> 547 bytes src/main/resources/locale/en.lang | 4 + src/main/resources/mobs/examplebossmob.png | Bin 0 -> 4070 bytes .../resources/objects/examplebaserock.png | Bin 0 -> 1078 bytes src/main/resources/objects/exampleore.png | Bin 0 -> 730 bytes src/main/resources/objects/rock.png | Bin 0 -> 5853 bytes 19 files changed, 292 insertions(+), 9 deletions(-) create mode 100644 src/main/java/examplemod/examples/ExampleAI.java rename src/main/java/examplemod/examples/{ => incursion}/ExampleBiome.java (96%) rename src/main/java/examplemod/examples/{ => incursion}/ExampleIncursionBiome.java (95%) rename src/main/java/examplemod/examples/{ => incursion}/ExampleIncursionLevel.java (97%) create mode 100644 src/main/java/examplemod/examples/items/ExampleOreItem.java create mode 100644 src/main/java/examplemod/examples/mobs/ExampleBossMob.java rename src/main/java/examplemod/examples/{ => mobs}/ExampleMob.java (99%) create mode 100644 src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java rename src/main/java/examplemod/examples/{ => objects}/ExampleObject.java (98%) create mode 100644 src/main/java/examplemod/examples/objects/ExampleOreRockObject.java create mode 100644 src/main/resources/items/examplebaserock.png create mode 100644 src/main/resources/items/exampleore.png create mode 100644 src/main/resources/items/exampleorerock.png create mode 100644 src/main/resources/mobs/examplebossmob.png create mode 100644 src/main/resources/objects/examplebaserock.png create mode 100644 src/main/resources/objects/exampleore.png create mode 100644 src/main/resources/objects/rock.png diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 6cc822c..68e084d 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -1,10 +1,19 @@ package examplemod; import examplemod.examples.*; +import examplemod.examples.incursion.ExampleBiome; +import examplemod.examples.incursion.ExampleIncursionBiome; +import examplemod.examples.incursion.ExampleIncursionLevel; import examplemod.examples.items.ExampleFoodItem; import examplemod.examples.items.ExampleHuntIncursionMaterialItem; import examplemod.examples.items.ExampleMaterialItem; import examplemod.examples.items.ExamplePotionItem; +import examplemod.examples.items.ExampleOreItem; +import examplemod.examples.mobs.ExampleMob; +import examplemod.examples.mobs.ExampleBossMob; +import examplemod.examples.objects.ExampleObject; +import examplemod.examples.objects.ExampleBaseRockObject; +import examplemod.examples.objects.ExampleOreRockObject; import necesse.engine.commands.CommandsManager; import necesse.engine.modLoader.annotations.ModEntry; import necesse.engine.registries.*; @@ -38,8 +47,16 @@ public void init() { // Register our objects ObjectRegistry.registerObject("exampleobject", new ExampleObject(), 2, true); + // Register a rock for the example incursion to use as cave walls + ExampleBaseRockObject exampleBaseRock = new ExampleBaseRockObject(); + ObjectRegistry.registerObject(ExampleBaseRockObject.ID, exampleBaseRock, -1.0F, true); + + // Register an ore rock that overlays onto our incursion rock + ObjectRegistry.registerObject(ExampleOreRockObject.ID, new ExampleOreRockObject(exampleBaseRock), -1.0F, true); + // Register our items ItemRegistry.registerItem("exampleitem", new ExampleMaterialItem(), 10, true); + ItemRegistry.registerItem(ExampleOreItem.ID, new ExampleOreItem(), 25, true); ItemRegistry.registerItem("examplehuntincursionitem", new ExampleHuntIncursionMaterialItem(), 50, true); ItemRegistry.registerItem("examplesword", new ExampleSwordItem(), 20, true); ItemRegistry.registerItem("examplestaff", new ExampleProjectileWeapon(), 30, true); @@ -49,6 +66,9 @@ public void init() { // Register our mob MobRegistry.registerMob("examplemob", ExampleMob.class, true); + // Register boss mob + MobRegistry.registerMob("examplebossmob",ExampleBossMob.class,true,true); + // Register our projectile ProjectileRegistry.registerProjectile("exampleprojectile", ExampleProjectile.class, "exampleprojectile", "exampleprojectile_shadow"); @@ -66,6 +86,8 @@ public void initResources() { // It will process your textures and save them again with a fixed alpha edge color ExampleMob.texture = GameTexture.fromFile("mobs/examplemob"); + + ExampleBossMob.texture = GameTexture.fromFile("mobs/examplebossmob"); } public void postInit() { diff --git a/src/main/java/examplemod/examples/ExampleAI.java b/src/main/java/examplemod/examples/ExampleAI.java new file mode 100644 index 0000000..f1a8094 --- /dev/null +++ b/src/main/java/examplemod/examples/ExampleAI.java @@ -0,0 +1,68 @@ +package examplemod.examples; + +import necesse.entity.mobs.GameDamage; +import necesse.entity.mobs.Mob; +import necesse.entity.mobs.ai.behaviourTree.composites.SelectorAINode; +import necesse.entity.mobs.ai.behaviourTree.leaves.CollisionChaserAINode; +import necesse.entity.mobs.ai.behaviourTree.leaves.WandererAINode; +import necesse.entity.mobs.ai.behaviourTree.trees.CollisionPlayerChaserAI; + +public class ExampleAI extends SelectorAINode { + /* + This node handles "find player -> chase -> try to attack" + We keep a reference so we can use its damage/knockback settings later. + */ + public final CollisionPlayerChaserAI chaser; + + // This node handles "walk around randomly" when there's nothing to chase. + public final WandererAINode wanderer; + + public ExampleAI(int searchDistance, GameDamage damage, int knockback, int wanderFrequency) { + /* + A SelectorAINode tries its children in order. + First one that can run/works is the behavior the mob uses. + + So: we add CHASING first, because we want chasing to "win" whenever possible. + */ + this.chaser = new CollisionPlayerChaserAI(searchDistance, damage, knockback) { + + // CollisionPlayerChaserAI has an attackTarget method it calls when in range. + // We override it so we can route the attack logic to OUR method below. + @Override + public boolean attackTarget(T mob, Mob target) { + // "ExampleAI.this" means "the outer ExampleAI instance" + // (because we're inside an anonymous inner class right now). + return ExampleAI.this.attackTarget(mob, target); + } + }; + addChild(this.chaser); + + /* + If the chaser doesn't have a target (or can't chase), + the selector will try the next child: wandering. + */ + this.wanderer = new WandererAINode<>(wanderFrequency); + addChild(this.wanderer); + } + + /* + This is the actual "do the hit" logic. + We keep it outside the chaser node so it's easy to change later. + */ + public boolean attackTarget(T mob, Mob target) { + /* + simpleAttack is a helper that does a basic melee attack: + - checks if the mob can attack right now + - applies damage + - applies knockback + - returns true if an attack happened + */ + return CollisionChaserAINode.simpleAttack( + mob, + target, + // Use the damage/knockback values that were passed into the chaser constructor. + this.chaser.damage, + this.chaser.knockback + ); + } +} diff --git a/src/main/java/examplemod/examples/ExampleBiome.java b/src/main/java/examplemod/examples/incursion/ExampleBiome.java similarity index 96% rename from src/main/java/examplemod/examples/ExampleBiome.java rename to src/main/java/examplemod/examples/incursion/ExampleBiome.java index a6ae51e..60d122b 100644 --- a/src/main/java/examplemod/examples/ExampleBiome.java +++ b/src/main/java/examplemod/examples/incursion/ExampleBiome.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.incursion; import necesse.engine.AbstractMusicList; import necesse.engine.MusicList; diff --git a/src/main/java/examplemod/examples/ExampleIncursionBiome.java b/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java similarity index 95% rename from src/main/java/examplemod/examples/ExampleIncursionBiome.java rename to src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java index 6671f98..e9ffdde 100644 --- a/src/main/java/examplemod/examples/ExampleIncursionBiome.java +++ b/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.incursion; import necesse.engine.network.server.Server; import necesse.engine.registries.ItemRegistry; @@ -26,13 +26,13 @@ public class ExampleIncursionBiome extends IncursionBiome { public ExampleIncursionBiome() { - super("reaper"); // The boss mob string ID for this incursion + super("examplebossmob"); // The boss mob string ID for this incursion } // Items required to be obtained when completing an extraction objective in this incursion @Override public Collection getExtractionItems(IncursionData data) { - return Collections.singleton(ItemRegistry.getItem("tungstenore")); + return Collections.singleton(ItemRegistry.getItem("exampleore")); } /** diff --git a/src/main/java/examplemod/examples/ExampleIncursionLevel.java b/src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java similarity index 97% rename from src/main/java/examplemod/examples/ExampleIncursionLevel.java rename to src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java index bfcecef..8e0b4d9 100644 --- a/src/main/java/examplemod/examples/ExampleIncursionLevel.java +++ b/src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.incursion; import examplemod.ExampleMod; import necesse.engine.GameEvents; @@ -45,7 +45,7 @@ public ExampleIncursionLevel(LevelIdentifier identifier, BiomeMissionIncursionDa public void generateLevel(BiomeMissionIncursionData incursionData, AltarData altarData) { // Create the cave generator using deep rock tiles for floors and walls - CaveGeneration cg = new CaveGeneration(this, "deeprocktile", "deeprock"); + CaveGeneration cg = new CaveGeneration(this, "deeprocktile", "examplebaserock"); // Seed the generator so this incursion layout is deterministic per mission cg.random.setSeed(incursionData.getUniqueID()); @@ -83,7 +83,7 @@ public void generateLevel(BiomeMissionIncursionData incursionData, AltarData alt // For extraction incursions, guarantee tungsten ore veins for objectives if (incursionData instanceof BiomeExtractionIncursionData) { - cg.generateGuaranteedOreVeins(40, 4, 8, ObjectRegistry.getObjectID("tungstenoredeeprock")); + cg.generateGuaranteedOreVeins(40, 4, 8, ObjectRegistry.getObjectID("exampleorerock")); } // Generate upgrade shard and alchemy shard ores cg.generateGuaranteedOreVeins(75, 6, 12, ObjectRegistry.getObjectID("upgradesharddeeprock")); diff --git a/src/main/java/examplemod/examples/items/ExampleOreItem.java b/src/main/java/examplemod/examples/items/ExampleOreItem.java new file mode 100644 index 0000000..bc9cfd9 --- /dev/null +++ b/src/main/java/examplemod/examples/items/ExampleOreItem.java @@ -0,0 +1,14 @@ +package examplemod.examples.items; + +import necesse.inventory.item.Item; +import necesse.inventory.item.matItem.MatItem; + +public class ExampleOreItem extends MatItem { + + public static final String ID = "exampleore"; + + public ExampleOreItem() { + super(500, Item.Rarity.UNCOMMON); + + } +} diff --git a/src/main/java/examplemod/examples/mobs/ExampleBossMob.java b/src/main/java/examplemod/examples/mobs/ExampleBossMob.java new file mode 100644 index 0000000..d0c8944 --- /dev/null +++ b/src/main/java/examplemod/examples/mobs/ExampleBossMob.java @@ -0,0 +1,134 @@ +package examplemod.examples.mobs; + +import examplemod.examples.ExampleAI; +import necesse.engine.eventStatusBars.EventStatusBarManager; +import necesse.engine.gameLoop.tickManager.TickManager; +import necesse.engine.registries.MusicRegistry; +import necesse.engine.sound.SoundManager; +import necesse.engine.util.GameRandom; +import necesse.entity.mobs.GameDamage; +import necesse.entity.mobs.MobDrawable; +import necesse.entity.mobs.PlayerMob; +import necesse.entity.mobs.ai.behaviourTree.BehaviourTreeAI; +import necesse.entity.mobs.hostile.bosses.BossMob; +import necesse.entity.particle.FleshParticle; +import necesse.entity.particle.Particle; +import necesse.gfx.camera.GameCamera; +import necesse.gfx.drawOptions.DrawOptions; +import necesse.gfx.drawables.OrderableDrawables; +import necesse.gfx.gameTexture.GameTexture; +import necesse.inventory.lootTable.LootTable; +import necesse.inventory.lootTable.lootItem.ChanceLootItem; +import necesse.level.maps.Level; +import necesse.level.maps.light.GameLight; + +import java.awt.*; +import java.util.List; + +public class ExampleBossMob extends BossMob { + + // Loaded in examplemod.ExampleMod.initResources() + public static GameTexture texture; + + public static LootTable lootTable = new LootTable( + ChanceLootItem.between(0.5f, "exampleitem", 1, 3) // 50% chance to drop between 1-3 example items + ); + + // MUST HAVE an empty constructor + public ExampleBossMob() { + super(200); + setSpeed(50); + setFriction(3); + this.shouldSave = false; + this.canDespawn = true; + this.isSummoned = true; + // Hitbox, collision box, and select box (for hovering) + collision = new Rectangle(-10, -7, 20, 14); + hitBox = new Rectangle(-14, -12, 28, 24); + selectBox = new Rectangle(-14, -7 - 34, 28, 48); + } + + @Override + public void init() { + super.init(); + // Setup AI + this.ai = new BehaviourTreeAI<>( + this, + new ExampleAI<>( + 1380, // search distance (in pixels) + new GameDamage(60), // collide damage + 150, // knockback + 12000 // wander frequency + ) + ); + } + + @Override + public LootTable getLootTable() { + return lootTable; + } + + @Override + public void spawnDeathParticles(float knockbackX, float knockbackY) { + // Spawn flesh debris particles + for (int i = 0; i < 4; i++) { + getLevel().entityManager.addParticle(new FleshParticle( + getLevel(), texture, + GameRandom.globalRandom.nextInt(5), // Randomize between the debris sprites + 8, + 32, + x, y, 20f, // Position + knockbackX, knockbackY // Basically start speed of the particles + ), Particle.GType.IMPORTANT_COSMETIC); + } + } + + @Override + protected void addDrawables(List list, OrderableDrawables tileList, OrderableDrawables topList, Level level, int x, int y, TickManager tickManager, GameCamera camera, PlayerMob perspective) { + super.addDrawables(list, tileList, topList, level, x, y, tickManager, camera, perspective); + // Tile positions are basically level positions divided by 32. getTileX() does this for us etc. + GameLight light = level.getLightLevel(getTileX(), getTileY()); + int drawX = camera.getDrawX(x) - 32; + int drawY = camera.getDrawY(y) - 51; + + // A helper method to get the sprite of the current animation/direction of this mob + Point sprite = getAnimSprite(x, y, getDir()); + + drawY += getBobbing(x, y); + drawY += getLevel().getTile(getTileX(), getTileY()).getMobSinkingAmount(this); + + DrawOptions drawOptions = texture.initDraw() + .sprite(sprite.x, sprite.y, 64) + .light(light) + .pos(drawX, drawY); + + list.add(new MobDrawable() { + @Override + public void draw(TickManager tickManager) { + drawOptions.draw(); + } + }); + + addShadowDrawables(tileList, level, x, y, light, camera); + } + + @Override + public int getRockSpeed() { + // Change the speed at which this mobs animation plays + return 20; + } + + @Override + public void clientTick() { + super.clientTick(); + + // Only show boss bar when the client player is close enough + if (isClientPlayerNearby()) { + EventStatusBarManager.registerMobHealthStatusBar(this); + } + // Optional: set boss music here too if you want + SoundManager.setMusic(MusicRegistry.AscendedReturn, SoundManager.MusicPriority.EVENT, 1.5F); + } + + +} diff --git a/src/main/java/examplemod/examples/ExampleMob.java b/src/main/java/examplemod/examples/mobs/ExampleMob.java similarity index 99% rename from src/main/java/examplemod/examples/ExampleMob.java rename to src/main/java/examplemod/examples/mobs/ExampleMob.java index c537747..c3151e4 100644 --- a/src/main/java/examplemod/examples/ExampleMob.java +++ b/src/main/java/examplemod/examples/mobs/ExampleMob.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.mobs; import necesse.engine.gameLoop.tickManager.TickManager; import necesse.engine.util.GameRandom; diff --git a/src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java b/src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java new file mode 100644 index 0000000..44ed536 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java @@ -0,0 +1,14 @@ +package examplemod.examples.objects; +import necesse.level.gameObject.RockObject; + +import java.awt.Color; + +public class ExampleBaseRockObject extends RockObject { + + public static final String ID = "examplebaserock"; + + public ExampleBaseRockObject() { + super("examplebaserock", new Color(92, 37, 23), "stone", "objects", "landscaping"); + this.toolTier = 5.0F; + } +} diff --git a/src/main/java/examplemod/examples/ExampleObject.java b/src/main/java/examplemod/examples/objects/ExampleObject.java similarity index 98% rename from src/main/java/examplemod/examples/ExampleObject.java rename to src/main/java/examplemod/examples/objects/ExampleObject.java index 7ad35be..f611cfe 100644 --- a/src/main/java/examplemod/examples/ExampleObject.java +++ b/src/main/java/examplemod/examples/objects/ExampleObject.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.objects; import necesse.engine.gameLoop.tickManager.TickManager; import necesse.entity.mobs.PlayerMob; diff --git a/src/main/java/examplemod/examples/objects/ExampleOreRockObject.java b/src/main/java/examplemod/examples/objects/ExampleOreRockObject.java new file mode 100644 index 0000000..f2e1e3d --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleOreRockObject.java @@ -0,0 +1,27 @@ +package examplemod.examples.objects; +import necesse.level.gameObject.RockObject; +import necesse.level.gameObject.RockOreObject; + +import java.awt.Color; + +/** + * Example ore rock that uses our ExampleIncursionDeepRockObject as its parent rock. + */ +public class ExampleOreRockObject extends RockOreObject { + + public static final String ID = "exampleorerock"; + + public ExampleOreRockObject(RockObject parentRock) { + super( + parentRock, + "oremask", + "exampleore", + new Color(90, 40, 160), + "exampleore", + 1, + 3, + 2, + true, + "objects", "landscaping"); + } +} diff --git a/src/main/resources/items/examplebaserock.png b/src/main/resources/items/examplebaserock.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc82382d32caeed91cd11fe1256e27b535a4b64 GIT binary patch literal 547 zcmV+;0^I$HP)KNKUVla79%p@lS{y)5Z zlVR?z!#T(nCA-Y zlN%CvT}g`1$qfmtAwY@GNev0CAwY@GNeu~<5TK^di46&q5Fp0qNU0oed4r{#z?wTq zDhU~A07xzgarul;`GCvq10Dhd9gyT|!vMkzGy;P^4b0np1nhTMD8TsC3Xq|b3vl_I z+|&;X!J$_U5Go1LLj;y~l5r%Op%(%$j}VG`Vsp*V4FUADfF2^mmKVb}4G>#W4xcnY zZ0> z*QX4_rcFq#{0|EdaQ7V7*e?T(0L(tn*fR3?fGjcgkvl?yWH!v6#~?8#dix&PlOVf6 zApmoc1~DN(g%J`Idj`BDq`Cu09Z?y&tzum1h13z1p&tUcO2#46HynQ1kksmf5la~j zE2(x-xh`^pns)l8%PKx6rY3i!K-6fDA22BL=JUq^97G- l)DFRFE{X$a9TM2m8vu$V!b$EgfkOZQ002ovPDHLkV1n8)-2(ss literal 0 HcmV?d00001 diff --git a/src/main/resources/items/exampleore.png b/src/main/resources/items/exampleore.png new file mode 100644 index 0000000000000000000000000000000000000000..62c0e6de60bc79adc39782324f14fcbbc4959a50 GIT binary patch literal 494 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$);LoovvyXX~Dpa^GyM`SSr1Gg{;GcwGYBLNg-FY)wsWq-ve!oa7!T7Uf~pbLyV zT^vIq4!@nYKkHC{$g%o=ku|MOinD*PG+j_%As%yty&&Ypli-q96E`}T~P;)j}&mU*EfGVEf~g_l)%WP3CWnXI=MO zkQ?>V*q`A`So4))>#b8$S9mXqjAUE#Nm7`hhL53QvBK{6mrw3jvAem%DA1PeTqo&DkpcmYQ80|fT&+s{-H2K1FfzZ6iDz&E_H6F~(?zP!r!hBNYlb4p?$CYZ#t^YaGdz@>T iPCLyD_g@gUR+!^gtm_QUoBx0T%HZkh=d#Wzp$Pz43&E8D literal 0 HcmV?d00001 diff --git a/src/main/resources/items/exampleorerock.png b/src/main/resources/items/exampleorerock.png new file mode 100644 index 0000000000000000000000000000000000000000..bfc82382d32caeed91cd11fe1256e27b535a4b64 GIT binary patch literal 547 zcmV+;0^I$HP)KNKUVla79%p@lS{y)5Z zlVR?z!#T(nCA-Y zlN%CvT}g`1$qfmtAwY@GNev0CAwY@GNeu~<5TK^di46&q5Fp0qNU0oed4r{#z?wTq zDhU~A07xzgarul;`GCvq10Dhd9gyT|!vMkzGy;P^4b0np1nhTMD8TsC3Xq|b3vl_I z+|&;X!J$_U5Go1LLj;y~l5r%Op%(%$j}VG`Vsp*V4FUADfF2^mmKVb}4G>#W4xcnY zZ0> z*QX4_rcFq#{0|EdaQ7V7*e?T(0L(tn*fR3?fGjcgkvl?yWH!v6#~?8#dix&PlOVf6 zApmoc1~DN(g%J`Idj`BDq`Cu09Z?y&tzum1h13z1p&tUcO2#46HynQ1kksmf5la~j zE2(x-xh`^pns)l8%PKx6rY3i!K-6fDA22BL=JUq^97G- l)DFRFE{X$a9TM2m8vu$V!b$EgfkOZQ002ovPDHLkV1n8)-2(ss literal 0 HcmV?d00001 diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index 3306996..55de7a6 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -3,9 +3,12 @@ exampletile=Example Tile [object] exampleobject=Example Object +examplebaserock=Example Rock +exampleore=Example Ore [item] exampleitem=Example Item +exampleore=Example Ore examplehuntincursionitem=Example Hunt Incursion Item examplepotionitem=Example Potion examplesword=Example Sword @@ -18,6 +21,7 @@ examplepotionitemtip= An example potion [mob] examplemob=Example Mob +examplebossmob=Example Boss [buff] examplebuff=Example Buff diff --git a/src/main/resources/mobs/examplebossmob.png b/src/main/resources/mobs/examplebossmob.png new file mode 100644 index 0000000000000000000000000000000000000000..a740a8f6992bd008acea61e4faf13cf37575efce GIT binary patch literal 4070 zcmbuCc~lcu`^P7Q3RJ;`Dy=r4Re?$YwJI9GAc`omi3ke$R3i-Z6nL6!i4WahmAd-{9N>F=-KJ!dj!?%X^xdG7Q1 zexKZQcXibLbp59g1nC|xUMFf?%3X)gma6_!QI!3jh1aJ0wtHGM+`7n*?wTn<3c zqon1^bDx0gjR8kITp%dc7=mcmA!r^<(S{)?YC8mt`a=*t1A+`L-K#ln18yw)?wI2t za0Wwh@QFH_&ZQ$>BnVoruRbtP=Dp9sq*mln7YD6TjD~^vp7mKeN(frH?dYKcCol7) z0^10FTef||yPB5Ucz<)qft%TTGjX6JtLVHT-6! zXoiQB4Sui=RHsSP(^j0&*N132{$FM%x8}=S&mfbY;13L}MQC{ZxN^=t~|IY^Htt>Lc zChDICzi|utjXG5s(7dl=V7JTrQ&K`U9aY}IdE$Q-A3!ff8>Fdh*^wI9NGSYK=;0aO z#X%NU_L@+;Cn9}1oIObAON5$`dtp6CtBvfCR~ei`77z>$MnUKB@$0>`$|tKM6|*_i zl#VUQLuts_AY=z!l9k0sdG|XD?Xj}DHWZ=(ZO5!Ivo&zy|Lpmi?Y?c8Mo;M7&nSq} zm_)+vYKMjygrYMt!L#!phGu3qrerTAwSW+AMD|hn@{c0l#Nkp0+;QrZK6i#fvi;4o z-A3g3j)e~#4>S^0S)Fu=)qT=A4XB{Azx9)HY2PZ6RFlWisd?E4mC3H}iO1dXph^|9 zr(m5rO{flBl}^t8P43+`+*c;^QMOrB84Mwz3fQedYuaN5tt<=G(DV3V*45z5khXq zVH~ll+GYv$<)JSk-eUbVApfVnyyUdV@|6AK1Tv#5Ag+*@0{{l&@s53U<3Zh5EL`sM zYbxi1xdO5~`n_Vs;>~~h$Tf+3@>Q>BV&bsp96P7Qc;M@wlp-Pt2`CY+tzWG6)bXj8 zFE6aT=bCjJ|D(6{#=q+|*W+4=MTi-3>O<+gPIiukQc1zL)NJNeVofVT!LkG;sc><> z^8l92RLfdo&6@6ROsx$wIsX1+I@LuX_E;?YWbx01Skn}xh(!VEVY@=UW_?{MRXd-M zlRW}$D*zZ<0*rgY)Z72%D^TDfYTM(TqtNTGsEYKD0B1l6tF7-^SlWvEmY_8%Qd!5&CxH z94`7H;^gy8GoPoZ{D+xQ#qUjzQ^_xIb?5(#k@aJo5Bo)r(sL#WuXmvw6Mu#HH|m+j zT9k^$mhd$pe~sQG^TaU9wsQw(+c1MX9a$!tW3<+>NK3Qf9~L~Hud@Q7MYt?xZ3C9j$%3j{&Ob#jZQHhtDL zQF(MWJb!_cH+QSgGIeY@Yb$jerTYor{yOljx%$=KZFk~}4U)V^Zp*MN(Qr~mw0D@N zfPvF34@E{K7suqRg?UT4ioQV~vSTES8B^k9+Ur_H6ElNbu9qU5*0*?0FQ%gFZoi>5 z%*^wP5a<=2p=L3qsmpKnTU2cMM#k-3XQDXLi!fa|*xxSMG zeP^0p+Va5NNfn|<_n-<>fc`?UrLz{+0>=#UVMZj9)!kRgtxk)lGdLFAEfwYS!7+4@ zn8tP>P44si>xoRAr--U3jbjlZw8+|zdW;IEmD1MVv2o!{&E6LcP*3st5OO%mH2>Vo0d%Hbu>h3i354Cb1Pxgvk%Z%EHWFYR$Nr#>eJlfDMZ`J9`@X)zvptwW zm%pD3WcEF9(g9tr+1Z*kuTsa;7bMVQnO+}Y~>>DA5E;(h~COQ zs0|y6zYGJOkdHOZSdx??AO`*#pTFX#q|kA0kGW;iz2fRM7JVk`xi{JepVkLUXoHq$ z=9E#~5=1aaS9P_`jaA-Blp_o?A>arvZe{(IAwvmu_xZ(@b*;ul6w1hXJZGeO@cVy_ z?+fkyR4r6Qgmlo)O-|5NYylpex?Y)CXG=br@<*j;FveP$0q>CM`&D5ZRhgwgjkYQR zy%yD|jVp|bxuX2+8LP+smS>e)m|KOwvOnjTx?$)fo1azm?7(y(2v;g!+@WMg%#BqP z+kUn*GhfmV-pJ~NUjpV_M(vyP*FvFkS6kfLQ<<%2~{TA6PcrvS9YP@pA8Y)0QqeDQx`YUegXb|?iUe~bBnWSiyk z;)LFk(|N;YS21eB%!J}!erYk>o4sdiltzWRbE38=l4^lv*)f*+`0`G3hWuX z&M8!pnNg1AAzJQ10?%D92e#te9eflaOBqF5^QYD*-Z=fB2e;^Tm}dOl(W=~ zI1(fU7*N?s>FVZXdvVdi0;7s*V{KMLYQr?sWBP+=qc-aV^86WQ8$r7yc5$*4Yi)08yCU{_j{?+;2+rX zkj_v1$)eF+-`%2JNo|B5>Qv7tJEX}kVXE3->q-ZXuQHO!?6qM(v)zU!+5ML9^{Oj% zjv%&!^l(*$nEpRY%8 zcIv!)eq?rnLw7x>Ei#JV=_xc#gn?k0^NoNmp0~^jEs=gpvrJ0T8nJX# s{W5kv9=jea$bceq)qTeQW8^xa*q*IJ_10?^cBsP~b#OhzwD-UIAGamwbpQYW literal 0 HcmV?d00001 diff --git a/src/main/resources/objects/examplebaserock.png b/src/main/resources/objects/examplebaserock.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ddf9211df20af773b34bea5e628b36aa17e8f3 GIT binary patch literal 1078 zcmeAS@N?(olHy`uVBq!ia0vp^4M2Q>gAGWY-;ikoq!^2X+?^P2p46!aa#+$GeH|GX zHuiJ>Nn{1`6_P!Id>I(3)PNdW7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXICJ|AhslL zcNc~a48;sw?4nmpfg+p*9+AZi4BWyX%*Zfnjs#GUy~NYkmHic?2q%-whHrIs3=GUw zo-U3d6}R5r^_?`^K*Tk1VuiBp>)4GVkC$#(J+D$#(}+?0Ez6nt6DEJ3+Zmi1?O!6h zu4RI!7tk<e9x@4IGO2ydR(=)Bwb zYqtWUio+XY1uLNj1_cHV2Brg63|vL8*H2n!x$c;oc7r)*!+tdfLFSJOUdFFizdDiO z$2*(VJni zg8&0qUVz~mBbVUcyd|=Z8nYS7SZ6aU6fv3nT^)N+h(S>Jz&r;|1}3N@;M9-*70drJ zxW0b=Z@!fZ!#z93S^u_PscTsDwY{Zd8jFwvlg$SC1v>Ni8#Zw^Y%=*f^Zic-15Or3 zfn2@^+Z!c6-gb~+a$q=fhH=$LiHU`(Sy^w^o|E9d`adq7iDmh$nWkmz>IZH`mS6t< zc-Q84xp(e$MHXLtY`=OR3n>qvy*`0K z=)rw2h8kb(yYG+b~~7y$x~BY5x-;5C>(+4QCP>)v^_`~PN$*XF+}cK@23`~Td# z=hwC!zon^e$zwL9n0XwQA9ZVQsRqy>d@3)Ni?a=>s zegT8Mfq~<6s*T%{E%o|$M4Wf;eEmjJSy^sBivddmg8{n%+i{yZ&IAs)F#=uob&^wF zSCwS$zW93n-g63!O~sP)YznUktA|Of>JUxloWXOGumBfyuGf0H53%7p00i_>zopr0M-|bP5=M^ literal 0 HcmV?d00001 diff --git a/src/main/resources/objects/exampleore.png b/src/main/resources/objects/exampleore.png new file mode 100644 index 0000000000000000000000000000000000000000..9308a723fcd3a0ea8534a69aa2cb296db995b1dd GIT binary patch literal 730 zcmV<00ww*4P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;&yra{vkl71T8V000SaNLh0L01FcU01FcV0GgZ_00007 zbV*G`2kHS61{F7tLVPR$00JIKL_t(&-tAbiZi7G!eMC%Y2Y!+)7}X2&o#pBnOA_`MvY`k?pZN0zi#z24=5#J_5ia9-!T)=f3dGFXIjG z>O3+z-|}z0NBWoZEI*dN74F3j>B`Iu)tZ+A>wmSQwDXfX`}u-lIk6GCn?p4W6nGSoe$2O~$CP47B{naV{UA zMe)THq04yjYQxLffj0lz)(4Utw*e@t1|?3{+7M9#_=5VKpAw-ZfD-0ad3k!o_8n!_ zfX7Jx_t+izJ#9_g@|cYC2ix!h3g9mp0YeDW*kfj{XSh+F7op4K!nOG^dpTB1c=IJN zHMUtDF1L#1FSng-)xvfN8_45~&%c!sFm>o(_{AwOwDU_vI`(?11{kXr@4gSnr_$q= z(Q^`kSmeFhAkX(yO|o=?v3qZ94}Ysp&{K&E1E$*ZQs;Z$5~o6T=&7$*s(%G z(*|?Z2>ITWxVe~HSuLehgVvZjL%#SqOnYx%ob)T>%r(SFI%-0Ppv7QinOYE ziS^Nn^AI*?e=^R0qXvh@nfsksW2!-(eJ?md%tBapbTQux`j{FEP;O;%52$)t)7Nu; z4YJ-VV;}}_`K-m6O;w$*b-qv4A6T_&QG;`JU}L`l4!!SnqIb`LABuGEqbrCwc>n+a M07*qoM6N<$g4Jv{i2wiq literal 0 HcmV?d00001 diff --git a/src/main/resources/objects/rock.png b/src/main/resources/objects/rock.png new file mode 100644 index 0000000000000000000000000000000000000000..aa51ed0427a5c368ea03de6a8132ead9438ed708 GIT binary patch literal 5853 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 Date: Tue, 20 Jan 2026 22:02:43 +0000 Subject: [PATCH 02/21] Add ExampleAI and ExampleAILeaf for mob behavior Introduces ExampleAI and ExampleAILeaf classes to provide custom AI behavior for mobs, including teleporting to incursion entrance and handling chasing and wandering logic. Updates ExampleBossMob to use the new ExampleAI implementation. --- .../examplemod/examples/ai/ExampleAI.java | 77 ++++++++++ .../examplemod/examples/ai/ExampleAILeaf.java | 143 ++++++++++++++++++ .../examples/mobs/ExampleBossMob.java | 2 +- 3 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/main/java/examplemod/examples/ai/ExampleAI.java create mode 100644 src/main/java/examplemod/examples/ai/ExampleAILeaf.java diff --git a/src/main/java/examplemod/examples/ai/ExampleAI.java b/src/main/java/examplemod/examples/ai/ExampleAI.java new file mode 100644 index 0000000..8ba9ec6 --- /dev/null +++ b/src/main/java/examplemod/examples/ai/ExampleAI.java @@ -0,0 +1,77 @@ +package examplemod.examples.ai; + +import necesse.entity.mobs.GameDamage; +import necesse.entity.mobs.Mob; +import necesse.entity.mobs.ai.behaviourTree.composites.SelectorAINode; +import necesse.entity.mobs.ai.behaviourTree.leaves.CollisionChaserAINode; +import necesse.entity.mobs.ai.behaviourTree.leaves.WandererAINode; +import necesse.entity.mobs.ai.behaviourTree.trees.CollisionPlayerChaserAI; + +public class ExampleAI extends SelectorAINode { + public final ExampleAILeaf teleporter; + + /* + This node handles "find player -> chase -> try to attack" + We keep a reference so we can use its damage/knockback settings later. + */ + public final CollisionPlayerChaserAI chaser; + + // This node handles "walk around randomly" when there's nothing to chase. + public final WandererAINode wanderer; + + public ExampleAI(int searchDistance, GameDamage damage, int knockback, int wanderFrequency) { + /* + A SelectorAINode tries its children in order. + First one that can run/works is the behaviour the mob uses. + + So: we add CHASING first, because we want chasing to "win" whenever possible. + */ + + + this.teleporter = new ExampleAILeaf<>(8, 10); + { + // 8 tiles = 256px open-space check, search within 10 tiles if it needs to move. + } + addChild(this.teleporter); + this.chaser = new CollisionPlayerChaserAI(searchDistance, damage, knockback) { + + // CollisionPlayerChaserAI has an attackTarget method it calls when in range. + // We override it so we can route the attack logic to OUR method below. + @Override + public boolean attackTarget(T mob, Mob target) { + // "ExampleAI.this" means "the outer ExampleAI instance" + // (because we're inside an anonymous inner class right now). + return ExampleAI.this.attackTarget(mob, target); + } + }; + addChild(this.chaser); + + /* + If the chaser doesn't have a target (or can't chase), + the selector will try the next child: wandering. + */ + this.wanderer = new WandererAINode<>(wanderFrequency); + addChild(this.wanderer); + } + + /* + attack logic. + We keep it outside the chaser node so it's easy to change later. + */ + public boolean attackTarget(T mob, Mob target) { + /* + simpleAttack is a helper that does a basic melee attack: + - checks if the mob can attack right now + - applies damage + - applies knockback + - returns true if an attack happened + */ + return CollisionChaserAINode.simpleAttack( + mob, + target, + // Use the damage/knockback values that were passed into the chaser constructor. + this.chaser.damage, + this.chaser.knockback + ); + } +} diff --git a/src/main/java/examplemod/examples/ai/ExampleAILeaf.java b/src/main/java/examplemod/examples/ai/ExampleAILeaf.java new file mode 100644 index 0000000..fae5455 --- /dev/null +++ b/src/main/java/examplemod/examples/ai/ExampleAILeaf.java @@ -0,0 +1,143 @@ +package examplemod.examples.ai; + +import java.awt.Point; +import java.awt.geom.Point2D; +import java.util.ArrayList; + +import necesse.engine.util.GameMath; +import necesse.engine.util.GameRandom; +import necesse.entity.mobs.Mob; +import necesse.entity.mobs.ai.behaviourTree.AINode; +import necesse.entity.mobs.ai.behaviourTree.AINodeResult; +import necesse.entity.mobs.ai.behaviourTree.Blackboard; +import necesse.level.maps.IncursionLevel; +import necesse.level.maps.Level; + +/** + Runs once after spawn: + If we are in an IncursionLevel and the mob is NOT inside the entrance open-space, + teleport it to a valid tile near the incursion return portal (entrance centre). + + "Entrance centre" = return portal position: + IncursionBiome.generateEntrance(...) clears an open area and calls addReturnPortalOnTile(...) + which ends up setting IncursionLevel returnPortalPosition/ID. + */ +public class ExampleAILeaf extends AINode { + + private boolean didCheck = false; + + // How close the mob must be to the entrance centre to count as "in the entrance area" + private final int openRadiusTiles; + + // How far around the portal we will search to find a valid teleport tile + private final int searchRadiusTiles; + + public ExampleAILeaf(int openRadiusTiles, int searchRadiusTiles) { + this.openRadiusTiles = Math.max(1, openRadiusTiles); + this.searchRadiusTiles = Math.max(this.openRadiusTiles, searchRadiusTiles); + } + + @Override + protected void onRootSet(AINode aiNode, T t, Blackboard blackboard) { + + } + + @Override + public void init(T mob, Blackboard blackboard) { + // Nothing to init; we just run once in tick(). + } + + @Override + public AINodeResult tick(T mob, Blackboard blackboard) { + // Only do this once per mob instance. + if (didCheck) return AINodeResult.FAILURE; + didCheck = true; + + // Only do this on the server (positions are authoritative there). + if (!mob.isServer()) return AINodeResult.FAILURE; + + Level level = mob.getLevel(); + if (!(level instanceof IncursionLevel)) return AINodeResult.FAILURE; + + IncursionLevel incursion = (IncursionLevel) level; + + // Entrance centre is the return portal position. + Point portalPos = incursion.getReturnPortalPosition(); + if (portalPos == null) return AINodeResult.FAILURE; + + float centerX = portalPos.x; + float centerY = portalPos.y; + + // If already inside the entrance open space, do nothing. + float openRadiusPx = this.openRadiusTiles * 32.0f; + if (mob.getDistance(centerX, centerY) <= openRadiusPx) { + return AINodeResult.FAILURE; + } + + // Otherwise teleport to a nearby valid spot close to the portal. + Point2D.Float dest = findValidTeleportPos(incursion, mob, centerX, centerY, this.searchRadiusTiles); + + if (dest != null) { + // "Direct" position set (sends movement packet if needed). + mob.stopMoving(); + mob.setPos(dest.x, dest.y, true); + } + + // Return FAILURE so parent Selector continues to chase/wander this same tick. + return AINodeResult.FAILURE; + } + + private static Point2D.Float findValidTeleportPos(IncursionLevel level, Mob mob, float centerX, float centerY, int searchRadiusTiles) { + int centerTileX = GameMath.getTileCoordinate(centerX); + int centerTileY = GameMath.getTileCoordinate(centerY); + + // Try rings from 0 outward (0 = centre) + for (int r = 0; r <= searchRadiusTiles; r++) { + ArrayList ring = buildRing(centerTileX, centerTileY, r); + + // Randomise the order a bit so we don't always pick the same spot + while (!ring.isEmpty()) { + Point p = ring.remove(GameRandom.globalRandom.nextInt(ring.size())); + + // Avoid liquid/shore like vanilla spawn logic + if (level.isLiquidTile(p.x, p.y)) continue; + if (level.isShore(p.x, p.y)) continue; + + int px = p.x * 32 + 16; + int py = p.y * 32 + 16; + + // Must not collide with the map, objects, etc. + if (mob.collidesWith(level, px, py)) continue; + + // Also avoid landing inside another mob/player + if (mob.collidesWithAnyMob(level, px, py)) continue; + + return new Point2D.Float(px, py); + } + } + + // No valid tile found + return null; + } + + private static ArrayList buildRing(int cx, int cy, int r) { + ArrayList points = new ArrayList<>(); + if (r == 0) { + points.add(new Point(cx, cy)); + return points; + } + + // Top and bottom edges + for (int dx = -r; dx <= r; dx++) { + points.add(new Point(cx + dx, cy - r)); + points.add(new Point(cx + dx, cy + r)); + } + // Left and right edges (excluding corners already added) + for (int dy = -r + 1; dy <= r - 1; dy++) { + points.add(new Point(cx - r, cy + dy)); + points.add(new Point(cx + r, cy + dy)); + } + + return points; + } +} diff --git a/src/main/java/examplemod/examples/mobs/ExampleBossMob.java b/src/main/java/examplemod/examples/mobs/ExampleBossMob.java index d0c8944..cab411e 100644 --- a/src/main/java/examplemod/examples/mobs/ExampleBossMob.java +++ b/src/main/java/examplemod/examples/mobs/ExampleBossMob.java @@ -1,6 +1,6 @@ package examplemod.examples.mobs; -import examplemod.examples.ExampleAI; +import examplemod.examples.ai.ExampleAI; import necesse.engine.eventStatusBars.EventStatusBarManager; import necesse.engine.gameLoop.tickManager.TickManager; import necesse.engine.registries.MusicRegistry; From 3eca28db652aead335fe5b50e6963a5f7fae1a59 Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Wed, 21 Jan 2026 00:36:25 +0000 Subject: [PATCH 03/21] Refactor item structure and add Example Bar item Moved sword and projectile weapon classes to a new tools subpackage. Added ExampleBarItem and its associated recipe and icon. Updated item and recipe registration to use new item IDs and structure. Renamed exampleore.png to exampleoreitem.png and updated references. Updated localization and added mob icons. --- src/main/java/examplemod/ExampleMod.java | 61 ++----------- .../java/examplemod/examples/ExampleAI.java | 68 -------------- .../examplemod/examples/ExampleRecipes.java | 83 ++++++++++++++++++ .../examples/items/ExampleBarItem.java | 12 +++ .../examples/items/ExampleOreItem.java | 2 - .../tools}/ExampleProjectileWeapon.java | 3 +- .../{ => items/tools}/ExampleSwordItem.java | 3 +- src/main/resources/items/examplebaritem.png | Bin 0 -> 657 bytes .../{exampleore.png => exampleoreitem.png} | Bin src/main/resources/items/examplestaff.png | Bin 444 -> 395 bytes src/main/resources/items/examplesword.png | Bin 446 -> 389 bytes src/main/resources/locale/en.lang | 3 +- .../resources/mobs/icons/examplebossmob.png | Bin 0 -> 402 bytes src/main/resources/mobs/icons/examplemob.png | Bin 0 -> 398 bytes src/main/resources/objects/rock.png | Bin 5853 -> 0 bytes 15 files changed, 109 insertions(+), 126 deletions(-) delete mode 100644 src/main/java/examplemod/examples/ExampleAI.java create mode 100644 src/main/java/examplemod/examples/ExampleRecipes.java create mode 100644 src/main/java/examplemod/examples/items/ExampleBarItem.java rename src/main/java/examplemod/examples/{ => items/tools}/ExampleProjectileWeapon.java (98%) rename src/main/java/examplemod/examples/{ => items/tools}/ExampleSwordItem.java (94%) create mode 100644 src/main/resources/items/examplebaritem.png rename src/main/resources/items/{exampleore.png => exampleoreitem.png} (100%) create mode 100644 src/main/resources/mobs/icons/examplebossmob.png create mode 100644 src/main/resources/mobs/icons/examplemob.png delete mode 100644 src/main/resources/objects/rock.png diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 68e084d..4f77f57 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -4,23 +4,19 @@ import examplemod.examples.incursion.ExampleBiome; import examplemod.examples.incursion.ExampleIncursionBiome; import examplemod.examples.incursion.ExampleIncursionLevel; -import examplemod.examples.items.ExampleFoodItem; -import examplemod.examples.items.ExampleHuntIncursionMaterialItem; -import examplemod.examples.items.ExampleMaterialItem; -import examplemod.examples.items.ExamplePotionItem; -import examplemod.examples.items.ExampleOreItem; +import examplemod.examples.items.*; +import examplemod.examples.items.tools.ExampleProjectileWeapon; +import examplemod.examples.items.tools.ExampleSwordItem; import examplemod.examples.mobs.ExampleMob; import examplemod.examples.mobs.ExampleBossMob; import examplemod.examples.objects.ExampleObject; import examplemod.examples.objects.ExampleBaseRockObject; import examplemod.examples.objects.ExampleOreRockObject; +import examplemod.examples.ExampleRecipes; import necesse.engine.commands.CommandsManager; import necesse.engine.modLoader.annotations.ModEntry; import necesse.engine.registries.*; import necesse.gfx.gameTexture.GameTexture; -import necesse.inventory.recipe.Ingredient; -import necesse.inventory.recipe.Recipe; -import necesse.inventory.recipe.Recipes; import necesse.level.maps.biomes.Biome; @ModEntry @@ -56,7 +52,8 @@ public void init() { // Register our items ItemRegistry.registerItem("exampleitem", new ExampleMaterialItem(), 10, true); - ItemRegistry.registerItem(ExampleOreItem.ID, new ExampleOreItem(), 25, true); + ItemRegistry.registerItem("exampleoreitem", new ExampleOreItem(), 25, true); + ItemRegistry.registerItem("examplebaritem", new ExampleBarItem(),50,true); ItemRegistry.registerItem("examplehuntincursionitem", new ExampleHuntIncursionMaterialItem(), 50, true); ItemRegistry.registerItem("examplesword", new ExampleSwordItem(), 20, true); ItemRegistry.registerItem("examplestaff", new ExampleProjectileWeapon(), 30, true); @@ -91,50 +88,8 @@ public void initResources() { } public void postInit() { - // Add recipes - // Example item recipe, crafted in inventory for 2 iron bars - Recipes.registerModRecipe(new Recipe( - "exampleitem", - 1, - RecipeTechRegistry.NONE, - new Ingredient[]{ - new Ingredient("ironbar", 2) - } - ).showAfter("woodboat")); // Show recipe after wood boat recipe - - // Example sword recipe, crafted in iron anvil using 4 example items and 5 copper bars - Recipes.registerModRecipe(new Recipe( - "examplesword", - 1, - RecipeTechRegistry.IRON_ANVIL, - new Ingredient[]{ - new Ingredient("exampleitem", 4), - new Ingredient("copperbar", 5) - } - )); - - // Example staff recipe, crafted in workstation using 4 example items and 10 gold bars - Recipes.registerModRecipe(new Recipe( - "examplestaff", - 1, - RecipeTechRegistry.WORKSTATION, - new Ingredient[]{ - new Ingredient("exampleitem", 4), - new Ingredient("goldbar", 10) - } - ).showAfter("exampleitem")); // Show the recipe after example item recipe - - // Example food item recipe - Recipes.registerModRecipe(new Recipe( - "examplefooditem", - 1, - RecipeTechRegistry.COOKING_POT, - new Ingredient[]{ - new Ingredient("bread", 1), - new Ingredient("strawberry", 2), - new Ingredient("sugar", 1) - } - )); + // load our recipes from the ExampleRecipes class + ExampleRecipes.registerRecipes(); // Add our example mob to default cave mobs. // Spawn tables use a ticket/weight system. In general, common mobs have about 100 tickets. diff --git a/src/main/java/examplemod/examples/ExampleAI.java b/src/main/java/examplemod/examples/ExampleAI.java deleted file mode 100644 index f1a8094..0000000 --- a/src/main/java/examplemod/examples/ExampleAI.java +++ /dev/null @@ -1,68 +0,0 @@ -package examplemod.examples; - -import necesse.entity.mobs.GameDamage; -import necesse.entity.mobs.Mob; -import necesse.entity.mobs.ai.behaviourTree.composites.SelectorAINode; -import necesse.entity.mobs.ai.behaviourTree.leaves.CollisionChaserAINode; -import necesse.entity.mobs.ai.behaviourTree.leaves.WandererAINode; -import necesse.entity.mobs.ai.behaviourTree.trees.CollisionPlayerChaserAI; - -public class ExampleAI extends SelectorAINode { - /* - This node handles "find player -> chase -> try to attack" - We keep a reference so we can use its damage/knockback settings later. - */ - public final CollisionPlayerChaserAI chaser; - - // This node handles "walk around randomly" when there's nothing to chase. - public final WandererAINode wanderer; - - public ExampleAI(int searchDistance, GameDamage damage, int knockback, int wanderFrequency) { - /* - A SelectorAINode tries its children in order. - First one that can run/works is the behavior the mob uses. - - So: we add CHASING first, because we want chasing to "win" whenever possible. - */ - this.chaser = new CollisionPlayerChaserAI(searchDistance, damage, knockback) { - - // CollisionPlayerChaserAI has an attackTarget method it calls when in range. - // We override it so we can route the attack logic to OUR method below. - @Override - public boolean attackTarget(T mob, Mob target) { - // "ExampleAI.this" means "the outer ExampleAI instance" - // (because we're inside an anonymous inner class right now). - return ExampleAI.this.attackTarget(mob, target); - } - }; - addChild(this.chaser); - - /* - If the chaser doesn't have a target (or can't chase), - the selector will try the next child: wandering. - */ - this.wanderer = new WandererAINode<>(wanderFrequency); - addChild(this.wanderer); - } - - /* - This is the actual "do the hit" logic. - We keep it outside the chaser node so it's easy to change later. - */ - public boolean attackTarget(T mob, Mob target) { - /* - simpleAttack is a helper that does a basic melee attack: - - checks if the mob can attack right now - - applies damage - - applies knockback - - returns true if an attack happened - */ - return CollisionChaserAINode.simpleAttack( - mob, - target, - // Use the damage/knockback values that were passed into the chaser constructor. - this.chaser.damage, - this.chaser.knockback - ); - } -} diff --git a/src/main/java/examplemod/examples/ExampleRecipes.java b/src/main/java/examplemod/examples/ExampleRecipes.java new file mode 100644 index 0000000..174e8bf --- /dev/null +++ b/src/main/java/examplemod/examples/ExampleRecipes.java @@ -0,0 +1,83 @@ +package examplemod.examples; + +import necesse.engine.registries.RecipeTechRegistry; +import necesse.inventory.recipe.Ingredient; +import necesse.inventory.recipe.Recipe; +import necesse.inventory.recipe.Recipes; + +/* +here is where we will register our recipes into the game. + there is potentially quite a few of them so this will allow us to maintain cleaner code +*/ +public class ExampleRecipes { + + //Put your recipe registrations in here + public static void registerRecipes(){ + + // Example Bar item smelted in the forge + Recipes.registerModRecipe(new Recipe( + "examplebaritem", + 1, + RecipeTechRegistry.FORGE, + new Ingredient[]{ + new Ingredient("exampleoreitem",2) + }) + ); + + + // Example item recipe, crafted in inventory for 2 iron bars + Recipes.registerModRecipe(new Recipe( + "exampleitem", + 1, + RecipeTechRegistry.NONE, + new Ingredient[]{ + new Ingredient("examplebaritem", 2) + } + ).showAfter("woodboat")); // Show recipe after wood boat recipe + + + // Example sword recipe, crafted in iron anvil using 4 example items and 5 copper bars + Recipes.registerModRecipe(new Recipe( + "examplesword", + 1, + RecipeTechRegistry.IRON_ANVIL, + new Ingredient[]{ + new Ingredient("exampleitem", 4), + new Ingredient("examplebaritem", 5) + } + )); + + // Example staff recipe, crafted in workstation using 4 example items and 10 gold bars + Recipes.registerModRecipe(new Recipe( + "examplestaff", + 1, + RecipeTechRegistry.WORKSTATION, + new Ingredient[]{ + new Ingredient("exampleitem", 4), + new Ingredient("examplebaritem", 10) + } + ).showAfter("exampleitem")); // Show the recipe after example item recipe + + // Example food item recipe + Recipes.registerModRecipe(new Recipe( + "examplefooditem", + 1, + RecipeTechRegistry.COOKING_POT, + new Ingredient[]{ + new Ingredient("bread", 1), + new Ingredient("strawberry", 2), + new Ingredient("sugar", 1) + } + )); + + // Example potion item recipe + Recipes.registerModRecipe(new Recipe( + "examplepotionitem", + 1, + RecipeTechRegistry.ALCHEMY, + new Ingredient[]{ + new Ingredient("speedpotion", 1), + } + )); + } +} diff --git a/src/main/java/examplemod/examples/items/ExampleBarItem.java b/src/main/java/examplemod/examples/items/ExampleBarItem.java new file mode 100644 index 0000000..0e5160f --- /dev/null +++ b/src/main/java/examplemod/examples/items/ExampleBarItem.java @@ -0,0 +1,12 @@ +package examplemod.examples.items; + +import necesse.inventory.item.Item; +import necesse.inventory.item.matItem.MatItem; + +public class ExampleBarItem extends MatItem { + + public ExampleBarItem() { + super(500, Item.Rarity.UNCOMMON); + + } +} diff --git a/src/main/java/examplemod/examples/items/ExampleOreItem.java b/src/main/java/examplemod/examples/items/ExampleOreItem.java index bc9cfd9..8e3ed98 100644 --- a/src/main/java/examplemod/examples/items/ExampleOreItem.java +++ b/src/main/java/examplemod/examples/items/ExampleOreItem.java @@ -5,8 +5,6 @@ public class ExampleOreItem extends MatItem { - public static final String ID = "exampleore"; - public ExampleOreItem() { super(500, Item.Rarity.UNCOMMON); diff --git a/src/main/java/examplemod/examples/ExampleProjectileWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleProjectileWeapon.java similarity index 98% rename from src/main/java/examplemod/examples/ExampleProjectileWeapon.java rename to src/main/java/examplemod/examples/items/tools/ExampleProjectileWeapon.java index 31aff93..fe57eb1 100644 --- a/src/main/java/examplemod/examples/ExampleProjectileWeapon.java +++ b/src/main/java/examplemod/examples/items/tools/ExampleProjectileWeapon.java @@ -1,5 +1,6 @@ -package examplemod.examples; +package examplemod.examples.items.tools; +import examplemod.examples.ExampleProjectile; import necesse.engine.localization.Localization; import necesse.engine.network.gameNetworkData.GNDItemMap; import necesse.engine.sound.SoundEffect; diff --git a/src/main/java/examplemod/examples/ExampleSwordItem.java b/src/main/java/examplemod/examples/items/tools/ExampleSwordItem.java similarity index 94% rename from src/main/java/examplemod/examples/ExampleSwordItem.java rename to src/main/java/examplemod/examples/items/tools/ExampleSwordItem.java index 68fc649..43e856f 100644 --- a/src/main/java/examplemod/examples/ExampleSwordItem.java +++ b/src/main/java/examplemod/examples/items/tools/ExampleSwordItem.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.items.tools; import necesse.inventory.item.Item; import necesse.inventory.item.toolItem.swordToolItem.SwordToolItem; @@ -16,6 +16,7 @@ public ExampleSwordItem() { .setUpgradedValue(1, 95); // Upgraded tier 1 damage attackRange.setBaseValue(120); // 120 range knockback.setBaseValue(100); // 100 knockback + } } diff --git a/src/main/resources/items/examplebaritem.png b/src/main/resources/items/examplebaritem.png new file mode 100644 index 0000000000000000000000000000000000000000..92d03dff9b0e1f276bbb9d2de2188d6dffcf84eb GIT binary patch literal 657 zcmV;C0&e|@P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;&yra{vkl71T8V000SaNLh0L01FcU01FcV0GgZ_00007 zbV*G`2kHS777sKhmrR5J00GrWL_t(o!|j&8OB_KIfWJ2$iW)^QqJ+mPlXR%LRcs9y@^35x(CmJ+`lpUQ=ky$(qpK6 zc(I8n|GZ{T;T63n?`kwhWKfU98gjpp&?Y5DN9#meU8%WZt0XE#VzyUW-4n3&@>qoSZ zq!)#HLO#z@;66~5f${7hSAQ<;t+k+{<$xjo=^g;^-PsQuFyy9?J%J(|H)Q$HQ3t>$ zx)3d_Uu}baAfLmztAdQYcn+Mk=b*jx{DX4@Xgw1oUqu!8$&Wd;UvNNAcqF98YA#X) z4gGYNHl#A-RT&uG>b@^C6DH|Yh3DI|3-FWs>)3p7nsKW?Ecxp_4NWzeSjG)yrc(0w r+y}}TiAo;dJE7l5RPtA2;645U_TrY%FCNJL00000NkvXXu0mjfX(kKr literal 0 HcmV?d00001 diff --git a/src/main/resources/items/exampleore.png b/src/main/resources/items/exampleoreitem.png similarity index 100% rename from src/main/resources/items/exampleore.png rename to src/main/resources/items/exampleoreitem.png diff --git a/src/main/resources/items/examplestaff.png b/src/main/resources/items/examplestaff.png index 40200b40a213fe3a77c6f60364f6c1c1bf3c70f0..1be7fd84548bc4d92b44468565e89015781e5ea5 100644 GIT binary patch delta 369 zcmdnP+|4{ext@WsILO_J@#aaLdIkmtmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweEpc6VX;z);M<#V&fq6ez-3;1OBO zz`!jG!i)^F=12eq*-JcqUD;nTiilftY!awnc^D{m-qXb~B;s&#f&}a0hKdjJT`Rd} zu-yvparrgpq0V7JHV{Z=lMedA_fH@}G3=pi+iso=#gFr)6b)B6GpHWDpSt7x0U$`& zv4}}Yz|g0Nky~&6`jm){N5!X4US4qN%nqln!W&bWH8KVOAC^%DVy2YvC72l4nPLm#96lj`?*43N5FjEE6#z-Q52;GsujiBimVLegak@nh9y2w6aYg3B@N82 zgoKEa6l$PJi$bDHE#f&@EVE<%D z2}y29V9kc;E`O(Gxxhj~e#0p7)MQy09CKfRLk1QSq!tfEIRIB&;tLsK(*g^%i`iQ% zB*8`ka|Kc51I?RcplHHZK2TJr!=eCW$*sqKu%-oI8o^sWP!t8!C^x8?1{jfZ1+3&G zl*Xvy07^oFsy!oEn!r(VVzrB^*^r!&pn5rg6z4>>6&y!9rvu(O1pq6kf&>e(<&^*c N002ovPDHLkV1mc&u$TY< diff --git a/src/main/resources/items/examplesword.png b/src/main/resources/items/examplesword.png index 57758c18ab539cd31d4e1408283dfebf51ec79f6..5aeae299e0a0ce1d833a519d0ffb95b299f8530e 100644 GIT binary patch delta 363 zcmdnT+{!#bxt@WsILO_J@#aaLdIkmtmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweEpc6VX;z);M<#V&fq6ez-3;1OBO zz`!jG!i)^F=12eq*-JcqUD;nTiileYE9TV)<^jb{db&7x{|kYH7oHAoWt?cWsD z#dvuEZ{t)RZb=Cs2$7fqrh6oFGK7Sf<(PDKsJt_}@1-LP_!xJ!9GSquxJOa&oPd3i6GL_^qqgD3mLv5^Z=@zEHl%ES*U)-o zt>`2k5Xcfd-(JtLpykNFlSvv)eLL5)m2+$eIQ_g>yA|XJiFdZY4<7)61u934YECmI s-JZ~Ls`KG=?hf9C2Su3{Cx|dGOiI+`yLPg~2k0vXPgg&ebxsLQ0M$-&ZU6uP delta 421 zcmV;W0b2fr1HJ>0BYy#fNklWXE*2~{ z;z|^Zw1_RDqkvzHg#i>oghGi_N3!73U_Vy~EXAt?QL@GH6)a?^8WJQYLTZNu&Jg%~ z{|`#w`0N)0^Qj#YWM@Nag#=1T2uomG5=;!=Ui<^cxls)tB!4X^vY@94SO`&6nxaGj zET5B-(qSP1at%4<1XkD6GbFGkLUKZaflRO| z{0sIedPv+_A%6*WfLg?Jc*tPOrYxkSC3FL*Q8ThoAu%(8LIRiuz#*}JGNjIU`uZ2oPfvteikkHx$)mP{Z(cEU=Xm z$ccHto8{C90h}>_TuvY2f(()NHEwg> P00000NkvXXu0mjf8`h~K diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index 55de7a6..75d4784 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -8,7 +8,8 @@ exampleore=Example Ore [item] exampleitem=Example Item -exampleore=Example Ore +exampleoreitem=Example Ore +examplebaritem=Example Bar examplehuntincursionitem=Example Hunt Incursion Item examplepotionitem=Example Potion examplesword=Example Sword diff --git a/src/main/resources/mobs/icons/examplebossmob.png b/src/main/resources/mobs/icons/examplebossmob.png new file mode 100644 index 0000000000000000000000000000000000000000..471e69df3ef160406c29442b51d0cb79c29b023d GIT binary patch literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gz!3HE#_5EXk6k~CayA$KhlREW44okYDuOkD) z#(wTUiL5}rLb6AYF9SoB8UsT^3j@P1pisjL28L1t28LG&3=CE?7#PG0=Ijcz0ZOnX zdAqwXd|)VM;9?iOVhR-DEbxddW?FiQ&TU$@#)ZgF!ztBrxlqvDJ z=;eR&{14e;f{qW5=ile!FaFhHxKYnFy!~C#uccf`v0gp=FCRa;xjlce$Ffzc9$oEO zH}9XG-LHqtt8}iozR+vmDdKE=-VluZBt literal 0 HcmV?d00001 diff --git a/src/main/resources/mobs/icons/examplemob.png b/src/main/resources/mobs/icons/examplemob.png new file mode 100644 index 0000000000000000000000000000000000000000..59e94f3d76950e12a1c2cc896467b1abacc8c1f5 GIT binary patch literal 398 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gz!3HE#_5EXk6k~CayA$KhlREW44okYDuOkD) z#(wTUiL5}rLb6AYF9SoB8UsT^3j@P1pisjL28L1t28LG&3=CE?7#PG0=Ijcz0ZOnX zdAqwXd|)VM;9?iOVhR-DEbxddW?Pw~@Uo`0{ z-1$ZEu=$=@PbGH*#LCU)@(XXXkNwHo%{4XCrGQJ$X$YWMY+vBS} z>*oE_+gJ2PB+T`NUi(gw=~cgWO2{6FX?d4aqbD07uCDX$=&a2NkGPY%*>4=R3QLPP zEE3+=vQx%eYhmHbZv`j$X0ZN0akA22x?RksW>KxL^OxzRxvXE?>ABqE%HM_lbK5uj lDPP!k;M%+eJG(R47>W#oj;-iT=L7nV!PC{xWt~$(697o{jpYCU literal 0 HcmV?d00001 diff --git a/src/main/resources/objects/rock.png b/src/main/resources/objects/rock.png deleted file mode 100644 index aa51ed0427a5c368ea03de6a8132ead9438ed708..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5853 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 Date: Wed, 21 Jan 2026 19:51:04 +0000 Subject: [PATCH 04/21] Add example sound and play packet for boss mob Introduces a new sound file and registers it with custom settings. Adds ExamplePlaySoundPacket to trigger sound playback on clients, and updates ExampleAILeaf to send this packet when teleporting mobs. Also fixes extraction item ID in ExampleIncursionBiome and moves ExamplePacket to a dedicated packets package. --- src/main/java/examplemod/ExampleMod.java | 21 +++- .../examplemod/examples/ai/ExampleAI.java | 62 ++++------ .../examplemod/examples/ai/ExampleAILeaf.java | 108 +++++++++++++----- .../incursion/ExampleIncursionBiome.java | 2 +- .../examples/{ => packets}/ExamplePacket.java | 2 +- .../packets/ExamplePlaySoundPacket.java | 45 ++++++++ src/main/resources/sound/examplesound.ogg | Bin 0 -> 16094 bytes 7 files changed, 172 insertions(+), 68 deletions(-) rename src/main/java/examplemod/examples/{ => packets}/ExamplePacket.java (98%) create mode 100644 src/main/java/examplemod/examples/packets/ExamplePlaySoundPacket.java create mode 100644 src/main/resources/sound/examplesound.ogg diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 4f77f57..d2fdcbf 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -13,9 +13,13 @@ import examplemod.examples.objects.ExampleBaseRockObject; import examplemod.examples.objects.ExampleOreRockObject; import examplemod.examples.ExampleRecipes; +import examplemod.examples.packets.ExamplePacket; +import examplemod.examples.packets.ExamplePlaySoundPacket; import necesse.engine.commands.CommandsManager; import necesse.engine.modLoader.annotations.ModEntry; import necesse.engine.registries.*; +import necesse.engine.sound.SoundSettings; +import necesse.engine.sound.gameSound.GameSound; import necesse.gfx.gameTexture.GameTexture; import necesse.level.maps.biomes.Biome; @@ -24,6 +28,8 @@ public class ExampleMod { // We define our static registered objects here, so they can be referenced elsewhere public static ExampleBiome EXAMPLE_BIOME; + public static GameSound EXAMPLESOUND; + public static SoundSettings EXAMPLESOUNDSETTINGS; public void init() { System.out.println("Hello world from my example mod!"); @@ -72,8 +78,10 @@ public void init() { // Register our buff BuffRegistry.registerBuff("examplebuff", new ExampleBuff()); - // Register our packet + // Register our packets PacketRegistry.registerPacket(ExamplePacket.class); + + PacketRegistry.registerPacket(ExamplePlaySoundPacket.class); } public void initResources() { @@ -83,8 +91,17 @@ public void initResources() { // It will process your textures and save them again with a fixed alpha edge color ExampleMob.texture = GameTexture.fromFile("mobs/examplemob"); - ExampleBossMob.texture = GameTexture.fromFile("mobs/examplebossmob"); + + //initialising the sound to be used by our boss mob + EXAMPLESOUND = GameSound.fromFile("examplesound"); + + // Optional settings (volume/pitch/falloff) – used when playing via SoundSettings + EXAMPLESOUNDSETTINGS = new SoundSettings(EXAMPLESOUND) + .volume(0.8f) + .basePitch(1.0f) + .pitchVariance(0.08f) + .fallOffDistance(900); } public void postInit() { diff --git a/src/main/java/examplemod/examples/ai/ExampleAI.java b/src/main/java/examplemod/examples/ai/ExampleAI.java index 8ba9ec6..22e4b2b 100644 --- a/src/main/java/examplemod/examples/ai/ExampleAI.java +++ b/src/main/java/examplemod/examples/ai/ExampleAI.java @@ -8,70 +8,58 @@ import necesse.entity.mobs.ai.behaviourTree.trees.CollisionPlayerChaserAI; public class ExampleAI extends SelectorAINode { - public final ExampleAILeaf teleporter; - /* - This node handles "find player -> chase -> try to attack" - We keep a reference so we can use its damage/knockback settings later. - */ + // My custom “fix spawn position” leaf. + // This runs first so the mob gets put somewhere sensible before doing anything else. + public final ExampleAILeaf teleporter; + + // Vanilla AI that does: find player -> chase -> when close enough, call attackTarget(). + // We keep it as a field so we can reuse the damage/knockback values from it. public final CollisionPlayerChaserAI chaser; - // This node handles "walk around randomly" when there's nothing to chase. + // Vanilla “walk around randomly” node. This is what happens when there’s no player to chase. public final WandererAINode wanderer; public ExampleAI(int searchDistance, GameDamage damage, int knockback, int wanderFrequency) { - /* - A SelectorAINode tries its children in order. - First one that can run/works is the behaviour the mob uses. - - So: we add CHASING first, because we want chasing to "win" whenever possible. - */ + // A Selector is basically: "try child #1, if it can run then use it, + // otherwise try child #2, otherwise child #3..." + // + // So the ORDER we add children is the ORDER of priority. + // 1) Teleport / reposition leaf (highest priority). + // (In my leaf: 8 tiles = how far to check for open space, 10 tiles = how far to search for a valid spot) this.teleporter = new ExampleAILeaf<>(8, 10); - { - // 8 tiles = 256px open-space check, search within 10 tiles if it needs to move. - } addChild(this.teleporter); + + // 2) Chase + attack (second priority). this.chaser = new CollisionPlayerChaserAI(searchDistance, damage, knockback) { - // CollisionPlayerChaserAI has an attackTarget method it calls when in range. - // We override it so we can route the attack logic to OUR method below. + // The chaser decides WHEN it should attack, but it asks us HOW to attack. + // So we override this and forward it to our own method below. @Override public boolean attackTarget(T mob, Mob target) { - // "ExampleAI.this" means "the outer ExampleAI instance" - // (because we're inside an anonymous inner class right now). return ExampleAI.this.attackTarget(mob, target); } }; addChild(this.chaser); - /* - If the chaser doesn't have a target (or can't chase), - the selector will try the next child: wandering. - */ + // 3) Wander around if we aren’t teleporting, and we aren’t chasing anyone. this.wanderer = new WandererAINode<>(wanderFrequency); addChild(this.wanderer); } - /* - attack logic. - We keep it outside the chaser node so it's easy to change later. - */ + // This is the “how to attack” part used by the chaser above. + // Keeping it here makes it easy to change later (special attacks, effects, etc). public boolean attackTarget(T mob, Mob target) { - /* - simpleAttack is a helper that does a basic melee attack: - - checks if the mob can attack right now - - applies damage - - applies knockback - - returns true if an attack happened - */ + + // simpleAttack is the vanilla helper for a basic melee hit. + // It handles cooldown/range stuff internally and returns true if an attack happened. return CollisionChaserAINode.simpleAttack( mob, target, - // Use the damage/knockback values that were passed into the chaser constructor. - this.chaser.damage, - this.chaser.knockback + this.chaser.damage, // use the damage we configured in the chaser + this.chaser.knockback // use the knockback we configured in the chaser ); } } diff --git a/src/main/java/examplemod/examples/ai/ExampleAILeaf.java b/src/main/java/examplemod/examples/ai/ExampleAILeaf.java index fae5455..ecfe7cf 100644 --- a/src/main/java/examplemod/examples/ai/ExampleAILeaf.java +++ b/src/main/java/examplemod/examples/ai/ExampleAILeaf.java @@ -4,6 +4,7 @@ import java.awt.geom.Point2D; import java.util.ArrayList; +import examplemod.examples.packets.ExamplePlaySoundPacket; import necesse.engine.util.GameMath; import necesse.engine.util.GameRandom; import necesse.entity.mobs.Mob; @@ -13,126 +14,179 @@ import necesse.level.maps.IncursionLevel; import necesse.level.maps.Level; -/** - Runs once after spawn: - If we are in an IncursionLevel and the mob is NOT inside the entrance open-space, - teleport it to a valid tile near the incursion return portal (entrance centre). +/* + This is a "run once after spawn" AI leaf. - "Entrance centre" = return portal position: - IncursionBiome.generateEntrance(...) clears an open area and calls addReturnPortalOnTile(...) - which ends up setting IncursionLevel returnPortalPosition/ID. - */ + The problem it solves: + • Sometimes a mob spawns in a bad spot in an Incursion (not in the cleared entrance area) + • If that happens, we move it into the entrance area so the fight / behaviour starts properly + + What we consider the "entrance area": + • The centre is the Incursion return portal position + • The biome generator clears open space around that portal +*/ public class ExampleAILeaf extends AINode { + // We only want to do the check once per mob. private boolean didCheck = false; - // How close the mob must be to the entrance centre to count as "in the entrance area" + // How close you need to be to the portal to count as "in the entrance area" + // (in tiles, converted to pixels later). private final int openRadiusTiles; - // How far around the portal we will search to find a valid teleport tile + // How far we search around the portal to find a safe landing spot. private final int searchRadiusTiles; public ExampleAILeaf(int openRadiusTiles, int searchRadiusTiles) { + // Just making sure we don't get silly values. this.openRadiusTiles = Math.max(1, openRadiusTiles); this.searchRadiusTiles = Math.max(this.openRadiusTiles, searchRadiusTiles); } @Override protected void onRootSet(AINode aiNode, T t, Blackboard blackboard) { - + // Nothing needed here for this leaf. } @Override public void init(T mob, Blackboard blackboard) { - // Nothing to init; we just run once in tick(). + // Nothing to init. We just do everything in tick() one time. } @Override public AINodeResult tick(T mob, Blackboard blackboard) { - // Only do this once per mob instance. + + // Don’t keep doing this every tick. We only run it once. if (didCheck) return AINodeResult.FAILURE; didCheck = true; - // Only do this on the server (positions are authoritative there). + // Only do this on the server. + // The server is the “real” source of truth for mob position. if (!mob.isServer()) return AINodeResult.FAILURE; Level level = mob.getLevel(); + + // This only applies to Incursion levels. if (!(level instanceof IncursionLevel)) return AINodeResult.FAILURE; IncursionLevel incursion = (IncursionLevel) level; - // Entrance centre is the return portal position. + // The "entrance centre" is the return portal position. + // If this is null, something is wrong / not generated yet, so bail. Point portalPos = incursion.getReturnPortalPosition(); if (portalPos == null) return AINodeResult.FAILURE; float centerX = portalPos.x; float centerY = portalPos.y; - // If already inside the entrance open space, do nothing. + // If we're already close enough to the portal, we count as “in the entrance area”. + // (Tiles -> pixels. Tiles are 32x32 in Necesse.) float openRadiusPx = this.openRadiusTiles * 32.0f; if (mob.getDistance(centerX, centerY) <= openRadiusPx) { + // Already fine, do nothing. return AINodeResult.FAILURE; } - // Otherwise teleport to a nearby valid spot close to the portal. + // We’re NOT in the entrance area, so we need to move the mob. + // Look for a valid spot near the portal. Point2D.Float dest = findValidTeleportPos(incursion, mob, centerX, centerY, this.searchRadiusTiles); + // If we found somewhere safe, teleport the mob there. if (dest != null) { - // "Direct" position set (sends movement packet if needed). + + // Stop any current movement so we don't fight with pathing. mob.stopMoving(); + + // Move instantly. + // The "true" here is important: it makes sure the move is synced properly. mob.setPos(dest.x, dest.y, true); + + // Sounds must be played on clients, not on the server. + // So we send a packet to nearby clients telling them to play the sound here. + mob.getLevel().getServer().network.sendToClientsWithEntity( + new ExamplePlaySoundPacket(mob.x, mob.y), + mob + ); } - // Return FAILURE so parent Selector continues to chase/wander this same tick. + // We always return FAILURE here because this leaf isn't meant to "take over" the AI. + // It's just a one-time fix, then the parent Selector can move on to chase/wander normally. return AINodeResult.FAILURE; } + /* + Find a safe tile near the portal to teleport to. + + We search in "rings" around the centre: + • r = 0 is the centre tile + • r = 1 is the tiles touching it + • r = 2 is the next ring out, etc + + For each tile we check: + • Not liquid + • Not shore + • Mob doesn't collide with the map + • Mob doesn't collide with another mob/player + */ private static Point2D.Float findValidTeleportPos(IncursionLevel level, Mob mob, float centerX, float centerY, int searchRadiusTiles) { int centerTileX = GameMath.getTileCoordinate(centerX); int centerTileY = GameMath.getTileCoordinate(centerY); - // Try rings from 0 outward (0 = centre) + // Try rings from centre outward until we find something. for (int r = 0; r <= searchRadiusTiles; r++) { ArrayList ring = buildRing(centerTileX, centerTileY, r); - // Randomise the order a bit so we don't always pick the same spot + // Shuffle-ish: pick random points so we don't always choose the same spot every time. while (!ring.isEmpty()) { Point p = ring.remove(GameRandom.globalRandom.nextInt(ring.size())); - // Avoid liquid/shore like vanilla spawn logic + // Don't teleport into water/shore. if (level.isLiquidTile(p.x, p.y)) continue; if (level.isShore(p.x, p.y)) continue; + // Convert tile coords to world pixel coords. + // +16 puts us in the centre of the tile. int px = p.x * 32 + 16; int py = p.y * 32 + 16; - // Must not collide with the map, objects, etc. + // Make sure the mob can actually stand there. if (mob.collidesWith(level, px, py)) continue; - // Also avoid landing inside another mob/player + // Also don't land inside another mob/player. if (mob.collidesWithAnyMob(level, px, py)) continue; + // This spot looks good. return new Point2D.Float(px, py); } } - // No valid tile found + // Couldn't find a valid spot. return null; } + /* + Build a list of tile points that make up a square "ring" around (cx, cy). + + r = 0: just the centre + r = 1: the outer edge of a 3x3 square + r = 2: the outer edge of a 5x5 square + etc + */ private static ArrayList buildRing(int cx, int cy, int r) { ArrayList points = new ArrayList<>(); + if (r == 0) { points.add(new Point(cx, cy)); return points; } - // Top and bottom edges + // Top + bottom edges for (int dx = -r; dx <= r; dx++) { points.add(new Point(cx + dx, cy - r)); points.add(new Point(cx + dx, cy + r)); } - // Left and right edges (excluding corners already added) + + // Left + right edges (skip corners because top/bottom already added them) for (int dy = -r + 1; dy <= r - 1; dy++) { points.add(new Point(cx - r, cy + dy)); points.add(new Point(cx + r, cy + dy)); diff --git a/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java b/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java index e9ffdde..e7981c3 100644 --- a/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java +++ b/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java @@ -32,7 +32,7 @@ public ExampleIncursionBiome() { // Items required to be obtained when completing an extraction objective in this incursion @Override public Collection getExtractionItems(IncursionData data) { - return Collections.singleton(ItemRegistry.getItem("exampleore")); + return Collections.singleton(ItemRegistry.getItem("exampleoreitem")); } /** diff --git a/src/main/java/examplemod/examples/ExamplePacket.java b/src/main/java/examplemod/examples/packets/ExamplePacket.java similarity index 98% rename from src/main/java/examplemod/examples/ExamplePacket.java rename to src/main/java/examplemod/examples/packets/ExamplePacket.java index f0255c8..6264da2 100644 --- a/src/main/java/examplemod/examples/ExamplePacket.java +++ b/src/main/java/examplemod/examples/packets/ExamplePacket.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.packets; import necesse.engine.network.NetworkPacket; import necesse.engine.network.Packet; diff --git a/src/main/java/examplemod/examples/packets/ExamplePlaySoundPacket.java b/src/main/java/examplemod/examples/packets/ExamplePlaySoundPacket.java new file mode 100644 index 0000000..fe80682 --- /dev/null +++ b/src/main/java/examplemod/examples/packets/ExamplePlaySoundPacket.java @@ -0,0 +1,45 @@ +package examplemod.examples.packets; + +import examplemod.ExampleMod; +import necesse.engine.network.NetworkPacket; +import necesse.engine.network.Packet; +import necesse.engine.network.PacketReader; +import necesse.engine.network.PacketWriter; +import necesse.engine.network.client.Client; +import necesse.engine.sound.SoundEffect; + +/** + * SERVER -> CLIENT packet: + * Tells the receiving client to play the example sound at a specific world position (x, y). + */ + +public class ExamplePlaySoundPacket extends Packet { + public final float x; + public final float y; + + // Decode (CLIENT receiving) + public ExamplePlaySoundPacket(byte[] data) { + super(data); + PacketReader r = new PacketReader(this); + x = r.getNextFloat(); + y = r.getNextFloat(); + } + + // Encode (SERVER sending) + public ExamplePlaySoundPacket(float x, float y) { + this.x = x; + this.y = y; + + PacketWriter w = new PacketWriter(this); + w.putNextFloat(x); + w.putNextFloat(y); + } + + // Runs ONLY on client + @Override + public void processClient(NetworkPacket packet, Client client) { + if (ExampleMod.EXAMPLESOUNDSETTINGS != null) { + ExampleMod.EXAMPLESOUNDSETTINGS.play(SoundEffect.effect(x, y)); + } + } +} diff --git a/src/main/resources/sound/examplesound.ogg b/src/main/resources/sound/examplesound.ogg new file mode 100644 index 0000000000000000000000000000000000000000..40c9c2073366ddebe3c9b5e88342db158104326a GIT binary patch literal 16094 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 literal 0 HcmV?d00001 From 978f2a7de659e804d4fbbfd946192127f3c6fdb7 Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Thu, 22 Jan 2026 01:14:22 +0000 Subject: [PATCH 05/21] Refactor item/object IDs and add ExampleStoneItem Standardized item and object IDs by removing 'item' suffixes and updating all references accordingly. Added a new ExampleStoneItem class and registered it. Updated resource file names and localization keys to match new IDs. --- src/main/java/examplemod/ExampleMod.java | 15 ++++++++------- .../java/examplemod/examples/ExampleRecipes.java | 14 +++++++------- .../incursion/ExampleIncursionBiome.java | 4 ++-- .../examples/items/ExampleStoneItem.java | 9 +++++++++ .../examples/objects/ExampleBaseRockObject.java | 4 +--- .../examples/objects/ExampleOreRockObject.java | 2 -- .../items/{examplebaritem.png => examplebar.png} | Bin .../{examplefooditem.png => examplefood.png} | Bin ...item.png => examplehuntincursionmaterial.png} | Bin .../items/{exampleoreitem.png => exampleore.png} | Bin .../{examplepotionitem.png => examplepotion.png} | Bin src/main/resources/locale/en.lang | 11 ++++++----- 12 files changed, 33 insertions(+), 26 deletions(-) create mode 100644 src/main/java/examplemod/examples/items/ExampleStoneItem.java rename src/main/resources/items/{examplebaritem.png => examplebar.png} (100%) rename src/main/resources/items/{examplefooditem.png => examplefood.png} (100%) rename src/main/resources/items/{examplehuntincursionitem.png => examplehuntincursionmaterial.png} (100%) rename src/main/resources/items/{exampleoreitem.png => exampleore.png} (100%) rename src/main/resources/items/{examplepotionitem.png => examplepotion.png} (100%) diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index d2fdcbf..5b0f488 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -51,20 +51,21 @@ public void init() { // Register a rock for the example incursion to use as cave walls ExampleBaseRockObject exampleBaseRock = new ExampleBaseRockObject(); - ObjectRegistry.registerObject(ExampleBaseRockObject.ID, exampleBaseRock, -1.0F, true); + ObjectRegistry.registerObject("examplebaserock", exampleBaseRock, -1.0F, true); // Register an ore rock that overlays onto our incursion rock - ObjectRegistry.registerObject(ExampleOreRockObject.ID, new ExampleOreRockObject(exampleBaseRock), -1.0F, true); + ObjectRegistry.registerObject("exampleorerock", new ExampleOreRockObject(exampleBaseRock), -1.0F, true); // Register our items ItemRegistry.registerItem("exampleitem", new ExampleMaterialItem(), 10, true); - ItemRegistry.registerItem("exampleoreitem", new ExampleOreItem(), 25, true); - ItemRegistry.registerItem("examplebaritem", new ExampleBarItem(),50,true); - ItemRegistry.registerItem("examplehuntincursionitem", new ExampleHuntIncursionMaterialItem(), 50, true); + ItemRegistry.registerItem("examplestone", new ExampleStoneItem(),15,true); + ItemRegistry.registerItem("exampleore", new ExampleOreItem(), 25, true); + ItemRegistry.registerItem("examplebar", new ExampleBarItem(),50,true); + ItemRegistry.registerItem("examplehuntincursionmaterial", new ExampleHuntIncursionMaterialItem(), 50, true); ItemRegistry.registerItem("examplesword", new ExampleSwordItem(), 20, true); ItemRegistry.registerItem("examplestaff", new ExampleProjectileWeapon(), 30, true); - ItemRegistry.registerItem("examplepotionitem", new ExamplePotionItem(), 10, true); - ItemRegistry.registerItem("examplefooditem", new ExampleFoodItem(),15, true); + ItemRegistry.registerItem("examplepotion", new ExamplePotionItem(), 10, true); + ItemRegistry.registerItem("examplefood", new ExampleFoodItem(),15, true); // Register our mob MobRegistry.registerMob("examplemob", ExampleMob.class, true); diff --git a/src/main/java/examplemod/examples/ExampleRecipes.java b/src/main/java/examplemod/examples/ExampleRecipes.java index 174e8bf..40c542a 100644 --- a/src/main/java/examplemod/examples/ExampleRecipes.java +++ b/src/main/java/examplemod/examples/ExampleRecipes.java @@ -16,11 +16,11 @@ public static void registerRecipes(){ // Example Bar item smelted in the forge Recipes.registerModRecipe(new Recipe( - "examplebaritem", + "examplebar", 1, RecipeTechRegistry.FORGE, new Ingredient[]{ - new Ingredient("exampleoreitem",2) + new Ingredient("exampleore",2) }) ); @@ -31,7 +31,7 @@ public static void registerRecipes(){ 1, RecipeTechRegistry.NONE, new Ingredient[]{ - new Ingredient("examplebaritem", 2) + new Ingredient("examplebar", 2) } ).showAfter("woodboat")); // Show recipe after wood boat recipe @@ -43,7 +43,7 @@ public static void registerRecipes(){ RecipeTechRegistry.IRON_ANVIL, new Ingredient[]{ new Ingredient("exampleitem", 4), - new Ingredient("examplebaritem", 5) + new Ingredient("examplebar", 5) } )); @@ -54,13 +54,13 @@ public static void registerRecipes(){ RecipeTechRegistry.WORKSTATION, new Ingredient[]{ new Ingredient("exampleitem", 4), - new Ingredient("examplebaritem", 10) + new Ingredient("examplebar", 10) } ).showAfter("exampleitem")); // Show the recipe after example item recipe // Example food item recipe Recipes.registerModRecipe(new Recipe( - "examplefooditem", + "examplefood", 1, RecipeTechRegistry.COOKING_POT, new Ingredient[]{ @@ -72,7 +72,7 @@ public static void registerRecipes(){ // Example potion item recipe Recipes.registerModRecipe(new Recipe( - "examplepotionitem", + "examplepotion", 1, RecipeTechRegistry.ALCHEMY, new Ingredient[]{ diff --git a/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java b/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java index e7981c3..379782a 100644 --- a/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java +++ b/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java @@ -32,7 +32,7 @@ public ExampleIncursionBiome() { // Items required to be obtained when completing an extraction objective in this incursion @Override public Collection getExtractionItems(IncursionData data) { - return Collections.singleton(ItemRegistry.getItem("exampleoreitem")); + return Collections.singleton(ItemRegistry.getItem("exampleore")); } /** @@ -42,7 +42,7 @@ public Collection getExtractionItems(IncursionData data) { @Override public LootTable getHuntDrop(IncursionData incursionData) { return new LootTable( - new ChanceLootItem(0.66F, "examplehuntincursionitem") + new ChanceLootItem(0.66F, "examplehuntincursionmaterial") ); } diff --git a/src/main/java/examplemod/examples/items/ExampleStoneItem.java b/src/main/java/examplemod/examples/items/ExampleStoneItem.java new file mode 100644 index 0000000..f217e3b --- /dev/null +++ b/src/main/java/examplemod/examples/items/ExampleStoneItem.java @@ -0,0 +1,9 @@ +package examplemod.examples.items; + +import necesse.inventory.item.placeableItem.StonePlaceableItem; + +public class ExampleStoneItem extends StonePlaceableItem { + public ExampleStoneItem(){ + super(100); + } +} diff --git a/src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java b/src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java index 44ed536..9379e67 100644 --- a/src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java +++ b/src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java @@ -5,10 +5,8 @@ public class ExampleBaseRockObject extends RockObject { - public static final String ID = "examplebaserock"; - public ExampleBaseRockObject() { - super("examplebaserock", new Color(92, 37, 23), "stone", "objects", "landscaping"); + super("examplebaserock", new Color(92, 37, 23), "examplestone", "objects", "landscaping"); this.toolTier = 5.0F; } } diff --git a/src/main/java/examplemod/examples/objects/ExampleOreRockObject.java b/src/main/java/examplemod/examples/objects/ExampleOreRockObject.java index f2e1e3d..6379944 100644 --- a/src/main/java/examplemod/examples/objects/ExampleOreRockObject.java +++ b/src/main/java/examplemod/examples/objects/ExampleOreRockObject.java @@ -9,8 +9,6 @@ */ public class ExampleOreRockObject extends RockOreObject { - public static final String ID = "exampleorerock"; - public ExampleOreRockObject(RockObject parentRock) { super( parentRock, diff --git a/src/main/resources/items/examplebaritem.png b/src/main/resources/items/examplebar.png similarity index 100% rename from src/main/resources/items/examplebaritem.png rename to src/main/resources/items/examplebar.png diff --git a/src/main/resources/items/examplefooditem.png b/src/main/resources/items/examplefood.png similarity index 100% rename from src/main/resources/items/examplefooditem.png rename to src/main/resources/items/examplefood.png diff --git a/src/main/resources/items/examplehuntincursionitem.png b/src/main/resources/items/examplehuntincursionmaterial.png similarity index 100% rename from src/main/resources/items/examplehuntincursionitem.png rename to src/main/resources/items/examplehuntincursionmaterial.png diff --git a/src/main/resources/items/exampleoreitem.png b/src/main/resources/items/exampleore.png similarity index 100% rename from src/main/resources/items/exampleoreitem.png rename to src/main/resources/items/exampleore.png diff --git a/src/main/resources/items/examplepotionitem.png b/src/main/resources/items/examplepotion.png similarity index 100% rename from src/main/resources/items/examplepotionitem.png rename to src/main/resources/items/examplepotion.png diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index 75d4784..f5d37dc 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -8,13 +8,14 @@ exampleore=Example Ore [item] exampleitem=Example Item -exampleoreitem=Example Ore -examplebaritem=Example Bar -examplehuntincursionitem=Example Hunt Incursion Item -examplepotionitem=Example Potion +examplestone=Example Stone +exampleore=Example Ore +examplebar=Example Bar +examplehuntincursionmaterial=Example Hunt Incursion Material +examplepotion=Example Potion examplesword=Example Sword examplestaff=Example Staff -examplefooditem=Example Food +examplefood=Example Food [itemtooltip] examplestafftip=Shoots a homing, piercing projectile From 17d29f0f2557c11b767d86ef465f6e584974ee4c Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Thu, 22 Jan 2026 01:58:24 +0000 Subject: [PATCH 06/21] Play sound for AI leaf on both teleport and failure Added logic to send a sound packet to clients when the AI leaf fails due to proximity, ensuring sounds are played for both teleport and non-teleport cases. Also added examplestone.png to resources. --- .../examplemod/examples/ai/ExampleAILeaf.java | 9 ++++++++- src/main/resources/items/examplestone.png | Bin 0 -> 467 bytes 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/items/examplestone.png diff --git a/src/main/java/examplemod/examples/ai/ExampleAILeaf.java b/src/main/java/examplemod/examples/ai/ExampleAILeaf.java index ecfe7cf..30d128a 100644 --- a/src/main/java/examplemod/examples/ai/ExampleAILeaf.java +++ b/src/main/java/examplemod/examples/ai/ExampleAILeaf.java @@ -83,6 +83,13 @@ public AINodeResult tick(T mob, Blackboard blackboard) { // (Tiles -> pixels. Tiles are 32x32 in Necesse.) float openRadiusPx = this.openRadiusTiles * 32.0f; if (mob.getDistance(centerX, centerY) <= openRadiusPx) { + + // Sounds must be played on clients, not on the server. + // So we send a packet to nearby clients telling them to play the sound here if teleport doesnt happen . + mob.getLevel().getServer().network.sendToClientsWithEntity( + new ExamplePlaySoundPacket(mob.x, mob.y), + mob + ); // Already fine, do nothing. return AINodeResult.FAILURE; } @@ -102,7 +109,7 @@ public AINodeResult tick(T mob, Blackboard blackboard) { mob.setPos(dest.x, dest.y, true); // Sounds must be played on clients, not on the server. - // So we send a packet to nearby clients telling them to play the sound here. + // So we send a packet to nearby clients telling them to play the sound here if teleport happens . mob.getLevel().getServer().network.sendToClientsWithEntity( new ExamplePlaySoundPacket(mob.x, mob.y), mob diff --git a/src/main/resources/items/examplestone.png b/src/main/resources/items/examplestone.png new file mode 100644 index 0000000000000000000000000000000000000000..5b0489d744636404ae4f0ea00d22bda663fb7c45 GIT binary patch literal 467 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$);LoovvyXX~Dpa^GyM`SSr1Gg{;GcwGYBLNg-FY)wsWq-ve#%N|Tt9XwC&;{b2 zE{-7)hm#W|UN%gO>HWns>;K1-@hLm}rrh*lmSxg;^2=t*!Tt%C{vT;!xjey>S>?x^ zMj&wPcAN{M7fxI#oj*&WDzjlie`12^VYRk}bGa{kwmj3G!zdJS^?u?Hj`aEW3npZ+ z^XwB)Fx{HWd|e{%lDeSYT?1}8(i@eU|1sRO^9DbiVl1@+602bw+|?PaJRXT2~frNzO=zRn7^oJhj+cBqCnk xmvitJKC Date: Thu, 22 Jan 2026 03:38:55 +0000 Subject: [PATCH 07/21] Add landscaping recipes and update item textures Introduces landscaping recipes for examplebaserock and exampleorerock in ExampleRecipes.java. Updates textures for examplebaserock.png, exampleorerock.png, and exampleore.png to reflect new or improved visuals. --- .../examplemod/examples/ExampleRecipes.java | 51 ++++++++++++------ src/main/resources/items/examplebaserock.png | Bin 547 -> 5617 bytes src/main/resources/items/exampleorerock.png | Bin 547 -> 5617 bytes src/main/resources/objects/exampleore.png | Bin 730 -> 8435 bytes 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/main/java/examplemod/examples/ExampleRecipes.java b/src/main/java/examplemod/examples/ExampleRecipes.java index 40c542a..058ea11 100644 --- a/src/main/java/examplemod/examples/ExampleRecipes.java +++ b/src/main/java/examplemod/examples/ExampleRecipes.java @@ -14,17 +14,6 @@ public class ExampleRecipes { //Put your recipe registrations in here public static void registerRecipes(){ - // Example Bar item smelted in the forge - Recipes.registerModRecipe(new Recipe( - "examplebar", - 1, - RecipeTechRegistry.FORGE, - new Ingredient[]{ - new Ingredient("exampleore",2) - }) - ); - - // Example item recipe, crafted in inventory for 2 iron bars Recipes.registerModRecipe(new Recipe( "exampleitem", @@ -36,7 +25,17 @@ public static void registerRecipes(){ ).showAfter("woodboat")); // Show recipe after wood boat recipe - // Example sword recipe, crafted in iron anvil using 4 example items and 5 copper bars + //FORGE RECIPES + Recipes.registerModRecipe(new Recipe( + "examplebar", + 1, + RecipeTechRegistry.FORGE, + new Ingredient[]{ + new Ingredient("exampleore",2) + }) + ); + + //IRON ANVIL RECIPES Recipes.registerModRecipe(new Recipe( "examplesword", 1, @@ -47,7 +46,7 @@ public static void registerRecipes(){ } )); - // Example staff recipe, crafted in workstation using 4 example items and 10 gold bars + //WORKSTATION RECIPES Recipes.registerModRecipe(new Recipe( "examplestaff", 1, @@ -58,7 +57,7 @@ public static void registerRecipes(){ } ).showAfter("exampleitem")); // Show the recipe after example item recipe - // Example food item recipe + //COOKING POT RECIPES Recipes.registerModRecipe(new Recipe( "examplefood", 1, @@ -70,7 +69,7 @@ public static void registerRecipes(){ } )); - // Example potion item recipe + //ALCHEMY RECIPES Recipes.registerModRecipe(new Recipe( "examplepotion", 1, @@ -79,5 +78,27 @@ public static void registerRecipes(){ new Ingredient("speedpotion", 1), } )); + + //LANDSCAPING RECIPES + Recipes.registerModRecipe(new Recipe( + "examplebaserock", + 1, + RecipeTechRegistry.LANDSCAPING, + new Ingredient[]{ + new Ingredient("examplestone", 5), + } + )); + + Recipes.registerModRecipe(new Recipe( + "exampleorerock", + 1, + RecipeTechRegistry.LANDSCAPING, + new Ingredient[]{ + new Ingredient("examplestone", 5), + new Ingredient("exampleore", 5), + } + )); + + } } diff --git a/src/main/resources/items/examplebaserock.png b/src/main/resources/items/examplebaserock.png index bfc82382d32caeed91cd11fe1256e27b535a4b64..f948e26509500a5cc4c8e3dc4a9c49f69375b0fc 100644 GIT binary patch literal 5617 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 From 785c7d2c48f5427deb4d3e5d44754ed88a410145 Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Fri, 23 Jan 2026 04:21:03 +0000 Subject: [PATCH 08/21] Add example loot table and preset, update incursion level Introduces ExampleLootTable and ExamplePreset classes for custom loot and structure generation. Updates ExampleIncursionLevel to use the new preset, demonstrating how to place custom structures and fill them with loot during incursion level generation. --- .../examplemod/examples/ExampleLootTable.java | 21 +++++++ .../examplemod/examples/ExamplePreset.java | 50 ++++++++++++++++ .../incursion/ExampleIncursionLevel.java | 57 ++++++++++++++----- 3 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 src/main/java/examplemod/examples/ExampleLootTable.java create mode 100644 src/main/java/examplemod/examples/ExamplePreset.java diff --git a/src/main/java/examplemod/examples/ExampleLootTable.java b/src/main/java/examplemod/examples/ExampleLootTable.java new file mode 100644 index 0000000..ef87e87 --- /dev/null +++ b/src/main/java/examplemod/examples/ExampleLootTable.java @@ -0,0 +1,21 @@ +package examplemod.examples; + +import necesse.inventory.lootTable.LootTable; +import necesse.inventory.lootTable.lootItem.LootItem; +import necesse.inventory.lootTable.lootItem.ChanceLootItem; +import necesse.inventory.lootTable.lootItem.OneOfLootItems; + +public class ExampleLootTable { + public static final LootTable exampleloottable = new LootTable( + new LootItem("exampleore", 8), + new LootItem("examplebar", 20), + new LootItem("examplepotion",1), + new LootItem("examplefood",1), + new OneOfLootItems( + new ChanceLootItem(0.60f, "examplesword"), + new ChanceLootItem(0.60f,"examplestaff") + ) + ); + + private ExampleLootTable() { } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/ExamplePreset.java b/src/main/java/examplemod/examples/ExamplePreset.java new file mode 100644 index 0000000..8f83c99 --- /dev/null +++ b/src/main/java/examplemod/examples/ExamplePreset.java @@ -0,0 +1,50 @@ +package examplemod.examples; + +import examplemod.ExampleMod; +import necesse.engine.registries.ObjectRegistry; +import necesse.engine.registries.TileRegistry; +import necesse.engine.util.GameRandom; +import necesse.level.maps.presets.Preset; + +public class ExamplePreset extends Preset { + public ExamplePreset(GameRandom random) { + // Pass a GameRandom so loot is randomized + super(15, 11); + + int floor = TileRegistry.getTileID("stonefloor"); + int wall = ObjectRegistry.getObjectID("stonewall"); + int air = ObjectRegistry.getObjectID("air"); + int storagebox = ObjectRegistry.getObjectID("storagebox"); // replace with what you want + + // Fill background + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + setTile(x, y, floor); + setObject(x, y, air); + } + } + + // Simple rectangle “room” + for (int x = 0; x < width; x++) { + setObject(x, 0, wall); + setObject(x, height - 1, wall); + } + for (int y = 0; y < height; y++) { + setObject(0, y, wall); + setObject(width - 1, y, wall); + } + + // Put a storagebox + int storageboxX = width / 2; + int storageboxY = height / 2; + setObject(storageboxX,storageboxY, storagebox,1); + + //Fill the storage box with loot from our example loot table + addInventory(ExampleLootTable.exampleloottable, random,storageboxX,storageboxY); + + // Optional: only allow placing if the whole area isn’t floor already (example rule) + addCanApplyRectEachPredicate(0, 0, width, height, 0, (level, levelX, levelY, dir) -> + !level.getTile(levelX, levelY).isFloor + ); + } +} diff --git a/src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java b/src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java index 8e0b4d9..e3f7acc 100644 --- a/src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java +++ b/src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java @@ -1,6 +1,7 @@ package examplemod.examples.incursion; import examplemod.ExampleMod; +import examplemod.examples.ExamplePreset; import necesse.engine.GameEvents; import necesse.engine.events.worldGeneration.GenerateCaveLayoutEvent; import necesse.engine.events.worldGeneration.GeneratedCaveOresEvent; @@ -15,6 +16,9 @@ import necesse.level.maps.incursion.BiomeExtractionIncursionData; import necesse.level.maps.incursion.BiomeMissionIncursionData; import necesse.level.maps.incursion.IncursionBiome; +import necesse.level.maps.presets.Preset; + +import java.awt.*; /** * Example incursion level. @@ -44,44 +48,67 @@ public ExampleIncursionLevel(LevelIdentifier identifier, BiomeMissionIncursionDa } public void generateLevel(BiomeMissionIncursionData incursionData, AltarData altarData) { - // Create the cave generator using deep rock tiles for floors and walls CaveGeneration cg = new CaveGeneration(this, "deeprocktile", "examplebaserock"); - - // Seed the generator so this incursion layout is deterministic per mission cg.random.setSeed(incursionData.getUniqueID()); - // Fire the cave layout generation event, allowing mods or perks to modify - // or cancel cave generation before the default logic runs GameEvents.triggerEvent( new GenerateCaveLayoutEvent(this, cg), - e -> { - cg.generateLevel(0.38F, 4, 3, 6); - } + e -> cg.generateLevel(0.38F, 4, 3, 6) ); - // Used to reserve space so later generation steps avoid overwriting the entrance + //entrance + perks (anything that must never be overwritten by perk presets) PresetGeneration entranceAndPerkPresets = new PresetGeneration(this); - // Generate an incursion entrance that clears terrain, - // blends edges, reserves space, and places the return portal - IncursionBiome.generateEntrance( + //your own structures (custom rooms, etc.) + PresetGeneration structurePresets = new PresetGeneration(this); + + // Generate entrance (this reserves space inside entranceAndPerkPresets) + int spawnSize = 32; + Point entranceMid = IncursionBiome.generateEntrance( this, entranceAndPerkPresets, cg.random, - 32, + spawnSize, cg.rockTile, "exampletile", "exampletile", "exampleobject" ); - // Now call incursion perks to generate their presets + // reserve the entrance space in structurePresets too, so your own structures don't overwrite the entrance area. + int ex = entranceMid.x - spawnSize / 2; + int ey = entranceMid.y - spawnSize / 2; + structurePresets.addOccupiedSpace(ex, ey, spawnSize, spawnSize); + + + //EXAMPLE PRESET + Preset examplePreset = new ExamplePreset(cg.random); + Point placedAt = structurePresets.findRandomValidPositionAndApply( + cg.random, + 2500,//realistically this would be lower if you didn't want it to be guaranteed + examplePreset, + 8, + true,// randomizeMirrorX + true, // randomizeMirrorY + true, // randomizeRotation + false // overrideCanPlace (false = respect canApply rules) + ); + + if (placedAt != null) { + structurePresets.addOccupiedSpace(placedAt.x, placedAt.y, examplePreset.width, examplePreset.height); + entranceAndPerkPresets.addOccupiedSpace(placedAt.x, placedAt.y, examplePreset.width, examplePreset.height); + } + + /* + Perk presets use entranceAndPerkPresets, so they avoid the entrance as well as any presets + you added to structurePresets + */ generatePresetsBasedOnPerks(altarData, entranceAndPerkPresets, cg.random, baseBiome); // This call clears all invalid objects/tiles, so that there are no cut in half beds, etc. GenerationTools.checkValid(this); - // For extraction incursions, guarantee tungsten ore veins for objectives + // For extraction incursions, guarantee example ore veins for objectives if (incursionData instanceof BiomeExtractionIncursionData) { cg.generateGuaranteedOreVeins(40, 4, 8, ObjectRegistry.getObjectID("exampleorerock")); } From 7512ef335f1256ac81bb0cecc3d108892af1b399 Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Fri, 23 Jan 2026 19:48:25 +0000 Subject: [PATCH 09/21] Refactor ExamplePreset and add utility classes Refactored ExamplePreset to use a script-based preset application instead of manual tile/object placement. Added ExamplePresetCode for the original code-based preset logic, and introduced MissingTextureReporter utility for handling missing texture errors with logging and on-screen notifications. --- .../examplemod/examples/ExamplePreset.java | 41 ++------ .../examples/ExamplePresetCode.java | 49 ++++++++++ .../examples/util/MissingTextureReporter.java | 98 +++++++++++++++++++ 3 files changed, 156 insertions(+), 32 deletions(-) create mode 100644 src/main/java/examplemod/examples/ExamplePresetCode.java create mode 100644 src/main/java/examplemod/examples/util/MissingTextureReporter.java diff --git a/src/main/java/examplemod/examples/ExamplePreset.java b/src/main/java/examplemod/examples/ExamplePreset.java index 8f83c99..5b57cb3 100644 --- a/src/main/java/examplemod/examples/ExamplePreset.java +++ b/src/main/java/examplemod/examples/ExamplePreset.java @@ -1,46 +1,23 @@ package examplemod.examples; -import examplemod.ExampleMod; -import necesse.engine.registries.ObjectRegistry; -import necesse.engine.registries.TileRegistry; import necesse.engine.util.GameRandom; import necesse.level.maps.presets.Preset; public class ExamplePreset extends Preset { + // Pass a GameRandom so loot is randomized public ExamplePreset(GameRandom random) { - // Pass a GameRandom so loot is randomized - super(15, 11); + //width and height of our preset + super(11, 11); - int floor = TileRegistry.getTileID("stonefloor"); - int wall = ObjectRegistry.getObjectID("stonewall"); - int air = ObjectRegistry.getObjectID("air"); - int storagebox = ObjectRegistry.getObjectID("storagebox"); // replace with what you want + /*string output of the preset from the game decoded from url safe base64 zlib compressed text + you don't actually need to decompress this but it makes showing whats going on easier */ + String examplePresetScript = "PRESET={width=11,height=11,tileIDs=[98, exampletile],tiles=[98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98],objectIDs=[0, air, 290, storagebox, 85, woodwall, 298, walltorch],objects=[85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 298, 0, 0, 0, 0, 0, 0, 0, 298, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 290, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 298, 0, 0, 0, 0, 0, 0, 0, 298, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85],rotations=[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2],tileObjectsClear=true,wallDecorObjectsClear=true,tableDecorObjectsClear=true,clearOtherWires=false}\n"; - // Fill background - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - setTile(x, y, floor); - setObject(x, y, air); - } - } - - // Simple rectangle “room” - for (int x = 0; x < width; x++) { - setObject(x, 0, wall); - setObject(x, height - 1, wall); - } - for (int y = 0; y < height; y++) { - setObject(0, y, wall); - setObject(width - 1, y, wall); - } - - // Put a storagebox - int storageboxX = width / 2; - int storageboxY = height / 2; - setObject(storageboxX,storageboxY, storagebox,1); + //actually apply the preset from examplePresetScript onto the world + this.applyScript(examplePresetScript); //Fill the storage box with loot from our example loot table - addInventory(ExampleLootTable.exampleloottable, random,storageboxX,storageboxY); + addInventory(ExampleLootTable.exampleloottable, random,5,5); // Optional: only allow placing if the whole area isn’t floor already (example rule) addCanApplyRectEachPredicate(0, 0, width, height, 0, (level, levelX, levelY, dir) -> diff --git a/src/main/java/examplemod/examples/ExamplePresetCode.java b/src/main/java/examplemod/examples/ExamplePresetCode.java new file mode 100644 index 0000000..be9deff --- /dev/null +++ b/src/main/java/examplemod/examples/ExamplePresetCode.java @@ -0,0 +1,49 @@ +package examplemod.examples; + +import necesse.engine.registries.ObjectRegistry; +import necesse.engine.registries.TileRegistry; +import necesse.engine.util.GameRandom; +import necesse.level.maps.presets.Preset; + +public class ExamplePresetCode extends Preset { + public ExamplePresetCode(GameRandom random) { + // Pass a GameRandom so loot is randomized + super(15, 11); + + int floor = TileRegistry.getTileID("stonefloor"); + int wall = ObjectRegistry.getObjectID("stonewall"); + int air = ObjectRegistry.getObjectID("air"); + int storagebox = ObjectRegistry.getObjectID("storagebox"); // replace with what you want + + // Fill background + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + setTile(x, y, floor); + setObject(x, y, air); + } + } + + // Simple rectangle “room” + for (int x = 0; x < width; x++) { + setObject(x, 0, wall); + setObject(x, height - 1, wall); + } + for (int y = 0; y < height; y++) { + setObject(0, y, wall); + setObject(width - 1, y, wall); + } + + // Put a storagebox + int storageboxX = width / 2; + int storageboxY = height / 2; + setObject(storageboxX,storageboxY, storagebox,1); + + //Fill the storage box with loot from our example loot table + addInventory(ExampleLootTable.exampleloottable, random,storageboxX,storageboxY); + + // Optional: only allow placing if the whole area isn’t floor already (example rule) + addCanApplyRectEachPredicate(0, 0, width, height, 0, (level, levelX, levelY, dir) -> + !level.getTile(levelX, levelY).isFloor + ); + } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/util/MissingTextureReporter.java b/src/main/java/examplemod/examples/util/MissingTextureReporter.java new file mode 100644 index 0000000..11f4f58 --- /dev/null +++ b/src/main/java/examplemod/examples/util/MissingTextureReporter.java @@ -0,0 +1,98 @@ +package examplemod.examples.util; + +import java.util.HashSet; + +import necesse.engine.GameLog; +import necesse.engine.util.GameUtils; +import necesse.engine.window.GameWindow; +import necesse.engine.window.WindowManager; +import necesse.gfx.GameResources; +import necesse.gfx.Renderer; +import necesse.gfx.gameFont.FontOptions; +import necesse.engine.screenHudManager.ScreenFloatTextFade; +import necesse.gfx.gameTexture.GameTexture; + +public class MissingTextureReporter { + + private static final HashSet seen = new HashSet<>(); + private static GameTexture fallback; + + public static GameTexture reportMissingTexture(String filePath, boolean outsideGame, boolean forceNotFinalize, Throwable thrown) { + // Normalize extension (matches vanilla) + String normalized = filePath == null ? "null" : GameUtils.formatFileExtension(filePath, "png"); + + // Find “who asked for this texture” (best effort) + String caller = findUsefulCaller(); + + // Log once per texture path (avoid spam) + if (seen.add(normalized)) { + GameLog.warn.println("[MissingTexture] Could not load: " + normalized + + " outsideGame=" + outsideGame + + " forceNotFinalize=" + forceNotFinalize + + (caller != null ? " caller=" + caller : "") + + " (" + thrown.getClass().getSimpleName() + ": " + thrown.getMessage() + ")"); + + // On-screen notice (client HUD) + showOnScreen("Missing texture: " + normalized); + } + + // Return a safe texture so the game keeps running + if (GameResources.error != null) return GameResources.error; + return getOrCreateFallback(); + } + + private static GameTexture getOrCreateFallback() { + if (fallback != null) return fallback; + + // 1x1 magenta fallback + GameTexture t = new GameTexture("missing-texture-fallback", 1, 1); + t.setPixel(0, 0, 255, 0, 255, 255); + t.makeFinal(); + fallback = t; + return fallback; + } + + private static void showOnScreen(String msg) { + try { + GameWindow window = WindowManager.getWindow(); + if (window == null) return; + + int x = Math.max(1, window.getHudWidth() / 2); + int y = 40; + + FontOptions opt = new FontOptions(16) + .color(255, 80, 80) + .outline(0, 0, 0); + + ScreenFloatTextFade text = new ScreenFloatTextFade(x, y, msg, opt); + text.avoidOtherText = true; + text.riseTime = 600; + text.hoverTime = 400; + text.fadeTime = 2200; + + Renderer.hudManager.addElement(text); + } catch (Throwable ignored) { + // Never crash from the reporter itself + } + } + + private static String findUsefulCaller() { + try { + StackTraceElement[] st = new Exception().getStackTrace(); + for (StackTraceElement e : st) { + String c = e.getClassName(); + if (c == null) continue; + + // Skip obvious internal frames + if (c.startsWith("net.bytebuddy.")) continue; + if (c.startsWith("examplemod.examples.patches.")) continue; + if (c.startsWith("examplemod.examples.util.MissingTextureReporter")) continue; + if (c.startsWith("necesse.gfx.gameTexture.GameTexture")) continue; + + return c + "#" + e.getMethodName() + ":" + e.getLineNumber(); + } + } catch (Throwable ignored) { + } + return null; + } +} From bab2f9ebae692e5c7c0c7c8d8336fa4c2ee8cf2e Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Sat, 24 Jan 2026 00:46:50 +0000 Subject: [PATCH 10/21] Add example wall, door, and window objects Introduced ExampleWallWindowDoorObject with registration logic for wall, door, and window objects. Added corresponding textures and updated localization entries for the new objects. Updated ExampleMod to register these new objects during initialization. --- src/main/java/examplemod/ExampleMod.java | 11 ++--- .../objects/ExampleWallWindowDoorObject.java | 38 ++++++++++++++++++ src/main/resources/items/exampledoor.png | Bin 0 -> 406 bytes src/main/resources/items/examplewall.png | Bin 0 -> 448 bytes src/main/resources/locale/en.lang | 2 + src/main/resources/objects/examplewall.png | Bin 0 -> 2796 bytes 6 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 src/main/java/examplemod/examples/objects/ExampleWallWindowDoorObject.java create mode 100644 src/main/resources/items/exampledoor.png create mode 100644 src/main/resources/items/examplewall.png create mode 100644 src/main/resources/objects/examplewall.png diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 5b0f488..88cbeea 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -9,9 +9,7 @@ import examplemod.examples.items.tools.ExampleSwordItem; import examplemod.examples.mobs.ExampleMob; import examplemod.examples.mobs.ExampleBossMob; -import examplemod.examples.objects.ExampleObject; -import examplemod.examples.objects.ExampleBaseRockObject; -import examplemod.examples.objects.ExampleOreRockObject; +import examplemod.examples.objects.*; import examplemod.examples.ExampleRecipes; import examplemod.examples.packets.ExamplePacket; import examplemod.examples.packets.ExamplePlaySoundPacket; @@ -49,13 +47,16 @@ public void init() { // Register our objects ObjectRegistry.registerObject("exampleobject", new ExampleObject(), 2, true); - // Register a rock for the example incursion to use as cave walls + // Register a rock object ExampleBaseRockObject exampleBaseRock = new ExampleBaseRockObject(); ObjectRegistry.registerObject("examplebaserock", exampleBaseRock, -1.0F, true); - // Register an ore rock that overlays onto our incursion rock + // Register an ore rock object that overlays onto our incursion rock ObjectRegistry.registerObject("exampleorerock", new ExampleOreRockObject(exampleBaseRock), -1.0F, true); + // Register a wall object, window object and door object + ExampleWallWindowDoorObject.registerWallsDoorsWindows(); + // Register our items ItemRegistry.registerItem("exampleitem", new ExampleMaterialItem(), 10, true); ItemRegistry.registerItem("examplestone", new ExampleStoneItem(),15,true); diff --git a/src/main/java/examplemod/examples/objects/ExampleWallWindowDoorObject.java b/src/main/java/examplemod/examples/objects/ExampleWallWindowDoorObject.java new file mode 100644 index 0000000..2dea232 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleWallWindowDoorObject.java @@ -0,0 +1,38 @@ +package examplemod.examples.objects; + +import necesse.level.gameObject.WallObject; +import necesse.inventory.item.toolItem.ToolType; + +import java.awt.Color; + + + +public final class ExampleWallWindowDoorObject { + private ExampleWallWindowDoorObject() {} + + public static int EXAMPLEWALL; + public static int EXAMPLEDOOR; + public static int EXAMPLEDOOROPEN; + public static int EXAMPLEWINDOW; + + public static void registerWallsDoorsWindows() { + // registers wall, door pair and window objecs + int[] ids = WallObject.registerWallObjects( + "example",// prefix + "examplewall", // texture name + "walloutlines", // outline texture + 0.0F, // tool tier + new Color(255, 220, 80), + ToolType.PICKAXE, + -1.0F, // wall broker value + -1.0F, // door broker value + true, // obtainable + true // count in stats + ); + + EXAMPLEWALL = ids[0]; + EXAMPLEDOOR = ids[1]; + EXAMPLEDOOROPEN = ids[2]; + EXAMPLEWINDOW = ids[3]; + } +} \ No newline at end of file diff --git a/src/main/resources/items/exampledoor.png b/src/main/resources/items/exampledoor.png new file mode 100644 index 0000000000000000000000000000000000000000..2a16f30df1f0929219d43df5ef6220b040072b6c GIT binary patch literal 406 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 jlHAEn`&1MG_ Date: Tue, 27 Jan 2026 23:05:53 +0000 Subject: [PATCH 11/21] Refactor mod initialization into loader classes Split ExampleMod initialization logic into dedicated loader classes for buffs, commands, incursions, items, mobs, objects, packets, projectiles, resources, and tiles. This improves code organization and readability. Also, moved ExampleRecipes and removed the unused MissingTextureReporter utility. --- src/main/java/examplemod/ExampleMod.java | 101 +++--------------- .../examplemod/Loaders/ExampleModBuffs.java | 11 ++ .../Loaders/ExampleModCommands.java | 12 +++ .../Loaders/ExampleModIncursions.java | 23 ++++ .../examplemod/Loaders/ExampleModItems.java | 26 +++++ .../examplemod/Loaders/ExampleModMobs.java | 15 +++ .../examplemod/Loaders/ExampleModObjects.java | 25 +++++ .../examplemod/Loaders/ExampleModPackets.java | 15 +++ .../Loaders/ExampleModProjectiles.java | 11 ++ .../Loaders/ExampleModResources.java | 31 ++++++ .../examplemod/Loaders/ExampleModTiles.java | 12 +++ .../{examples => Loaders}/ExampleRecipes.java | 2 +- .../examples/util/MissingTextureReporter.java | 98 ----------------- 13 files changed, 197 insertions(+), 185 deletions(-) create mode 100644 src/main/java/examplemod/Loaders/ExampleModBuffs.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModCommands.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModIncursions.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModItems.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModMobs.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModObjects.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModPackets.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModProjectiles.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModResources.java create mode 100644 src/main/java/examplemod/Loaders/ExampleModTiles.java rename src/main/java/examplemod/{examples => Loaders}/ExampleRecipes.java (99%) delete mode 100644 src/main/java/examplemod/examples/util/MissingTextureReporter.java diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 88cbeea..5b4bc41 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -1,24 +1,13 @@ package examplemod; -import examplemod.examples.*; +import examplemod.Loaders.*; +import examplemod.examples.ExampleChatCommand; +import examplemod.Loaders.ExampleRecipes; import examplemod.examples.incursion.ExampleBiome; -import examplemod.examples.incursion.ExampleIncursionBiome; -import examplemod.examples.incursion.ExampleIncursionLevel; -import examplemod.examples.items.*; -import examplemod.examples.items.tools.ExampleProjectileWeapon; -import examplemod.examples.items.tools.ExampleSwordItem; -import examplemod.examples.mobs.ExampleMob; -import examplemod.examples.mobs.ExampleBossMob; -import examplemod.examples.objects.*; -import examplemod.examples.ExampleRecipes; -import examplemod.examples.packets.ExamplePacket; -import examplemod.examples.packets.ExamplePlaySoundPacket; import necesse.engine.commands.CommandsManager; import necesse.engine.modLoader.annotations.ModEntry; -import necesse.engine.registries.*; import necesse.engine.sound.SoundSettings; import necesse.engine.sound.gameSound.GameSound; -import necesse.gfx.gameTexture.GameTexture; import necesse.level.maps.biomes.Biome; @ModEntry @@ -32,91 +21,31 @@ public class ExampleMod { public void init() { System.out.println("Hello world from my example mod!"); - // Register a simple biome that will not appear in natural world gen. - EXAMPLE_BIOME = BiomeRegistry.registerBiome("exampleincursion", new ExampleBiome(), false); - - // Register the incursion biome with tier requirement 1. - IncursionBiomeRegistry.registerBiome("exampleincursion", new ExampleIncursionBiome(), 1); - - // Register the level class used for the incursion. - LevelRegistry.registerLevel("exampleincursionlevel", ExampleIncursionLevel.class); - - // Register our tiles - TileRegistry.registerTile("exampletile", new ExampleTile(), 1, true); - - // Register our objects - ObjectRegistry.registerObject("exampleobject", new ExampleObject(), 2, true); - - // Register a rock object - ExampleBaseRockObject exampleBaseRock = new ExampleBaseRockObject(); - ObjectRegistry.registerObject("examplebaserock", exampleBaseRock, -1.0F, true); - - // Register an ore rock object that overlays onto our incursion rock - ObjectRegistry.registerObject("exampleorerock", new ExampleOreRockObject(exampleBaseRock), -1.0F, true); - - // Register a wall object, window object and door object - ExampleWallWindowDoorObject.registerWallsDoorsWindows(); - - // Register our items - ItemRegistry.registerItem("exampleitem", new ExampleMaterialItem(), 10, true); - ItemRegistry.registerItem("examplestone", new ExampleStoneItem(),15,true); - ItemRegistry.registerItem("exampleore", new ExampleOreItem(), 25, true); - ItemRegistry.registerItem("examplebar", new ExampleBarItem(),50,true); - ItemRegistry.registerItem("examplehuntincursionmaterial", new ExampleHuntIncursionMaterialItem(), 50, true); - ItemRegistry.registerItem("examplesword", new ExampleSwordItem(), 20, true); - ItemRegistry.registerItem("examplestaff", new ExampleProjectileWeapon(), 30, true); - ItemRegistry.registerItem("examplepotion", new ExamplePotionItem(), 10, true); - ItemRegistry.registerItem("examplefood", new ExampleFoodItem(),15, true); - - // Register our mob - MobRegistry.registerMob("examplemob", ExampleMob.class, true); - - // Register boss mob - MobRegistry.registerMob("examplebossmob",ExampleBossMob.class,true,true); - - // Register our projectile - ProjectileRegistry.registerProjectile("exampleprojectile", ExampleProjectile.class, "exampleprojectile", "exampleprojectile_shadow"); - - // Register our buff - BuffRegistry.registerBuff("examplebuff", new ExampleBuff()); - - // Register our packets - PacketRegistry.registerPacket(ExamplePacket.class); - - PacketRegistry.registerPacket(ExamplePlaySoundPacket.class); + // The examples are split into different classes here for readability, but you can register them directly here in init if you wish + ExampleModIncursions.load(); + ExampleModTiles.load(); + ExampleModObjects.load(); + ExampleModItems.load(); + ExampleModMobs.load(); + ExampleModProjectiles.load(); + ExampleModBuffs.load(); + ExampleModPackets.load(); } public void initResources() { - // Sometimes your textures will have a black or other outline unintended under rotation or scaling - // This is caused by alpha blending between transparent pixels and the edge - // To fix this, run the preAntialiasTextures gradle task - // It will process your textures and save them again with a fixed alpha edge color + ExampleModResources.load(); - ExampleMob.texture = GameTexture.fromFile("mobs/examplemob"); - ExampleBossMob.texture = GameTexture.fromFile("mobs/examplebossmob"); - - //initialising the sound to be used by our boss mob - EXAMPLESOUND = GameSound.fromFile("examplesound"); - - // Optional settings (volume/pitch/falloff) – used when playing via SoundSettings - EXAMPLESOUNDSETTINGS = new SoundSettings(EXAMPLESOUND) - .volume(0.8f) - .basePitch(1.0f) - .pitchVariance(0.08f) - .fallOffDistance(900); } public void postInit() { - // load our recipes from the ExampleRecipes class + // load our recipes from the ExampleRecipes class so we can keep this class easy to read ExampleRecipes.registerRecipes(); + // Add our example mob to default cave mobs. // Spawn tables use a ticket/weight system. In general, common mobs have about 100 tickets. Biome.defaultCaveMobs .add(100, "examplemob"); - - // Register our server chat command - CommandsManager.registerServerCommand(new ExampleChatCommand()); } } diff --git a/src/main/java/examplemod/Loaders/ExampleModBuffs.java b/src/main/java/examplemod/Loaders/ExampleModBuffs.java new file mode 100644 index 0000000..fce8b12 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModBuffs.java @@ -0,0 +1,11 @@ +package examplemod.Loaders; + +import examplemod.examples.ExampleBuff; +import necesse.engine.registries.BuffRegistry; + +public class ExampleModBuffs { + public static void load(){ + // Register our buff + BuffRegistry.registerBuff("examplebuff", new ExampleBuff()); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModCommands.java b/src/main/java/examplemod/Loaders/ExampleModCommands.java new file mode 100644 index 0000000..e60f03b --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModCommands.java @@ -0,0 +1,12 @@ +package examplemod.Loaders; + +import examplemod.examples.ExampleChatCommand; +import necesse.engine.commands.CommandsManager; + +public class ExampleModCommands { + public static void load(){ + + // Register our server chat command + CommandsManager.registerServerCommand(new ExampleChatCommand()); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModIncursions.java b/src/main/java/examplemod/Loaders/ExampleModIncursions.java new file mode 100644 index 0000000..e072748 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModIncursions.java @@ -0,0 +1,23 @@ +package examplemod.Loaders; + +import examplemod.ExampleMod; +import examplemod.examples.incursion.ExampleBiome; +import examplemod.examples.incursion.ExampleIncursionBiome; +import examplemod.examples.incursion.ExampleIncursionLevel; +import necesse.engine.registries.BiomeRegistry; +import necesse.engine.registries.IncursionBiomeRegistry; +import necesse.engine.registries.LevelRegistry; + +public class ExampleModIncursions { + public static void load() { + + // Register a simple biome that will not appear in natural world gen. + ExampleMod.EXAMPLE_BIOME = BiomeRegistry.registerBiome("exampleincursion", new ExampleBiome(), false); + + // Register the incursion biome with tier requirement 1. + IncursionBiomeRegistry.registerBiome("exampleincursion", new ExampleIncursionBiome(), 1); + + // Register the level class used for the incursion. + LevelRegistry.registerLevel("exampleincursionlevel", ExampleIncursionLevel.class); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModItems.java b/src/main/java/examplemod/Loaders/ExampleModItems.java new file mode 100644 index 0000000..c7a4c28 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModItems.java @@ -0,0 +1,26 @@ +package examplemod.Loaders; + +import examplemod.examples.items.*; +import examplemod.examples.items.tools.ExampleProjectileWeapon; +import examplemod.examples.items.tools.ExampleSwordItem; +import necesse.engine.registries.ItemRegistry; + +public class ExampleModItems { + public static void load(){ + + // Materials + ItemRegistry.registerItem("exampleitem", new ExampleMaterialItem(), 10, true); + ItemRegistry.registerItem("examplestone", new ExampleStoneItem(), 15, true); + ItemRegistry.registerItem("exampleore", new ExampleOreItem(), 25, true); + ItemRegistry.registerItem("examplebar", new ExampleBarItem(), 50, true); + ItemRegistry.registerItem("examplehuntincursionmaterial", new ExampleHuntIncursionMaterialItem(), 50, true); + + // Weapons / tools + ItemRegistry.registerItem("examplesword", new ExampleSwordItem(), 20, true); + ItemRegistry.registerItem("examplestaff", new ExampleProjectileWeapon(), 30, true); + + // Consumables + ItemRegistry.registerItem("examplepotion", new ExamplePotionItem(), 10, true); + ItemRegistry.registerItem("examplefood", new ExampleFoodItem(), 15, true); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModMobs.java b/src/main/java/examplemod/Loaders/ExampleModMobs.java new file mode 100644 index 0000000..f8196f4 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModMobs.java @@ -0,0 +1,15 @@ +package examplemod.Loaders; + +import examplemod.examples.mobs.ExampleBossMob; +import examplemod.examples.mobs.ExampleMob; +import necesse.engine.registries.MobRegistry; + +public class ExampleModMobs { + public static void load(){ + // Register our mob + MobRegistry.registerMob("examplemob", ExampleMob.class, true); + + // Register boss mob + MobRegistry.registerMob("examplebossmob", ExampleBossMob.class,true,true); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModObjects.java b/src/main/java/examplemod/Loaders/ExampleModObjects.java new file mode 100644 index 0000000..25670a8 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModObjects.java @@ -0,0 +1,25 @@ +package examplemod.Loaders; + +import examplemod.examples.objects.ExampleBaseRockObject; +import examplemod.examples.objects.ExampleObject; +import examplemod.examples.objects.ExampleOreRockObject; +import examplemod.examples.objects.ExampleWallWindowDoorObject; +import necesse.engine.registries.ObjectRegistry; + +public class ExampleModObjects { + + public static void load(){ + // Register our objects + ObjectRegistry.registerObject("exampleobject", new ExampleObject(), 2, true); + + // Register a rock object + ExampleBaseRockObject exampleBaseRock = new ExampleBaseRockObject(); + ObjectRegistry.registerObject("examplebaserock", exampleBaseRock, -1.0F, true); + + // Register an ore rock object that overlays onto our incursion rock + ObjectRegistry.registerObject("exampleorerock", new ExampleOreRockObject(exampleBaseRock), -1.0F, true); + + // Register a wall object, window object and door object + ExampleWallWindowDoorObject.registerWallsDoorsWindows(); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModPackets.java b/src/main/java/examplemod/Loaders/ExampleModPackets.java new file mode 100644 index 0000000..8f09fd9 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModPackets.java @@ -0,0 +1,15 @@ +package examplemod.Loaders; + +import examplemod.examples.packets.ExamplePacket; +import examplemod.examples.packets.ExamplePlaySoundPacket; +import necesse.engine.registries.PacketRegistry; + +public class ExampleModPackets { + + public static void load() { + // Register our packets + PacketRegistry.registerPacket(ExamplePacket.class); + + PacketRegistry.registerPacket(ExamplePlaySoundPacket.class); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModProjectiles.java b/src/main/java/examplemod/Loaders/ExampleModProjectiles.java new file mode 100644 index 0000000..c910ab9 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModProjectiles.java @@ -0,0 +1,11 @@ +package examplemod.Loaders; + +import examplemod.examples.ExampleProjectile; +import necesse.engine.registries.ProjectileRegistry; + +public class ExampleModProjectiles { + public static void load(){ + // Register our projectile + ProjectileRegistry.registerProjectile("exampleprojectile", ExampleProjectile.class, "exampleprojectile", "exampleprojectile_shadow"); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModResources.java b/src/main/java/examplemod/Loaders/ExampleModResources.java new file mode 100644 index 0000000..918470d --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModResources.java @@ -0,0 +1,31 @@ +package examplemod.Loaders; + +import examplemod.ExampleMod; +import examplemod.examples.mobs.ExampleBossMob; +import examplemod.examples.mobs.ExampleMob; +import necesse.engine.sound.SoundSettings; +import necesse.engine.sound.gameSound.GameSound; +import necesse.gfx.gameTexture.GameTexture; + +public class ExampleModResources { + public static void load(){ + // Sometimes your textures will have a black or other outline unintended under rotation or scaling + // This is caused by alpha blending between transparent pixels and the edge + // To fix this, run the preAntialiasTextures gradle task + // It will process your textures and save them again with a fixed alpha edge color + + ExampleMob.texture = GameTexture.fromFile("mobs/examplemob"); + ExampleBossMob.texture = GameTexture.fromFile("mobs/examplebossmob"); + + //initialising the sound to be used by our boss mob + ExampleMod.EXAMPLESOUND = GameSound.fromFile("examplesound"); + + // Optional settings (volume/pitch/falloff) – used when playing via SoundSettings + ExampleMod.EXAMPLESOUNDSETTINGS = new SoundSettings(ExampleMod.EXAMPLESOUND) + .volume(0.8f) + .basePitch(1.0f) + .pitchVariance(0.08f) + .fallOffDistance(900); + } +} + diff --git a/src/main/java/examplemod/Loaders/ExampleModTiles.java b/src/main/java/examplemod/Loaders/ExampleModTiles.java new file mode 100644 index 0000000..4b5a9d8 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModTiles.java @@ -0,0 +1,12 @@ +package examplemod.Loaders; + +import examplemod.examples.ExampleTile; +import necesse.engine.registries.TileRegistry; + +public class ExampleModTiles { + + public static void load(){ + // Register our tiles + TileRegistry.registerTile("exampletile", new ExampleTile(), 1, true); + } +} diff --git a/src/main/java/examplemod/examples/ExampleRecipes.java b/src/main/java/examplemod/Loaders/ExampleRecipes.java similarity index 99% rename from src/main/java/examplemod/examples/ExampleRecipes.java rename to src/main/java/examplemod/Loaders/ExampleRecipes.java index 058ea11..59ead0a 100644 --- a/src/main/java/examplemod/examples/ExampleRecipes.java +++ b/src/main/java/examplemod/Loaders/ExampleRecipes.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.Loaders; import necesse.engine.registries.RecipeTechRegistry; import necesse.inventory.recipe.Ingredient; diff --git a/src/main/java/examplemod/examples/util/MissingTextureReporter.java b/src/main/java/examplemod/examples/util/MissingTextureReporter.java deleted file mode 100644 index 11f4f58..0000000 --- a/src/main/java/examplemod/examples/util/MissingTextureReporter.java +++ /dev/null @@ -1,98 +0,0 @@ -package examplemod.examples.util; - -import java.util.HashSet; - -import necesse.engine.GameLog; -import necesse.engine.util.GameUtils; -import necesse.engine.window.GameWindow; -import necesse.engine.window.WindowManager; -import necesse.gfx.GameResources; -import necesse.gfx.Renderer; -import necesse.gfx.gameFont.FontOptions; -import necesse.engine.screenHudManager.ScreenFloatTextFade; -import necesse.gfx.gameTexture.GameTexture; - -public class MissingTextureReporter { - - private static final HashSet seen = new HashSet<>(); - private static GameTexture fallback; - - public static GameTexture reportMissingTexture(String filePath, boolean outsideGame, boolean forceNotFinalize, Throwable thrown) { - // Normalize extension (matches vanilla) - String normalized = filePath == null ? "null" : GameUtils.formatFileExtension(filePath, "png"); - - // Find “who asked for this texture” (best effort) - String caller = findUsefulCaller(); - - // Log once per texture path (avoid spam) - if (seen.add(normalized)) { - GameLog.warn.println("[MissingTexture] Could not load: " + normalized - + " outsideGame=" + outsideGame - + " forceNotFinalize=" + forceNotFinalize - + (caller != null ? " caller=" + caller : "") - + " (" + thrown.getClass().getSimpleName() + ": " + thrown.getMessage() + ")"); - - // On-screen notice (client HUD) - showOnScreen("Missing texture: " + normalized); - } - - // Return a safe texture so the game keeps running - if (GameResources.error != null) return GameResources.error; - return getOrCreateFallback(); - } - - private static GameTexture getOrCreateFallback() { - if (fallback != null) return fallback; - - // 1x1 magenta fallback - GameTexture t = new GameTexture("missing-texture-fallback", 1, 1); - t.setPixel(0, 0, 255, 0, 255, 255); - t.makeFinal(); - fallback = t; - return fallback; - } - - private static void showOnScreen(String msg) { - try { - GameWindow window = WindowManager.getWindow(); - if (window == null) return; - - int x = Math.max(1, window.getHudWidth() / 2); - int y = 40; - - FontOptions opt = new FontOptions(16) - .color(255, 80, 80) - .outline(0, 0, 0); - - ScreenFloatTextFade text = new ScreenFloatTextFade(x, y, msg, opt); - text.avoidOtherText = true; - text.riseTime = 600; - text.hoverTime = 400; - text.fadeTime = 2200; - - Renderer.hudManager.addElement(text); - } catch (Throwable ignored) { - // Never crash from the reporter itself - } - } - - private static String findUsefulCaller() { - try { - StackTraceElement[] st = new Exception().getStackTrace(); - for (StackTraceElement e : st) { - String c = e.getClassName(); - if (c == null) continue; - - // Skip obvious internal frames - if (c.startsWith("net.bytebuddy.")) continue; - if (c.startsWith("examplemod.examples.patches.")) continue; - if (c.startsWith("examplemod.examples.util.MissingTextureReporter")) continue; - if (c.startsWith("necesse.gfx.gameTexture.GameTexture")) continue; - - return c + "#" + e.getMethodName() + ":" + e.getLineNumber(); - } - } catch (Throwable ignored) { - } - return null; - } -} From ebde9ad5239418d5c1544352a9de7a9430b6874b Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Thu, 29 Jan 2026 05:05:42 +0000 Subject: [PATCH 12/21] Add example armor set, categories, and refactor items Introduces example helmet, chestplate, and boots armor items with set bonus buff, including textures and registration. Adds ExampleModCategories for item and crafting category trees, updates item and recipe loaders, and reorganizes example items and buffs into subpackages for better structure. Updates localization for new items, buffs, and categories. --- src/main/java/examplemod/ExampleMod.java | 6 +- .../examplemod/Loaders/ExampleModBuffs.java | 3 +- .../Loaders/ExampleModCategories.java | 113 ++++++++++++++++++ .../examplemod/Loaders/ExampleModItems.java | 15 ++- ...pleRecipes.java => ExampleModRecipes.java} | 68 ++++++++++- .../buffs/ExampleArmorSetBonusBuff.java | 24 ++++ .../examples/{ => buffs}/ExampleBuff.java | 2 +- .../items/armor/ExampleBootsArmorItem.java | 16 +++ .../items/armor/ExampleChestArmorItem.java | 17 +++ .../items/armor/ExampleHelmetArmorItem.java | 24 ++++ .../{ => consumable}/ExampleFoodItem.java | 2 +- .../{ => consumable}/ExamplePotionItem.java | 2 +- .../items/{ => materials}/ExampleBarItem.java | 2 +- .../ExampleHuntIncursionMaterialItem.java | 2 +- .../{ => materials}/ExampleMaterialItem.java | 2 +- .../items/{ => materials}/ExampleOreItem.java | 2 +- .../{ => materials}/ExampleStoneItem.java | 2 +- .../resources/buffs/examplearmorsetbonus.png | Bin 0 -> 398 bytes src/main/resources/items/exampleboots.png | Bin 0 -> 402 bytes .../resources/items/examplechestplate.png | Bin 0 -> 558 bytes src/main/resources/items/examplehelmet.png | Bin 0 -> 485 bytes src/main/resources/locale/en.lang | 22 +++- .../player/armor/examplearms_left.png | Bin 0 -> 1491 bytes .../player/armor/examplearms_right.png | Bin 0 -> 1513 bytes .../resources/player/armor/exampleboots.png | Bin 0 -> 2600 bytes .../resources/player/armor/examplechest.png | Bin 0 -> 2152 bytes .../resources/player/armor/examplehelmet.png | Bin 0 -> 2833 bytes 27 files changed, 302 insertions(+), 22 deletions(-) create mode 100644 src/main/java/examplemod/Loaders/ExampleModCategories.java rename src/main/java/examplemod/Loaders/{ExampleRecipes.java => ExampleModRecipes.java} (60%) create mode 100644 src/main/java/examplemod/examples/buffs/ExampleArmorSetBonusBuff.java rename src/main/java/examplemod/examples/{ => buffs}/ExampleBuff.java (95%) create mode 100644 src/main/java/examplemod/examples/items/armor/ExampleBootsArmorItem.java create mode 100644 src/main/java/examplemod/examples/items/armor/ExampleChestArmorItem.java create mode 100644 src/main/java/examplemod/examples/items/armor/ExampleHelmetArmorItem.java rename src/main/java/examplemod/examples/items/{ => consumable}/ExampleFoodItem.java (95%) rename src/main/java/examplemod/examples/items/{ => consumable}/ExamplePotionItem.java (85%) rename src/main/java/examplemod/examples/items/{ => materials}/ExampleBarItem.java (82%) rename src/main/java/examplemod/examples/items/{ => materials}/ExampleHuntIncursionMaterialItem.java (81%) rename src/main/java/examplemod/examples/items/{ => materials}/ExampleMaterialItem.java (80%) rename src/main/java/examplemod/examples/items/{ => materials}/ExampleOreItem.java (82%) rename src/main/java/examplemod/examples/items/{ => materials}/ExampleStoneItem.java (79%) create mode 100644 src/main/resources/buffs/examplearmorsetbonus.png create mode 100644 src/main/resources/items/exampleboots.png create mode 100644 src/main/resources/items/examplechestplate.png create mode 100644 src/main/resources/items/examplehelmet.png create mode 100644 src/main/resources/player/armor/examplearms_left.png create mode 100644 src/main/resources/player/armor/examplearms_right.png create mode 100644 src/main/resources/player/armor/exampleboots.png create mode 100644 src/main/resources/player/armor/examplechest.png create mode 100644 src/main/resources/player/armor/examplehelmet.png diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 5b4bc41..356c4d6 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -1,10 +1,7 @@ package examplemod; import examplemod.Loaders.*; -import examplemod.examples.ExampleChatCommand; -import examplemod.Loaders.ExampleRecipes; import examplemod.examples.incursion.ExampleBiome; -import necesse.engine.commands.CommandsManager; import necesse.engine.modLoader.annotations.ModEntry; import necesse.engine.sound.SoundSettings; import necesse.engine.sound.gameSound.GameSound; @@ -22,6 +19,7 @@ public void init() { System.out.println("Hello world from my example mod!"); // The examples are split into different classes here for readability, but you can register them directly here in init if you wish + ExampleModCategories.load(); ExampleModIncursions.load(); ExampleModTiles.load(); ExampleModObjects.load(); @@ -39,7 +37,7 @@ public void initResources() { public void postInit() { // load our recipes from the ExampleRecipes class so we can keep this class easy to read - ExampleRecipes.registerRecipes(); + ExampleModRecipes.registerRecipes(); // Add our example mob to default cave mobs. diff --git a/src/main/java/examplemod/Loaders/ExampleModBuffs.java b/src/main/java/examplemod/Loaders/ExampleModBuffs.java index fce8b12..45d4f3b 100644 --- a/src/main/java/examplemod/Loaders/ExampleModBuffs.java +++ b/src/main/java/examplemod/Loaders/ExampleModBuffs.java @@ -1,11 +1,12 @@ package examplemod.Loaders; -import examplemod.examples.ExampleBuff; +import examplemod.examples.buffs.*; import necesse.engine.registries.BuffRegistry; public class ExampleModBuffs { public static void load(){ // Register our buff BuffRegistry.registerBuff("examplebuff", new ExampleBuff()); + BuffRegistry.registerBuff("examplearmorsetbonus", new ExampleArmorSetBonusBuff()); } } diff --git a/src/main/java/examplemod/Loaders/ExampleModCategories.java b/src/main/java/examplemod/Loaders/ExampleModCategories.java new file mode 100644 index 0000000..d6e3bef --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModCategories.java @@ -0,0 +1,113 @@ +package examplemod.Loaders; + +import necesse.engine.localization.message.LocalMessage; +import necesse.inventory.item.ItemCategory; + +public final class ExampleModCategories { + private ExampleModCategories() {} + + /* + * Necesse has TWO category trees: + * + * 1) Item categories (ItemCategory master tree) + * * Used for Creative inventory browsing / item lists / general item grouping. + * * This is what setItemCategory(...) uses on items/objects. + * + * 2) Crafting categories (ItemCategory.craftingManager tree) + * * Used by crafting station recipe lists and their filter/grouping UI. + * * This is what setCraftingCategory(...) uses on items/objects (and also what Recipe.setCraftingCategory uses). + * + * Important rule: + * If you want to use a category path, you MUST create that exact path in the corresponding manager + * BEFORE any items/objects/recipes try to reference it. + * + * + * ------------------------------------------------------------------------- + * Ways to APPLY categories: + * + * A) Apply on the object/item itself (recommended baseline) + * Object registration (GameObject): + * new ExampleObject() + * .setItemCategory(ROOT, PLACEABLES, DECOR) // Creative / item browsing + * .setCraftingCategory(ROOT, PLACEABLES, DECOR); // Crafting menu default + * + * Item registration (Item): + * new ExampleItem() + * .setItemCategory(ROOT, MATERIALS) + * .setCraftingCategory(ROOT, MATERIALS); + * + * What this affects: + * Creative inventory category placement (itemCategoryTree) + * Default crafting category used by stations/recipes if not overridden (craftingCategoryTree) + * + * B) Apply on the recipe (vanilla does this often) + * Recipe override: + * new Recipe(...) + * .setCraftingCategory(ROOT, PLACEABLES, DECOR); + * + * What this affects: + * Where the RECIPE appears in the crafting UI. + * Useful when a station collapses category depth (e.g. Workstation depth=1). + * You can force “Placeables/Decor” grouping even when the station normally trims deeper paths. + * + * Rule of thumb: + * Put the “true” category on the item/object (so creative inventory and general lists are correct). + * Use Recipe.setCraftingCategory(...) when you want a specific crafting UI grouping. + */ + + // Category paths (so you can reuse them consistently) + public static final String ROOT = "examplemod"; + + public static final String INCURSION = "incursion"; + public static final String MATERIALS = "materials"; + public static final String CONSUMABLES = "consumables"; + public static final String TOOLS = "tools"; + public static final String WEAPONS = "weapons"; + public static final String ARMOR = "armor"; + public static final String PLACEABLES = "placeables"; + public static final String FURNITURE = "furniture"; + public static final String LIGHTING = "lighting"; + public static final String DECOR = "decor"; + + public static void load() { + + // ===== Normal item category tree (Creative inventory / item browsing) ===== + // "Z-..." usually pushes it toward the bottom; change if you want it earlier. + // The category "path" is built from these strings, e.g.: + // ROOT -> PLACEABLES -> DECOR + // becomes: + // examplemod.placeables.decor + ItemCategory.createCategory("Z-EX-0", new LocalMessage("itemcategory", "examplemod"), ROOT); + + ItemCategory.createCategory("Z-EX-1", new LocalMessage("itemcategory", "examplemod_materials"), ROOT, MATERIALS); + + ItemCategory.createCategory("Z-EX-2", new LocalMessage("itemcategory", "examplemod_consumables"), ROOT, CONSUMABLES); + ItemCategory.createCategory("Z-EX-3", new LocalMessage("itemcategory", "examplemod_tools"), ROOT, TOOLS); + ItemCategory.createCategory("Z-EX-4", new LocalMessage("itemcategory", "examplemod_weapons"), ROOT, WEAPONS); + ItemCategory.createCategory("Z-EX-5", new LocalMessage("itemcategory", "examplemod_armor"), ROOT, ARMOR); + ItemCategory.createCategory("Z-EX-6", new LocalMessage("itemcategory", "examplemod_incursion"), ROOT, INCURSION); + ItemCategory.createCategory("Z-EX-7", new LocalMessage("itemcategory", "examplemod_placeables"), ROOT, PLACEABLES); + + // Subcategories under Placeables (Creative) + ItemCategory.createCategory("Z-EX-7A", new LocalMessage("itemcategory", "examplemod_furniture"), ROOT, PLACEABLES, FURNITURE); + ItemCategory.createCategory("Z-EX-7B", new LocalMessage("itemcategory", "examplemod_lighting"), ROOT, PLACEABLES, LIGHTING); + ItemCategory.createCategory("Z-EX-7C", new LocalMessage("itemcategory", "examplemod_decor"), ROOT, PLACEABLES, DECOR); + + // ===== Crafting filter tree (Crafting station recipe list/filter UI) ===== + // If you want the same category paths to work in crafting menus, + // you must mirror the same tree here. + ItemCategory.craftingManager.createCategory("Z-EX-0", new LocalMessage("itemcategory", "examplemod"), ROOT); + ItemCategory.craftingManager.createCategory("Z-EX-1", new LocalMessage("itemcategory", "examplemod_materials"), ROOT, MATERIALS); + ItemCategory.craftingManager.createCategory("Z-EX-2", new LocalMessage("itemcategory", "examplemod_consumables"), ROOT, CONSUMABLES); + ItemCategory.craftingManager.createCategory("Z-EX-3", new LocalMessage("itemcategory", "examplemod_tools"), ROOT, TOOLS); + ItemCategory.craftingManager.createCategory("Z-EX-4", new LocalMessage("itemcategory", "examplemod_weapons"), ROOT, WEAPONS); + ItemCategory.craftingManager.createCategory("Z-EX-5", new LocalMessage("itemcategory", "examplemod_armor"), ROOT, ARMOR); + ItemCategory.craftingManager.createCategory("Z-EX-6", new LocalMessage("itemcategory", "examplemod_incursion"), ROOT, INCURSION); + ItemCategory.craftingManager.createCategory("Z-EX-7", new LocalMessage("itemcategory", "examplemod_placeables"), ROOT, PLACEABLES); + + // Subcategories under Placeables (Crafting) + ItemCategory.craftingManager.createCategory("Z-EX-7A", new LocalMessage("itemcategory", "examplemod_furniture"), ROOT, PLACEABLES, FURNITURE); + ItemCategory.craftingManager.createCategory("Z-EX-7B", new LocalMessage("itemcategory", "examplemod_lighting"), ROOT, PLACEABLES, LIGHTING); + ItemCategory.craftingManager.createCategory("Z-EX-7C", new LocalMessage("itemcategory", "examplemod_decor"), ROOT, PLACEABLES, DECOR); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModItems.java b/src/main/java/examplemod/Loaders/ExampleModItems.java index c7a4c28..abc11d6 100644 --- a/src/main/java/examplemod/Loaders/ExampleModItems.java +++ b/src/main/java/examplemod/Loaders/ExampleModItems.java @@ -1,9 +1,15 @@ package examplemod.Loaders; -import examplemod.examples.items.*; +import examplemod.examples.items.armor.ExampleBootsArmorItem; +import examplemod.examples.items.armor.ExampleChestArmorItem; +import examplemod.examples.items.armor.ExampleHelmetArmorItem; +import examplemod.examples.items.consumable.ExampleFoodItem; +import examplemod.examples.items.consumable.ExamplePotionItem; +import examplemod.examples.items.materials.*; import examplemod.examples.items.tools.ExampleProjectileWeapon; import examplemod.examples.items.tools.ExampleSwordItem; import necesse.engine.registries.ItemRegistry; +import necesse.inventory.item.ItemCategory; public class ExampleModItems { public static void load(){ @@ -15,10 +21,15 @@ public static void load(){ ItemRegistry.registerItem("examplebar", new ExampleBarItem(), 50, true); ItemRegistry.registerItem("examplehuntincursionmaterial", new ExampleHuntIncursionMaterialItem(), 50, true); - // Weapons / tools + // Tools ItemRegistry.registerItem("examplesword", new ExampleSwordItem(), 20, true); ItemRegistry.registerItem("examplestaff", new ExampleProjectileWeapon(), 30, true); + // Armor + ItemRegistry.registerItem("examplehelmet", new ExampleHelmetArmorItem(), 200f, true); + ItemRegistry.registerItem("examplechestplate", new ExampleChestArmorItem(), 250f, true); + ItemRegistry.registerItem("exampleboots", new ExampleBootsArmorItem(), 180f, true); + // Consumables ItemRegistry.registerItem("examplepotion", new ExamplePotionItem(), 10, true); ItemRegistry.registerItem("examplefood", new ExampleFoodItem(), 15, true); diff --git a/src/main/java/examplemod/Loaders/ExampleRecipes.java b/src/main/java/examplemod/Loaders/ExampleModRecipes.java similarity index 60% rename from src/main/java/examplemod/Loaders/ExampleRecipes.java rename to src/main/java/examplemod/Loaders/ExampleModRecipes.java index 59ead0a..fa53d5c 100644 --- a/src/main/java/examplemod/Loaders/ExampleRecipes.java +++ b/src/main/java/examplemod/Loaders/ExampleModRecipes.java @@ -9,7 +9,7 @@ here is where we will register our recipes into the game. there is potentially quite a few of them so this will allow us to maintain cleaner code */ -public class ExampleRecipes { +public class ExampleModRecipes { //Put your recipe registrations in here public static void registerRecipes(){ @@ -46,16 +46,74 @@ public static void registerRecipes(){ } )); - //WORKSTATION RECIPES Recipes.registerModRecipe(new Recipe( "examplestaff", 1, + RecipeTechRegistry.IRON_ANVIL, + new Ingredient[]{ + new Ingredient("exampleitem", 5), + new Ingredient("examplebar", 4) + } + )); + + Recipes.registerModRecipe(new Recipe( + "examplehelmet", + 1, + RecipeTechRegistry.IRON_ANVIL, + new Ingredient[] { + new Ingredient("examplebar", 8), + new Ingredient("exampleitem", 2) + } + )); + + Recipes.registerModRecipe(new Recipe( + "examplechestplate", + 1, + RecipeTechRegistry.IRON_ANVIL, + new Ingredient[] { + new Ingredient("examplebar", 14), + new Ingredient("exampleitem", 4) + } + )); + + Recipes.registerModRecipe(new Recipe( + "exampleboots", + 1, + RecipeTechRegistry.IRON_ANVIL, + new Ingredient[] { + new Ingredient("examplebar", 10), + new Ingredient("exampleitem", 3) + } + )); + + //WORKSTATION RECIPES + Recipes.registerModRecipe(new Recipe( + "examplewall", + 1, RecipeTechRegistry.WORKSTATION, new Ingredient[]{ - new Ingredient("exampleitem", 4), - new Ingredient("examplebar", 10) + new Ingredient("examplestone", 7) } - ).showAfter("exampleitem")); // Show the recipe after example item recipe + )); + + Recipes.registerModRecipe(new Recipe( + "exampledoor", + 1, + RecipeTechRegistry.WORKSTATION, + new Ingredient[]{ + new Ingredient("examplestone", 7) + } + )); + + Recipes.registerModRecipe(new Recipe( + "exampleobject", + 1, + RecipeTechRegistry.WORKSTATION, + new Ingredient[]{ + new Ingredient("examplestone", 7), + new Ingredient("exampleitem", 3) + } + )); //COOKING POT RECIPES Recipes.registerModRecipe(new Recipe( diff --git a/src/main/java/examplemod/examples/buffs/ExampleArmorSetBonusBuff.java b/src/main/java/examplemod/examples/buffs/ExampleArmorSetBonusBuff.java new file mode 100644 index 0000000..65df04f --- /dev/null +++ b/src/main/java/examplemod/examples/buffs/ExampleArmorSetBonusBuff.java @@ -0,0 +1,24 @@ +package examplemod.examples.buffs; + +import necesse.engine.modifiers.ModifierValue; +import necesse.entity.mobs.buffs.ActiveBuff; +import necesse.entity.mobs.buffs.BuffEventSubscriber; +import necesse.entity.mobs.buffs.BuffModifiers; +import necesse.entity.mobs.buffs.staticBuffs.armorBuffs.setBonusBuffs.SimpleSetBonusBuff; + +public class ExampleArmorSetBonusBuff extends SimpleSetBonusBuff { + public ExampleArmorSetBonusBuff() { + super( + new ModifierValue<>(BuffModifiers.ALL_DAMAGE, 0.10f), + new ModifierValue<>(BuffModifiers.SPEED, 0.10f) + ); + } + + @Override + public void init(ActiveBuff buff, BuffEventSubscriber eventSubscriber) { + this.canCancel = false; + this.isPassive = true; + this.isVisible = true; + super.init(buff, eventSubscriber); + } +} diff --git a/src/main/java/examplemod/examples/ExampleBuff.java b/src/main/java/examplemod/examples/buffs/ExampleBuff.java similarity index 95% rename from src/main/java/examplemod/examples/ExampleBuff.java rename to src/main/java/examplemod/examples/buffs/ExampleBuff.java index 2dec99d..e2310f5 100644 --- a/src/main/java/examplemod/examples/ExampleBuff.java +++ b/src/main/java/examplemod/examples/buffs/ExampleBuff.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.buffs; import necesse.entity.mobs.buffs.ActiveBuff; import necesse.entity.mobs.buffs.BuffEventSubscriber; diff --git a/src/main/java/examplemod/examples/items/armor/ExampleBootsArmorItem.java b/src/main/java/examplemod/examples/items/armor/ExampleBootsArmorItem.java new file mode 100644 index 0000000..1297dfc --- /dev/null +++ b/src/main/java/examplemod/examples/items/armor/ExampleBootsArmorItem.java @@ -0,0 +1,16 @@ +package examplemod.examples.items.armor; + +import necesse.inventory.item.Item; +import necesse.inventory.item.armorItem.BootsArmorItem; +import necesse.inventory.lootTable.presets.FeetArmorLootTable; + +public class ExampleBootsArmorItem extends BootsArmorItem { + public ExampleBootsArmorItem() { + + super(2, //armorValue + 300, //enchantCost + Item.Rarity.UNCOMMON, //rarity + "exampleboots", //textureName + FeetArmorLootTable.feetArmor); //lootTableCategory + } +} diff --git a/src/main/java/examplemod/examples/items/armor/ExampleChestArmorItem.java b/src/main/java/examplemod/examples/items/armor/ExampleChestArmorItem.java new file mode 100644 index 0000000..6831006 --- /dev/null +++ b/src/main/java/examplemod/examples/items/armor/ExampleChestArmorItem.java @@ -0,0 +1,17 @@ +package examplemod.examples.items.armor; + +import necesse.inventory.item.Item; +import necesse.inventory.item.armorItem.ChestArmorItem; +import necesse.inventory.lootTable.presets.BodyArmorLootTable; + +public class ExampleChestArmorItem extends ChestArmorItem { + public ExampleChestArmorItem() { + super(4, //armorValue + 300, //enchantCost + Item.Rarity.UNCOMMON, //rarity + "examplechest", //bodyTextureName + "examplearms", //armsTextureName + BodyArmorLootTable.bodyArmor); //lootTableCategory + + } +} diff --git a/src/main/java/examplemod/examples/items/armor/ExampleHelmetArmorItem.java b/src/main/java/examplemod/examples/items/armor/ExampleHelmetArmorItem.java new file mode 100644 index 0000000..965fe75 --- /dev/null +++ b/src/main/java/examplemod/examples/items/armor/ExampleHelmetArmorItem.java @@ -0,0 +1,24 @@ +package examplemod.examples.items.armor; + +import necesse.engine.registries.DamageTypeRegistry; +import necesse.inventory.item.Item; +import necesse.inventory.item.armorItem.SetHelmetArmorItem; +import necesse.inventory.lootTable.presets.ArmorSetsLootTable; +import necesse.inventory.lootTable.presets.HeadArmorLootTable; + +public class ExampleHelmetArmorItem extends SetHelmetArmorItem { + public ExampleHelmetArmorItem() { + super( + 3, //armor value + DamageTypeRegistry.MELEE, //damage class for enchant scaling etc + 300, //enchant cost + HeadArmorLootTable.headArmor, //head armor loot category + ArmorSetsLootTable.armorSets, //armor SETS loot category + Item.Rarity.UNCOMMON, + "examplehelmet", //helmet texture name + "examplechestplate", //chest item STRING ID + "exampleboots", //boots item STRING ID + "examplearmorsetbonus" //buff STRING ID + ); + } +} diff --git a/src/main/java/examplemod/examples/items/ExampleFoodItem.java b/src/main/java/examplemod/examples/items/consumable/ExampleFoodItem.java similarity index 95% rename from src/main/java/examplemod/examples/items/ExampleFoodItem.java rename to src/main/java/examplemod/examples/items/consumable/ExampleFoodItem.java index 1f5ce13..ce4279b 100644 --- a/src/main/java/examplemod/examples/items/ExampleFoodItem.java +++ b/src/main/java/examplemod/examples/items/consumable/ExampleFoodItem.java @@ -1,4 +1,4 @@ -package examplemod.examples.items; +package examplemod.examples.items.consumable; import necesse.engine.modifiers.ModifierValue; import necesse.entity.mobs.buffs.BuffModifiers; diff --git a/src/main/java/examplemod/examples/items/ExamplePotionItem.java b/src/main/java/examplemod/examples/items/consumable/ExamplePotionItem.java similarity index 85% rename from src/main/java/examplemod/examples/items/ExamplePotionItem.java rename to src/main/java/examplemod/examples/items/consumable/ExamplePotionItem.java index 17bdcdc..a2293fa 100644 --- a/src/main/java/examplemod/examples/items/ExamplePotionItem.java +++ b/src/main/java/examplemod/examples/items/consumable/ExamplePotionItem.java @@ -1,4 +1,4 @@ -package examplemod.examples.items; +package examplemod.examples.items.consumable; import necesse.inventory.item.placeableItem.consumableItem.potionConsumableItem.SimplePotionItem; diff --git a/src/main/java/examplemod/examples/items/ExampleBarItem.java b/src/main/java/examplemod/examples/items/materials/ExampleBarItem.java similarity index 82% rename from src/main/java/examplemod/examples/items/ExampleBarItem.java rename to src/main/java/examplemod/examples/items/materials/ExampleBarItem.java index 0e5160f..7251215 100644 --- a/src/main/java/examplemod/examples/items/ExampleBarItem.java +++ b/src/main/java/examplemod/examples/items/materials/ExampleBarItem.java @@ -1,4 +1,4 @@ -package examplemod.examples.items; +package examplemod.examples.items.materials; import necesse.inventory.item.Item; import necesse.inventory.item.matItem.MatItem; diff --git a/src/main/java/examplemod/examples/items/ExampleHuntIncursionMaterialItem.java b/src/main/java/examplemod/examples/items/materials/ExampleHuntIncursionMaterialItem.java similarity index 81% rename from src/main/java/examplemod/examples/items/ExampleHuntIncursionMaterialItem.java rename to src/main/java/examplemod/examples/items/materials/ExampleHuntIncursionMaterialItem.java index 8c15c37..f54d3fb 100644 --- a/src/main/java/examplemod/examples/items/ExampleHuntIncursionMaterialItem.java +++ b/src/main/java/examplemod/examples/items/materials/ExampleHuntIncursionMaterialItem.java @@ -1,4 +1,4 @@ -package examplemod.examples.items; +package examplemod.examples.items.materials; import necesse.inventory.item.matItem.MatItem; diff --git a/src/main/java/examplemod/examples/items/ExampleMaterialItem.java b/src/main/java/examplemod/examples/items/materials/ExampleMaterialItem.java similarity index 80% rename from src/main/java/examplemod/examples/items/ExampleMaterialItem.java rename to src/main/java/examplemod/examples/items/materials/ExampleMaterialItem.java index a20ddb6..12e9220 100644 --- a/src/main/java/examplemod/examples/items/ExampleMaterialItem.java +++ b/src/main/java/examplemod/examples/items/materials/ExampleMaterialItem.java @@ -1,4 +1,4 @@ -package examplemod.examples.items; +package examplemod.examples.items.materials; import necesse.inventory.item.matItem.MatItem; diff --git a/src/main/java/examplemod/examples/items/ExampleOreItem.java b/src/main/java/examplemod/examples/items/materials/ExampleOreItem.java similarity index 82% rename from src/main/java/examplemod/examples/items/ExampleOreItem.java rename to src/main/java/examplemod/examples/items/materials/ExampleOreItem.java index 8e3ed98..6a04fcb 100644 --- a/src/main/java/examplemod/examples/items/ExampleOreItem.java +++ b/src/main/java/examplemod/examples/items/materials/ExampleOreItem.java @@ -1,4 +1,4 @@ -package examplemod.examples.items; +package examplemod.examples.items.materials; import necesse.inventory.item.Item; import necesse.inventory.item.matItem.MatItem; diff --git a/src/main/java/examplemod/examples/items/ExampleStoneItem.java b/src/main/java/examplemod/examples/items/materials/ExampleStoneItem.java similarity index 79% rename from src/main/java/examplemod/examples/items/ExampleStoneItem.java rename to src/main/java/examplemod/examples/items/materials/ExampleStoneItem.java index f217e3b..faeff08 100644 --- a/src/main/java/examplemod/examples/items/ExampleStoneItem.java +++ b/src/main/java/examplemod/examples/items/materials/ExampleStoneItem.java @@ -1,4 +1,4 @@ -package examplemod.examples.items; +package examplemod.examples.items.materials; import necesse.inventory.item.placeableItem.StonePlaceableItem; diff --git a/src/main/resources/buffs/examplearmorsetbonus.png b/src/main/resources/buffs/examplearmorsetbonus.png new file mode 100644 index 0000000000000000000000000000000000000000..b4edeee83d053b964ec4c2fadb52a57f5ddc91e9 GIT binary patch literal 398 zcmV;90df9`P)JJj;T|CTa%_R$nn8I{@T!5SEZOrg{i~%!HYbVkdI2K!YCa z^NMcj20%wCJOEJA9XJGN>2vg;L=OqDYw-E}$9KwNgM$-QKYB>)9s4iYj2P55)u;12;< zNWgpyj+%6AuR4;b;6*n4M$r<$RWT0YB0Wd zdX}cGVxS$2Ks&%m3DhD7H_2d4b6WL?kh3Jna+Dqthy%kgh0u-(C?u$ckvlucc4Fpz0B*gr7$M$;Y5)KL07*qoM6N<$f>61k5dZ)H literal 0 HcmV?d00001 diff --git a/src/main/resources/items/exampleboots.png b/src/main/resources/items/exampleboots.png new file mode 100644 index 0000000000000000000000000000000000000000..98ab72f23b0d4965bfb0c19f5b7283a6641b3c1e GIT binary patch literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVw7cMVB^{OdkawLnx~6n zNJQ(~X?uAO83?$^cen}|D?|m9Em*KLNsvoy!J?%J?Ho}@csc?X?0T^IQFo`qwwCy0 z)$hvn^WRzoXo&oq+bFtbhW*{=v$|*AU!Es%`NQ-r=gx#b5DvPsTyH0{vUWuR|AAS( zRr+48fgY7B?y2UpZ+p|QebOvr26w-;KeeYZgy@9qb-l;OVd%xM?#>kn=QzF-dEc%F zU9$_j*^`-VyTFhC$)f|?r*PNp`%;`S$KzeYuSvcVvrm@GIed@(;5q*Z--=)bWA2Mz m?H?RD8+}oACv&UQLsm`}m%p-e%+COQ$l&Sf=d#Wzp$PyUWRed6 literal 0 HcmV?d00001 diff --git a/src/main/resources/items/examplechestplate.png b/src/main/resources/items/examplechestplate.png new file mode 100644 index 0000000000000000000000000000000000000000..19048bad6cbe6f97aed11a0153498555d716b371 GIT binary patch literal 558 zcmV+}0@3}6P)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007 zbV*G`2kHSG0RTA7*ybMq00DAIL_t(oh3%J3N&`U39AS<&mUO^!njp7abxryK| z>+A%P+{2$Eh|x_zuVCEhDko6JO&&8Mq!0H(xaV z$WyJDYjrp9(`YO^QOr*{u9}*|@=C;0Aa%xKe$DN|fF*_I#_`I;Q9#Tuxd7mSApker z0r1QV3+#j=0QTu>oyn0n3J7AZ9gF#xH=tlR?A-$}>W_?H^V)2h zIEw4Bas_5;Rk@J4Au-xv8z__!ykANP`LrnFSHTu-0A3kWw#86qs&A*)0g7Dk@NsZA zWgGv*f91xAI6?A0jfKrl`D|!34IT!Gfuq)ha4EcY$|%hxC*51 w1)uF6Rdw$XR{_B*F~?7NT*~~K|AK#f1IJ8@{H!MhFaQ7m07*qoM6N<$g25}~NdN!< literal 0 HcmV?d00001 diff --git a/src/main/resources/items/examplehelmet.png b/src/main/resources/items/examplehelmet.png new file mode 100644 index 0000000000000000000000000000000000000000..820f9fc893bca82a238af5a21239501649a2ea2f GIT binary patch literal 485 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVw7cMRNSzlejd;Tnw~C> zArY-_ryJ%m849?@EA=&MOya(vddoS7U34eA`vW;o*17o;L^NLRUZ1Pj++a~MB z9v5>a?w|Wq@@&0a|22U%*6)v{&(kyQwEtQ3bl-Z6jZx2+?om0!`A6JfquHKMYzMft57cvuq-)M`SZ;jvukPEk3=?N;yPWfP&N+wu zpIw-CZC9MraFcoIlMSA}+xDgZU(Ixga|Y{@`Rl{(?I>VMkTJtBi0lPxy zT$O&TuE4I^IjQ4Gsa~tF!GHEsohvq9KXcus#$VpydgXnVDTg>i{107vXgD`-UA1W( zlhtO)&D;m(RE9ocZrAajf6UwA)~3nK;+&TIyQD)b7{A{Rh}tAkqg*xD`ytDrue`l0 W!VE<3W-0)~k-^i|&t;ucLK6Uz?zW%+ literal 0 HcmV?d00001 diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index 7cac2ee..c824585 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -15,9 +15,13 @@ exampleore=Example Ore examplebar=Example Bar examplehuntincursionmaterial=Example Hunt Incursion Material examplepotion=Example Potion +examplefood=Example Food examplesword=Example Sword examplestaff=Example Staff -examplefood=Example Food +examplehelmet=Example Helmet +examplechestplate=Example Chestplate +exampleboots=Example Boots + [itemtooltip] examplestafftip=Shoots a homing, piercing projectile @@ -29,9 +33,23 @@ examplebossmob=Example Boss [buff] examplebuff=Example Buff +examplearmorsetbonus=Example Armor Set Bonus [biome] exampleincursion=Example Incursion [incursion] -exampleincursion=Example Incursion \ No newline at end of file +exampleincursion=Example Incursion + +[itemcategory] +examplemod=ExampleMod +examplemod_materials=Example Materials +examplemod_tools=Example Tools +examplemod_weapons=Example Weapons +examplemod_armor=Example Armor +examplemod_placeables=Example Placeables +examplemod_furniture=Example Furniture +examplemod_lighting=Example Lighting +examplemod_decor=Example Decor +examplemod_consumables=Example Consumables +examplemod_incursion=Example Incursion \ No newline at end of file diff --git a/src/main/resources/player/armor/examplearms_left.png b/src/main/resources/player/armor/examplearms_left.png new file mode 100644 index 0000000000000000000000000000000000000000..dd90fff8ed32654182b7c6619a6febc155ae771d GIT binary patch literal 1491 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/player/armor/examplehelmet.png b/src/main/resources/player/armor/examplehelmet.png new file mode 100644 index 0000000000000000000000000000000000000000..687a019636f5c54c2b18f81ca1356666383610da GIT binary patch literal 2833 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 Date: Sun, 1 Feb 2026 03:28:31 +0000 Subject: [PATCH 13/21] Refactor and update mod category structure and objects Reworked ExampleModCategories to align with vanilla creative menu roots and subcategories, and updated item/crafting category registration for mod objects. Modified ExampleModObjects to assign new categories to registered objects and added a new object and recipe (exampleobject2) using the new mod-specific category. Updated localization keys to match new category names and structure. --- src/main/java/examplemod/ExampleMod.java | 1 - .../Loaders/ExampleModCategories.java | 183 +++++++++--------- .../examplemod/Loaders/ExampleModObjects.java | 10 +- .../examplemod/Loaders/ExampleModRecipes.java | 10 + src/main/resources/locale/en.lang | 14 +- 5 files changed, 111 insertions(+), 107 deletions(-) diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 356c4d6..53a5e32 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -32,7 +32,6 @@ public void init() { public void initResources() { ExampleModResources.load(); - } public void postInit() { diff --git a/src/main/java/examplemod/Loaders/ExampleModCategories.java b/src/main/java/examplemod/Loaders/ExampleModCategories.java index d6e3bef..66ec22d 100644 --- a/src/main/java/examplemod/Loaders/ExampleModCategories.java +++ b/src/main/java/examplemod/Loaders/ExampleModCategories.java @@ -7,107 +7,102 @@ public final class ExampleModCategories { private ExampleModCategories() {} /* - * Necesse has TWO category trees: + * IMPORTANT (Creative Menu requirement) + * ------------------------------------ + * The Creative menu tabs currently only browse a small set of hard-coded ROOT categories but this will be changed in the future. * - * 1) Item categories (ItemCategory master tree) - * * Used for Creative inventory browsing / item lists / general item grouping. - * * This is what setItemCategory(...) uses on items/objects. + * Placeables tab roots: tiles / objects / wiring + * Items tab roots: equipment / consumable / materials / misc + * Mobs tab roots: mobs * - * 2) Crafting categories (ItemCategory.craftingManager tree) - * * Used by crafting station recipe lists and their filter/grouping UI. - * * This is what setCraftingCategory(...) uses on items/objects (and also what Recipe.setCraftingCategory uses). - * - * Important rule: - * If you want to use a category path, you MUST create that exact path in the corresponding manager - * BEFORE any items/objects/recipes try to reference it. - * - * - * ------------------------------------------------------------------------- - * Ways to APPLY categories: - * - * A) Apply on the object/item itself (recommended baseline) - * Object registration (GameObject): - * new ExampleObject() - * .setItemCategory(ROOT, PLACEABLES, DECOR) // Creative / item browsing - * .setCraftingCategory(ROOT, PLACEABLES, DECOR); // Crafting menu default - * - * Item registration (Item): - * new ExampleItem() - * .setItemCategory(ROOT, MATERIALS) - * .setCraftingCategory(ROOT, MATERIALS); - * - * What this affects: - * Creative inventory category placement (itemCategoryTree) - * Default crafting category used by stations/recipes if not overridden (craftingCategoryTree) - * - * B) Apply on the recipe (vanilla does this often) - * Recipe override: - * new Recipe(...) - * .setCraftingCategory(ROOT, PLACEABLES, DECOR); - * - * What this affects: - * Where the RECIPE appears in the crafting UI. - * Useful when a station collapses category depth (e.g. Workstation depth=1). - * You can force “Placeables/Decor” grouping even when the station normally trims deeper paths. - * - * Rule of thumb: - * Put the “true” category on the item/object (so creative inventory and general lists are correct). - * Use Recipe.setCraftingCategory(...) when you want a specific crafting UI grouping. + * So: your itemCategoryTree MUST start with one of those roots, otherwise the item/object + * will not appear in Creative even though it is registered. */ - // Category paths (so you can reuse them consistently) - public static final String ROOT = "examplemod"; + // VANILLA PLACABLE + // ===== ROOT ===== + public static final String ROOT_TILES = "tiles"; + public static final String ROOT_OBJECTS = "objects"; + public static final String ROOT_WIRING = "wiring"; + + // ===== VANILLA: TILES children ===== + public static final String TILES_FLOORS = "floors"; + public static final String TILES_LIQUIDS = "liquids"; + public static final String TILES_TERRAIN = "terrain"; - public static final String INCURSION = "incursion"; - public static final String MATERIALS = "materials"; - public static final String CONSUMABLES = "consumables"; - public static final String TOOLS = "tools"; - public static final String WEAPONS = "weapons"; - public static final String ARMOR = "armor"; - public static final String PLACEABLES = "placeables"; - public static final String FURNITURE = "furniture"; - public static final String LIGHTING = "lighting"; - public static final String DECOR = "decor"; + // ===== VANILLA: OBJECTS children ===== + public static final String OBJECTS_SEEDS = "seeds"; + public static final String OBJECTS_CRAFTINGSTATIONS = "craftingstations"; + public static final String OBJECTS_LIGHTING = "lighting"; + public static final String OBJECTS_FURNITURE = "furniture"; + public static final String OBJECTS_DECORATIONS = "decorations"; + public static final String OBJECTS_WALLSANDDOORS = "wallsanddoors"; + public static final String OBJECTS_FENCESANDGATES = "fencesandgates"; + public static final String OBJECTS_COLUMNS = "columns"; + public static final String OBJECTS_TRAPS = "traps"; + public static final String OBJECTS_LANDSCAPING = "landscaping"; + public static final String OBJECTS_MISC = "misc"; + + // ===== VANILLA: FURNITURE children ===== + public static final String FURNITURE_MISC = "misc"; + public static final String FURNITURE_OAK = "oak"; + public static final String FURNITURE_SPRUCE = "spruce"; + public static final String FURNITURE_PINE = "pine"; + public static final String FURNITURE_MAPLE = "maple"; + public static final String FURNITURE_BIRCH = "birch"; + public static final String FURNITURE_WILLOW = "willow"; + public static final String FURNITURE_DUNGEON = "dungeon"; + public static final String FURNITURE_BONE = "bone"; + public static final String FURNITURE_DRYAD = "dryad"; + public static final String FURNITURE_BAMBOO = "bamboo"; + public static final String FURNITURE_DEADWOOD = "deadwood"; + + // ===== VANILLA: DECORATIONS children ===== + public static final String DECORATIONS_PAINTINGS = "paintings"; + public static final String DECORATIONS_CARPETS = "carpets"; + public static final String DECORATIONS_POTS = "pots"; + public static final String DECORATIONS_BANNERS = "banners"; + + // ===== VANILLA: LANDSCAPING children ===== + public static final String LANDSCAPING_FORESTROCKSANDORES = "forestrocksandores"; + public static final String LANDSCAPING_SNOWROCKSANDORES = "snowrocksandores"; + public static final String LANDSCAPING_PLAINSROCKSANDORES = "plainsrocksandores"; + public static final String LANDSCAPING_SWAMPROCKSANDORES = "swamprocksandores"; + public static final String LANDSCAPING_DESERTROCKSANDORES = "desertrocksandores"; + public static final String LANDSCAPING_INCURSIONROCKSANDORES = "incursionrocksandores"; + public static final String LANDSCAPING_CRYSTALS = "crystals"; + public static final String LANDSCAPING_TABLEDECORATIONS = "tabledecorations"; + public static final String LANDSCAPING_PLANTS = "plants"; + public static final String LANDSCAPING_MASONRY = "masonry"; + public static final String LANDSCAPING_MISC = "misc"; + + // ===== VANILLA: WIRING children ===== + public static final String WIRING_LOGICGATES = "logicgates"; + + + // YOUR MOD ROOT CATEGORY + public static final String MOD = "examplemod"; + // YOUR MOD SUB CATEGORY + public static final String MOD_OBJECTS = "objects"; public static void load() { - // ===== Normal item category tree (Creative inventory / item browsing) ===== - // "Z-..." usually pushes it toward the bottom; change if you want it earlier. - // The category "path" is built from these strings, e.g.: - // ROOT -> PLACEABLES -> DECOR - // becomes: - // examplemod.placeables.decor - ItemCategory.createCategory("Z-EX-0", new LocalMessage("itemcategory", "examplemod"), ROOT); - - ItemCategory.createCategory("Z-EX-1", new LocalMessage("itemcategory", "examplemod_materials"), ROOT, MATERIALS); - - ItemCategory.createCategory("Z-EX-2", new LocalMessage("itemcategory", "examplemod_consumables"), ROOT, CONSUMABLES); - ItemCategory.createCategory("Z-EX-3", new LocalMessage("itemcategory", "examplemod_tools"), ROOT, TOOLS); - ItemCategory.createCategory("Z-EX-4", new LocalMessage("itemcategory", "examplemod_weapons"), ROOT, WEAPONS); - ItemCategory.createCategory("Z-EX-5", new LocalMessage("itemcategory", "examplemod_armor"), ROOT, ARMOR); - ItemCategory.createCategory("Z-EX-6", new LocalMessage("itemcategory", "examplemod_incursion"), ROOT, INCURSION); - ItemCategory.createCategory("Z-EX-7", new LocalMessage("itemcategory", "examplemod_placeables"), ROOT, PLACEABLES); - - // Subcategories under Placeables (Creative) - ItemCategory.createCategory("Z-EX-7A", new LocalMessage("itemcategory", "examplemod_furniture"), ROOT, PLACEABLES, FURNITURE); - ItemCategory.createCategory("Z-EX-7B", new LocalMessage("itemcategory", "examplemod_lighting"), ROOT, PLACEABLES, LIGHTING); - ItemCategory.createCategory("Z-EX-7C", new LocalMessage("itemcategory", "examplemod_decor"), ROOT, PLACEABLES, DECOR); - - // ===== Crafting filter tree (Crafting station recipe list/filter UI) ===== - // If you want the same category paths to work in crafting menus, - // you must mirror the same tree here. - ItemCategory.craftingManager.createCategory("Z-EX-0", new LocalMessage("itemcategory", "examplemod"), ROOT); - ItemCategory.craftingManager.createCategory("Z-EX-1", new LocalMessage("itemcategory", "examplemod_materials"), ROOT, MATERIALS); - ItemCategory.craftingManager.createCategory("Z-EX-2", new LocalMessage("itemcategory", "examplemod_consumables"), ROOT, CONSUMABLES); - ItemCategory.craftingManager.createCategory("Z-EX-3", new LocalMessage("itemcategory", "examplemod_tools"), ROOT, TOOLS); - ItemCategory.craftingManager.createCategory("Z-EX-4", new LocalMessage("itemcategory", "examplemod_weapons"), ROOT, WEAPONS); - ItemCategory.craftingManager.createCategory("Z-EX-5", new LocalMessage("itemcategory", "examplemod_armor"), ROOT, ARMOR); - ItemCategory.craftingManager.createCategory("Z-EX-6", new LocalMessage("itemcategory", "examplemod_incursion"), ROOT, INCURSION); - ItemCategory.craftingManager.createCategory("Z-EX-7", new LocalMessage("itemcategory", "examplemod_placeables"), ROOT, PLACEABLES); - - // Subcategories under Placeables (Crafting) - ItemCategory.craftingManager.createCategory("Z-EX-7A", new LocalMessage("itemcategory", "examplemod_furniture"), ROOT, PLACEABLES, FURNITURE); - ItemCategory.craftingManager.createCategory("Z-EX-7B", new LocalMessage("itemcategory", "examplemod_lighting"), ROOT, PLACEABLES, LIGHTING); - ItemCategory.craftingManager.createCategory("Z-EX-7C", new LocalMessage("itemcategory", "examplemod_decor"), ROOT, PLACEABLES, DECOR); + // ITEM CATEGORIES (not Creative-visible right now, but valid categories) + ItemCategory.createCategory("Z-EXAMPLEMOD", + new LocalMessage("itemcategory", "examplemodrootcat"), + MOD); + + ItemCategory.createCategory("Z-EXAMPLEMOD-OBJECTS", + new LocalMessage("itemcategory", "examplemodobjectssubcat"), + MOD, MOD_OBJECTS); + + // CRAFTING CATEGORIES + ItemCategory.craftingManager.createCategory("Z-EXAMPLEMOD", + new LocalMessage("itemcategory", "examplemodrootcat"), + MOD); + + ItemCategory.craftingManager.createCategory("Z-EXAMPLEMOD", + new LocalMessage("itemcategory", "examplemodobjectscat"), + MOD,MOD_OBJECTS); } } diff --git a/src/main/java/examplemod/Loaders/ExampleModObjects.java b/src/main/java/examplemod/Loaders/ExampleModObjects.java index 25670a8..3c87cb2 100644 --- a/src/main/java/examplemod/Loaders/ExampleModObjects.java +++ b/src/main/java/examplemod/Loaders/ExampleModObjects.java @@ -6,12 +6,20 @@ import examplemod.examples.objects.ExampleWallWindowDoorObject; import necesse.engine.registries.ObjectRegistry; +//NOTE item and crafting categories subject to change public class ExampleModObjects { public static void load(){ // Register our objects - ObjectRegistry.registerObject("exampleobject", new ExampleObject(), 2, true); + ObjectRegistry.registerObject("exampleobject", new ExampleObject() + .setItemCategory(ExampleModCategories.ROOT_OBJECTS,ExampleModCategories.OBJECTS_COLUMNS) + .setCraftingCategory(ExampleModCategories.ROOT_OBJECTS,ExampleModCategories.OBJECTS_COLUMNS), 2, true); + + //this wont currently display in creative due to how creative is coded but this is subject to change + ObjectRegistry.registerObject("exampleobject2", new ExampleObject() + .setItemCategory(ExampleModCategories.MOD,ExampleModCategories.MOD_OBJECTS) + .setCraftingCategory(ExampleModCategories.MOD,ExampleModCategories.MOD_OBJECTS), 2, true); // Register a rock object ExampleBaseRockObject exampleBaseRock = new ExampleBaseRockObject(); ObjectRegistry.registerObject("examplebaserock", exampleBaseRock, -1.0F, true); diff --git a/src/main/java/examplemod/Loaders/ExampleModRecipes.java b/src/main/java/examplemod/Loaders/ExampleModRecipes.java index fa53d5c..0d127b7 100644 --- a/src/main/java/examplemod/Loaders/ExampleModRecipes.java +++ b/src/main/java/examplemod/Loaders/ExampleModRecipes.java @@ -115,6 +115,16 @@ public static void registerRecipes(){ } )); + Recipes.registerModRecipe(new Recipe( + "exampleobject2", + 1, + RecipeTechRegistry.WORKSTATION, + new Ingredient[]{ + new Ingredient("examplestone", 7), + new Ingredient("exampleitem", 3) + } + )); + //COOKING POT RECIPES Recipes.registerModRecipe(new Recipe( "examplefood", diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index c824585..e5f47a6 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -3,6 +3,7 @@ exampletile=Example Tile [object] exampleobject=Example Object +exampleobject2 = Example Object Mod Category examplebaserock=Example Rock exampleore=Example Ore examplewall=Example Wall @@ -42,14 +43,5 @@ exampleincursion=Example Incursion exampleincursion=Example Incursion [itemcategory] -examplemod=ExampleMod -examplemod_materials=Example Materials -examplemod_tools=Example Tools -examplemod_weapons=Example Weapons -examplemod_armor=Example Armor -examplemod_placeables=Example Placeables -examplemod_furniture=Example Furniture -examplemod_lighting=Example Lighting -examplemod_decor=Example Decor -examplemod_consumables=Example Consumables -examplemod_incursion=Example Incursion \ No newline at end of file +examplemodrootcat=ExampleMod +examplemodobjectsubcat=ExampleMod Objects \ No newline at end of file From 28cd55e7ce39655a418a0461dbf83fa17c788daf Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Mon, 2 Feb 2026 02:47:07 +0000 Subject: [PATCH 14/21] Add example tree, sapling, log, and chair objects Introduces new ExampleTreeObject, ExampleTreeSaplingObject, ExampleLogItem, and ExampleWoodChairObject classes, along with their registration in loaders and crafting recipes. Adds related item/object categories, updates localization, and includes new/updated resource images for these items and objects. Also renames ExampleSwordItem to ExampleSwordWeapon. --- .../Loaders/ExampleModCategories.java | 16 +++++++++-- .../examplemod/Loaders/ExampleModItems.java | 6 ++-- .../examplemod/Loaders/ExampleModObjects.java | 22 ++++++++------ .../examplemod/Loaders/ExampleModRecipes.java | 20 ++++++------- .../items/materials/ExampleLogItem.java | 11 +++++++ ...SwordItem.java => ExampleSwordWeapon.java} | 4 +-- .../examples/objects/ExampleTreeObject.java | 27 ++++++++++++++++++ .../objects/ExampleTreeSaplingObject.java | 12 ++++++++ .../objects/ExampleWoodChairObject.java | 11 +++++++ src/main/resources/items/examplebaserock.png | Bin 5617 -> 789 bytes src/main/resources/items/exampleboots.png | Bin 402 -> 402 bytes src/main/resources/items/examplechair.png | Bin 0 -> 446 bytes .../resources/items/examplechestplate.png | Bin 558 -> 558 bytes src/main/resources/items/exampledoor.png | Bin 406 -> 445 bytes src/main/resources/items/examplelog.png | Bin 0 -> 446 bytes src/main/resources/items/examplesapling.png | Bin 0 -> 480 bytes src/main/resources/items/exampletree.png | Bin 0 -> 606 bytes src/main/resources/items/examplewall.png | Bin 448 -> 545 bytes src/main/resources/locale/en.lang | 6 +++- src/main/resources/objects/examplechair.png | Bin 0 -> 725 bytes src/main/resources/objects/examplesapling.png | Bin 0 -> 466 bytes src/main/resources/objects/exampletree.png | Bin 0 -> 17702 bytes .../resources/particles/exampleleaves.png | Bin 0 -> 600 bytes 23 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 src/main/java/examplemod/examples/items/materials/ExampleLogItem.java rename src/main/java/examplemod/examples/items/tools/{ExampleSwordItem.java => ExampleSwordWeapon.java} (87%) create mode 100644 src/main/java/examplemod/examples/objects/ExampleTreeObject.java create mode 100644 src/main/java/examplemod/examples/objects/ExampleTreeSaplingObject.java create mode 100644 src/main/java/examplemod/examples/objects/ExampleWoodChairObject.java create mode 100644 src/main/resources/items/examplechair.png create mode 100644 src/main/resources/items/examplelog.png create mode 100644 src/main/resources/items/examplesapling.png create mode 100644 src/main/resources/items/exampletree.png create mode 100644 src/main/resources/objects/examplechair.png create mode 100644 src/main/resources/objects/examplesapling.png create mode 100644 src/main/resources/objects/exampletree.png create mode 100644 src/main/resources/particles/exampleleaves.png diff --git a/src/main/java/examplemod/Loaders/ExampleModCategories.java b/src/main/java/examplemod/Loaders/ExampleModCategories.java index 66ec22d..65dd8c0 100644 --- a/src/main/java/examplemod/Loaders/ExampleModCategories.java +++ b/src/main/java/examplemod/Loaders/ExampleModCategories.java @@ -85,6 +85,8 @@ private ExampleModCategories() {} // YOUR MOD SUB CATEGORY public static final String MOD_OBJECTS = "objects"; + public static final String EXAMPLEWOOD = "examplewood"; + public static void load() { // ITEM CATEGORIES (not Creative-visible right now, but valid categories) @@ -93,16 +95,24 @@ public static void load() { MOD); ItemCategory.createCategory("Z-EXAMPLEMOD-OBJECTS", - new LocalMessage("itemcategory", "examplemodobjectssubcat"), + new LocalMessage("itemcategory", "examplemodobjectsubcat"), MOD, MOD_OBJECTS); + ItemCategory.createCategory("Z-EXAMPLEMOD-OBJECTS-FURNATURE", + new LocalMessage("itemcategory", "examplemodfurnaturesubcat"), + MOD, MOD_OBJECTS,EXAMPLEWOOD); + // CRAFTING CATEGORIES ItemCategory.craftingManager.createCategory("Z-EXAMPLEMOD", new LocalMessage("itemcategory", "examplemodrootcat"), MOD); - ItemCategory.craftingManager.createCategory("Z-EXAMPLEMOD", - new LocalMessage("itemcategory", "examplemodobjectscat"), + ItemCategory.craftingManager.createCategory("Z-EXAMPLEMOD-OBJECTS", + new LocalMessage("itemcategory", "examplemodobjectsubcat"), MOD,MOD_OBJECTS); + + ItemCategory.craftingManager.createCategory("Z-EXAMPLEMOD-OBJECTS-FURNATURE", + new LocalMessage("itemcategory", "examplemodfurnaturesubcat"), + MOD,MOD_OBJECTS,EXAMPLEWOOD); } } diff --git a/src/main/java/examplemod/Loaders/ExampleModItems.java b/src/main/java/examplemod/Loaders/ExampleModItems.java index abc11d6..198e969 100644 --- a/src/main/java/examplemod/Loaders/ExampleModItems.java +++ b/src/main/java/examplemod/Loaders/ExampleModItems.java @@ -7,9 +7,8 @@ import examplemod.examples.items.consumable.ExamplePotionItem; import examplemod.examples.items.materials.*; import examplemod.examples.items.tools.ExampleProjectileWeapon; -import examplemod.examples.items.tools.ExampleSwordItem; +import examplemod.examples.items.tools.ExampleSwordWeapon; import necesse.engine.registries.ItemRegistry; -import necesse.inventory.item.ItemCategory; public class ExampleModItems { public static void load(){ @@ -20,9 +19,10 @@ public static void load(){ ItemRegistry.registerItem("exampleore", new ExampleOreItem(), 25, true); ItemRegistry.registerItem("examplebar", new ExampleBarItem(), 50, true); ItemRegistry.registerItem("examplehuntincursionmaterial", new ExampleHuntIncursionMaterialItem(), 50, true); + ItemRegistry.registerItem("examplelog", new ExampleLogItem().setItemCategory("materials","logs"),10,true); // Tools - ItemRegistry.registerItem("examplesword", new ExampleSwordItem(), 20, true); + ItemRegistry.registerItem("examplesword", new ExampleSwordWeapon(), 20, true); ItemRegistry.registerItem("examplestaff", new ExampleProjectileWeapon(), 30, true); // Armor diff --git a/src/main/java/examplemod/Loaders/ExampleModObjects.java b/src/main/java/examplemod/Loaders/ExampleModObjects.java index 3c87cb2..6953650 100644 --- a/src/main/java/examplemod/Loaders/ExampleModObjects.java +++ b/src/main/java/examplemod/Loaders/ExampleModObjects.java @@ -1,9 +1,6 @@ package examplemod.Loaders; -import examplemod.examples.objects.ExampleBaseRockObject; -import examplemod.examples.objects.ExampleObject; -import examplemod.examples.objects.ExampleOreRockObject; -import examplemod.examples.objects.ExampleWallWindowDoorObject; +import examplemod.examples.objects.*; import necesse.engine.registries.ObjectRegistry; //NOTE item and crafting categories subject to change @@ -16,10 +13,7 @@ public static void load(){ .setItemCategory(ExampleModCategories.ROOT_OBJECTS,ExampleModCategories.OBJECTS_COLUMNS) .setCraftingCategory(ExampleModCategories.ROOT_OBJECTS,ExampleModCategories.OBJECTS_COLUMNS), 2, true); - //this wont currently display in creative due to how creative is coded but this is subject to change - ObjectRegistry.registerObject("exampleobject2", new ExampleObject() - .setItemCategory(ExampleModCategories.MOD,ExampleModCategories.MOD_OBJECTS) - .setCraftingCategory(ExampleModCategories.MOD,ExampleModCategories.MOD_OBJECTS), 2, true); + // Register a rock object ExampleBaseRockObject exampleBaseRock = new ExampleBaseRockObject(); ObjectRegistry.registerObject("examplebaserock", exampleBaseRock, -1.0F, true); @@ -29,5 +23,17 @@ public static void load(){ // Register a wall object, window object and door object ExampleWallWindowDoorObject.registerWallsDoorsWindows(); + + // Register a tree object + ObjectRegistry.registerObject("exampletree",new ExampleTreeObject(),0.0F,false,false,false); + + // Register a sapling object + ObjectRegistry.registerObject("examplesapling", new ExampleTreeSaplingObject(),10,true); + + // Register a furnature object this won't currently display in creative due to how creative is coded but this is subject to change + ObjectRegistry.registerObject("examplechair", new ExampleWoodChairObject() + .setItemCategory(ExampleModCategories.MOD,ExampleModCategories.MOD_OBJECTS,ExampleModCategories.EXAMPLEWOOD) + .setCraftingCategory(ExampleModCategories.MOD,ExampleModCategories.MOD_OBJECTS,ExampleModCategories.EXAMPLEWOOD),50,true); + } } diff --git a/src/main/java/examplemod/Loaders/ExampleModRecipes.java b/src/main/java/examplemod/Loaders/ExampleModRecipes.java index 0d127b7..84a1a44 100644 --- a/src/main/java/examplemod/Loaders/ExampleModRecipes.java +++ b/src/main/java/examplemod/Loaders/ExampleModRecipes.java @@ -115,16 +115,6 @@ public static void registerRecipes(){ } )); - Recipes.registerModRecipe(new Recipe( - "exampleobject2", - 1, - RecipeTechRegistry.WORKSTATION, - new Ingredient[]{ - new Ingredient("examplestone", 7), - new Ingredient("exampleitem", 3) - } - )); - //COOKING POT RECIPES Recipes.registerModRecipe(new Recipe( "examplefood", @@ -167,6 +157,16 @@ public static void registerRecipes(){ } )); + //CARPENTER RECIPES + Recipes.registerModRecipe(new Recipe( + "examplechair", + 1, + RecipeTechRegistry.CARPENTER, + new Ingredient[]{ + new Ingredient("examplelog", 5), + } + )); + } } diff --git a/src/main/java/examplemod/examples/items/materials/ExampleLogItem.java b/src/main/java/examplemod/examples/items/materials/ExampleLogItem.java new file mode 100644 index 0000000..5d2a978 --- /dev/null +++ b/src/main/java/examplemod/examples/items/materials/ExampleLogItem.java @@ -0,0 +1,11 @@ +package examplemod.examples.items.materials; + +import necesse.inventory.item.matItem.MatItem; + +public class ExampleLogItem extends MatItem { + + public ExampleLogItem() { + super(500,Rarity.UNCOMMON, new String[]{"anylog"}); + + } +} diff --git a/src/main/java/examplemod/examples/items/tools/ExampleSwordItem.java b/src/main/java/examplemod/examples/items/tools/ExampleSwordWeapon.java similarity index 87% rename from src/main/java/examplemod/examples/items/tools/ExampleSwordItem.java rename to src/main/java/examplemod/examples/items/tools/ExampleSwordWeapon.java index 43e856f..76fad1e 100644 --- a/src/main/java/examplemod/examples/items/tools/ExampleSwordItem.java +++ b/src/main/java/examplemod/examples/items/tools/ExampleSwordWeapon.java @@ -4,11 +4,11 @@ import necesse.inventory.item.toolItem.swordToolItem.SwordToolItem; // Extends SwordToolItem -public class ExampleSwordItem extends SwordToolItem { +public class ExampleSwordWeapon extends SwordToolItem { // Weapon attack textures are loaded from resources/player/weapons/ - public ExampleSwordItem() { + public ExampleSwordWeapon() { super(400, null); rarity = Item.Rarity.UNCOMMON; attackAnimTime.setBaseValue(300); // 300 ms attack time diff --git a/src/main/java/examplemod/examples/objects/ExampleTreeObject.java b/src/main/java/examplemod/examples/objects/ExampleTreeObject.java new file mode 100644 index 0000000..ca7fae6 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleTreeObject.java @@ -0,0 +1,27 @@ +package examplemod.examples.objects; + +import necesse.inventory.lootTable.LootTable; +import necesse.level.gameObject.TreeObject; +import necesse.level.maps.Level; + +import java.awt.*; + +public class ExampleTreeObject extends TreeObject { + public ExampleTreeObject(){ + super("exampletree", // textureName + "examplelog", // logStringID + "examplesapling", // saplingStringID + new Color(116, 69, 43), // mapColor + 45, // leavesCenterWidth + 60, // leavesMinHeight + 110, // leavesMaxHeight + "exampleleaves"); // leavesTextureName + } + + // Optional: override drops if you want something different than the base TreeObject default + // (base TreeObject drops 1-2 saplings + 4-5 logs, splitItems(5)) + @Override + public LootTable getLootTable(Level level, int layerID, int tileX, int tileY) { + return super.getLootTable(level, layerID, tileX, tileY); + } +} diff --git a/src/main/java/examplemod/examples/objects/ExampleTreeSaplingObject.java b/src/main/java/examplemod/examples/objects/ExampleTreeSaplingObject.java new file mode 100644 index 0000000..74c8d0b --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleTreeSaplingObject.java @@ -0,0 +1,12 @@ +package examplemod.examples.objects; + +import necesse.level.gameObject.TreeSaplingObject; + +public class ExampleTreeSaplingObject extends TreeSaplingObject { + + public ExampleTreeSaplingObject(){ + super("examplesapling", "exampletree", 1800, 2700, true); + + } + +} diff --git a/src/main/java/examplemod/examples/objects/ExampleWoodChairObject.java b/src/main/java/examplemod/examples/objects/ExampleWoodChairObject.java new file mode 100644 index 0000000..eaf623f --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleWoodChairObject.java @@ -0,0 +1,11 @@ +package examplemod.examples.objects; + +import necesse.level.gameObject.furniture.ChairObject; + +import java.awt.*; + +public class ExampleWoodChairObject extends ChairObject { + public ExampleWoodChairObject(){ + super("examplechair", new Color(116, 69, 43)); + } +} diff --git a/src/main/resources/items/examplebaserock.png b/src/main/resources/items/examplebaserock.png index f948e26509500a5cc4c8e3dc4a9c49f69375b0fc..fe2a36aeb8e0dcdf80ff52c67d3678a2f07894fd 100644 GIT binary patch delta 658 zcmV;D0&V^AE0qS2a|y-(egMCkr%gkVd>?-T0s<)-5n{Vg00076NklPu1 z5QSfPk{p0DAtAvom&h(ch!v4YBqYQlin8Pw*?{0ubAiAVi*Cnd*DJSsT3LmfMZ5h| z-*efn_5gt3CELjC^*I@7qw~$i)KxQnz&9-o)30>0U4=S4$EUWcdOulGcsh59;E#X! zBXG6jX4NPF{KVaUdO4T|g+isp`1#ka<*Af3m*3&zD?5blfpzfm9?f2HU`s6k#0fYC zZt+@;hU?i{cnU_~8S4cxe=Z6<0&C#)7*<}60$GAJaJKTpQHb_{b~4w6F>GVRG8}(TZL8o-y$Kf3T3|9kS5TkG*xG)by zQLyN(@C5;F*`wQdNR`jpWSA2eh_c8S6 z2;4b{?i8E?I9rE}t|v)%G2CX~ejXSVVZj5L&D|^VDah}+d;Gh=q-fu>qGXD}lhqLORrfju0OWCPLI0O^gsL3u{ZG zg~rD(u(2>PQ9C}tI_k$ri8V$E!Nmd0nsiC%0B?Og0N`;p62^%bO!jYO!`(xp^mP6+(cx3I%J}&U1KHT&Pg_XOaQSCPJJmP#*`1~wUHb&zcX~>l zSMhVYSL_?ae_&-};W8G63IXf18k)R)PpcAoi9^cBw`nH_j3u?ghTm0V5nj>5A8<5i z7n2=1xs7#c;`${6b5?+7)YTB8X}WeOkFuuZ8R(2$CAorJDF#pYT{CQ1Bw#P(d?Hi` zv!HJ}+_hSXA_3xT-F7Abu!8GKaAZz&A>9Eav`a4FoTEFH(Ygy!2RbA7>t_i{uad;$ zorQDmk+Px+CDiVO9a5hgR5|_uq(CDas>Ni9ae5`FM~SQ z*pfTT&Voz5Uvf}25uh@;qL@0ga2#a$_dHd(pqC0jfZGKn)0Kc&%6!uaIZ`44QU#n$ zTgxg2ae`bJ3K2~^004>POzngqg>8;WBa1-V8QyO5 z!MqL7*R+{G0VOUJfv9*yG5m#v0{BO@5xSRRxL7@YGJd1N1Uzdw@|A%(G@~{-TX{CIcNSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVq#*F<%yf~_Z!d!Or9=| zArY-_ryb;C3>0zTkGz?9_S%&93#JIoU-0LcPTKl6I#c`=mg@Xr%y!jG?7f?FbH`;L zHREN+D=b!j&nu7nKJVUc-al5I3tFtUcE{J8QTcG&KqXY>(=?_{DJLd%u5SpR$TfS$ z?$Y{4Yql`d^2msm>(%UQhPo!PC{xWt~$(69D_fuuA{{ literal 0 HcmV?d00001 diff --git a/src/main/resources/items/examplechestplate.png b/src/main/resources/items/examplechestplate.png index 19048bad6cbe6f97aed11a0153498555d716b371..23dc5457e38b6fd56ab32a6c7f006344a3202dc0 100644 GIT binary patch delta 33 pcmZ3-vW{g!G3POcI)?qTmiszStdZwrVq(%_+@Pgczi~+yBLKuQ3k3iG delta 33 ocmZ3-vW{g!G3S2L_t(oh3(RzZo*I)hT&(+JOTv?7l5mu z8-8vHS3$S}M6ZBw6(n|+pery~rVbfxDXkzo$@;41^y{DVz5Quv`R}~c4Ngrj zO$N`+Yt4RbGV~d|Jqm>8BO}n!sm!08!sCU(fpI5<4@0)K9x@ZTAg1~)d(n&Pc(QTJC( z=_mN^3&irJB^Vob=I`!te9~_suYum%NE`VLoSg7(BKYm{Aisg&>|B(q{U`DoczA%RDL6QE z7RB;~1-QEY=xHObfuL`B&|F?hegmb{E)Vh>xVU`u0e@~_-~|W+GhS=z!zTa$002ov JPDHLkV1hCCd~pB( diff --git a/src/main/resources/items/examplelog.png b/src/main/resources/items/examplelog.png new file mode 100644 index 0000000000000000000000000000000000000000..1740e636423bde8e19806c198d34bbd16813ffa4 GIT binary patch literal 446 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVq#*FHc(%w_8sT~CQlc~ zkcif|(++YTG7xFAKWZuBQpomkL3M?IW6Xi(?MeqVT_(ugaW3R4WbZCxuwD6fb>{44 z0S9ZAtLWYh#7<-RwEahXXDx5shjU!SJ3znHwjhVgN} zLVGfU)t2Pq`L+{$7F`g0v*+yfuWN5fUOy!=;kx0GSb2-7$_l+Jk|itSwlKw=Q}0QB zZC9P^|Vt@P?gxtI4{=xeimb9hW^0Vv)J>@orS^#Z|tqnFD=fz3cv)nfyI$ gxbzc?4Bv5KZYJlpnExr)fC0qd>FVdQ&MBb@0Ho)msQ>@~ literal 0 HcmV?d00001 diff --git a/src/main/resources/items/examplesapling.png b/src/main/resources/items/examplesapling.png new file mode 100644 index 0000000000000000000000000000000000000000..f94db9fa5a56c1346ed748ff18ed7b6c86ddd7d6 GIT binary patch literal 480 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVq#*Flu>urmWX zNJQ(~X@*^g97Nje#iJdQH{aNmUa(Q@jZTZM*hh}0*4Egrm)kZ3eBl#oYHb(DJ)*rv zAuR79W7_IM-!m#_));jCoUQWtneDS${d$fiRb{D%xaZfL*wLc7dG(zCFP=_2nlt4p z>nxKJMb0XrWi0QfH(Z+M{NQJkPeaSA827w$N=1MFam1X_xG+&^;krbjhqYd+amoA- z<~S@`et7fU;sZ>zmbe3-t*vm`Xn>&10!pT+`Qkq(#}Dp+Y3-b7_&^ zfgii$mMXFT3r|_2{!1rP`f7=v!bjPT%K<%_j~F*3W`{oB$XW9D+|PZ!-Sb{ZUoY@G zaEg_|vWP+C1oMZpd=E~>Fx1bzeae#Ywf!9{Q_g}%EHlo>owRHWSy{(uxY+H~v)^T) PaAojx^>bP0l+XkK*ZjO4 literal 0 HcmV?d00001 diff --git a/src/main/resources/items/exampletree.png b/src/main/resources/items/exampletree.png new file mode 100644 index 0000000000000000000000000000000000000000..46507e4656ce6f9c178cc5b2b01cf656ef41d0a6 GIT binary patch literal 606 zcmV-k0-^nhP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007 zbV*G`2kHU>0vtGIGdKbO00E*&L_t(oh2_>wOH@%D$MH`QxD(SLbs>X6NQ6wJub?1` z)-Boz?OOB>W-;raHxRh!1%x2tiQ_|%hH0X$+)S%8Gv_{Zy^EM5{Z{AU|Nr0f z``&Z!IsEUscG&b~2n6RZy#VKagH2meT*F+{KXw9|7Nfj7 z=0iv!iS!5r?b-)R9$?_GVb0^$qr#kz?X|x1ppaLlbP0rAJ!LW)A9mPjclzxed&oNu zb;^`3fn^VocQo8@wwi;sruXgDT%Acb!J!|8&qzz|fy6 zI{&BL&LzKUtwjB|?RQT6LjIj6dluBb5fzabR&ai0ZbYSDK~@(oz%5gMjobIF)&fRO zpz=M=Qp$C zg};ugyoCvqPk;uEN(US`05D~$J?pws^crBj-k5P}K4%U;k zux1TFo3=GrOkBv{iGT?oDmsl+PgnJLSTCf{C^E?c@>4^rsD7Vb@;D< u4S5Q=ei8mEAUWN#Eflb*|F0|Xk1w-iS95;F*bM*x00{s|MNUMnLSTZJ@2+Y9 delta 302 zcmV+}0nz@U1i%B3n|}cq03i#=iwN%k009I^L_t(oh3%Fx4uU`sMW4n3FJMLC4J5Xn ziPLx{5-Q^rAQXlRV4>0K7i`#tMH9#9@0gjLW#8XjfM3_bm&nN<04r86O>pA|z>(wN zT+5fbzd@tRJ8!ohMJ^NnV z=XpaDd;z-zq?#Ilgk(}YR`v-!lvI6-E$sR$z3=CCjK#eU748MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!ItFh z?!xdN1Q+aGJ{c&&S>O>_%)r1c48n{Iv*t(u1=&kHeO=jKF)=a8>na@X*J5B`lJseT~s`p~byeXE`=;&N z?k8Qi)qU30SkY>NK8&OuMQ((KTl8z<^7eLnKl zS=Qsz`uI%ii3|rz)fo54tTs3NemiFW!rso`Z+6{HpI?0;VdeEm6^DYOoDa70-z$t` z?lCFS+#Ukfk*egZ$kmE$jc$D0lgMXy`3JpoKTH z8%nOq-`%v9@s<)}(`i43^_CU0_W7mF^SJlooBy*1`nA4$u3h@_f8UF(SBpIx9$0I-*1-OTMU@tjJ-+Pt^7cR<8^pGNPV0(M8kt@Qg- z;+NM5DeQQ|vcd93-N#Ss&Y%AHef!o*#jW$ZHSU9?Kt`4{Jb1xy@FHW=?B^;U%zpk( kn%5Pu&;O?iEE4}z+rRv{O!^zAmn=xg)78&qol`;+0DV|G3jhEB literal 0 HcmV?d00001 diff --git a/src/main/resources/objects/examplesapling.png b/src/main/resources/objects/examplesapling.png new file mode 100644 index 0000000000000000000000000000000000000000..bfa5421729df8c604fa1b6bcd201d5f1ca0cc968 GIT binary patch literal 466 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVq#)a5IZU6V+3@8n5TskwLoM_R~dgKsi&Yi{rDelfzXAV6@l^YlCEeO-Ub@8J7 zq|C$HGoQUry=QK$-)g_#&+n~-QNexPhz&K7pE-Z#vw1r?nqOGHT)N0HUuoUJFH!>M zVrp~yCFgUVj7iTsYPG?>mdz&p$!QOt6XB){&k3)Zaz9U@maS!l8cXJC$5juA-WwJ-ES{Nt&{ewM$o14SVE-M~(tEA1ZM^Qhk+?AN%Qxzopr0N$Id A0RR91 literal 0 HcmV?d00001 diff --git a/src/main/resources/objects/exampletree.png b/src/main/resources/objects/exampletree.png new file mode 100644 index 0000000000000000000000000000000000000000..769e6eb089db27237c62792255080cf17cd4170f GIT binary patch literal 17702 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/particles/exampleleaves.png b/src/main/resources/particles/exampleleaves.png new file mode 100644 index 0000000000000000000000000000000000000000..796ca9816fa5f7cfedb59d4f17d652d277c65751 GIT binary patch literal 600 zcmV-e0;m0nP)z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;%JBegMCkr%giu000SaNLh0L01FcU01FcV0GgZ_00007 zbV*G`2kHU>0w^u8#4jEI00EpyL_t(oh3%HXN&-<7hQFc*ut16SW~*9+1ffvtprCC~ z6up4pdKPyb^a4>7v~AHgg1|Q7N>|Y%C=~bt(_%Qob!M(JS4~Ckw;8>j``>fV`8^5_ zhr{7;IA+7rtP_}bt~mGg_xC&v)NFaW=`0>Aqi#&=%%nM>>H~bx_}6oy_d4MSfJdGa z<3}#wqH^tB#@2eOYmAQ-(A4Q@XUgmX@Wy+l#G?C6n4B#6NPcUq0+1tTrIR&3DcTp` zL-$S9fl1xHQx9{e36S>+!IJ_F^=XVV7Vwl~AA%0s+B!+6L@``zs}38i1L-p+ia76r zvw`)t>HyHEPl;mas5B=^ev*eEMQhRm;Dy&j9V-Xw7axZmZ##UQwGlR&!yJ>9s#gh~ zRJc*M#<*AkrNeua0oYJK{3{0000 Date: Mon, 2 Feb 2026 03:14:06 +0000 Subject: [PATCH 15/21] Add examplesapling loot and update assets Added 'examplesapling' to the loot table and updated the English localization with 'examplechair'. Also updated the examplesound.ogg sound asset. --- .../examplemod/examples/ExampleLootTable.java | 1 + src/main/resources/locale/en.lang | 1 + src/main/resources/sound/examplesound.ogg | Bin 16094 -> 27010 bytes 3 files changed, 2 insertions(+) diff --git a/src/main/java/examplemod/examples/ExampleLootTable.java b/src/main/java/examplemod/examples/ExampleLootTable.java index ef87e87..e9de350 100644 --- a/src/main/java/examplemod/examples/ExampleLootTable.java +++ b/src/main/java/examplemod/examples/ExampleLootTable.java @@ -11,6 +11,7 @@ public class ExampleLootTable { new LootItem("examplebar", 20), new LootItem("examplepotion",1), new LootItem("examplefood",1), + new LootItem("examplesapling",1), new OneOfLootItems( new ChanceLootItem(0.60f, "examplesword"), new ChanceLootItem(0.60f,"examplestaff") diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index 39bfa46..fa195d4 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -10,6 +10,7 @@ examplesapling=Example Sapling exampletree=Example Tree examplewall=Example Wall exampledoor=Example Door +examplechair=Example Chair [item] exampleitem=Example Item diff --git a/src/main/resources/sound/examplesound.ogg b/src/main/resources/sound/examplesound.ogg index 40c9c2073366ddebe3c9b5e88342db158104326a..08ebdb8fb7f01d8ad30905c4278e7ea2b57b76dd 100644 GIT binary patch delta 23297 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 BcB=pY delta 12295 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 Date: Mon, 2 Feb 2026 03:39:32 +0000 Subject: [PATCH 16/21] Add detailed comments to example preset classes Expanded documentation and inline comments in ExampleLootTable, ExamplePreset, and ExamplePresetCode to clarify the structure, usage, and logic of presets and loot tables. These changes improve code readability and serve as a guide for developers learning how to create and use presets and loot tables in the mod. --- .../examplemod/Loaders/ExampleModRecipes.java | 1 - .../examplemod/examples/ExampleLootTable.java | 50 +++++-- .../examplemod/examples/ExamplePreset.java | 116 ++++++++++++++-- .../examples/ExamplePresetCode.java | 124 ++++++++++++++---- 4 files changed, 246 insertions(+), 45 deletions(-) diff --git a/src/main/java/examplemod/Loaders/ExampleModRecipes.java b/src/main/java/examplemod/Loaders/ExampleModRecipes.java index 84a1a44..b1e9662 100644 --- a/src/main/java/examplemod/Loaders/ExampleModRecipes.java +++ b/src/main/java/examplemod/Loaders/ExampleModRecipes.java @@ -167,6 +167,5 @@ public static void registerRecipes(){ } )); - } } diff --git a/src/main/java/examplemod/examples/ExampleLootTable.java b/src/main/java/examplemod/examples/ExampleLootTable.java index e9de350..8ad567a 100644 --- a/src/main/java/examplemod/examples/ExampleLootTable.java +++ b/src/main/java/examplemod/examples/ExampleLootTable.java @@ -5,18 +5,48 @@ import necesse.inventory.lootTable.lootItem.ChanceLootItem; import necesse.inventory.lootTable.lootItem.OneOfLootItems; +/** + * This loot table can be referenced from presets, object entities (like storage boxes), + * mobs, or any system that accepts a LootTable instance. + */ public class ExampleLootTable { + + /** + * A reusable LootTable instance. + * The LootTable constructor takes a list of "loot entries" which are rolled when loot is generated. + * Each entry can be: + * - guaranteed items (LootItem) + * - probabilistic items (ChanceLootItem) + * - groups like "pick one of these" (OneOfLootItems) + */ public static final LootTable exampleloottable = new LootTable( - new LootItem("exampleore", 8), - new LootItem("examplebar", 20), - new LootItem("examplepotion",1), - new LootItem("examplefood",1), - new LootItem("examplesapling",1), - new OneOfLootItems( - new ChanceLootItem(0.60f, "examplesword"), - new ChanceLootItem(0.60f,"examplestaff") - ) + + // Guaranteed drops: + // LootItem(String itemStringID, int amount) + // These are always added when the table is rolled. + new LootItem("exampleore", 8), + new LootItem("examplebar", 20), + new LootItem("examplepotion", 1), + new LootItem("examplefood", 1), + new LootItem("examplesapling", 1), + + // Group entry: OneOfLootItems will attempt to pick ONE option from the list below. + // In your case, the options are "chance-based" items. + new OneOfLootItems( + + // ChanceLootItem(float chance, String itemStringID) + // 0.60f = 60% chance for this item to be granted IF this option is selected. + // Because these are inside OneOfLootItems, the group will choose a single option, + // then that option rolls its chance. + new ChanceLootItem(0.60f, "examplesword"), + new ChanceLootItem(0.60f, "examplestaff") + ) ); - private ExampleLootTable() { } + /** + * Private constructor to prevent instantiation. + * This class is intended to be used statically: ExampleLootTable.exampleloottable + */ + private ExampleLootTable() { + } } \ No newline at end of file diff --git a/src/main/java/examplemod/examples/ExamplePreset.java b/src/main/java/examplemod/examples/ExamplePreset.java index 5b57cb3..708721b 100644 --- a/src/main/java/examplemod/examples/ExamplePreset.java +++ b/src/main/java/examplemod/examples/ExamplePreset.java @@ -3,25 +3,119 @@ import necesse.engine.util.GameRandom; import necesse.level.maps.presets.Preset; +/** + * ExamplePreset (Script-based) + * + * This preset is the same idea as your code-built room, but it is created using a big text string + * in Necesse's "PRESET script" format. + * + * Think of it like this: + * - The big string contains a saved layout (tiles + objects + rotations) + * - applyScript(...) reads that string and loads it into this Preset + * - Then we do extra steps (like filling a chest with loot) + */ public class ExamplePreset extends Preset { - // Pass a GameRandom so loot is randomized + + /** + * Constructor + * + * You pass in GameRandom so anything random (like loot) can be rolled properly. + * In world generation, Necesse often uses a seeded random so the same world seed + * produces the same results every time. + */ public ExamplePreset(GameRandom random) { - //width and height of our preset + + // Create a preset that is 11 tiles wide and 11 tiles tall. + // The Preset parent class uses this to create arrays for tiles/objects/rotations. super(11, 11); - /*string output of the preset from the game decoded from url safe base64 zlib compressed text - you don't actually need to decompress this but it makes showing whats going on easier */ - String examplePresetScript = "PRESET={width=11,height=11,tileIDs=[98, exampletile],tiles=[98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98],objectIDs=[0, air, 290, storagebox, 85, woodwall, 298, walltorch],objects=[85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 298, 0, 0, 0, 0, 0, 0, 0, 298, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 290, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 298, 0, 0, 0, 0, 0, 0, 0, 298, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85],rotations=[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2],tileObjectsClear=true,wallDecorObjectsClear=true,tableDecorObjectsClear=true,clearOtherWires=false}\n"; + /* + * This is a PRESET script string. + * + * It's basically a "saved blueprint" of a structure. + * The game can export these, and you can paste them into code like this. + * + * The important parts (high level): + * + * width / height + * - Size of the structure. + * + * tileIDs + tiles + * - "tileIDs" is a small list (palette) of tile types used in this preset. + * - "tiles" is the full grid. + * - Each number in "tiles" refers to an entry from tileIDs. + * + * objectIDs + objects + * - Same idea as tiles, but for objects (walls, torches, air, storagebox). + * - "objectIDs" is the palette. + * - "objects" is the full grid. + * + * rotations + * - Rotation for each placed object (same length/order as the objects grid). + * - Most objects use rotation 0/1/2/3 for directions. + * + * ...Clear flags... + * - These tell the game whether it should clear decorations/walls/etc when stamping the preset. + * + * The string is huge because it contains *every tile* in the 11x11 grid. + * 11 x 11 = 121 entries, which matches the long arrays you see. + */ + String examplePresetScript = + "PRESET={width=11,height=11," + + "tileIDs=[98, exampletile]," + + "tiles=[98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98]," + + "objectIDs=[0, air, 290, storagebox, 85, woodwall, 298, walltorch]," + + "objects=[85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 298, 0, 0, 0, 0, 0, 0, 0, 298, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 290, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 85, 298, 0, 0, 0, 0, 0, 0, 0, 298, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85]," + + "rotations=[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2]," + + "tileObjectsClear=true,wallDecorObjectsClear=true,tableDecorObjectsClear=true," + + "clearOtherWires=false}\n"; - //actually apply the preset from examplePresetScript onto the world + /* + * applyScript(...) reads that big PRESET string and fills in: + * - which tiles exist at each coordinate + * - which objects exist at each coordinate + * - which rotations the objects use + * + * After this line runs, this Preset now "contains" that room layout. + */ this.applyScript(examplePresetScript); - //Fill the storage box with loot from our example loot table - addInventory(ExampleLootTable.exampleloottable, random,5,5); + /* + * Add loot into the storage box inside the preset. + * + * IMPORTANT IDEA: + * Coordinates here are PRESET coordinates, not world coordinates. + * + * So (5, 5) means: + * - 5 tiles from the left edge of the preset + * - 5 tiles from the top edge of the preset + * + * We are assuming the storage box was placed at that coordinate in the script. + */ + addInventory(ExampleLootTable.exampleloottable, random, 5, 5); - // Optional: only allow placing if the whole area isn’t floor already (example rule) - addCanApplyRectEachPredicate(0, 0, width, height, 0, (level, levelX, levelY, dir) -> - !level.getTile(levelX, levelY).isFloor + /* + * Optional placement rule: + * + * addCanApplyRectEachPredicate checks a rectangle area and decides if the preset is allowed + * to be stamped there. + * + * This can prevent things like: + * - placing the room on top of an existing base + * - overwriting important tiles + * + * The lambda (level, levelX, levelY, dir) -> ... is a short way to write a function. + * + * Our rule says: + * "If the world tile is already a floor, do NOT allow the preset to be placed." + * + * The ! means "not". + * So: + * - if isFloor is true, !isFloor is false (so placement fails) + * - if isFloor is false, !isFloor is true (so placement is allowed) + */ + addCanApplyRectEachPredicate(0, 0, width, height, 0, + (level, levelX, levelY, dir) -> !level.getTile(levelX, levelY).isFloor ); } } diff --git a/src/main/java/examplemod/examples/ExamplePresetCode.java b/src/main/java/examplemod/examples/ExamplePresetCode.java index be9deff..e4c644b 100644 --- a/src/main/java/examplemod/examples/ExamplePresetCode.java +++ b/src/main/java/examplemod/examples/ExamplePresetCode.java @@ -5,45 +5,123 @@ import necesse.engine.util.GameRandom; import necesse.level.maps.presets.Preset; +/** + * ExamplePresetCode + * This class describes a small "structure" (a room) that the game can stamp into the world. + * In Necesse, a "Preset" is basically a small grid of tiles + objects that can be placed onto a level. + * This version builds the room using normal Java code (loops and variables), + * instead of using a big PRESET={...} text script. + */ public class ExamplePresetCode extends Preset { + + /** + * Constructor + * Constructors run when you create the object: new ExamplePresetCode(random) + * The GameRandom is passed in so things like loot can be randomized, + * but still be repeatable (important for world generation). + */ public ExamplePresetCode(GameRandom random) { - // Pass a GameRandom so loot is randomized + + // This calls the Preset parent class constructor. + // It sets the size of the preset to 15 tiles wide and 11 tiles tall. super(15, 11); - int floor = TileRegistry.getTileID("stonefloor"); - int wall = ObjectRegistry.getObjectID("stonewall"); - int air = ObjectRegistry.getObjectID("air"); - int storagebox = ObjectRegistry.getObjectID("storagebox"); // replace with what you want + /* + * Tiles and Objects in Necesse use numeric IDs internally. + * + * - TileRegistry.getTileID("name") looks up a TILE by its string ID + * - ObjectRegistry.getObjectID("name") looks up an OBJECT by its string ID + * + * We store those numbers in variables so we can use them repeatedly. + */ + int floor = TileRegistry.getTileID("stonefloor"); // ground tile + int wall = ObjectRegistry.getObjectID("stonewall"); // wall object + int air = ObjectRegistry.getObjectID("air"); // "nothing here" object + int storagebox = ObjectRegistry.getObjectID("storagebox"); // chest/container object - // Fill background - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - setTile(x, y, floor); - setObject(x, y, air); + /* + * Fill the entire preset area with a base: + * + * - Every tile becomes stone floor + * - Every object becomes air (empty) + * + * width and height are fields from the Preset parent class (because we called super(15, 11)). + */ + for (int x = 0; x < width; x++) { // loop across columns (left -> right) + for (int y = 0; y < height; y++) { // loop across rows (top -> bottom) + setTile(x, y, floor); // place the floor tile at (x, y) + setObject(x, y, air); // clear any object at (x, y) } } - // Simple rectangle “room” + /* + * Build the walls around the edge of the preset. + * + * First: top wall (y = 0) and bottom wall (y = height - 1) + */ for (int x = 0; x < width; x++) { - setObject(x, 0, wall); - setObject(x, height - 1, wall); + setObject(x, 0, wall); // top edge + setObject(x, height - 1, wall); // bottom edge } + + /* + * Next: left wall (x = 0) and right wall (x = width - 1) + */ for (int y = 0; y < height; y++) { - setObject(0, y, wall); - setObject(width - 1, y, wall); + setObject(0, y, wall); // left edge + setObject(width - 1, y, wall); // right edge } - // Put a storagebox + /* + * Choose a position in the middle of the room for the storage box. + * + * width / 2 and height / 2 are integer division in Java. + * Example: 15 / 2 becomes 7 (Java drops the .5) + */ int storageboxX = width / 2; int storageboxY = height / 2; - setObject(storageboxX,storageboxY, storagebox,1); - //Fill the storage box with loot from our example loot table - addInventory(ExampleLootTable.exampleloottable, random,storageboxX,storageboxY); + /* + * Place the storage box object at the center. + * + * setObject(x, y, objectID, rotation) + * + * Some objects use rotation to decide which way they face. + * 0/1/2/3 usually mean different directions. + * For a storage box it usually doesn't matter much, but we set it anyway. + */ + setObject(storageboxX, storageboxY, storagebox, 1); + + /* + * Fill the storage box with loot. + * + * ExampleLootTable.exampleloottable is your custom LootTable from the other class. + * + * addInventory(...) searches for an object with an inventory at that position + * (like a storagebox) and then generates loot into it. + */ + addInventory(ExampleLootTable.exampleloottable, random, storageboxX, storageboxY); - // Optional: only allow placing if the whole area isn’t floor already (example rule) - addCanApplyRectEachPredicate(0, 0, width, height, 0, (level, levelX, levelY, dir) -> - !level.getTile(levelX, levelY).isFloor + /* + * OPTIONAL SAFETY RULE (CanApply predicate): + * + * This says: "Only allow this preset to be placed if the area is suitable." + * + * addCanApplyRectEachPredicate(...) checks every tile in a rectangle. + * If ANY tile fails the test, the preset cannot be applied there. + * + * Our test: + * !level.getTile(levelX, levelY).isFloor + * + * Meaning: + * - If the tile already IS a floor, then this returns false (bad) + * - If the tile is NOT a floor, then this returns true (good) + * + * In plain English: + * "Don't place this room on top of an area that already has flooring." + */ + addCanApplyRectEachPredicate(0, 0, width, height, 0, + (level, levelX, levelY, dir) -> !level.getTile(levelX, levelY).isFloor ); } -} \ No newline at end of file +} From 6498fcabb7da1ffc85ff20028de5578d2ba15a49 Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Mon, 2 Feb 2026 21:34:09 +0000 Subject: [PATCH 17/21] Add pre-antialias textures Gradle task Add a new Gradle task (preAntialiasTextures) to run PreAntialiasTextures via Necesse.jar for processing all resource texture folders. The task is grouped under "necesse" and accepts the project resource directories as a semicolon-separated argument. Numerous PNG assets were updated as a result of running the pre-antialiasing process. --- build.gradle | 10 ++++++++++ src/main/resources/items/examplebar.png | Bin 657 -> 784 bytes src/main/resources/items/examplebaserock.png | Bin 789 -> 1025 bytes src/main/resources/items/exampleboots.png | Bin 402 -> 406 bytes src/main/resources/items/examplechair.png | Bin 446 -> 423 bytes .../resources/items/examplechestplate.png | Bin 558 -> 648 bytes src/main/resources/items/exampledoor.png | Bin 445 -> 427 bytes src/main/resources/items/examplefood.png | Bin 354 -> 431 bytes src/main/resources/items/examplehelmet.png | Bin 485 -> 534 bytes .../items/examplehuntincursionmaterial.png | Bin 317 -> 284 bytes .../items/exampleincursiontablet.png | Bin 332 -> 463 bytes src/main/resources/items/examplelog.png | Bin 446 -> 477 bytes src/main/resources/items/exampleore.png | Bin 494 -> 549 bytes src/main/resources/items/exampleorerock.png | Bin 5617 -> 1025 bytes src/main/resources/items/examplepotion.png | Bin 469 -> 599 bytes src/main/resources/items/examplesapling.png | Bin 480 -> 514 bytes src/main/resources/items/examplestaff.png | Bin 395 -> 451 bytes src/main/resources/items/examplestone.png | Bin 467 -> 520 bytes src/main/resources/items/examplesword.png | Bin 389 -> 464 bytes src/main/resources/items/exampletree.png | Bin 606 -> 716 bytes src/main/resources/items/examplewall.png | Bin 545 -> 560 bytes src/main/resources/mobs/examplebossmob.png | Bin 4070 -> 10700 bytes .../resources/mobs/icons/examplebossmob.png | Bin 402 -> 449 bytes src/main/resources/mobs/icons/examplemob.png | Bin 398 -> 441 bytes .../resources/objects/examplebaserock.png | Bin 1078 -> 2183 bytes src/main/resources/objects/examplechair.png | Bin 725 -> 1048 bytes src/main/resources/objects/exampleore.png | Bin 8435 -> 2088 bytes src/main/resources/objects/examplesapling.png | Bin 466 -> 547 bytes src/main/resources/objects/exampletree.png | Bin 17702 -> 30999 bytes src/main/resources/objects/examplewall.png | Bin 2796 -> 5257 bytes .../resources/particles/exampleleaves.png | Bin 600 -> 946 bytes .../player/armor/examplearms_left.png | Bin 1491 -> 7333 bytes .../player/armor/examplearms_right.png | Bin 1513 -> 7328 bytes .../resources/player/armor/exampleboots.png | Bin 2600 -> 9571 bytes .../resources/player/armor/examplechest.png | Bin 2152 -> 8431 bytes .../resources/player/armor/examplehelmet.png | Bin 2833 -> 7592 bytes 36 files changed, 10 insertions(+) diff --git a/build.gradle b/build.gradle index 6104eeb..aa88ff0 100644 --- a/build.gradle +++ b/build.gradle @@ -103,6 +103,16 @@ task createModInfoFile(type: JavaExec) { // Makes compiling also create mod info file classes.dependsOn("createModInfoFile") +task preAntialiasTextures(type: JavaExec) { + group "necesse" + description "Runs pre-antialiasing on all textures in the resources folder" + + classpath = files(gameDirectory + "/Necesse.jar") + + main "PreAntialiasTextures" + args "-folders", "${sourceSets.main.resources.srcDirs.stream().map {file -> file.path}.toArray().join(";")}" +} + task runClient(type: JavaExec) { group "necesse" description "Run client with current mod" diff --git a/src/main/resources/items/examplebar.png b/src/main/resources/items/examplebar.png index 92d03dff9b0e1f276bbb9d2de2188d6dffcf84eb..f23f5a87b38e921058fca9a3c717a528ce4a27c2 100644 GIT binary patch delta 761 zcmVVfd5ax3Q23dv&43DuoKv_nS@$QrRC~m+OB9w%{f1u?I3mGPXE#d1I zL9r~bFbdsaSVM#)2cY?!q}T(67EC{KNRSf(czyn<@D12uuz!$&xiX$14;CUQAwiS_ zDDgRZFrkM8DIoy&`A^Yb3~Z&mV6%Vx{t2c(^}JPg<>}>M9@P5 zuLE%Te38{&hJVNYp?c1Wfq{vEks*192Lsa&;>$N;LI5MqnU63r{QLV4BN6@wdi@&1 zZLmXNl3EOEV1Fihh`{8rI)KQg86n|(jr~=af69c->I~k^c%@b!3 zWqir-8g4e&0WhCy$!IWKdvb$l*L&SJWq6MkQcOSb=Kzr9xJttRAj@GsKT&uLt3xdM ztr!@O0KNa0f#C+%eFh#0cCb1{V8sLqfpzb1Od{KPPmqU!jb zII!3ihkuxPiQy>&C{~2;F){#c!m#KP!!?Gh|8Eg?0J_g_Fx)}a1oY5z3}!Mc(J?WgrcRLM*nAGthtmP* zK1a{TFbjhiyufCm$2lRN6LbIrg@BOHu{nSd=yGZXV5&uF$;@TwM{x>z$iT|@WFq4n z-Cy7+fW;B4JfOt$#QGdx6u?svF(E>(&#^iHJ++b&BBc79lthR(M2Jo8xT2M$egQN@ rFq+%=@;RaOL{34($UsFf;N5co)hztjDNsxU00000NkvXXu0mjfV5(MN delta 633 zcmV-<0*3vN29X7jBYyw^b5ch_0olnce*gdg1ZP1_K>z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;&yra{vkl71T8V z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHS777sKhmw!xz0005iNkl zze^lJ6o9`s9*P=8FrtMP!Jk|&=vml`g+@yo3;Vzp`6C2jL2NA|VyR*whhQfXI|)R@ z6AzLI7AK-u+y!nY#X0J%Yi@7P!bM-T%6CKojvX@R+>N_Lruwtv30>C8D7&R|^`B zII;SZ^aYwoL|OGpY8P+cvl~;O5a!ZjsC;;_i75ZPW>4W2y(jN#G)H7mkHi{szmd=j z_+LyWM`>JrpcO6rD*Uc!jzrXK2e{q2ra`p;kRb~|y?>N)z>u%A4Zr|BFK=MC^AI}i zwvme-R9>PRNYbkTHr%zq0XI$#J*flhN3@Wn7lnF4KF?C%K2Vi`@$4X1e=hBb@^C6DH|Yh3DI| z3-FWs>)3p7nsKW?Ecxp_4NWzeSjG)yrc(0w+y}}TiAo;dJE7l5RPtA2;645U_TrY% TFCNJL00000NkvXXu0mjfq0|bt diff --git a/src/main/resources/items/examplebaserock.png b/src/main/resources/items/examplebaserock.png index fe2a36aeb8e0dcdf80ff52c67d3678a2f07894fd..358c39d54c1b35850e6be7d1fc9c599739823995 100644 GIT binary patch delta 1004 zcmVqb)F-S3>n~P#6a_~Td9L2{Q7&_qm{|pcaG5{iotQ6u=bngZ+ zRG|x@L_0VHU@lZ)kiqaVD+3Dy2(vISGk`FJ4H9Dpi-Qs|1b_brTJ)b0=tLBZZXn1G zn0*Y;9Ke9=^KA@$?;t+>2iFVrIU~ByG5wDcxC}6_Gcqv!hq1jGN`#=Qz(Pz6SipY< zQY=SA2Cmc!G!I)s1Q!7q4j{*J1_qGT=&9mA1G0l~ISy+GK!O?^@OXm#Kit~?NRh>W z17HaS>>)sk<$uU_VfQWJP{0xe#Q7XzIU?v8NDl>EY2ZJel8cx^5w~_mV6}rDLl_|d z$}$W%0lMK}Pk>c3VvryKMx?CB_#eSTD8}LdgzeZ3WnjSO445cF4zJ~KOR(kwVnPAt zcQSmA623SbKqwRtK~IX$fgWQZm4(e8IfEBVpbzGFhNVbe+>UIk{u}M(4z%=nr1}R z-N3+r5I_$BM(nO-!Wkkk^|<1kkpX`N266z*QnYye&w!~JyF&85qn7B@-aw1BkueMcLd%LU_{O@@aXu5mN*Id7{eJTApnwP zW`LB2zd?5FV3+_c0TFSIKO;fBiRoL22)ejCLjkNr1lju^PY59Vj~Nn(uqD=FEU^Lh z7k`EWP%T4s1Or}x-Ny_Jm?b8XG1x-@(-8~=0V5U_psE~CM#RVktUxo^VCFC~5C<3; zu(gtz{xbusa2%GyOJC5~1m2PYW;i|?*_j|8~q+_2*_+^Bu^lF2vln^ z>|~gbPADw!1+@!9-V6pv?NF5E~^2>b=|{y_0>bjui_tsz`AAs;g! zs^0(24D(Se2ie7lWsC^!20I1=20NgOK^RvkU`qoqwFeo_Gl1|Rh6`XJY{PYo12{$r ab`$_%a0;l&B}DiD0000z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;%JBegMCkr%giu z000SaNLh0L02U0x231Vt>0(00076NklPu15QSfPk{p0DAtAvom&h(ch!v4YBqYQlin8Pw*?{0ubAiAVi*Cnd*DJSsT3Lmf zMZ5h|-*efn_5gt3CELjC^*I@7qw~$i)KxQnz&9-o)30>0U4=S4$EUWcdOulGcsh59 z;E(ttaJA!R)qf}e{KVaUdO4T|g+isp`1#ka<*Af3m*3&zD?5blfpzfm9?f2HU`s6k z#0fYCZt+@;hU?i{cnU_~8S4cxe=Z6<0&C#)7*<}60$GAJaJKTpQHb_{b~4w6F>GVRG8|8BtKd&YH-F(s6a>-y$Kf3T3|9kS5TkG* zxG)byQLyN(@C5;F*`wQdNR`jpWSA2u_IV0}Sk9R8S62;4b{?i8E?I9rE}t|v)%G2CX~ejXSVVZj5L&D|^VDah}+d;?M_9RL6T diff --git a/src/main/resources/items/exampleboots.png b/src/main/resources/items/exampleboots.png index 240c79a295fb9d94c0041af88b5d47a7453a6cca..4f01a02cd361ee078906a93b5d5a4a8838b46b38 100644 GIT binary patch delta 380 zcmV-?0fYXM1C|4jBYy#1NklCWI1|V!r~t0Lqd)-XD~%c3rJ}Pqa0zOhtJU+j2sfM5TJ_BVHTl>6f9T3K@o5C-etHCmS<;RWBkeR3q>tZi~%S{k~u&F7|E&&K$a7z z4T;V~C=P)65SMOjK1Wl_2nq?HIYf6hP@;ecXEPGnA|loS@F-xUa=}Bid81vk(T>T0 ahX4RuL~9#t)3P1_0000z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;%JBegMCkr%giu z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHU>0x2o3vVYn}0002iNklTmVNRJ}#s4hS(y0IuBB7(-qT$Zq!6Qr13J*^FsxJ)lkx z0000xgatcM)_X z(YfG0!$TCqgcyVfRU$-{lr+l)KNx;8=rC%6F}`3RIt0Eke1n@ayKXvGGiNu=gNp;x z3M_>1hQ!b*2e=rxFno>dV{)^^&M21cXAm;*@x7RhTM+pH~jY&#+ z7<#!tgFzkc!GEB-KormRHccQoGYohL;O!THic+ALz$Ka-gDgqDd&KY0BYyw^b5ch_0olnce*gdg1ZP1_K>z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^ z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHU>0v!xqoPYiH00033Nkl@1W|Af+t)0c%AmVU68v*|^h8CNrkTcsQ%~ zZg*byp5D6+{5pxCggUj0Uwq6Y@Y^sXRUGn`0tfw(LCnDrMgZDNLD#nw0t&u(6$W-|YG3^sRxjE@4~% zmaHC{(8oZU^j*x|aa8m%;78kRdG=9^=wrbB3WT9G^6YaF{ulHyAg{on=){_4!;{mo zx=$AnyMe%YEfcLk{;7la9Dr|}ge%s-eP?3IeMiEej~)T(TveVByMaGftuHaUZ7fAF P00000NkvXXu0mjf|HrO= diff --git a/src/main/resources/items/examplechestplate.png b/src/main/resources/items/examplechestplate.png index 23dc5457e38b6fd56ab32a6c7f006344a3202dc0..e12990509b00069cce688fc8cb818fb33ce792c8 100644 GIT binary patch delta 624 zcmV-$0+0Q!1c(KYBYy%fGi3$l62jGfh zsLx4?Es!2u<{^g+wh+JwVq~8KeTdf~un>*%zvda3^EK)fRL8i5@ZL= zK2S&?hroZ3<^2pjVEYd<90Ako88(9Ha}4LfG)%sbp#;U9F!6~DlfmMQ3=oe7GWdh} zwhT648mJE?jW96`Fu+IyFrUL*4D9gB*&_VdkRy zcfdmcTo7R^Xn$aF2y-#aM@9^WU?Et@z~s@zVIcvN|IYx4dh~({B?P`Pe4}}aK(_-W z7yM%Q#h}0-4|c=@hKFDp#)tV3mNsCtX|*XzT0r*!x;a4pAkPIbpTc|s^8vazsXmA4 zhnWX6e^8YJjGzhxR))jMbeN0LQ$0f~YHY*uIj-CRtA9g~a{)!840zu1PZOy6X&JJ9-SkLWof6$D0VLnHzAWes~JS*Ehu1Az@=+V+7TnAcKHx zU_JT^r8P}#2ZfZL5qfi-5iO)}%HzvOr1Xpk^>i4?$*}|8JqG~yM(5?X&znvF0000< KMNUMnLSTaR9SdIo delta 534 zcmV+x0_pvT1+D~;BYyw^b5ch_0olnce*gdg1ZP1_K>z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;%JBegMCkr%giu z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHU>0xAKpDt{q=0004UNkl($m%ZGV>e$k{>W3Um}_-6@Y854J5kI}Ij)+T!}3bRQy_K5 zVt&o-!hj`(=6}ZV%EVDX%rChB;DI3kH{1d6%nRl5GtNyQ`L7rNkWCBhgd+g<>1v(H zkvIwnVy+#F`ItALU^wjE12F23j9>HGY??TV>#=eLW@=Trkhvi-+F=_glo7mNN(lM1 zDB@Sa7Ht4t8B?~!P-m)dr`G|BT=4L5a5rTf1q3k_#DBp1>oV7R`Gq(Nl(N&jdfKti zdivH>ou{fpZ%DW*NWR&siU#WdeDVd%o~a<_Qp~SQ^#^ek5Trztrt!DfF!nni#-1<% zsym~VE0F97eGjNJPQ3+(92mcBDtg4Y3Z(7@pY0x1b?*^Z0l_OV$4_}&%KV!Df`5Di Y$4ra-tPdvzFaQ7m07*qoM6N<$f|iHhssI20 diff --git a/src/main/resources/items/exampledoor.png b/src/main/resources/items/exampledoor.png index 715846a3bb280b62ecd1f2a72a699f4724f5d133..911d60c2630ab6b9abbfbe3a8bff8e28f8e54062 100644 GIT binary patch delta 402 zcmV;D0d4-h1FHj&BYy#MNklt#+wacKIdiNW%$hSnIs1Q^)LeUfZc#EB!~%t{~&{$8Jxj}@iXw_ z_1H&-k6`(|40~Y?#v2k?9f0O@ib@Jl=)ugx783YE0LAB;41bzLIYf#<3Pl~EkQn^g z5a#n03@b>AiU@`XSX#iVZ}5izy3gM-yk!Wj^TX<^eN9K<;>3p7;17X3hCHyZCNWF` z)4~kGa9@Ia3ZVN$20#c> z_?Y1_Sp7GKZ(y34ff=ixu!aE00n`owTxJq%4`A~-wz!vNkOli5=5t~_Fm!SOAs>z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^ z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHU>0v;3ixqqX;00032NklD!Z7>40z%RB-F2^WB?pBsK|30Fb50z|KXa1|tTcyNT(fpI2|}V^#XrxpydaZjzEDC9{7L& O0000O8umGCKEdL5@Z zEJWBqX(D07X9w0q2=lpsAUDH@_g^V;0MHCZpc!B{U<(O+A@CohF(|@{qG(vR;uy>W zSVIEE0cbv_YW@I)Cd_=~kiZrKC_X=P>=s21frTKhkQlt#5P#-#6Du`}LV!>R4(@^o zzF08pM4L=zhl zLnj0fX+ae1^Y*?@ln_{5QU@+mj6~%ALqh^w@)@OwFyI42Cj?-r9zE_ECm~UX^!2WkQ{fANx4E?$Q-JhhE z^@P&FpiBddY-}tHj~>0DN|y;}4mc2iW-|Wz^#>zB@U@D;4v>=*p{S^VIRIz~BdM)o zs)PhCpTm5Gw{r?@#Zufcp|oej$Us9d;N5co`$vE`nGgP(qyPW_07*qoM6N<$f+d%% AumAu6 delta 328 zcmZ3_{D^6SaygNCr>`qKgrSP-hU6MiRY2o!;Jk2A`A>qCVD@3 TdSs?F0}yz+`njxgN@xNA0~CW0 diff --git a/src/main/resources/items/examplehelmet.png b/src/main/resources/items/examplehelmet.png index 820f9fc893bca82a238af5a21239501649a2ea2f..69341c63f8e971958b3f208f64294d5f3e287015 100644 GIT binary patch delta 509 zcmV6Xe5MR^EJ!|-Aq?hV zydi=kiZ%RPz_)o5bJYvMGoT!5VV{TTeP+7Al}89x~m`6rv6 zfcdz5zKLNIOg&0&81O{MLTWI>d}y`F3PtNNhGS&K*x=8GptwVdKcam}tiHjY209oz zz`m|vs6g?(6@LS28bA*vm_9;jWAKLn%m?TpfgU2T;33xMF!ci-0@zAI253iCc>5fA)OI&LIPwtOg}zXkdg~fLIUPfbi;}D zIZPi>ybC{VdWF#_11}Xvo2mGOl#bd-U00000NkvXXu0mjf24mCO delta 460 zcmV;-0Wz@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^ z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHSG0RbVfsDFQ+0003gNklV4e5Jg`ij)5qW3(zInLu>~Xxd%t!9!UkAZ;%uy>9_z?+=Ddv685q?<4DnLk-weg z8O?tlkJb>ZJMYG4o-Q?sKlyUyzOPC87c#4q^Br3Nj+_SfKk@*;f+b*+7%zcZ=cT+P z!U_BrFtIkg@_z=v3oF2X3lwK5oIs~D)%`8)%>a>@w$p6=oX$YM^F#u=w;`N>*#o8I zut`m}zGwfd0>TNH1*D&^R^GU90%i|v*Z{C*ovk09 zcH+S5E0ibz`5c5Ln3$;^0w6PC<|7-9k_(_g5B7NtpCDBo(emj%%sdo32WKM0=5?44 z&_f2vXDHf;4Z*=50_Z+O4+)r0afJ|}gf#d=fZDkMJw%3H8hN00hzy-_09XEpi4&5? zl^cdnNr+w^5L=N9-82C6A+C}TR~<3*$^n>*36=HamJb7-21Z*%44|em48!6Z+9(CJ z4XK8a8@kALV(pqCH%_V8GG!#SUqGAQ37U^aJEsF)4gdfGym$y%S!rVc0000G(&67Iy3=9k`>5jgR3=A9lx&I`x0{IHb9znhg3{`3j3=J&| z48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!ItFh?gFHN;HUF$kZGI+9+AZi4BWyX z%*Zfnjs#GUy~NYkmHibX8ohI2xS e^wuu-gBlDBmt@UWma)tRd%)Ax&t;ucLK6UGpJ0Lj diff --git a/src/main/resources/items/exampleincursiontablet.png b/src/main/resources/items/exampleincursiontablet.png index 979cfa6905c409c44a3350e17f2979c45a6f3de8..7f5c3ceae01adc20775436e44ff6199215992732 100644 GIT binary patch delta 438 zcmV;n0ZIPM0?z}GBYy#wNklA13dHs?s2LSal0`-F3fG;HQg}{H10qI#KU_;)&c|}xo2#Sb-)y*=ydgj+F45BhdWaC47Dm&+ z;0*y-9OFvugwg=6@&Y~A4E{8L9ukD|Ke0X^dL<#LJ|nh-L`ehRxCLldH{voIr8fM= zBf#)hLILcE$A3Sm2FRCQOM z;MF&X%YkQKAufOa9ztJ*dPGnVRUDnqfToT>xDCn>U}RusW?*>#9=$_?D$dLdY45|- zf%t#_g8U272MalbIM%Sh+B0Hw3_=)$&<5*Zpm#WM@h@RI8UOEJhchDZ^^9=!Xwbz8 g@$sgD0q>pz0J43L1q=K31^@s607*qoM6N<$f=@}sfdBvi delta 306 zcmV-20nPr;1Iz-DBYyw^b5ch_0Itp)=>Px$21!IgR9J=WmOT!FKoCYhjin8QXf$GC z?IpZ|r!cYh7}h49!Yg~#A8%uQ5o zpdnq5Do>KQFy3{&!gvjP4QNosuVSwOZ7+E*a3iL@V6Op<@9zQHW2sWD2c+)>$6f;j zH=*slY2}3d28d@XGo^9^_y6e8xYQX?Wxe{a^8kju{Jh;X%Xm2D=fUn_xWxvdb2g@F zHU_tK$H}krIV)J4LgfbJGdq79sEcV`oAdhwDmU=w3P)gEcc+5wfB*mh07*qoM6N<$ Ef}?SX^Z)<= diff --git a/src/main/resources/items/examplelog.png b/src/main/resources/items/examplelog.png index 1740e636423bde8e19806c198d34bbd16813ffa4..c0f130b5aaa90e3a9aea7ddc164fa03ff59c1c54 100644 GIT binary patch delta 452 zcmV;#0XzP_1Kk6VBYy#;NklQUY za9YDcgEWYqtr+pyK~XkD_c1pEH^WzkuP6=xiZKGkz;3`664VF*xP>5#tQjm(JbaYl zI7}XENRZ?J96ly07KIsv7-0I5LxP+T!0Tg}Yg8DNz)Tpukbhw*){wyK09-zvUpEV@ zuje$)Cpk?M69Po}7-Tcdw;=WbhQmZT2CD;LJ_p6yTZVTy4MO%YvgM=%1HKUW53)R- zAsWRzU|ce!FeEY%>*LQ1pYgf}#R0_n9A=_Joju%e2L^kFhNczU<4IF zu(BIlIe=yeBLf8h#R0HXimPRV%N~3!9zt%zmk3cp0zGCBFmxF470;vw2HqACG$b&Z uSd{pfm^8pZ%{V3$BBNci(XQEmhX4SUa)a9GP&SbO00000BYyw^b5ch_0olnce*gdg1ZP1_K>z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^ z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHU>0vj+VsedN-00033Nkl32JxVKco0Ecz=O9U!6`(L9^6B53ULREb^tx8?W<{ppvCOcR^x9EXI( z1mt1l4L@Ustkl}LMnZsee&Zf{E)$VR$5#SC-v_J`n5sD~nl>fuJE6|;i>$lAN`UMK z3$-+^VVyvfD?z5?QUn~-@!eW&(W*`B15r#JOMd@0G#>rKF{SbZ91h153j#xiT>oU& P00000NkvXXu0mjf@Xn$V diff --git a/src/main/resources/items/exampleore.png b/src/main/resources/items/exampleore.png index 62c0e6de60bc79adc39782324f14fcbbc4959a50..d9433bd8755c3dafd0dba3224728c6582f422b2c 100644 GIT binary patch delta 525 zcmV+o0`mRt1EmCzBYy$xNklpg% zoa$L9NQ1;?A4Yt3;LV1(e9F(j!|;*e3sDXLs$~SK1-k)TNDvbO7#4vH^k%RH8>+e4 z3abaN`yK;}Z)G?P(~mVIusQ(db5RCChSv=5sZvsaLJejva(_r*jRL4bu*24rVaS=jsd! zV2!sK5NQCUhJR2lQ1Hd?G?XYncO4;LffD4FxhpZ0AtypYX5cIb5OK~x7J!A!2H$0P z^$*R;2<9c94YF|G!BRa);QNLL44j)4hzhyE8v?LYk1s8tmWqhn0m`1RxQCguhv6jF z5E+yqzzC{@rZTKVsS8Lc2|?up%;!LJz&;0>$%w6DLx0JJQ~s|4E6!mI!z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;&yra{vkl71T8V z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHS701hjwFMqG{0003pNkl zziPrz6vlu4S$u^;A)ELFg3u?Z7hJ>#a8&5zQ*`Q(v1=i?1WY$KLFf}maR~%`1;@Db zq)i%ck{iK@`KCME`z7Z)zk3p>RqNi>Fg^InOPFWhf;a6mn%7RCZCdFwPXP2*gVb|7 zwUuS4OQKm>27jdT8500}4gh$gAiM9=$-g8%*`zX2JqFHI{*C{$qm^m8Q(G(^VvN|z zF@sR2x!aFk@L~ywVY2cr765yK*xsyLV5ySVP$zE`;JrIq2#x2!Z_mvn$++r++n$6v z4c*6REz|=XU7ya%%H>hr1cZ7W3-PHK#IC3GUVPSk-A+gXHp|A}ub524B9qYP5LIsD zBz?<4DBzuIjXbzC1IZ-vN-9qAsU`!3{|RS|Lwy3vLY`MopjNFD2>M+`mqb)F-S3>n~P#6a_~Td9L2{Q7&_qm{|pcaG5{iotQ6u=bngZ+ zRG|x@L_0VHU@lZ)kiqaVD+3Dy2(vISGk`FJ4H9Dpi-Qs|1b_brTJ)b0=tLBZZXn1G zn0*Y;9Ke9=^KA@$?;t+>2iFVrIU~ByG5wDcxC}6_Gcqv!hq1jGN`#=Qz(Pz6SipY< zQY=SA2Cmc!G!I)s1Q!7q4j{*J1_qGT=&9mA1G0l~ISy+GK!O?^@OXm#Kit~?NRh>W z17HaS>>)sk<$uU_VfQWJP{0xe#Q7XzIU?v8NDl>EY2ZJel8cx^5w~_mV6}rDLl_|d z$}$W%0lMK}Pk>c3VvryKMx?CB_#eSTD8}LdgzeZ3WnjSO445cF4zJ~KOR(kwVnPAt zcQSmA623SbKqwRtK~IX$fgWQZm4(e8IfEBVpbzGFhNVbe+>UIk{u}M(4z%=nr1}R z-N3+r5I_$BM(nO-!Wkkk^|<1kkpX`N266z*QnYye&w!~JyF&85qn7B@-aw1BkueMcLd%LU_{O@@aXu5mN*Id7{eJTApnwP zW`LB2zd?5FV3+_c0TFSIKO;fBiRoL22)ejCLjkNr1lju^PY59Vj~Nn(uqD=FEU^Lh z7k`EWP%T4s1Or}x-Ny_Jm?b8XG1x-@(-8~=0V5U_psE~CM#RVktUxo^VCFC~5C<3; zu(gtz{xbusa2%GyOJC5~1m2PYW;i|?*_j|8~q+_2*_+^Bu^lF2vln^ z>|~gbPADw!1+@!9-V6pv?NF5E~^2>b=|{y_0>bjui_tsz`AAs;g! zs^0(24D(Se2ie7lWsC^!20I1=20NgOK^RvkU`qoqwFeo_Gl1|Rh6`XJY{PYo12{$r ab`$_%a0;l&B}DiD0000Gh=q-fu>qGXD}lhqLORrfju0OWCPLI0O^gsL3u{ZG zg~rD(u(2>PQ9C}tI_k$ri8V$E!Nmd0nsiC%0B?Og0N`;p62^%bO!jYO!`(xp^mP6+(cx3I%J}&U1KHT&Pg_XOaQSCPJJmP#*`1~wUHb&zcX~>l zSMhVYSL_?ae_&-};W8G63IXf18k)R)PpcAoi9^cBw`nH_j3u?ghTm0V5nj>5A8<5i z7n2=1xs7#c;`${6b5?+7)YTB8X}WeOkFuuZ8R(2$CAorJDF#pYT{CQ1Bw#P(d?Hi` zv!HJ}+_hSXA_3xT-F7Abu!8GKaAZz&A>9Eav`a4FoTEFH(Ygy!2RbA7>t_i{uad;$ zorQDmk+Px+CDiVO9a5hgR5|_uq(CDas>Ni9ae5`FM~SQ z*pfTT&Voz5Uvf}25uh@;qL@0ga2#a$_dHd(pqC0jfZGKn)0Kc&%6!uaIZ`44QU#n$ zTgxg2ae`bJ3K2~^004>POzngqg>8;WBa1-V8QyO5 z!MqL7*R+{G0VOUJfv9*yG5m#v0{BO@5xSRRxL7@YGJd1N1Uzdw@|A%(G@~{-TX{CIcJ&%1J?wQBYy%ONkl+16~JI zSIj~&Ohi$Q;m42PcrDVWQx7pA08eah?muGiDTNqz)@cEXp$_{J!2B)U zvq;JvSRDZK0l&Bi!^f9z7$^ZCGhpUn3kiH7@E>G3%*TwcAkIMdIm~hApdsSB0un;7 zw1E^NSaSyx!+!t+f@uKm^N0mRIrS`5o$-nUqST`}fSPfSZac2DfGq@2;{Lt%Ca_sb z3_@VK<4+G>pH;AwfaTviLiG7ug`^2wX<_iD0a)BKFg%3%SRTdaFrVTM4v0R4IoMSW z{#<}?_d^sfpr-{yh&*H<1q|L0fR$tnu<{G7gs)%;MSt@scAvw|VHiSbfDu$^!1Ar{ zzFcU&o{W|TR%7)NEMx@tL-cL#hE(Z5Ga0c}Y$yd0thPe0&l#uyK$eqQ8-lHc#Uac` z6^{5_15-yR?qQ}3>T&=jEx=sta}^S|=p`J?$FMkusmB%fFnO%iF*VZyuDHkR0JKPx$k4Z#9R9J=Wm$6F2P!xv0YDWhZad1#~ zN5Q#qaB2kyK}4`FI=P5q@ByTvxH#!lK}x})V4d{|Dh~Dmf`j6s4^W81?Ww&tY0?zo z2J~N&d+&b__y5nyNdkWznL#)1t*`jqwpjbnL9^ki-_NP*#R0_=wCyMLso*xn?6xJO` m{9j;xBbta)Y{I$&|M&(x8foIp@+k!X0000M)A4A;{MtOn+`jVhaIAkby8C!h8e^ z2^ftY66oq+4w7P!!0R>?2f$J_v8kCF*%4nFz!?(eb!Kq8^BA(>?2}EW7|?wT3`wv& zFjN__B~YvmphZZ4EXP)Y5|a%X#TmpH4lx`B=Wi7TWw?(gHBDuB$M7D^7h({^8Y8%T z4pWTR0WcTmFn^|#9o@KmPLu=CY-Rk+@R?y1!`lDm43IRCSC@^Vq`0Y^L5M+!k(Yso zXa^Hj5>EL)6KtRrqd7r?-vJ|K%m3}vbpS5YCN@oB5C-}gB!8aaB3aQixEds+dLh3q z2d*WCA&LQ%21qqyz(atfRxvaYg3AM5241jd1sMdu^kO51$1u+`9{Yci=ztj7X#nI+ zB%hC*T)@cAzz*^)dfShz)-qXLGj~Q8xC21NGc@c;N_?>Rq9hSA{$}{i$ilz^@;TXk fh0)IGfR_URm)3w=9uB_S00000NkvXXu0mjf)j!Ng delta 455 zcmV;&0XY7G1mFXZBYyw^b5ch_0olnce*gdg1ZP1_K>z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^ z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHU>0vQ}9M}H`m0003bNklwsge?~F2!e%$U5e?puu$|47J`L`5N*UOtRPly;Q?l= zaZSu5%&ahq`I{v3<~`P3!i+*xP&RQtDOJn<(9>4l+^{AG;|>edJ?7t z@0Wnmo;G96Br^B<|bKu>t zMDAUQ^;O4e_Bz)%Cy?j@b@C#f1bCx-j*Y~GBR>Fm6k_vra!$bbyI!Rt2mV)NtS9;` zSsT@KP9X6eh|^GvDdGXJVQW?6u?ckj&iTGgi=OBk*FbPiz{&*xIdT9L$OG`r4&cdL x0Dqm^$~gh+KioPs32@>Bn9p9xIe}EEegQF~M#|>SgWX}cqJIM7o(SqV3ndM}Qnw<5 z0!gM*k}I%e3=d!Y z#aa*SpDYIUIe)n!fi)YVyPTHg0t*TG4WqK z3oHy&2Des7f=vYG3Zlvfnm5Tn(S)yjpr}rVMFGf?TaW)>O$)#@g13C2C<>@iZcsA~ zFe2v)SjkB!iBZJ?l!OFTdq%J{furQaY8O?rAvqyI^&@fsDb9&%D~@(f2fT9%0HJ7n UZV@rL=Kufz07*qoM6N<$f(?$Sg#Z8m delta 369 zcmX@i+|4{ext@WsILO_J@#aaLdIkmtmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweEpc6VX;z);M<#V&fq6ez-3;1OBO zz`!jG!i)^F=12eq*-JcqUD;nTiilftY!awnc^D{m-qXb~B;s&#f&}a0hKdjJT`Rd} zu-yvparrgpq0V7JHV{Z=lMedA_fH@}G3=pi+iso=#gFr)6b)B6GpHWDpSt7x0U$`& zv4}}Yz|g0Nky~&6`jm){N5!X4US4qN%nqln!W&bWH8KVOAC^%DVy2YvC72l4nPLm#950cbua zDb^$z#2H}vv4sRtQ2;dn>?1-xhq+FRK^@F&Wax$oF=BH76Mw@10~Tu85A!k1JYX6i z%4n<(K+oqK4D1X)7=F?utnh^ZJez?mhxw2#Kmw~n?l63Wi=W?ckZ8xEIDnGWPHymE z3jw0?IX(-oG2A3A1jzOI(haj1U{OQJQc&rMtvo;u0g`|~4S+%dglN(0ejx@{niW_0N9J1 delta 442 zcmeBRxy(F4xt@WsILO_J@#aaLdIkmtmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweEpc6VX;z);M<#V&fq6ez-3;1OBO zz`!jG!i)^F=12eq*-JcqUD;nTiZPm*%qp(m@qVvcqr6O&?}iCY>k0Y^EISpK$5_krtNA6Fiw!e#~hE0>^I0xgdJs#D&uNvm~lA z8z%H8CYT;pYg;&%`@(0-GwnHyLJ?Q*C;s3_pMSq#LIyj}J^=;Ot;x*SCGsw*3+ml9 zsOOe5T@ulCmGQ|BpcVY{Zk^PX)Z}8^#=zN@(PqN)FRfIKVD61u%RRX diff --git a/src/main/resources/items/examplesword.png b/src/main/resources/items/examplesword.png index 5aeae299e0a0ce1d833a519d0ffb95b299f8530e..5e8aa5e825360cf43af0134fedf97d08fdf123aa 100644 GIT binary patch delta 439 zcmV;o0Z9IZ1JDDIBYy#xNklWXE*2~{ z;z|^Zw1_RDqd=2EodFa=ghGi_N3!73u##aRSW1OK8B7~9*uX-Dsv$vgBBXXm;0%Gg z4EIq2=Pbh!nuG+|*^pWxfl?B}5||W&B*Qa?=ioSRV`u>TTz{EC5j{=7LWrW$6eS8^ z`J9xL4hsp8Yse`lu)3a}A%Qg!k`odPWC9i<^f7|cL={5=N}6Dx62R*KQbIzJL4kp+ zx&&kiAq?{@C?tUF|Hv3vV*UqVBr%xs|7dC%u-QXYIRMiL%$fuP9uyMVimb#(5HTSD zQ-f|Q$TFBO(SJkY)(Xl0AdDO`*z96qU?9!0$f~JPGm_*098P5fg+w?*JUEx^pDYGe z_Vo2%qI^b`CfPrU#|-2GYJ|Z5hcEsz%&imxyW!rmKVVu(iiLh5K#WbWasoZwP*jsa z4a3{9fVHfEX$0m@6gvmJS}0008174GD9Y~26=002ovPDHLkV1f#xw*~+J delta 363 zcmcb>+{!#bxt@WsILO_J@#aaLdIkmtmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweEpc6VX;z);M<#V&fq6ez-3;1OBO zz`!jG!i)^F=12eq*-JcqUD;nTiileYE9TV)<^jb{db&7x{|kYH7oHAoWt?cWsD z#dvuEZ{t)RZb=Cs2$7fqrh6oFGK7Sf<(PDKsJt_}@1-LP_!xJ!9GSquxJOa&oPd3i6GL_^qqgD3mLv5^Z=@zEHl%ES*U)-o zt>`2k5Xcfd-(JtLpykNFlSvv)eLL5)m2+$eIQ_g>yA|XJiFdZY4<7)61u934YECmI s-JZ~Ls`KG=?hf9C2Su3{Cx|dGOiI+`yLPg~2k0vXPgg&ebxsLQ0PfUtfB*mh diff --git a/src/main/resources/items/exampletree.png b/src/main/resources/items/exampletree.png index 46507e4656ce6f9c178cc5b2b01cf656ef41d0a6..772af699ccda22cf4af322283752b483c3cad0fd 100644 GIT binary patch delta 693 zcmV;m0!sbf1k44HBYy&uNkli;AFu=^i7Jm}>Lf}8haxVsVtUi}w zkU(+MJ%;;W{so3hU|Neo1596IxPdn$P#gersStx8F!tUvP!sS2qYPU}U<-l&?-<@Q zL@|Vc?f=a11x(*$xQ*f-1qL}3J}ki*FzBJ^Q)W;C^EWeW!x|Dy3I4QLJXz=rVp1t2X6?#QZ=wZL2*2?4=*%bfXm%yc!&~G zFbSABLoWnO8H};|2sus->I~o>Ghi@anA0>L!=s=q4HA68@Dghv4Bj+wm*E~(7Yi^5 zz{Roo_yfZS22#MF3;{+^1%gzDgFPg{Adca&l)6L|v45VXes~d%9_K(a8GkVRz%T=) zAcECahh1! zrWgZ}fPa^PhXJPl?*IEZbrIDj1X=FG;0ZPa76O+TuAmsI$Djk|OEE}-=`9S~$!`-< z;&XJbfej0>+jYu|@$*A~i$UY^-y<~OIp&^4FHH`Rt bipx;|_;u3o0tj){00000NkvXXu0mjfmZB_W delta 582 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 diff --git a/src/main/resources/mobs/icons/examplebossmob.png b/src/main/resources/mobs/icons/examplebossmob.png index 471e69df3ef160406c29442b51d0cb79c29b023d..068bd480a50a1ed098746980dba2e0ed7e5b025f 100644 GIT binary patch delta 424 zcmV;Z0ayN#1Hl83BYy#iNkl@aYpt1_Sjl0`-8+hPfQw#s5JH7J7P;P-3)%V2Yr%Eur=WBO!VV2&SKfK*TYU8?gYl(VDLo STC$4(0000z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;&yra{vkl71T8V z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHS801`O{U4P_)0002iNklEq(rw{N3JrmCvq)r_v5{w_ZH;RC8H)J5nn zhq)9(J>6Z54sl>EO2r|L4-AZp7nqu2%~>$5BpFY7X_=hk365&!-Sn)wq7c-*Q*6?(Sr!2scp8wD%Rp0`I+85Z`6Et@*%>V#^ W9A6E7F)!u-0000K9KZ$-#}WnJ|aIUCx5$QU*!@$hR=_ z80BL;KxTn$M0e>!hHrT7k!RpSkq5dQ#%E+=pb@~DDhRn0=6@ELI&_zVQ$Hix+uS9W-NoF|Ss6|(gte8G7zH;moRu`hX9OhD7>gks%(47l&5xR@`^^z9M#UHlUm#-Q>TR8OJQ zvc#lQkjv2337KpEd3W+E1J!dLuc|BqsGS1BAQyw`yZ>Br5)8nIWaO5Y0NcpH&BXw8 z5#DA1Pz~dcuh{EFjFu2A@}RXXq4otMdJ71qpM^kDV;LkjDFOg}*^X(NVz@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;&yra{vkl71T8V z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHS802Lep7=NY$0002eNklUmow2cjB&a2A;UMkn&la| zP+cCI2@iAngfX!$MOTOJa{8qTVqHp%59#CLGt*b1NSb7ZGp8R z4wwc1$jNyymp)vwgB2?EpQbKmM6a!hNvAl}{h?2thqF#2(7wRdo}jslX$AmtF;d2; Sjb{!30000G-8 diff --git a/src/main/resources/objects/examplebaserock.png b/src/main/resources/objects/examplebaserock.png index c7ddf9211df20af773b34bea5e628b36aa17e8f3..1dd1765b135b6e6df45cb8dc77e83841787f94d9 100644 GIT binary patch literal 2183 zcmZve3se(V8pkJ-*F;E20+ca4LwKkQIEC<1AYdSgS{{0|Ygvjz1q@WXzNJ!SA}xZB z5Q+yVwh}jptf*C+aztbqe5}y5l^8%2aG^d*m4_|}R)pCb+1;MqZO%#N%e^z-z2A5L zzyFz{qy#m8o@5>bLHy;K*wx@lfNuxF0l&A-Wko;`lCV5BW=&4&hSxN$xVivslu~lZ zFS4a9l|6J-3#0xlR8>!yW3gpQf_=0l_&s@+*yRqL;@h5*-MGOp95>}B-QBUM?|EZB zJCRIk%B3`!r~cIG%FI}NX?M|1?wO{H1*6eftU&M~Fs==^YbB)r0vq2tUr=_kcBy{E zyL;cB>FV;qVz=(0=R}|Y+J+=C!`K09QZ!w$!aRjrrrQtax#`ns2N&+-e%|bLH!+d6 z1ooL_qDVJRT&S>|>CoeVYWdN-3>7TqX2n$qeygCBIjea2g3#Ryx9g9<1w_~Rp`C&8 z!|KKD&w3OdZ!-cIs%-)s9fTTLXlXcJ<4c&24~UMzDtk%`<0xAe#!{^$%wpUGq3u#C zP%@M6!;-`VFpBLv^R<%V*bvX@9Me2>Fx;9!?+}&@w<-;c?@pcES}7;Xp?rCl`NII# z-RU-Oim0K?V#p0<28wMGpmQe%JF=En-Dn?LJ<(b=tajGPE+dk!_%4Gyd`HL@6ARs3 zaQLAR-OL1OjNX(+plg*oVYk(u>YrfM-#lL(pzuWu)ho?I;$>cv7f~7|4%g4RkFd!} zSoPt3^H#zv$39TvU~^|+)i<=(5u>Eo?I|BY4Sir{6km6*;LHWrO-!RtbL}HZ83?$K3C$_m`5Nw-MWgXuHW<55eel&Ux~5VZoojuH9b-f15buGW@g62^L!S0x~pjornmx?f;8q zePT%igOFl-HndSb`Q~v3s$%pji46HoZag9nd$t7gI_On=%rWA&*!y%mOn!1H$F-3! zzXy67H(<~}#$2X!cgPrRs(#!qnt9x5Zr0_WWW8(|7=bzJD0S6E(0ovU$D$&{qP)T_@7PkXam z86HbJZv2?`NpdiupJ83t&^b%no@;dS+4Cqi0tk{vQlr8;uWVMila0o~=8E=V_B=JL|#`2Fl--$BxX*%_bkGlq4 z%oLhmtV*jb9culZ)-iTiAJUg1eld8^PUW)Jvr#$Rxy!w$UwPJvxLnI`C>J`#ugJ>Z z6cD^(;il1=Ybn`HcIDagb>S0q(y3C7e@tQj(*qv`oXBMr`HPD9xnMeTY$%LE{#Yt# z1h8^r`rn*d&Ou2$WaQzdB-eK7Ty+2?P$*C&&@&M1T1q?V4gj%G01z;miI5EppjP9@ zdH)vfe>99EWC#pEHO|iign*kt2=x7HSo0aBU5uNYu&Sr(Tq_LM01S@*hsX{OEue)0 zY_p0%!k}4z92fv0*u}4*#zqIg014%HN&wRqs*tanQ|0%w)_qH?U|Xm*Tb((?%A9-* zU|}r;gnVli7777#ssU@x2POZTer_d`5S_D(?K9Su_+*dJnie?yumYf6Yh4kH-h5+3 zAm{k(T>2g%F75v5`P75wi{7uhD6EgT6qWL%?vWtnvG77g>ZsQpI(jPDbDFPBhzwpm z0-pJ_VjmezhmzfpEcA`1b}A$=kQ%yh^O3_o)yG|7Ln=j^H7>x2YIPzBY&=Yfj!mfT zff1wrGwN-~oDa^a6x`tqTblq5ZAM)W)d->Q57}2u`}$Ia!Mi(Wb2xhntd<8#m^);v zT|w*Zk^0@*;7Hq`itb6hbD6A*Hf|=Y-TOsxkAucD!9SeFG7OQBYh|+eiBA#!0)BmtY67%V2$=ZX(Tr91x-%Vrb1&K2|5vjDyA0p|PjD-V00000 literal 1078 zcmeAS@N?(olHy`uVBq!ia0vp^4M2Q>gAGWY-;ikoq!^2X+?^P2p46!aa#+$GeH|GX zHuiJ>Nn{1`6_P!Id>I(3)PNdW7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXICJ|AhslL zcNc~a48;sw?4nmpfg+p*9+AZi4BWyX%*Zfnjs#GUy~NYkmHic?2q%-whHrIs3=GUw zo-U3d6}R5r^_?`^K*Tk1VuiBp>)4GVkC$#(J+D$#(}+?0Ez6nt6DEJ3+Zmi1?O!6h zu4RI!7tk<e9x@4IGO2ydR(=)Bwb zYqtWUio+XY1uLNj1_cHV2Brg63|vL8*H2n!x$c;oc7r)*!+tdfLFSJOUdFFizdDiO z$2*(VJni zg8&0qUVz~mBbVUcyd|=Z8nYS7SZ6aU6fv3nT^)N+h(S>Jz&r;|1}3N@;M9-*70drJ zxW0b=Z@!fZ!#z93S^u_PscTsDwY{Zd8jFwvlg$SC1v>Ni8#Zw^Y%=*f^Zic-15Or3 zfn2@^+Z!c6-gb~+a$q=fhH=$LiHU`(Sy^w^o|E9d`adq7iDmh$nWkmz>IZH`mS6t< zc-Q84xp(e$MHXLtY`=OR3n>qvy*`0K z=)rw2h8kb(yYG+b~~7y$x~BY5x-;5C>(+4QCP>)v^_`~PN$*XF+}cK@23`~Td# z=hwC!zon^e$zwL9n0XwQA9ZVQsRqy>d@3)Ni?a=>s zegT8Mfq~<6s*T%{E%o|$M4Wf;eEmjJSy^sBivddmg8{n%+i{yZ&IAs)F#=uob&^wF zSCwS$zW93n-g63!O~sP)YznUktA|Of>JUxloWXOGumBfyuGf0H53%7p00i_>zopr0M-|bP5=M^ diff --git a/src/main/resources/objects/examplechair.png b/src/main/resources/objects/examplechair.png index faf4597172461331cdacdee44f263b8b04885188..24218ef5434f53c6d0f52eacced22b111561ab8d 100644 GIT binary patch delta 1027 zcmV+e1pNEe1(*nsBYy+mNklXgRz%&~9BRd2}69B_#0vIg>7)Gmr z(L^vpLtwND7_96z7)Gmr(L^v3LtwNGFxmzf&H1BkfYCI-FxmzfO#~w{ z1XxDno%j$4VDu$Q@rM7KhfJ`JCIHf-iqP~a<50>}gL*ocP=Ai2S5AYugPDPu;V;8q zya@oPh7qU+oCfIaK3oY8pF4&^34klW>7CR5gCbUlK@c3y!cZC(=x-U`gPHFb-oxBW zclY5)0C?RoIu5`vvcZsP1Hf|LJ7`WDsAz-b{QC?KQJkR2AP*}61~f&H8cw5)Ks?c< z!>9?Cm1K});C}`}QeyTi!&e5F{wx2l587g8GyxEfn*Yxko->#+n1WTp>Uur~J`|lW zaaaz#!*FMy+5#jcHj)y+aO?*&jJ5$t4uJvb2gB;#focPMV)!&9k_xHeL{tfY)(^%T z5TL#?O#M(A5gVxm0V!3$fcAsm4sh!qmOx;2KYEFPOMjf+35(Ql8r0(eUl_jN3_pBw zBP};m5>6!d0_fQf26^lS!wYbHdNR0y=~E1+!88v8&w#`;Ea#*50tT{;KuI{^O#lPk z4<0Q6h)d(NE&&EbKNx-FjgHx$k%NJq;ReG^aPLQhK^<&0t{e|k2bKrwVf?}HlkTMy zso^v_5PyUxieMvnu#%9J*aSuXU>*U4`Gx2NfIj=hFv7uLY6DOb6+^ZJ-~o=!dojA< z_4~H}yM{$FCFD;~SbPD_J&@yBq7%RX*1=f)CBz^!AS06B7`}ns1EcAf!f^T*7#0jP zPXI$LD}m}@N@wF>`q>y*8GbSRCMgI&dZ=Z;7=MEp1E?WNi#QlERRDU9lVFg*8y>*% z0&301{Rc(99D^*_K0@}w9QBCdG1csU#qbI~8;Q%)K>NrlnMPLxF^p_5WJ&-o2Ce~( zDPnUzOdTvo(%aFbhSO*XFv3fK(F8D>02oHW2o8ZE+Y6xg$mBqegc55%spAu)RlsO3 zV1H>#i;#|kx*)f|7Zy?!b^bB1TdNa7)HSe4uR1U zV6+4nEdWMx2v9N?up2Gls^}b$qxPc)IDA>*KUxKh@B)yS5`dAHffu+t{xdkJK!lvI6-E$sR$z3=CCj3=9n| z3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG?e4kz*hIH*ONp z@9piH$~fOyQQ?Q3UBi224zYcW*PL1!R%{gyx{>v`F-76#Oy4<^zy7KBE!cchvVCU0 zUMCCCdivGOLK~K8U?9iSYC+aVKKJwLB*5lLq_)P1G3%Uwt8A<@HDvhk~P=54Q5(D~x0AF)7mA9s<^ps_yX4?#8?5 z5}`eo>(}+3{hO)evpX&tEGN32F{}Q4bGww+q+eE3Rqnp5TXgZ$kmE$jc$D0lgMXy`3Jpp7@P8%nOq-`%v9@s<)}(`i43 z^_CU0_W7mF^SJlooBy*1`nA4$u3h@_f8UF(SBpIx9$0vggFIdWECu|917+}GA~(u z`#oDQ{tD`2r2A%!?MBhM%~9x z>&~D4_*W7F*CDj&>#{!W_L6|m3$rwS}0|5V$* W{J2c|8>g2nNXXOG&t;ucLK6U7;W(K9 diff --git a/src/main/resources/objects/exampleore.png b/src/main/resources/objects/exampleore.png index 7250791c2d032e46477da3f5cb6c07a35e7af563..d56047fa6e5ca345bb83c97e3526015b9c0d2d85 100644 GIT binary patch delta 2076 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 N07*qoLz@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^ z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHU>0w5O27Jp1K0003NNklYyd2Zf$PF5XLbw-T{wh?}`!+`yj)Ta8aTS{PLB00000NkvXXu0mjf$3d!D diff --git a/src/main/resources/objects/exampletree.png b/src/main/resources/objects/exampletree.png index 769e6eb089db27237c62792255080cf17cd4170f..31c3476491424f5e83458c2f0026c4bad7b3d737 100644 GIT binary patch literal 30999 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 diff --git a/src/main/resources/objects/examplewall.png b/src/main/resources/objects/examplewall.png index 8e1b1337199d9a4c252184757dfea649a69cdda9..a77d7c6cec4a1491dd97ddbfbf423ea04cd4044d 100644 GIT binary patch literal 5257 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 jlHAEn`&1MG_FZU$B$1hKKHBV-Ot z56pcUMrsTxb%_{8y>5C17sDHXP*t!z&xmFcyet3(o+*PdSb8_ZUN8;v2m^jVY?p@- zhcPhm|G$8#fPaU9hXLdnP#_Xe&p;&jf8*RO1{)u1uyRRM_HpqK#}j?X0Ym;tE`X7mSZ5o8c#_z#5GVs}o{d=z8R%NUR(tjq(YykBTp2$z}c z4D1ZBl!YrL!rUjxAi?mR;RlL4MC-)Bd{G85hQm!qaDRFoX8;0Y_CF|4o?LqhPS&uP zftiGytO>;M|Mv{<8O#|>;mR&HU1E>|IvOMZDqleKLWZSad3-4ulvP0~52P@jAsWS6 zkeMKXPYjrSF~aaB!LouY{2pa zJ%*8OLx1)-10Db?OWC326uMIVIz6}$dPHDL$uPyZ$~urdvYE&!246}9rFL9q+Sb{C zt+i#aVd!b+W#9de2o@>Yz(j%hNTz{1~s@QWJiJw1xW}q2r<58c!w=G|NjJ5 z`9Go6KC(HWK!cSnuo%W;4!YTlK(!3O6bnvaC4UTsV0q+-K(-5CN(2Sq7huqU41nbT zh9*?&^o(>Ft?VrrjvYLK&5sNi0my&{AbT993>0|SVg{c%pz;M&wt&=}Y=UGFV$6Zr z11r0b?SPe8ATdJ43wmnaY-J!LFuyYZQwBJOK`8_l%g7#oJ^Kyg zy?^KT@!1WpO=THmAStE^Q@OaL7~{+5uNcSxj6l7h?1i^>2UcK=pb89&`S3ClQgr=d zz*j`zav#*NGZr7HB+B0e%wr@q1;Z>Q#+^X580JG$AUpsm>Dl1xiGWfN zPK>Kez!Rdl%wnJr;B_Yv?xD!~(II^bV-^np!Sfk<{2DA#00000NkvXXu0mjf9Sw-{ delta 576 zcmV-G0>AyT2iOFVBYyw^b5ch_0olnce*gdg1ZP1_K>z@;j|==^1poj5AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUy24YJ`L;%JBegMCkr%giu z000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2kHU>0w^u8#D6ay0004;Nkl!6@*P!zp@;CdE!9rOZG6tr#8HiE!5;!0Q1BPbO3 z0@GqR!*yn^GgnPT?zb7ep8MZ(&-pzH4u`|xa5!ee(ySAhcdj`1_4oHY4b*ITy6G$) zE2C~q>&&D%pnvKEe9-vUbE5Y;;Rt|7o)hCoF5sea?On#!da7%Tj}_3=>1b!l>;mw{ zd#1#q`%RdfEcr-&Ypep0BWI|EAGbV~S?}D>|^|tB&(0`{-iDKxeG$%@al7}EgYtjPX zh1WzKD+lTqABP=pJA9qB5jL8`9Fvu*R|%d}xKX#pxL5(D!+Vqg*kUs=Nf9-5MrPFy zzcHPj>V~r=t;12YI3J)+Ez}hBv(lU>`GwyAWjrqtD+lTqABVhGAWtx3nyNLno@fe_ za&;Dr)j5^f08sa8vCsF_q1CUPuBVgLdA7P~`jh%!naP2}W`M)tm?eJ?c!^`>JK{3{ O0000r!QCK&`>orY%$iBnjbF zy&=Z>s^s5oLrN+1;6+NvN{WYL0A1Zy=|l%O$ZZvqr{m|13+b!Nwr8SW(K z<~!%!|NQ6szW@IuAx=zp^>T$Eh`vA)lLSEoBmOU>QSgyDI}RU$AfL4hVj>r3r!Kbv zATR|YbX7BRbUC|c^g#1tE4F8n>kBLfAt{eDTjY zoD*$lBERD={9rUrZY-kV#ZFm0XEfyaQWL_O*_|@4ksWf*rkh4|#Hnh`M!gg*ts?rw zTE|2ZUur^_W79}uF#c##l++QYU&h3C1r1xGG)z&9-2mvU?6fm#2?1mta>?SAX$D7{ z^=#*>#rQi@q^X&yUr)0&rTLg;Lg$a+Zy`ZOwDcuZJ#eT)lmC6$%DaFXsddYqOc2D z7#BSW!QHH2Q8FkT5kua#cCdDN zvZfI0w+74z*@#q^Tn%iK*-QFudM;$R$=S(74&M~a%~f)1T(HYv19JC;0XCi|wB3E4 z=qr>}fLxrsJsm=T)b3gQ8f==5bt{T*aW|5sl19PRlaX+f2WkrR3nPniY|GHynx58> z7PF!isMnASZ-1oZR=dEjw$TO^)w}Y!d^_t3s|~q}c8~`8)4HH7_m&h>&_6;n-?2n+AeY%rGTv4syU?*?eb5wzkPyw26g?9Gg(?_>{-V&EXW$0Zj(Ci|1+8xPaH^zv4`0syFZlg z&ZO53CB1F*yf?ebdGsMx8Ri$s6qVyNFCD`G4vv_J6uHm2^x5T72W?MC!43f&(!P;X zv5sG5OY-z1&7n=n%l z<{1)BjS`vnr^Tpo1~WK>q9@VL=w^2+ZtS$sRue*hL?xj;s7$piUt(ux6kRXXC{|IZ z6zOS0l?|5+wnibPVS2Vj5(_Fb<2(W5;5w#$CxfQW*~mRQ1-{)}fL7l3)Cu=RurV$4AAvf>Re)y_#nT%3E?I#pGdOb8+7zG>OV_~{U? zh^o)-z-uu9D_>L2LtB5dy?=#9v4OiNl|;frO1sxKrm~|APi$78ld~4MVw>PKH9HR zb>9T~c~jo6`pSFMJJAVCi=0xCv1a5@L`x^h`ICf=7l!AKa&nIHS!Tg!TjHfG%|M+u z(F}n^qq+Lj_u9vTCD3jyT#X+A~rrQgNR@+S1^IE~u%tdzfF)F_ve_}1_vnspJGHr%iM_M(t z`*>(&uziJf?%}gd4%qy-Aov0IW3sedtHd6?w?&o8m8uU?`R$~aQodEq7F_7KCEp8t zG{Eg@-zucHPjFi?my)AZ(i_>Gl6!>U`=~rLj}XPCu;ri7($o##mlaO?uwg(Msl(>o zdb2fYhg%iH}3L(b+~+L{N~46(=5#JS$a&7^Ogm__4TmLpK?d{3(-S*gH%!jt6>wT zSdh$(+8nv9k7fF(^1H5U?mqvB@Y`KGh-9I66m zqc5Wk2Y4}Sk;6utSFtEL8{59GQ}R3E33W+6%!a?=M)+d=Y)b_=y5XO)Bs+bxYo#*S z`_9UBl-S}!@a3a9oHqA#<%zAui{d4!$bDS|lgUlc5u9Y^c=Sz!ufwk)sby`MsI!?v z`m3Gs6a=caW z;+<7^=WB01^__gvrU$nq8bxxswtx6l7%a*`8)y29o!-SK;Wv=Vzdd9}M)+H22PACS zTB7pi^Q>7hJXevKGh>f9CCsTlKs5(YV_hT_<(zX7Uj~5<*o|3!lmr60&CKZ@(CvA_ zsW%|VB)kdgGCcwfG&O4WE`CGrfg+^bD9pHzIU*>H;7nYnvuY4PxXBO0O@6_sfk5bl zoMDr{?*GT+kGK6#vmZ3xJ~sNJVdStqr~hk@_BrJ}P0E{8LvnoHJTDiYmy6GEM;L-G Xzq=^ejV;T9@V^D~<6^#vk{0|MRS&^+ literal 1491 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 literal 2600 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 literal 2152 zcmbtUeNJou6YX^Q1 zd?ameBESM~t4hSjkg}o;ro!;-y1CfbE2hrf-D+@PE?JVc-phhQBJLln8>D_~d43}ffTk{&?W2+9mEwi+ zxGSUZ)5niaKdbezO!LVKu@$CH!HND4rs!E%za@?kwB_$|A3H)tY%#-fM#E^Ht$nM| zokfj}Fj%;9mGeQKKl(xg;zgNA=nCG?nYr$;o8@;hy^FoHr?=h`OP&85nw0Q0@3{*T z=DjE?K?*kw=J_o@5Z(V;U#qBJz!!st7sZI6N2 zPrm7$cBJyLm7e-W#H(oIQZ|5h4=p5Uvf?KOn{6@dEyIeo7F$QVBhuAHvz;#gx6vcB z!RE6J2bKmmKQi3Eh;3^_W&WzM;viCfN|6vCtdM>Q<%w8ZG+DSqO-X)?t*nYMV-}rU z7j0)?sihJFX_@A)k5m?ct)}@XBfj9`J@~^mlz!Y-FR9j6smM{geuP>}mF91b`H(~G zJBg^0wAPacoLN)`Tebi(E$76~Y@O|>Pn^eKKl$W*_tnJ$b<9Z)5p;!K8S@3vCN_$+ zzT`0pZThpL+q;k2>A%37;%owhH-~zed0f@lDu|)9-y2}WPatj#Rpu=);-))1BFvBKf3V8~W$lp7rNXjLCR{&r$AIRq*otyrszd zq343zVNK68((&TrEOuWLbY^4#QVsVNs!_zEs1skQ?hvoji_NX?9z3zqb9a%cn+pc{ zlo*+1wS`x|pN+c#LZ;C=v7)-j)E|fJYepcc=DbuQ~0#f=UN59)8sTuvFiXN zbXGw!*FGrLoejk)A?@zkcgBy{Ydy*yQ#jB><=}y2q<1^3a>XhmWy|h`qar96gnWxVW zu>b%7 diff --git a/src/main/resources/player/armor/examplehelmet.png b/src/main/resources/player/armor/examplehelmet.png index 687a019636f5c54c2b18f81ca1356666383610da..bded8d4006e5bc1d8820dcfc7d530fb56e12ecaf 100644 GIT binary patch literal 7592 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 AiU0rr literal 2833 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 Date: Tue, 3 Feb 2026 18:16:43 +0000 Subject: [PATCH 18/21] Add example grass biome, tile, object & seed Introduce a new example grass system and reorganize map/incursion packages: - Add ExampleBiome (examples.maps.biomes) and new ExampleModBiomes loader; ExampleMod now loads biomes. - Implement overworld biome generation, spawn tables, generator stack and region placement logic. - Add ExampleGrassTile (terrain splatter tile) with growth/spread, loot and rendering logic, plus splat texture. - Add ExampleGrassObject (drops ExampleGrassSeed) and ExampleGrassSeedItem (places examplegrasstile). - Register new tile/object/item in ExampleModTiles, ExampleModObjects and ExampleModItems. - Add resource assets: item and object textures and tile splat PNGs. - Update locale/en.lang with names for new tile, object and seed. - Move incursion/map classes into examples.maps.incursion and rename ExampleTile package to examples.tiles; remove old incursion ExampleBiome file. These changes add a complete example grass biome + gameplay loop (tile growth, objects, and seed drops) and tidy up package structure for map/incursion examples. --- src/main/java/examplemod/ExampleMod.java | 3 +- .../examplemod/Loaders/ExampleModBiomes.java | 15 ++ .../Loaders/ExampleModIncursions.java | 10 +- .../examplemod/Loaders/ExampleModItems.java | 1 + .../examplemod/Loaders/ExampleModObjects.java | 3 + .../examplemod/Loaders/ExampleModTiles.java | 4 +- .../examples/incursion/ExampleBiome.java | 36 ---- .../items/materials/ExampleGrassSeedItem.java | 21 +++ .../examples/maps/biomes/ExampleBiome.java | 178 ++++++++++++++++++ .../incursion/ExampleIncursionBiome.java | 2 +- .../incursion/ExampleIncursionLevel.java | 2 +- .../examples/objects/ExampleGrassObject.java | 21 +++ .../examples/tiles/ExampleGrassTile.java | 106 +++++++++++ .../examples/{ => tiles}/ExampleTile.java | 2 +- src/main/resources/items/examplegrass.png | Bin 0 -> 465 bytes src/main/resources/items/examplegrassseed.png | Bin 0 -> 535 bytes src/main/resources/locale/en.lang | 3 + src/main/resources/objects/examplegrass.png | Bin 0 -> 1186 bytes .../tiles/examplegrasstile_splat.png | Bin 0 -> 11459 bytes 19 files changed, 358 insertions(+), 49 deletions(-) create mode 100644 src/main/java/examplemod/Loaders/ExampleModBiomes.java delete mode 100644 src/main/java/examplemod/examples/incursion/ExampleBiome.java create mode 100644 src/main/java/examplemod/examples/items/materials/ExampleGrassSeedItem.java create mode 100644 src/main/java/examplemod/examples/maps/biomes/ExampleBiome.java rename src/main/java/examplemod/examples/{ => maps}/incursion/ExampleIncursionBiome.java (98%) rename src/main/java/examplemod/examples/{ => maps}/incursion/ExampleIncursionLevel.java (99%) create mode 100644 src/main/java/examplemod/examples/objects/ExampleGrassObject.java create mode 100644 src/main/java/examplemod/examples/tiles/ExampleGrassTile.java rename src/main/java/examplemod/examples/{ => tiles}/ExampleTile.java (97%) create mode 100644 src/main/resources/items/examplegrass.png create mode 100644 src/main/resources/items/examplegrassseed.png create mode 100644 src/main/resources/objects/examplegrass.png create mode 100644 src/main/resources/tiles/examplegrasstile_splat.png diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 53a5e32..eb11d66 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -1,7 +1,7 @@ package examplemod; import examplemod.Loaders.*; -import examplemod.examples.incursion.ExampleBiome; +import examplemod.examples.maps.biomes.ExampleBiome; import necesse.engine.modLoader.annotations.ModEntry; import necesse.engine.sound.SoundSettings; import necesse.engine.sound.gameSound.GameSound; @@ -20,6 +20,7 @@ public void init() { // The examples are split into different classes here for readability, but you can register them directly here in init if you wish ExampleModCategories.load(); + ExampleModBiomes.load(); ExampleModIncursions.load(); ExampleModTiles.load(); ExampleModObjects.load(); diff --git a/src/main/java/examplemod/Loaders/ExampleModBiomes.java b/src/main/java/examplemod/Loaders/ExampleModBiomes.java new file mode 100644 index 0000000..b24b3f8 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModBiomes.java @@ -0,0 +1,15 @@ +package examplemod.Loaders; + +import examplemod.ExampleMod; +import examplemod.examples.maps.biomes.ExampleBiome; +import necesse.engine.registries.BiomeRegistry; + +public class ExampleModBiomes { + public static void load() { + // Register a simple biome that will not appear in natural world gen. + ExampleMod.EXAMPLE_BIOME = BiomeRegistry.registerBiome("examplebiome", new ExampleBiome(), false); + } +} + + + diff --git a/src/main/java/examplemod/Loaders/ExampleModIncursions.java b/src/main/java/examplemod/Loaders/ExampleModIncursions.java index e072748..6f328d7 100644 --- a/src/main/java/examplemod/Loaders/ExampleModIncursions.java +++ b/src/main/java/examplemod/Loaders/ExampleModIncursions.java @@ -1,19 +1,13 @@ package examplemod.Loaders; -import examplemod.ExampleMod; -import examplemod.examples.incursion.ExampleBiome; -import examplemod.examples.incursion.ExampleIncursionBiome; -import examplemod.examples.incursion.ExampleIncursionLevel; -import necesse.engine.registries.BiomeRegistry; +import examplemod.examples.maps.incursion.ExampleIncursionBiome; +import examplemod.examples.maps.incursion.ExampleIncursionLevel; import necesse.engine.registries.IncursionBiomeRegistry; import necesse.engine.registries.LevelRegistry; public class ExampleModIncursions { public static void load() { - // Register a simple biome that will not appear in natural world gen. - ExampleMod.EXAMPLE_BIOME = BiomeRegistry.registerBiome("exampleincursion", new ExampleBiome(), false); - // Register the incursion biome with tier requirement 1. IncursionBiomeRegistry.registerBiome("exampleincursion", new ExampleIncursionBiome(), 1); diff --git a/src/main/java/examplemod/Loaders/ExampleModItems.java b/src/main/java/examplemod/Loaders/ExampleModItems.java index 198e969..5cff93b 100644 --- a/src/main/java/examplemod/Loaders/ExampleModItems.java +++ b/src/main/java/examplemod/Loaders/ExampleModItems.java @@ -20,6 +20,7 @@ public static void load(){ ItemRegistry.registerItem("examplebar", new ExampleBarItem(), 50, true); ItemRegistry.registerItem("examplehuntincursionmaterial", new ExampleHuntIncursionMaterialItem(), 50, true); ItemRegistry.registerItem("examplelog", new ExampleLogItem().setItemCategory("materials","logs"),10,true); + ItemRegistry.registerItem("examplegrassseed", new ExampleGrassSeedItem(),1,true); // Tools ItemRegistry.registerItem("examplesword", new ExampleSwordWeapon(), 20, true); diff --git a/src/main/java/examplemod/Loaders/ExampleModObjects.java b/src/main/java/examplemod/Loaders/ExampleModObjects.java index 6953650..136cd75 100644 --- a/src/main/java/examplemod/Loaders/ExampleModObjects.java +++ b/src/main/java/examplemod/Loaders/ExampleModObjects.java @@ -35,5 +35,8 @@ public static void load(){ .setItemCategory(ExampleModCategories.MOD,ExampleModCategories.MOD_OBJECTS,ExampleModCategories.EXAMPLEWOOD) .setCraftingCategory(ExampleModCategories.MOD,ExampleModCategories.MOD_OBJECTS,ExampleModCategories.EXAMPLEWOOD),50,true); + // Register a grass object + ObjectRegistry.registerObject("examplegrass",new ExampleGrassObject(),1,true); + } } diff --git a/src/main/java/examplemod/Loaders/ExampleModTiles.java b/src/main/java/examplemod/Loaders/ExampleModTiles.java index 4b5a9d8..f97d9eb 100644 --- a/src/main/java/examplemod/Loaders/ExampleModTiles.java +++ b/src/main/java/examplemod/Loaders/ExampleModTiles.java @@ -1,6 +1,7 @@ package examplemod.Loaders; -import examplemod.examples.ExampleTile; +import examplemod.examples.tiles.ExampleGrassTile; +import examplemod.examples.tiles.ExampleTile; import necesse.engine.registries.TileRegistry; public class ExampleModTiles { @@ -8,5 +9,6 @@ public class ExampleModTiles { public static void load(){ // Register our tiles TileRegistry.registerTile("exampletile", new ExampleTile(), 1, true); + TileRegistry.registerTile("examplegrasstile", new ExampleGrassTile(),1,false,false,true); } } diff --git a/src/main/java/examplemod/examples/incursion/ExampleBiome.java b/src/main/java/examplemod/examples/incursion/ExampleBiome.java deleted file mode 100644 index 60d122b..0000000 --- a/src/main/java/examplemod/examples/incursion/ExampleBiome.java +++ /dev/null @@ -1,36 +0,0 @@ -package examplemod.examples.incursion; - -import necesse.engine.AbstractMusicList; -import necesse.engine.MusicList; -import necesse.engine.registries.MusicRegistry; -import necesse.entity.mobs.PlayerMob; -import necesse.level.maps.Level; -import necesse.level.maps.biomes.Biome; -import necesse.level.maps.biomes.MobSpawnTable; - -// A minimalist biome used solely for the ExampleIncursion -// the Example Mob is used here as the enemy spawn -public class ExampleBiome extends Biome { - - public static MobSpawnTable critters = new MobSpawnTable() - .include(Biome.defaultCaveCritters); - - public static MobSpawnTable mobs = new MobSpawnTable() - .add(100,"examplemob"); - - @Override - public AbstractMusicList getLevelMusic(Level level, PlayerMob perspective) { - return new MusicList(MusicRegistry.ForestPath); - } - - @Override - public MobSpawnTable getCritterSpawnTable(Level level) { - return critters; - } - - @Override - public MobSpawnTable getMobSpawnTable(Level level) { - return mobs; - } - -} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/items/materials/ExampleGrassSeedItem.java b/src/main/java/examplemod/examples/items/materials/ExampleGrassSeedItem.java new file mode 100644 index 0000000..7e8164b --- /dev/null +++ b/src/main/java/examplemod/examples/items/materials/ExampleGrassSeedItem.java @@ -0,0 +1,21 @@ +package examplemod.examples.items.materials; + +import necesse.inventory.item.placeableItem.tileItem.GrassSeedItem; + +/** + * A seed item that turns dirt into our custom grass tile when placed. + * + * Vanilla uses GrassSeedItem for grass seeds. It handles: + * Only placing on dirt + * Tile placement + preview + * Consuming the item (unless in god mode) + * "Grass seed" style tooltip and crafting ingredients + */ +public class ExampleGrassSeedItem extends GrassSeedItem { + + public ExampleGrassSeedItem() { + // This must match your TileRegistry stringID + // i.e. TileRegistry.registerTile("examplegrasstile", ...) + super("examplegrasstile"); + } +} diff --git a/src/main/java/examplemod/examples/maps/biomes/ExampleBiome.java b/src/main/java/examplemod/examples/maps/biomes/ExampleBiome.java new file mode 100644 index 0000000..3ff999c --- /dev/null +++ b/src/main/java/examplemod/examples/maps/biomes/ExampleBiome.java @@ -0,0 +1,178 @@ +package examplemod.examples.maps.biomes; + +import necesse.engine.AbstractMusicList; +import necesse.engine.MusicList; +import necesse.engine.registries.MusicRegistry; +import necesse.engine.registries.TileRegistry; +import necesse.engine.util.GameRandom; +import necesse.engine.world.biomeGenerator.BiomeGeneratorStack; +import necesse.entity.mobs.PlayerMob; +import necesse.level.maps.Level; +import necesse.level.maps.biomes.Biome; +import necesse.level.maps.biomes.MobSpawnTable; +import necesse.level.maps.regionSystem.Region; + +import java.awt.Color; + +/** + * Example overworld biome (1.1.x "infinite surface" style). + * + * Key idea for new modders: + * ------------------------ + * In Necesse 1.1.x, the overworld surface is generated in *regions* by the game's SurfaceLevel. + * SurfaceLevel does NOT create a special "ExampleBiomeLevel" per biome anymore. + * + * Instead, SurfaceLevel asks the Biome for: + * - which ground tile to paint (getGenerationTerrainTileID) + * - what "noise/vein" patterns to use for placing objects (initializeGeneratorStack) + * - what objects to place in each region (generateRegionSurfaceTerrain) + * + * So: if you want a biome that changes the overworld terrain + trees, these are the methods to override. + */ +public class ExampleBiome extends Biome { + + // -------------------------- + // Spawn tables + // -------------------------- + // MobSpawnTable controls what can spawn in the biome. + // Vanilla provides some defaults; we "include" them and then add our own entries. + + /** Small critters (e.g. butterflies, squirrels) for surface regions of this biome. */ + public static final MobSpawnTable surfaceCritters = new MobSpawnTable() + .include(Biome.defaultSurfaceCritters); + + /** Critters for cave levels (if the player is in caves). */ + public static final MobSpawnTable caveCritters = new MobSpawnTable() + .include(Biome.defaultCaveCritters); + + /** Hostile/neutral mobs for the surface. We add our custom mob "examplemob". */ + public static final MobSpawnTable surfaceMobs = new MobSpawnTable() + .include(Biome.defaultSurfaceMobs) + .add(30, "examplemob"); // weight: higher = more likely relative to other entries + + /** Hostile/neutral mobs for caves. Usually a different balance than the surface. */ + public static final MobSpawnTable caveMobs = new MobSpawnTable() + .include(Biome.defaultCaveMobs) + .add(100, "examplemob"); + + public ExampleBiome() { + super(); + + // Generation weight decides how often this biome is chosen when the world is generating new regions. + // Vanilla values are typically around ~0.5 to ~1.5. Keep it in that range while testing. + // (Huge numbers can cause problems with spawn finding and biome distribution.) + this.setGenerationWeight(1.0F); + } + + // -------------------------- + // Overworld world generation hooks (SurfaceLevel -> Biome) + // -------------------------- + + /** + * This is the *base ground tile* that SurfaceLevel paints for this biome in new surface regions. + * + * We look up our custom tile by string ID. If it isn't registered (returns -1), + * we fall back to vanilla grass so the game doesn't break and we can still load the world. + */ + @Override + public int getGenerationTerrainTileID() { + int exampleGrass = TileRegistry.getTileID("examplegrasstile"); + if (exampleGrass == -1) { + // -1 means "not found in registry" (usually a typo or missing registration). + return TileRegistry.grassID; + } + return exampleGrass; + } + + /** + * The BiomeGeneratorStack is a helper that stores "noise patterns" used during worldgen. + * + * Think of it like: "make a map of blobs/veins for trees", then when placing objects + * we can say "place trees only on those blobs" to get natural clumps. + * + * This method is called as part of generator setup, not every tick. + */ + @Override + public void initializeGeneratorStack(BiomeGeneratorStack stack) { + super.initializeGeneratorStack(stack); + + // Register a simplex-based "vein" pattern named "exampleTrees". + // We'll reference this by name later when placing our trees. + // + // The parameters control clump size / branching / frequency. + // If you want denser or larger clumps, we can tweak these values. + stack.addRandomSimplexVeinsBranch("exampleTrees", 2.0F, 0.2F, 1.0F, 0); + } + + /** + * Called when SurfaceLevel is generating a specific *region* of the overworld surface. + * + * This is where you place objects like: + * - trees + * - grass tufts + * - flowers + * - rocks, etc. + * + * IMPORTANT: This runs during world generation for new/unexplored regions. + * It does NOT retroactively change already-generated terrain. + */ + @Override + public void generateRegionSurfaceTerrain(Region region, BiomeGeneratorStack stack, GameRandom random) { + super.generateRegionSurfaceTerrain(region, stack, random); + + // Cache our terrain tile ID so we place objects only on the correct ground. + final int grassTile = getGenerationTerrainTileID(); + + // Place our custom tree object using the "exampleTrees" vein pattern. + // This creates forest-like clusters instead of a perfectly even distribution. + stack.startPlaceOnVein(this, region, random, "exampleTrees") + .onlyOnTile(grassTile) // only place on our biome's land tile + .chance(0.10D) // density inside valid vein areas (tweak for more/less) + .placeObject("exampletree"); + + // Place vanilla "grass" decoration objects on top of our tile. + // This is purely visual and helps the biome feel "alive". + stack.startPlace(this, region, random) + .chance(0.40D) // overall density of grass objects + .onlyOnTile(grassTile) + .placeObject("grass"); + } + + /** + * Used by some debug tools/views to show biomes as solid colors. + * Not required for gameplay, but helpful when testing generation. + */ + @Override + public Color getDebugBiomeColor() { + return new Color(128, 0, 128); + } + + // -------------------------- + // Ambient music + spawns + // -------------------------- + + /** + * Music selection for this biome. Here we reuse vanilla forest music. + * You can swap to a different MusicRegistry path if you want. + */ + @Override + public AbstractMusicList getLevelMusic(Level level, PlayerMob perspective) { + return new MusicList(MusicRegistry.ForestPath); + } + + /** + * Critter spawns depend on whether the current level is a cave level. + */ + @Override + public MobSpawnTable getCritterSpawnTable(Level level) { + return level.isCave ? caveCritters : surfaceCritters; + } + + /** + * Mob spawns depend on whether the current level is a cave level. + */ + @Override + public MobSpawnTable getMobSpawnTable(Level level) { + return level.isCave ? caveMobs : surfaceMobs; + } +} diff --git a/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java b/src/main/java/examplemod/examples/maps/incursion/ExampleIncursionBiome.java similarity index 98% rename from src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java rename to src/main/java/examplemod/examples/maps/incursion/ExampleIncursionBiome.java index 379782a..5a4d9d6 100644 --- a/src/main/java/examplemod/examples/incursion/ExampleIncursionBiome.java +++ b/src/main/java/examplemod/examples/maps/incursion/ExampleIncursionBiome.java @@ -1,4 +1,4 @@ -package examplemod.examples.incursion; +package examplemod.examples.maps.incursion; import necesse.engine.network.server.Server; import necesse.engine.registries.ItemRegistry; diff --git a/src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java b/src/main/java/examplemod/examples/maps/incursion/ExampleIncursionLevel.java similarity index 99% rename from src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java rename to src/main/java/examplemod/examples/maps/incursion/ExampleIncursionLevel.java index e3f7acc..5118631 100644 --- a/src/main/java/examplemod/examples/incursion/ExampleIncursionLevel.java +++ b/src/main/java/examplemod/examples/maps/incursion/ExampleIncursionLevel.java @@ -1,4 +1,4 @@ -package examplemod.examples.incursion; +package examplemod.examples.maps.incursion; import examplemod.ExampleMod; import examplemod.examples.ExamplePreset; diff --git a/src/main/java/examplemod/examples/objects/ExampleGrassObject.java b/src/main/java/examplemod/examples/objects/ExampleGrassObject.java new file mode 100644 index 0000000..22c1e17 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleGrassObject.java @@ -0,0 +1,21 @@ +package examplemod.examples.objects; + +import necesse.inventory.lootTable.LootTable; +import necesse.inventory.lootTable.lootItem.ChanceLootItem; +import necesse.level.gameObject.GrassObject; +import necesse.level.maps.Level; + +public class ExampleGrassObject extends GrassObject { + + public ExampleGrassObject() { + // "examplegrass" is the texture name, 2 = variants/height setting used by GrassObject + super("examplegrass", 2); + } + + public LootTable getLootTable(Level level, int tileX, int tileY) { + // 4% chance, tweak to match vanilla feel + return new LootTable( + new ChanceLootItem(0.04F, "examplegrassseed") + ); + } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/tiles/ExampleGrassTile.java b/src/main/java/examplemod/examples/tiles/ExampleGrassTile.java new file mode 100644 index 0000000..d7f6c8b --- /dev/null +++ b/src/main/java/examplemod/examples/tiles/ExampleGrassTile.java @@ -0,0 +1,106 @@ +package examplemod.examples.tiles; + +import java.awt.Color; +import java.awt.Point; + +import necesse.engine.registries.ObjectRegistry; +import necesse.engine.util.GameMath; +import necesse.engine.util.GameRandom; +import necesse.gfx.gameTexture.GameTextureSection; +import necesse.inventory.lootTable.LootItemInterface; +import necesse.inventory.lootTable.LootTable; +import necesse.inventory.lootTable.lootItem.ChanceLootItem; +import necesse.level.gameObject.GameObject; +import necesse.level.gameTile.TerrainSplatterTile; +import necesse.level.maps.Level; +import necesse.level.maps.regionSystem.SimulatePriorityList; + +public class ExampleGrassTile extends TerrainSplatterTile { + // You can tweak these to change growth/spread speeds + public static double growChance = GameMath.getAverageSuccessRuns(7000.0D); + public static double spreadChance = GameMath.getAverageSuccessRuns(850.0D); + + private final GameRandom drawRandom = new GameRandom(); + + public ExampleGrassTile() { + // IMPORTANT: this string must match your texture name in resources/tiles/ + // (e.g. resources/tiles/examplegrass.png) + super(false, "examplegrasstile"); + + this.mapColor = new Color(70, 120, 40); // minimap color + this.canBeMined = true; + this.isOrganic = true; + } + + @Override + public LootTable getLootTable(Level level, int tileX, int tileY) { + // Option A: drop vanilla grassseed + // return new LootTable(new ChanceLootItem(0.04F, "grassseed")); + + // Option B: drop your own seed item (if you register one) + return new LootTable(new LootItemInterface[]{ + new ChanceLootItem(0.04F, "examplegrassseed") + }); + } + + @Override + public void addSimulateLogic(Level level, int x, int y, long ticks, SimulatePriorityList list, boolean sendChanges) { + addSimulateGrow(level, x, y, growChance, ticks, "examplegrass", list, sendChanges); + } + + // Same helper pattern vanilla uses (simplified) + public static void addSimulateGrow(Level level, int tileX, int tileY, double chance, long ticks, + String growObjectID, SimulatePriorityList list, boolean sendChanges) { + if (level.getObjectID(tileX, tileY) == 0) { + double runs = Math.max(1.0D, GameMath.getRunsForSuccess(chance, GameRandom.globalRandom.nextDouble())); + long remainingTicks = (long)(ticks - runs); + if (remainingTicks > 0L) { + GameObject obj = ObjectRegistry.getObject(ObjectRegistry.getObjectID(growObjectID)); + if (obj.canPlace(level, tileX, tileY, 0, false) == null) { + list.add(tileX, tileY, remainingTicks, () -> { + if (obj.canPlace(level, tileX, tileY, 0, false) == null) { + obj.placeObject(level, tileX, tileY, 0, false); + level.objectLayer.setIsPlayerPlaced(tileX, tileY, false); + if (sendChanges) level.sendObjectUpdatePacket(tileX, tileY); + } + }); + } + } + } + } + + @Override + public double spreadToDirtChance() { + // This is what makes dirt convert into your grass when adjacent + return spreadChance; + } + + @Override + public void tick(Level level, int x, int y) { + if (!level.isServer()) return; + + // Grow your grass object on empty tiles + if (level.getObjectID(x, y) == 0 && GameRandom.globalRandom.getChance(growChance)) { + GameObject grassObj = ObjectRegistry.getObject(ObjectRegistry.getObjectID("examplegrass")); + if (grassObj.canPlace(level, x, y, 0, false) == null) { + grassObj.placeObject(level, x, y, 0, false); + level.objectLayer.setIsPlayerPlaced(x, y, false); + level.sendObjectUpdatePacket(x, y); + } + } + } + + @Override + public Point getTerrainSprite(GameTextureSection terrainTexture, Level level, int tileX, int tileY) { + int tile; + synchronized (drawRandom) { + tile = drawRandom.seeded(getTileSeed(tileX, tileY)).nextInt(terrainTexture.getHeight() / 32); + } + return new Point(0, tile); // column 0, random row + } + + @Override + public int getTerrainPriority() { + return 100; // same as vanilla grass + } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/ExampleTile.java b/src/main/java/examplemod/examples/tiles/ExampleTile.java similarity index 97% rename from src/main/java/examplemod/examples/ExampleTile.java rename to src/main/java/examplemod/examples/tiles/ExampleTile.java index 9189d36..2b981be 100644 --- a/src/main/java/examplemod/examples/ExampleTile.java +++ b/src/main/java/examplemod/examples/tiles/ExampleTile.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.tiles; import necesse.engine.util.GameRandom; import necesse.gfx.gameTexture.GameTexture; diff --git a/src/main/resources/items/examplegrass.png b/src/main/resources/items/examplegrass.png new file mode 100644 index 0000000000000000000000000000000000000000..1fba177452e63d5fe4cc4438e34385a4fde7fa38 GIT binary patch literal 465 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*8Lmk8ZS<8K$fg+p*9+AZi4BWyX%*Zfnjs#GUy~NYkmHibHvw)`hJCWyBKo^L5 zx;TbJw7#8cn0LrQz;(W$>4i3>I}OYV8b#eDD<=fZni5jD@j}2Q7mv)vGRk^Fg$#KP zT}xVH4uovJ(foF+z$D#G>u1UKtN!1vENLTmzFz%8ujpxu%6T73j3%4DpLKe1;W_W= zOEgssNKF>+~tD(ZldESuV3947xQP-cMTK81~>H;Z>+7RWqzTA*W;7!eTFlfxrcghE;*y}y!=f=a?!#v>jx$ZCpW)nuZ>>c z`G{Zm=1;lpOYIi?V|&6UFL|6HHt)@5|Iew}bN8Hxne-}FP;y|S0C zHU73wmc4Mo@6U(Xul9eaN@X*-<#F@KtW&%dHtd&ug3l_x?EwZe1B0ilpUXO@geCyg CL#@OB literal 0 HcmV?d00001 diff --git a/src/main/resources/items/examplegrassseed.png b/src/main/resources/items/examplegrassseed.png new file mode 100644 index 0000000000000000000000000000000000000000..35103ba85d2752ebede5af6932fcaca0c98e8502 GIT binary patch literal 535 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*8Lmk8ZS<8K$fg+p*9+AZi4BWyX%*Zfnjs#GUy~NYkmHibHvw*Jkyvn*lpbMfs zT^vIqTHj9F>vhCIq}Bc?`;EAZQ7n!s3f)b=Ma-f(n%4G~cCFmLQIv(dx|W68dgGL% zK^MJRu8K=^e_eO7^w`-aT-jm%Q&0YRGDp8$t#SW4$=6J~OKM;I(@dXwZQfR?4O#2n zxp?k#6qA0GXw&&jutT-}DQ|;a`HFw}o+kYbD_pr77?~d!S1M)}lxLVuKVZwxptDip z&HR7G4k{`xlCR|+%$n`Rk|llPl=NFI=7X|7gU#n8;|Z&DmDdXLmNPb2bOKPOBwPis3k= z=QweV?}E2h&*X0{VDwcA-S}jq(%-%Dhgbc!P@8L0X6G~KK)*;(VAcKZBo}qR2l4Oy Xc4f1tvz@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;%JBegMCkr%giu000SaNLh0L01FcU01FcV0GgZ_00007 zbV*G`2kHU?5E~j>4F6gH00ZVpL_t(|ob8)GZ`42##UF@3cSM8+q`-%wfQ~8>N<&En z4KzqJP#`2KIzAH$1&IWT1R~I;I0`blyJyesAMe_ReZMA(Z*OOJ#+i9Lb^sxS5JI%a zCGUfGc)eWz{Sh9!W7o%r#t9+j!{w@U^!!$u|D{(V z1t3JjL@NNxtlru0JAMEFcCZ5gxQ43${GI~C^QLCh?eP6sO9`a*wsP-_p??ptNC61ZFwqJil}|WI8qXQr%jC7Y zsj(H-v+q3>Nrm6HurqgPUEWzZw-VWYtk+|3Pl(5xLhzJDF04q*KgQ5XY4;t!4`Z-@ zElwV}_XGbu)aw060SM7B)f7N3oLOVfbfF5F%2dy7x#HSW6|TVE_qZY(x%*(d_Vgz- zzH~f{P({t{0dVCvKe7_zdD%Z~KNPoEY~Q>d!>;pFxzK$q1t3JjbQHi`-&kU5XIjr5 ze{L1gI}@kE`#8lZ0AO*E(Z^Oop{G9vD}z$;U~0{4*nQ6x)x+~oNGZh<^1ofTb*3sG zDF7iFW_CRQ!#jJ1&bmXf?lr#w$7gfPyyrOI`2JXLq8VHP=0a^hnBPmi|KXkO*-q}h z#=b9Nkpd8+VO#~^WjxD_w{ffYd!@R-X7>;W%k}w=@EHJb6MKW^_pmdd?cbDuUFY=_ zWFP4I_veKoSp?osFltqrP~5-b8-RMNTDmgx zd=cvzT6s3Ux5b}P@hZU9I8=yx61MN!_9Ofo8@D2`63dOh6o3#7)A!7n&l;gjk~)4U zp1soRh@Efj{m;dIEBl^$pG>G1khDH203jOY4+E(O$OPuh-T(jq07*qoM6N<$g2T`m A>MCL4ob9pkVS)K|v!lzuwUd_{YjC=WHw$-YUc@ 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 literal 0 HcmV?d00001 From 7f2bb19cff01b15cc5e9e9585cdd0e164448c6d1 Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Wed, 4 Feb 2026 02:32:03 +0000 Subject: [PATCH 19/21] Add example level event, object, and loader Introduce example event plumbing and a placeable object demonstrating LevelEvent and GameEvent usage. Adds ExampleLevelEvent (server-side level event) and ExampleEvent (lightweight game event), an ExampleLevelEventObject that spawns the level event and triggers the game event, and ExampleModEvents to register the level event and a listener. Updates ExampleMod to load the events and registers the new object in ExampleModObjects. Also adds sprites for the object and a locale entry (en.lang). --- src/main/java/examplemod/ExampleMod.java | 1 + .../examplemod/Loaders/ExampleModEvents.java | 33 ++++ .../examplemod/Loaders/ExampleModObjects.java | 3 + .../examples/events/ExampleEvent.java | 39 +++++ .../examples/events/ExampleLevelEvent.java | 65 ++++++++ .../objects/ExampleLevelEventObject.java | 148 ++++++++++++++++++ .../items/exampleleveleventobject.png | Bin 0 -> 289 bytes src/main/resources/locale/en.lang | 1 + .../objects/exampleleveleventobject.png | Bin 0 -> 289 bytes 9 files changed, 290 insertions(+) create mode 100644 src/main/java/examplemod/Loaders/ExampleModEvents.java create mode 100644 src/main/java/examplemod/examples/events/ExampleEvent.java create mode 100644 src/main/java/examplemod/examples/events/ExampleLevelEvent.java create mode 100644 src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java create mode 100644 src/main/resources/items/exampleleveleventobject.png create mode 100644 src/main/resources/objects/exampleleveleventobject.png diff --git a/src/main/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index eb11d66..7319641 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -20,6 +20,7 @@ public void init() { // The examples are split into different classes here for readability, but you can register them directly here in init if you wish ExampleModCategories.load(); + ExampleModEvents.load();; ExampleModBiomes.load(); ExampleModIncursions.load(); ExampleModTiles.load(); diff --git a/src/main/java/examplemod/Loaders/ExampleModEvents.java b/src/main/java/examplemod/Loaders/ExampleModEvents.java new file mode 100644 index 0000000..e3a3fad --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModEvents.java @@ -0,0 +1,33 @@ +package examplemod.Loaders; + +import examplemod.examples.events.ExampleEvent; +import examplemod.examples.events.ExampleLevelEvent; +import necesse.engine.GameEventListener; +import necesse.engine.GameEvents; +import necesse.engine.network.server.ServerClient; +import necesse.engine.registries.LevelEventRegistry; + +public class ExampleModEvents { + public static void load() { + // Register our Level Event to the registry + LevelEventRegistry.registerEvent("examplelevelevent", ExampleLevelEvent.class); + + // Register our ExampleEvent Listener + GameEvents.addListener(ExampleEvent.class, new GameEventListener() { + @Override + public void onEvent(ExampleEvent event) { + if (event.level == null || !event.level.isServer()) return; + + ServerClient client = event.level.getServer().getClient(event.clientSlot); + if (client != null) { + client.sendChatMessage(event.message); + client.sendChatMessage("PONG: this message was sent from the ExampleEvent Listener "); + } + } + }); + + } +} + + + diff --git a/src/main/java/examplemod/Loaders/ExampleModObjects.java b/src/main/java/examplemod/Loaders/ExampleModObjects.java index 136cd75..4d316d4 100644 --- a/src/main/java/examplemod/Loaders/ExampleModObjects.java +++ b/src/main/java/examplemod/Loaders/ExampleModObjects.java @@ -38,5 +38,8 @@ public static void load(){ // Register a grass object ObjectRegistry.registerObject("examplegrass",new ExampleGrassObject(),1,true); + // Register an object which uses a level event + ObjectRegistry.registerObject("exampleleveleventobject", new ExampleLevelEventObject(),1,true); + } } diff --git a/src/main/java/examplemod/examples/events/ExampleEvent.java b/src/main/java/examplemod/examples/events/ExampleEvent.java new file mode 100644 index 0000000..83e74e5 --- /dev/null +++ b/src/main/java/examplemod/examples/events/ExampleEvent.java @@ -0,0 +1,39 @@ +package examplemod.examples.events; + +import necesse.engine.events.GameEvent; +import necesse.level.maps.Level; + +/** + * A simple custom "game event" for our mod. + * + * This is NOT a LevelEvent (it does not exist in the world, does not tick, and is not drawn). + * Instead, it's a lightweight message object you pass through your own event system + * (for example: GameEvents.triggerEvent(...)) so other parts of your mod can react. + * + * Think of it like: "something happened" + "here is the data about what happened". + */ +public class ExampleEvent extends GameEvent { + + /** + * The level where the thing happened + */ + public final Level level; + + /** + * Which connected player this event is about. + */ + public final int clientSlot; + + /** + * Example payload data listeners can use. + */ + public final String message; + + public ExampleEvent(Level level, int clientSlot) { + this.level = level; + this.clientSlot = clientSlot; + + // Simple demo message that listeners can read. + this.message = "PING: this message was sent from the ExampleEvent"; + } +} diff --git a/src/main/java/examplemod/examples/events/ExampleLevelEvent.java b/src/main/java/examplemod/examples/events/ExampleLevelEvent.java new file mode 100644 index 0000000..b60321f --- /dev/null +++ b/src/main/java/examplemod/examples/events/ExampleLevelEvent.java @@ -0,0 +1,65 @@ +package examplemod.examples.events; + +import necesse.engine.network.PacketReader; +import necesse.engine.network.PacketWriter; +import necesse.engine.network.server.ServerClient; +import necesse.entity.levelEvent.LevelEvent; + +/** + * Very simple LevelEvent demo: + * Server-side only: sends a chat message to a specific player, then ends. + * + * Note: If you spawn this with events.addHidden(...), clients will never receive it, + * so the packet methods below won't be used (they're included for when/if you use events.add(...)). + */ +public class ExampleLevelEvent extends LevelEvent { + + // Which ServerClient (player) to message. -1 = invalid/unset. + private int targetSlot = -1; + + // Message to send (kept non-null for safety). + private String message = ""; + + // Required empty constructor for registry/network spawning + public ExampleLevelEvent() { + } + + public ExampleLevelEvent(int targetSlot) { + this.targetSlot = targetSlot; + this.message = "ZING: this message was sent from the ExampleLevelEvent"; + } + + @Override + public void init() { + super.init(); + + // Only the server can access ServerClient and send chat packets. + if (isServer()) { + ServerClient client = level.getServer().getClient(targetSlot); + if (client != null) { + client.sendChatMessage(message); + } + + // We're done immediately (no ticking needed). + over(); + } + } + + @Override + public void setupSpawnPacket(PacketWriter writer) { + super.setupSpawnPacket(writer); + + // Write fields in a fixed order... + writer.putNextInt(targetSlot); + writer.putNextString(message); + } + + @Override + public void applySpawnPacket(PacketReader reader) { + super.applySpawnPacket(reader); + + // ...and read them back in the same order. + targetSlot = reader.getNextInt(); + message = reader.getNextString(); + } +} diff --git a/src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java b/src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java new file mode 100644 index 0000000..459dab9 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java @@ -0,0 +1,148 @@ +package examplemod.examples.objects; + +import examplemod.examples.events.ExampleEvent; +import examplemod.examples.events.ExampleLevelEvent; +import necesse.engine.GameEvents; +import necesse.engine.gameLoop.tickManager.TickManager; +import necesse.entity.mobs.PlayerMob; +import necesse.gfx.camera.GameCamera; +import necesse.gfx.drawOptions.texture.TextureDrawOptionsEnd; +import necesse.gfx.drawables.LevelSortedDrawable; +import necesse.gfx.drawables.OrderableDrawables; +import necesse.gfx.gameTexture.GameTexture; +import necesse.level.gameObject.GameObject; +import necesse.level.maps.Level; +import necesse.level.maps.light.GameLight; + +import java.awt.Rectangle; +import java.util.List; + +/* + * Basic placeable object that: + * draws a single 32x32 sprite in the world + * + * spawns a LevelEvent that sends a chat message (event handles the message, not the object) + * and also fires an ExampleEvent which triggers an event listener to run its code + */ +public class ExampleLevelEventObject extends GameObject { + + // Loaded once from your mod resources in loadTextures() + private GameTexture texture; + + public ExampleLevelEventObject() { + // 32x32 collision/selection box + super(new Rectangle(32, 32)); + this.isSolid = true; + } + + @Override + public void loadTextures() { + super.loadTextures(); + + // Loads: src/main/resources/objects/exampleleveleventobject.png + this.texture = GameTexture.fromFile("objects/exampleleveleventobject"); + } + + @Override + public void addDrawables(List list, OrderableDrawables tileList, + Level level, int tileX, int tileY, TickManager tickManager, + GameCamera camera, PlayerMob perspective) { + + // Lighting at this tile (so the sprite matches world lighting) + GameLight light = level.getLightLevel(tileX, tileY); + + // Convert tile coords -> screen draw coords + int drawX = camera.getTileDrawX(tileX); + int drawY = camera.getTileDrawY(tileY); + + // Build the draw options once, then draw them inside the drawable + final TextureDrawOptionsEnd opts = this.texture.initDraw() + .sprite(0, 0, 32) // first sprite, 32x32 + .light(light) + .pos(drawX, drawY); + + // Add a drawable so the engine draws it in correct Y-sort order + list.add(new LevelSortedDrawable(this, tileX, tileY) { + @Override + public int getSortY() { + return 16; // typical "middle of the tile" sort value for 1-tile objects + } + + @Override + public void draw(TickManager tickManager) { + opts.draw(); + } + }); + } + + @Override + public void drawPreview(Level level, int tileX, int tileY, int rotation, float alpha, + PlayerMob player, GameCamera camera) { + + // This is the translucent "ghost" preview when placing the object + GameLight light = level.getLightLevel(tileX, tileY); + int drawX = camera.getTileDrawX(tileX); + int drawY = camera.getTileDrawY(tileY); + + this.texture.initDraw() + .sprite(0, 0, 32) + .light(light) + .alpha(alpha) + .draw(drawX, drawY); + } + + @Override + public boolean canInteract(Level level, int x, int y, PlayerMob player) { + return true; + } + + @Override + public void interact(Level level, int x, int y, PlayerMob player) { + + /* + * interact(...) is called on BOTH sides in multiplayer: + * client: when you click / interact locally + * server: when the server processes the interaction + * + * Anything that changes game state (spawning events, sending chat, triggering mod logic) + * should be done on the SERVER to avoid double-running and desync. + */ + if (level.isServer()) { + + /* + * In multiplayer, a PlayerMob on the server is tied to a ServerClient. + * The "slot" is a simple way to identify which connected client/player we mean. + * We'll pass this into our event so it knows who to target. + */ + int clientSlot = player.getServerClient().slot; + + /* + * Spawn our LevelEvent. + * + * We keep the object simple: it just creates the event. + * The ExampleLevelEvent itself contains the "what happens" logic (like sending a message). + */ + ExampleLevelEvent ev = new ExampleLevelEvent(clientSlot); + + /* + * addHidden(...) means "server-only": + * the event is added to the level's event manager + * it will NOT send a spawn packet to clients + * + * Use addHidden when the event is purely server logic (like chat/logging). + * Use events.add(ev) if you want clients to also receive/tick/draw the event. + */ + level.entityManager.events.addHidden(ev); + + + /* + * This is an example of triggering an event (in this case ExampleEvent) + * which will fire the event listener for ExampleEvent to run its code + */ + GameEvents.triggerEvent(new ExampleEvent(level, clientSlot)); + } + + // Always call super unless you specifically want to block default behavior. + super.interact(level, x, y, player); + } +} diff --git a/src/main/resources/items/exampleleveleventobject.png b/src/main/resources/items/exampleleveleventobject.png new file mode 100644 index 0000000000000000000000000000000000000000..957d455f4644d965454c71f7d510f499443d5232 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*8Lmk8ZS<8K$fg+p*9+AZi4BWyX%*Zfnjs#GUy~NYkmHibH3xl3mVbv=>piru( zi(^PcYw{N1ZGX=jG&E!?=*pKKSi$TjdjJHSx*pAVAINf5=V6T7BxW`n7PdQ#Dz1!1 z3Z^Rx8yWc<8Os@N1~zEB_dH_?I+&p+QNZ5wtO+RiIAe3o6~@JU5~T+mtQi=zIr%w% T@UOZ9w2;Bm)z4*}Q$iB}h6GWA literal 0 HcmV?d00001 diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index ba1e2e8..8e92f3c 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -13,6 +13,7 @@ examplegrass=Example Grass examplewall=Example Wall exampledoor=Example Door examplechair=Example Chair +exampleleveleventobject=Example Level Event Object [item] exampleitem=Example Item diff --git a/src/main/resources/objects/exampleleveleventobject.png b/src/main/resources/objects/exampleleveleventobject.png new file mode 100644 index 0000000000000000000000000000000000000000..957d455f4644d965454c71f7d510f499443d5232 GIT binary patch literal 289 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*8Lmk8ZS<8K$fg+p*9+AZi4BWyX%*Zfnjs#GUy~NYkmHibH3xl3mVbv=>piru( zi(^PcYw{N1ZGX=jG&E!?=*pKKSi$TjdjJHSx*pAVAINf5=V6T7BxW`n7PdQ#Dz1!1 z3Z^Rx8yWc<8Os@N1~zEB_dH_?I+&p+QNZ5wtO+RiIAe3o6~@JU5~T+mtQi=zIr%w% T@UOZ9w2;Bm)z4*}Q$iB}h6GWA literal 0 HcmV?d00001 From e3265679ccc7a9243e5e8fb98a58269add3bde5a Mon Sep 17 00:00:00 2001 From: Jamesp1989SL Date: Thu, 5 Feb 2026 02:23:38 +0000 Subject: [PATCH 20/21] Add summon weapon, mob, and level-event object Introduce a summon weapon and associated summon mob plus event-driven demo changes. Added ExampleSummonOrbWeapon, ExampleSummonWeaponMob, ExampleObjectEntity and new item/mob textures; registered the summon mob and item, and added an iron anvil recipe for the summon orb. Expanded ExampleLevelEvent to support server-side damage/health-change logic and client-side particle effects, plus proper spawn packet serialization. Replaced direct object interaction with an ObjectEntity (ExampleObjectEntity) for tile-based triggers and refactored ExampleLevelEventObject to spawn that entity. Renamed projectile/sword classes and assets for clarity (ExampleProjectileWeapon -> ExampleMagicProjectileWeapon, ExampleSwordWeapon -> ExampleMeleeSwordWeapon) and updated item registrations, recipes, and locale keys/tooltips accordingly. Misc: minor formatting and comments updates across examples and resource additions/deletions for new sprites. --- .../examplemod/Loaders/ExampleModItems.java | 10 +- .../examplemod/Loaders/ExampleModMobs.java | 4 + .../examplemod/Loaders/ExampleModRecipes.java | 14 +- .../Loaders/ExampleModResources.java | 2 + .../examples/ExampleConstructorPatch.java | 1 - .../examples/ExampleObjectEntity.java | 97 +++++++++++++ .../examples/events/ExampleEvent.java | 28 ++-- .../examples/events/ExampleLevelEvent.java | 135 ++++++++++++++++-- ...java => ExampleMagicProjectileWeapon.java} | 6 +- ...apon.java => ExampleMeleeSwordWeapon.java} | 4 +- .../items/tools/ExampleSummonOrbWeapon.java | 29 ++++ .../examples/mobs/ExampleSummonWeaponMob.java | 87 +++++++++++ .../objects/ExampleLevelEventObject.java | 103 +++---------- ...examplestaff.png => examplemagicstaff.png} | Bin ...examplesword.png => examplemeleesword.png} | Bin src/main/resources/items/examplesummonorb.png | Bin 0 -> 385 bytes src/main/resources/locale/en.lang | 8 +- src/main/resources/mobs/examplesummonmob.png | Bin 0 -> 4665 bytes .../resources/mobs/icons/examplesummonmob.png | Bin 0 -> 442 bytes .../player/weapons/examplemagicstaff.png | Bin 0 -> 467 bytes .../player/weapons/examplemeleesword.png | Bin 0 -> 451 bytes .../resources/player/weapons/examplestaff.png | Bin 540 -> 0 bytes .../player/weapons/examplesummonorb.png | Bin 0 -> 348 bytes .../resources/player/weapons/examplesword.png | Bin 516 -> 0 bytes 24 files changed, 404 insertions(+), 124 deletions(-) create mode 100644 src/main/java/examplemod/examples/ExampleObjectEntity.java rename src/main/java/examplemod/examples/items/tools/{ExampleProjectileWeapon.java => ExampleMagicProjectileWeapon.java} (96%) rename src/main/java/examplemod/examples/items/tools/{ExampleSwordWeapon.java => ExampleMeleeSwordWeapon.java} (86%) create mode 100644 src/main/java/examplemod/examples/items/tools/ExampleSummonOrbWeapon.java create mode 100644 src/main/java/examplemod/examples/mobs/ExampleSummonWeaponMob.java rename src/main/resources/items/{examplestaff.png => examplemagicstaff.png} (100%) rename src/main/resources/items/{examplesword.png => examplemeleesword.png} (100%) create mode 100644 src/main/resources/items/examplesummonorb.png create mode 100644 src/main/resources/mobs/examplesummonmob.png create mode 100644 src/main/resources/mobs/icons/examplesummonmob.png create mode 100644 src/main/resources/player/weapons/examplemagicstaff.png create mode 100644 src/main/resources/player/weapons/examplemeleesword.png delete mode 100644 src/main/resources/player/weapons/examplestaff.png create mode 100644 src/main/resources/player/weapons/examplesummonorb.png delete mode 100644 src/main/resources/player/weapons/examplesword.png diff --git a/src/main/java/examplemod/Loaders/ExampleModItems.java b/src/main/java/examplemod/Loaders/ExampleModItems.java index 5cff93b..cf31ab5 100644 --- a/src/main/java/examplemod/Loaders/ExampleModItems.java +++ b/src/main/java/examplemod/Loaders/ExampleModItems.java @@ -6,8 +6,9 @@ import examplemod.examples.items.consumable.ExampleFoodItem; import examplemod.examples.items.consumable.ExamplePotionItem; import examplemod.examples.items.materials.*; -import examplemod.examples.items.tools.ExampleProjectileWeapon; -import examplemod.examples.items.tools.ExampleSwordWeapon; +import examplemod.examples.items.tools.ExampleMagicProjectileWeapon; +import examplemod.examples.items.tools.ExampleMeleeSwordWeapon; +import examplemod.examples.items.tools.ExampleSummonOrbWeapon; import necesse.engine.registries.ItemRegistry; public class ExampleModItems { @@ -23,8 +24,9 @@ public static void load(){ ItemRegistry.registerItem("examplegrassseed", new ExampleGrassSeedItem(),1,true); // Tools - ItemRegistry.registerItem("examplesword", new ExampleSwordWeapon(), 20, true); - ItemRegistry.registerItem("examplestaff", new ExampleProjectileWeapon(), 30, true); + ItemRegistry.registerItem("examplemeleesword", new ExampleMeleeSwordWeapon(), 20, true); + ItemRegistry.registerItem("examplemagicstaff", new ExampleMagicProjectileWeapon(), 30, true); + ItemRegistry.registerItem("examplesummonorb", new ExampleSummonOrbWeapon(),40,true); // Armor ItemRegistry.registerItem("examplehelmet", new ExampleHelmetArmorItem(), 200f, true); diff --git a/src/main/java/examplemod/Loaders/ExampleModMobs.java b/src/main/java/examplemod/Loaders/ExampleModMobs.java index f8196f4..80acd22 100644 --- a/src/main/java/examplemod/Loaders/ExampleModMobs.java +++ b/src/main/java/examplemod/Loaders/ExampleModMobs.java @@ -2,6 +2,7 @@ import examplemod.examples.mobs.ExampleBossMob; import examplemod.examples.mobs.ExampleMob; +import examplemod.examples.mobs.ExampleSummonWeaponMob; import necesse.engine.registries.MobRegistry; public class ExampleModMobs { @@ -11,5 +12,8 @@ public static void load(){ // Register boss mob MobRegistry.registerMob("examplebossmob", ExampleBossMob.class,true,true); + + // Register summon mob + MobRegistry.registerMob("examplesummonmob", ExampleSummonWeaponMob.class,true,false); } } diff --git a/src/main/java/examplemod/Loaders/ExampleModRecipes.java b/src/main/java/examplemod/Loaders/ExampleModRecipes.java index b1e9662..deab3f5 100644 --- a/src/main/java/examplemod/Loaders/ExampleModRecipes.java +++ b/src/main/java/examplemod/Loaders/ExampleModRecipes.java @@ -37,7 +37,7 @@ public static void registerRecipes(){ //IRON ANVIL RECIPES Recipes.registerModRecipe(new Recipe( - "examplesword", + "examplemeleesword", 1, RecipeTechRegistry.IRON_ANVIL, new Ingredient[]{ @@ -47,7 +47,7 @@ public static void registerRecipes(){ )); Recipes.registerModRecipe(new Recipe( - "examplestaff", + "examplemagicstaff", 1, RecipeTechRegistry.IRON_ANVIL, new Ingredient[]{ @@ -56,6 +56,16 @@ public static void registerRecipes(){ } )); + Recipes.registerModRecipe(new Recipe( + "examplesummonorb", + 1, + RecipeTechRegistry.IRON_ANVIL, + new Ingredient[]{ + new Ingredient("exampleitem", 3), + new Ingredient("examplebar", 2) + } + )); + Recipes.registerModRecipe(new Recipe( "examplehelmet", 1, diff --git a/src/main/java/examplemod/Loaders/ExampleModResources.java b/src/main/java/examplemod/Loaders/ExampleModResources.java index 918470d..0244312 100644 --- a/src/main/java/examplemod/Loaders/ExampleModResources.java +++ b/src/main/java/examplemod/Loaders/ExampleModResources.java @@ -3,6 +3,7 @@ import examplemod.ExampleMod; import examplemod.examples.mobs.ExampleBossMob; import examplemod.examples.mobs.ExampleMob; +import examplemod.examples.mobs.ExampleSummonWeaponMob; import necesse.engine.sound.SoundSettings; import necesse.engine.sound.gameSound.GameSound; import necesse.gfx.gameTexture.GameTexture; @@ -16,6 +17,7 @@ public static void load(){ ExampleMob.texture = GameTexture.fromFile("mobs/examplemob"); ExampleBossMob.texture = GameTexture.fromFile("mobs/examplebossmob"); + ExampleSummonWeaponMob.texture = GameTexture.fromFile("mobs/examplesummonmob"); //initialising the sound to be used by our boss mob ExampleMod.EXAMPLESOUND = GameSound.fromFile("examplesound"); diff --git a/src/main/java/examplemod/examples/ExampleConstructorPatch.java b/src/main/java/examplemod/examples/ExampleConstructorPatch.java index 7b48c45..0ad921a 100644 --- a/src/main/java/examplemod/examples/ExampleConstructorPatch.java +++ b/src/main/java/examplemod/examples/ExampleConstructorPatch.java @@ -22,5 +22,4 @@ static void onExit(@Advice.This RabbitMob rabbitMob) { // Debug message to know it's working System.out.println("Exited RabbitMob constructor: " + rabbitMob.getStringID()); } - } diff --git a/src/main/java/examplemod/examples/ExampleObjectEntity.java b/src/main/java/examplemod/examples/ExampleObjectEntity.java new file mode 100644 index 0000000..74eae65 --- /dev/null +++ b/src/main/java/examplemod/examples/ExampleObjectEntity.java @@ -0,0 +1,97 @@ +package examplemod.examples; + +import examplemod.examples.events.ExampleEvent; +import examplemod.examples.events.ExampleLevelEvent; +import necesse.engine.GameEvents; +import necesse.engine.network.server.ServerClient; +import necesse.entity.objectEntity.ObjectEntity; +import necesse.level.maps.Level; + +import java.awt.*; + +public class ExampleObjectEntity extends ObjectEntity { + // Tracks whether a player was on it last tick (so we only trigger once per step-on) + private boolean pressed = false; + + // Small cooldown to avoid rapid re-triggers if the player jitters on the edge + private long nextTriggerTime = 0L; + + + public ExampleObjectEntity(Level level, int tileX, int tileY) { + // Create an ObjectEntity instance for the object placed at (tileX, tileY) on this level. + // The string is this ObjectEntity's type/id used by the engine for identification (save/load/sync). + super(level, "examplelevelevent", tileX, tileY); + + // no need to save this is only a demo + this.shouldSave = false; + } + + private Rectangle getWorldTileRect() { + // Full tile area in world pixels + return new Rectangle(tileX * 32, tileY * 32, 32, 32); + } + + @Override + public void serverTick() { + super.serverTick(); + + // Get the level + Level level = getLevel(); + + // Get the current time + long now = level.getTime(); + Rectangle tileRect = getWorldTileRect(); + + boolean hasPlayerOnTile = false; + + + // Check all connected server clients + for (ServerClient client : level.getServer().getClients()) { + if (client == null || client.playerMob == null) continue; + + // we want to specifically target the player rather than any mob + if (client.playerMob.getCollision().intersects(tileRect)) { + hasPlayerOnTile = true; + break; + } + } + + // if there's a player on the tile, and it's not pressed, and it's time to check + if (hasPlayerOnTile && !pressed && now >= nextTriggerTime) { + pressed = true; + nextTriggerTime = now + 300; // 300 time units cooldown (tweak as you like) + + int px = tileX * 32 + 16; + int py = tileY * 32 + 16; + + // If your ExampleLevelEvent targets a player slot, pick the first player standing on it: + int targetSlot = -1; + for (ServerClient client : level.getServer().getClients()) { + if (client != null && client.playerMob != null && client.playerMob.getCollision().intersects(tileRect)) { + targetSlot = client.slot; + + break; + } + } + + /* + * This is an example of triggering a level event (in this case ExampleLevelEvent). + * Use events.add(...) when both client and server need to be sent it. + * Use events.addHidden(...) when only the server needs to be sent it. + */ + level.entityManager.events.add(new ExampleLevelEvent(targetSlot, px, py)); + + /* + * This is an example of triggering an event (in this case ExampleEvent) + * which will fire the event listener for ExampleEvent to run its code + */ + GameEvents.triggerEvent(new ExampleEvent(level, targetSlot)); + } + + // Reset when nobody is standing on it + if (!hasPlayerOnTile) { + pressed = false; + } + } +} + diff --git a/src/main/java/examplemod/examples/events/ExampleEvent.java b/src/main/java/examplemod/examples/events/ExampleEvent.java index 83e74e5..4c4cc7e 100644 --- a/src/main/java/examplemod/examples/events/ExampleEvent.java +++ b/src/main/java/examplemod/examples/events/ExampleEvent.java @@ -3,37 +3,33 @@ import necesse.engine.events.GameEvent; import necesse.level.maps.Level; -/** - * A simple custom "game event" for our mod. +/* + * ExampleEvent is a small "notification" object for our mod. * - * This is NOT a LevelEvent (it does not exist in the world, does not tick, and is not drawn). - * Instead, it's a lightweight message object you pass through your own event system - * (for example: GameEvents.triggerEvent(...)) so other parts of your mod can react. + * Compared to a LevelEvent + * it does not exist in the world + * it does not tick + * it does not draw anything * - * Think of it like: "something happened" + "here is the data about what happened". + * Instead, we create one and pass it through GameEvents.triggerEvent(...) + * so any registered listeners can react. */ public class ExampleEvent extends GameEvent { - /** - * The level where the thing happened - */ + // The level where the event happened public final Level level; - /** - * Which connected player this event is about. - */ + // The slot id of the player this event relates to public final int clientSlot; - /** - * Example payload data listeners can use. - */ + // Simple data payload for the demo public final String message; public ExampleEvent(Level level, int clientSlot) { this.level = level; this.clientSlot = clientSlot; - // Simple demo message that listeners can read. + // For a real mod you would usually pass the message into the constructor. this.message = "PING: this message was sent from the ExampleEvent"; } } diff --git a/src/main/java/examplemod/examples/events/ExampleLevelEvent.java b/src/main/java/examplemod/examples/events/ExampleLevelEvent.java index b60321f..82fefe1 100644 --- a/src/main/java/examplemod/examples/events/ExampleLevelEvent.java +++ b/src/main/java/examplemod/examples/events/ExampleLevelEvent.java @@ -4,28 +4,50 @@ import necesse.engine.network.PacketWriter; import necesse.engine.network.server.ServerClient; import necesse.entity.levelEvent.LevelEvent; +import necesse.entity.levelEvent.mobAbilityLevelEvent.MobHealthChangeEvent; +import necesse.entity.mobs.GameDamage; +import necesse.entity.particle.Particle; + +import java.awt.Color; /** - * Very simple LevelEvent demo: - * Server-side only: sends a chat message to a specific player, then ends. - * - * Note: If you spawn this with events.addHidden(...), clients will never receive it, - * so the packet methods below won't be used (they're included for when/if you use events.add(...)). + * LevelEvent + * Server: sends a chat message to a specific player. + * Client: spawns a quick burst of particles at a world position. + * events.addHidden(ev) server only + * events.add(ev) server + clients */ public class ExampleLevelEvent extends LevelEvent { // Which ServerClient (player) to message. -1 = invalid/unset. private int targetSlot = -1; - // Message to send (kept non-null for safety). + // Message to send. private String message = ""; + // World position (pixels) where the particle effect should appear. + private int worldX; + private int worldY; + + // Simple lifetime for the client effect (in ticks). + private int ticksLeft = 20; + + // Small guard so the server only sends the message once. + private boolean sentServerMessage = false; + // Required empty constructor for registry/network spawning public ExampleLevelEvent() { } - public ExampleLevelEvent(int targetSlot) { + /** + * targetSlot player to message + * worldX particle position X in pixels (tileX * 32 + 16 is tile center) + * worldY particle position Y in pixels + */ + public ExampleLevelEvent(int targetSlot, int worldX, int worldY) { this.targetSlot = targetSlot; + this.worldX = worldX; + this.worldY = worldY; this.message = "ZING: this message was sent from the ExampleLevelEvent"; } @@ -33,33 +55,122 @@ public ExampleLevelEvent(int targetSlot) { public void init() { super.init(); - // Only the server can access ServerClient and send chat packets. - if (isServer()) { + // Server side: send the message once. + if (isServer() && !sentServerMessage) { + sentServerMessage = true; + + //target the specific client that triggered the event and make sure it's not returned null ServerClient client = level.getServer().getClient(targetSlot); if (client != null) { + + GameDamage dmg = new GameDamage(30F); + + //Apply damage + int healthBefore = client.playerMob.getHealth(); + client.playerMob.isServerHit(dmg, worldX, worldY, 0.0F, null); + int healthAfter = client.playerMob.getHealth(); + + //How much damage was actually taken + int damageTaken = healthBefore - healthAfter; + + if (damageTaken > 0) { + //Restore that amount clamped to current max health, and compute the healing applied because this is a demo, and we aren't mean xD + int finalHealth = Math.min(client.playerMob.getMaxHealth(), healthAfter + damageTaken); + int healApplied = finalHealth - healthAfter; + + //send a health change event to apply + level.entityManager.events.add(new MobHealthChangeEvent(client.playerMob, finalHealth, healApplied)); + } + client.sendChatMessage(message); } - // We're done immediately (no ticking needed). + // Do NOT call over() here if you want clients to see the event. + // Let it exist long enough for the spawn packet to be processed. + } + } + + @Override + public void serverTick() { + super.serverTick(); + + // If this event was added as hidden, it will never go to clients, + // so we can end it right away after doing server work. + if (sentServerMessage) { over(); } } + @Override + public void clientTick() { + super.clientTick(); + + // Quick particle burst for a short time, then end. + if (ticksLeft-- <= 0) { + over(); + return; + } + + Color c = new Color(120, 200, 255); + + for (int i = 0; i < 6; i++) { + float ox = (float) (Math.random() * 24.0 - 12.0); // -12..12 px + float oy = (float) (Math.random() * 24.0 - 12.0); + + level.entityManager + .addParticle(worldX + ox, worldY + oy, Particle.GType.COSMETIC) + .color(c) + .sizeFades(30, 60) + .fadesAlphaTime(250, 150) + .lifeTime(350); + } + } + @Override public void setupSpawnPacket(PacketWriter writer) { super.setupSpawnPacket(writer); - // Write fields in a fixed order... + /* + * setupSpawnPacket(...) is called when the server spawns this LevelEvent to clients + * (when you use level.entityManager.events.add/level.entityManager.events.addHidden) + * + * Anything you want the client-side version of this event to know must be written here. + * The client will read these values in applySpawnPacket(...) in the exact same order. + */ + + // Who the event should target (which player/server client slot) writer.putNextInt(targetSlot); + + // Any text payload we want associated with the event writer.putNextString(message); + + // World position for effects (these should be pixel coords, not tile coords) + writer.putNextInt(worldX); + writer.putNextInt(worldY); + + // How long the client-side effect should run + writer.putNextInt(ticksLeft); } @Override public void applySpawnPacket(PacketReader reader) { super.applySpawnPacket(reader); - // ...and read them back in the same order. + /* + * applySpawnPacket(...) is called on the client when it receives the spawn packet + * for this LevelEvent. + * + * Make sure you read values back in the same order you wrote them in setupSpawnPacket(...), + * otherwise you'll desync fields and get confusing bugs. + */ + + // Read target player slot + message payload targetSlot = reader.getNextInt(); message = reader.getNextString(); + + // Read effect position + lifetime + worldX = reader.getNextInt(); + worldY = reader.getNextInt(); + ticksLeft = reader.getNextInt(); } } diff --git a/src/main/java/examplemod/examples/items/tools/ExampleProjectileWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleMagicProjectileWeapon.java similarity index 96% rename from src/main/java/examplemod/examples/items/tools/ExampleProjectileWeapon.java rename to src/main/java/examplemod/examples/items/tools/ExampleMagicProjectileWeapon.java index fe57eb1..f2440ae 100644 --- a/src/main/java/examplemod/examples/items/tools/ExampleProjectileWeapon.java +++ b/src/main/java/examplemod/examples/items/tools/ExampleMagicProjectileWeapon.java @@ -18,13 +18,13 @@ import necesse.level.maps.Level; // Extends MagicProjectileToolItem -public class ExampleProjectileWeapon extends MagicProjectileToolItem { +public class ExampleMagicProjectileWeapon extends MagicProjectileToolItem { // This weapon will shoot out some projectiles. // Different classes for specific projectile weapon are already in place that you can use: // GunProjectileToolItem, BowProjectileToolItem, BoomerangToolItem, etc. - public ExampleProjectileWeapon() { + public ExampleMagicProjectileWeapon() { super(400, null); rarity = Rarity.RARE; attackAnimTime.setBaseValue(300); @@ -42,7 +42,7 @@ public ExampleProjectileWeapon() { @Override public ListGameTooltips getPreEnchantmentTooltips(InventoryItem item, PlayerMob perspective, GameBlackboard blackboard) { ListGameTooltips tooltips = super.getPreEnchantmentTooltips(item, perspective, blackboard); - tooltips.add(Localization.translate("itemtooltip", "examplestafftip")); + tooltips.add(Localization.translate("itemtooltip", "examplemagicstafftip")); return tooltips; } diff --git a/src/main/java/examplemod/examples/items/tools/ExampleSwordWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleMeleeSwordWeapon.java similarity index 86% rename from src/main/java/examplemod/examples/items/tools/ExampleSwordWeapon.java rename to src/main/java/examplemod/examples/items/tools/ExampleMeleeSwordWeapon.java index 76fad1e..1e72fee 100644 --- a/src/main/java/examplemod/examples/items/tools/ExampleSwordWeapon.java +++ b/src/main/java/examplemod/examples/items/tools/ExampleMeleeSwordWeapon.java @@ -4,11 +4,11 @@ import necesse.inventory.item.toolItem.swordToolItem.SwordToolItem; // Extends SwordToolItem -public class ExampleSwordWeapon extends SwordToolItem { +public class ExampleMeleeSwordWeapon extends SwordToolItem { // Weapon attack textures are loaded from resources/player/weapons/ - public ExampleSwordWeapon() { + public ExampleMeleeSwordWeapon() { super(400, null); rarity = Item.Rarity.UNCOMMON; attackAnimTime.setBaseValue(300); // 300 ms attack time diff --git a/src/main/java/examplemod/examples/items/tools/ExampleSummonOrbWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleSummonOrbWeapon.java new file mode 100644 index 0000000..77d32d7 --- /dev/null +++ b/src/main/java/examplemod/examples/items/tools/ExampleSummonOrbWeapon.java @@ -0,0 +1,29 @@ +package examplemod.examples.items.tools; + +import necesse.entity.mobs.itemAttacker.FollowPosition; +import necesse.inventory.item.Item; +import necesse.inventory.item.toolItem.summonToolItem.SummonToolItem; +import necesse.inventory.lootTable.presets.SummonWeaponsLootTable; + +public class ExampleSummonOrbWeapon extends SummonToolItem { + public ExampleSummonOrbWeapon() { + // mobStringID, followPosition, summonSpaceTaken, enchantCost, lootTableCategory + super("examplesummonmob", + FollowPosition.PYRAMID, + 1.0F, + 400, + SummonWeaponsLootTable.summonWeapons); + + this.rarity = Item.Rarity.UNCOMMON; + + // This damage is what gets injected into your minion via mob.updateDamage(getAttackDamage(item)) + this.attackDamage.setBaseValue(50.0F).setUpgradedValue(1.0F, 45.0F); + + // Offset the X location of the attack texture + this.attackXOffset = 15; + // Offset the X location of the attack texture + this.attackYOffset = 10; + + + } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/mobs/ExampleSummonWeaponMob.java b/src/main/java/examplemod/examples/mobs/ExampleSummonWeaponMob.java new file mode 100644 index 0000000..781a1dc --- /dev/null +++ b/src/main/java/examplemod/examples/mobs/ExampleSummonWeaponMob.java @@ -0,0 +1,87 @@ +package examplemod.examples.mobs; + +import java.awt.*; +import java.util.List; + +import necesse.engine.gameLoop.tickManager.TickManager; +import necesse.entity.mobs.MobDrawable; +import necesse.entity.mobs.PlayerMob; +import necesse.entity.mobs.ai.behaviourTree.BehaviourTreeAI; +import necesse.entity.mobs.ai.behaviourTree.trees.PlayerFollowerCollisionChaserAI; +import necesse.entity.mobs.summon.summonFollowingMob.attackingFollowingMob.AttackingFollowingMob; +import necesse.gfx.camera.GameCamera; +import necesse.gfx.drawOptions.DrawOptions; +import necesse.gfx.drawables.OrderableDrawables; +import necesse.gfx.gameTexture.GameTexture; +import necesse.level.maps.Level; +import necesse.level.maps.light.GameLight; + +public class ExampleSummonWeaponMob extends AttackingFollowingMob { + + // Loaded in examplemod.ExampleMod.initResources() + public static GameTexture texture; + + public ExampleSummonWeaponMob() { + super(20); // health + setSpeed(60.0F); + setFriction(2.0F); + this.attackCooldown = 500; + + this.collision = new Rectangle(-10, -7, 20, 14); + this.hitBox = new Rectangle(-12, -14, 24, 24); + this.selectBox = new Rectangle(-13, -14, 26, 24); + } + + @Override + public void init() { + super.init(); + + // Range, damage, knockback, cooldown etc. + // This uses this.summonDamage which SummonToolItem injects at spawn time. + this.ai = new BehaviourTreeAI<>(this, + new PlayerFollowerCollisionChaserAI<>( + 576, // target range + this.summonDamage, + 30, // knockback + 500, // attack windup? + 640, // chase range + 64 // give up / pathing distance + ) + ); + } + + @Override + protected void addDrawables(List list, OrderableDrawables tileList, OrderableDrawables topList, Level level, int x, int y, TickManager tickManager, GameCamera camera, PlayerMob perspective) { + super.addDrawables(list, tileList, topList, level, x, y, tickManager, camera, perspective); + // Tile positions are basically level positions divided by 32. getTileX() does this for us etc. + GameLight light = level.getLightLevel(getTileX(), getTileY()); + int drawX = camera.getDrawX(x) - 32; + int drawY = camera.getDrawY(y) - 51; + + // A helper method to get the sprite of the current animation/direction of this mob + Point sprite = getAnimSprite(x, y, getDir()); + + drawY += getBobbing(x, y); + drawY += getLevel().getTile(getTileX(), getTileY()).getMobSinkingAmount(this); + + DrawOptions drawOptions = texture.initDraw() + .sprite(sprite.x, sprite.y, 64) + .light(light) + .pos(drawX, drawY); + + list.add(new MobDrawable() { + @Override + public void draw(TickManager tickManager) { + drawOptions.draw(); + } + }); + + addShadowDrawables(tileList, level, x, y, light, camera); + } + + @Override + public int getRockSpeed() { + // Change the speed at which this mobs animation plays + return 20; + } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java b/src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java index 459dab9..51b5b83 100644 --- a/src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java +++ b/src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java @@ -1,10 +1,9 @@ package examplemod.examples.objects; -import examplemod.examples.events.ExampleEvent; -import examplemod.examples.events.ExampleLevelEvent; -import necesse.engine.GameEvents; +import examplemod.examples.ExampleObjectEntity; import necesse.engine.gameLoop.tickManager.TickManager; import necesse.entity.mobs.PlayerMob; +import necesse.entity.objectEntity.ObjectEntity; import necesse.gfx.camera.GameCamera; import necesse.gfx.drawOptions.texture.TextureDrawOptionsEnd; import necesse.gfx.drawables.LevelSortedDrawable; @@ -18,21 +17,20 @@ import java.util.List; /* - * Basic placeable object that: - * draws a single 32x32 sprite in the world - * - * spawns a LevelEvent that sends a chat message (event handles the message, not the object) - * and also fires an ExampleEvent which triggers an event listener to run its code + * Basic placeable demo object that: + * - draws a 32x32 sprite in the world + * - on interact (server side), spawns our ExampleLevelEvent + * - also triggers our custom ExampleEvent through GameEvents (so listeners can react) */ public class ExampleLevelEventObject extends GameObject { - // Loaded once from your mod resources in loadTextures() + // Loaded once from mod resources in loadTextures() private GameTexture texture; public ExampleLevelEventObject() { - // 32x32 collision/selection box - super(new Rectangle(32, 32)); - this.isSolid = true; + //no physics shape + super(new Rectangle()); + this.isSolid = false; } @Override @@ -40,6 +38,7 @@ public void loadTextures() { super.loadTextures(); // Loads: src/main/resources/objects/exampleleveleventobject.png + // (no ".png" in the string) this.texture = GameTexture.fromFile("objects/exampleleveleventobject"); } @@ -48,38 +47,30 @@ public void addDrawables(List list, OrderableDrawables tile Level level, int tileX, int tileY, TickManager tickManager, GameCamera camera, PlayerMob perspective) { - // Lighting at this tile (so the sprite matches world lighting) + // Match sprite lighting to the level light at this tile GameLight light = level.getLightLevel(tileX, tileY); - // Convert tile coords -> screen draw coords + // Convert tile coordinates to screen draw coordinates int drawX = camera.getTileDrawX(tileX); int drawY = camera.getTileDrawY(tileY); - // Build the draw options once, then draw them inside the drawable + // Build draw options once (sprite + lighting + position) final TextureDrawOptionsEnd opts = this.texture.initDraw() - .sprite(0, 0, 32) // first sprite, 32x32 + .sprite(0, 0, 32) // sprite index (0,0), size 32 .light(light) .pos(drawX, drawY); - // Add a drawable so the engine draws it in correct Y-sort order - list.add(new LevelSortedDrawable(this, tileX, tileY) { - @Override - public int getSortY() { - return 16; // typical "middle of the tile" sort value for 1-tile objects - } - - @Override - public void draw(TickManager tickManager) { - opts.draw(); - } - }); + /* + */ + tileList.add(tm -> opts.draw()); } + @Override public void drawPreview(Level level, int tileX, int tileY, int rotation, float alpha, PlayerMob player, GameCamera camera) { - // This is the translucent "ghost" preview when placing the object + // Placement preview ("ghost" sprite) while holding the item GameLight light = level.getLightLevel(tileX, tileY); int drawX = camera.getTileDrawX(tileX); int drawY = camera.getTileDrawY(tileY); @@ -92,57 +83,7 @@ public void drawPreview(Level level, int tileX, int tileY, int rotation, float a } @Override - public boolean canInteract(Level level, int x, int y, PlayerMob player) { - return true; - } - - @Override - public void interact(Level level, int x, int y, PlayerMob player) { - - /* - * interact(...) is called on BOTH sides in multiplayer: - * client: when you click / interact locally - * server: when the server processes the interaction - * - * Anything that changes game state (spawning events, sending chat, triggering mod logic) - * should be done on the SERVER to avoid double-running and desync. - */ - if (level.isServer()) { - - /* - * In multiplayer, a PlayerMob on the server is tied to a ServerClient. - * The "slot" is a simple way to identify which connected client/player we mean. - * We'll pass this into our event so it knows who to target. - */ - int clientSlot = player.getServerClient().slot; - - /* - * Spawn our LevelEvent. - * - * We keep the object simple: it just creates the event. - * The ExampleLevelEvent itself contains the "what happens" logic (like sending a message). - */ - ExampleLevelEvent ev = new ExampleLevelEvent(clientSlot); - - /* - * addHidden(...) means "server-only": - * the event is added to the level's event manager - * it will NOT send a spawn packet to clients - * - * Use addHidden when the event is purely server logic (like chat/logging). - * Use events.add(ev) if you want clients to also receive/tick/draw the event. - */ - level.entityManager.events.addHidden(ev); - - - /* - * This is an example of triggering an event (in this case ExampleEvent) - * which will fire the event listener for ExampleEvent to run its code - */ - GameEvents.triggerEvent(new ExampleEvent(level, clientSlot)); - } - - // Always call super unless you specifically want to block default behavior. - super.interact(level, x, y, player); + public ObjectEntity getNewObjectEntity(Level level, int x, int y) { + return new ExampleObjectEntity(level, x, y); } } diff --git a/src/main/resources/items/examplestaff.png b/src/main/resources/items/examplemagicstaff.png similarity index 100% rename from src/main/resources/items/examplestaff.png rename to src/main/resources/items/examplemagicstaff.png diff --git a/src/main/resources/items/examplesword.png b/src/main/resources/items/examplemeleesword.png similarity index 100% rename from src/main/resources/items/examplesword.png rename to src/main/resources/items/examplemeleesword.png diff --git a/src/main/resources/items/examplesummonorb.png b/src/main/resources/items/examplesummonorb.png new file mode 100644 index 0000000000000000000000000000000000000000..d662c7030376255342fd7aab454e5b3c4e0dfd76 GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVq#_D)r)yK4P@j|PZ!6K zh}Pr;307rUgGs9r^OjzyulaQH?RyWS$(4`mQ9y+XR=fvl{BjLSoET&eI6o0@aq{Qhu*F%Um*=iP17n28opgt@vPYU2 z`570zW{Q~e=oIe@yY{1hKdj__u=1z)#8(|G%yLYNbr)R>`=}wo%5d<2TcX!5GeMw- O7(8A5T-G@yGywouZh(CN literal 0 HcmV?d00001 diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index 8e92f3c..7c67aea 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -25,20 +25,22 @@ examplegrassseed=Example Grass Seed examplehuntincursionmaterial=Example Hunt Incursion Material examplepotion=Example Potion examplefood=Example Food -examplesword=Example Sword -examplestaff=Example Staff +examplemeleesword=Example Melee Sword +examplemagicstaff=Example Magic Staff +examplesummonorb=Example Summon Orb examplehelmet=Example Helmet examplechestplate=Example Chestplate exampleboots=Example Boots [itemtooltip] -examplestafftip=Shoots a homing, piercing projectile +examplemagicstafftip=Shoots a homing, piercing projectile examplepotionitemtip= An example potion [mob] examplemob=Example Mob examplebossmob=Example Boss +examplesummonmob=Example Summon Mob [buff] examplebuff=Example Buff diff --git a/src/main/resources/mobs/examplesummonmob.png b/src/main/resources/mobs/examplesummonmob.png new file mode 100644 index 0000000000000000000000000000000000000000..3f85699afdf8ea147cad00e2ea8ac2dd2dcb806a GIT binary patch literal 4665 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 BSz-VH literal 0 HcmV?d00001 diff --git a/src/main/resources/mobs/icons/examplesummonmob.png b/src/main/resources/mobs/icons/examplesummonmob.png new file mode 100644 index 0000000000000000000000000000000000000000..14ad9f5c8bab6b095bc0bfa597eb0ca351469b5a GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^Vn8gz!3HE#_5EXk6k~CayA$KhlREW44okYDuOkD) z#(wTUiL5}rLb6AYF9SoB8UsT^3j@P1pisjL28L1t28LG&3=CE?7#PG0=Ijcz0ZOnX zdAqwX9Al_s*gtEzuQO1Dv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVgee^cX6$ZAW-O^ zr;B5VN9WOLhI~y1B5n0OYI%O|<~fS-9eBZe$8$>S;tdNpcbgY{VbI$lELN_@F2t_P zcb5A@#>yF6>bSSmuCK|I%Vl)7*mUm4j#bBfgxg9}W(kW7G(X++kn(tbvoj6ha^sn=u;}!CA7{C12d1b#uI(6OLySZ=m7+$=det7kg z6RSIKn(BS(vil&$U-`F4WtlO9$D>VV=`wEK>~FT@y$abYUhFVdQ&MBb@0J>nN?f?J) literal 0 HcmV?d00001 diff --git a/src/main/resources/player/weapons/examplemagicstaff.png b/src/main/resources/player/weapons/examplemagicstaff.png new file mode 100644 index 0000000000000000000000000000000000000000..0b2ed0ce28dd1d1544de4fc1d5c614ba1eb9687f GIT binary patch literal 467 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEjKx9jPK-BC>eK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e4;GjG>NU|E%S{&Oi~)0*}aI1_o|n5N2eUHAey{$X?><>&pI$iIssxCt=}?k3biQ zd%8G=M6|xWVawNKz~J!k`FTzs6GwCQ{D%wF<~4d3Fqpr8@Z`WbNtrKldOKzoaLO#W zXvPwtA#&O~f@Moxrm4uUFXGGHwRvy#djf(sX*A5z19=vL;u+@+A7=vB<1KsV`KVQC_ShfDw?G>qhQu;H#u2q%% zZ@`#fc|Pt#cc#+rqWP8Ye-_Ub2|4{&e&5`*2D_8{1$4C7YA1zpbZzm7Qq@=$ddbT% zP6L}LOp?~9rp5v|X)w{&ccuo>ox%zg+ z!9#l`dwChm;-=X2FZb`yI>x=tlKrYtxmJ((o7K0t_pWohVLtmnAu!Y!JYD@<);T3K F0RZ=Lrjq~w literal 0 HcmV?d00001 diff --git a/src/main/resources/player/weapons/examplemeleesword.png b/src/main/resources/player/weapons/examplemeleesword.png new file mode 100644 index 0000000000000000000000000000000000000000..be64290018974b32623b47692d777f557d2dbc27 GIT binary patch literal 451 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofvPP)Tsw@SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6XxjAYL$MSD+10f-TA0 z-G$*8Lmk8ZS<8K$fg+p*9+AZi4BWyX%*Zfnjs#GUy~NYkmHibHD+9BsclWYyKo_uk zx;TbJw7#8Y%hzNe<39hGu*HlQDtz~x+0H$9_uvVmkBqNP!{p}O=>-?6Emllcn|F|f zJvV9V78i|a{o5Iq`re-Y=`p`(RaXA@h0kKsuKYJ#*{AQE9mdcjvHDlU@l59?jo@8| zD<^V0Go75$&fefJkpAYpMf!@wR|SuBY~SBtdLZ0!$Ls2p;LygE~bs{8ZBfT6_T>FVdQ&MBb@0R7XZN&o-= literal 0 HcmV?d00001 diff --git a/src/main/resources/player/weapons/examplestaff.png b/src/main/resources/player/weapons/examplestaff.png deleted file mode 100644 index b277ac2e9d54f6e1c3cefffe05044f830f899209..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 540 zcmV+%0^|LOP)4M~b}9L#~V3`rJB%0W_cKe-`El`@N#A<06GnuL}i zN!3!H79q((&DIkwLXsx^3u=ZW3oY88)U4ZS;y^S{GlI%V$J|#aH4d=>LsJJ56Ox3= zM|y-H*1)>8LJ}+vY)9hlB{DG#5`e2kPM1*vN^&(_qL`Fi4a!h34}tX2b({rXNWxMY zFt?-hZRnaw$SEIL1~8AHl#j69Bf5JA#DS!QeK@{Ea{HEjtmSN z`?>!lvI6-E$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZKG z?e49SyvJQj%)RuPXF>%sRCroVws%A|q;%&Q+&LmuzvIZs zZ5=TOoYpu+*z8TMIDOzigLUX5rsm`B2J#1LTIMk&erh@JK>zgx#x+MIY*J;dB<#Kl gT1gb;wmoEL&?&HgJ$pr1HPEdLp00i_>zopr0N-kL&;S4c literal 0 HcmV?d00001 diff --git a/src/main/resources/player/weapons/examplesword.png b/src/main/resources/player/weapons/examplesword.png deleted file mode 100644 index d2845b07c82a332cbfe3f21d0a5d6a5ff38465ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 516 zcmV+f0{i`mP)4$1pN7Vs+C%IF<#;CFnuLI1nMngv(MMd1eMs$l(e-S~->l-C+B>n7NFl4EW!eJgGFY^LEdTQGFF0h7LJHl@G|idd5Q6!bnDoN0#=-zo zM~jd`@h!fP;#Gpg?iR;aun?n3NU>l`8v_|q1QYe=`+rc1Gl~dKAum9q;k%@*5Vi5jG zy6LnGq5ltG{AHM13F+tEdj@HFDoL>nTnG^pKIlDUn)Do?i5l;)3bt|zlsRachR8{u zv Date: Fri, 6 Feb 2026 05:04:06 +0000 Subject: [PATCH 21/21] Add arrow ammo, bow, projectile & arrow buff Introduce arrow ammo, a ranged bow, projectile, and associated regen buff plus assets and registry updates. Adds ExampleArrowItem (arrow stats and projectile lookup), ExampleRangedBowWeapon (bow stats/behavior), ExampleArrowProjectile (custom draw, target filtering, hit logic that applies a 4s regen buff and 50% drop chance), and ExampleArrowBuff (server-side heal over time: 5 HP per 250ms). Registers new items, ammo and projectiles in ExampleModItems/ExampleModProjectiles and registers the buff in ExampleModBuffs. Also renamed/moved several example classes/packages (ExampleArmorSetBonusBuff -> ExampleArmorSetBuff, ExampleMagicProjectileWeapon -> ExampleMagicStaffWeapon, ExampleProjectile moved into projectiles package) and updated localization entries and sprite resources. --- .../examplemod/Loaders/ExampleModBuffs.java | 3 +- .../examplemod/Loaders/ExampleModItems.java | 10 +- .../Loaders/ExampleModProjectiles.java | 6 +- ...onusBuff.java => ExampleArmorSetBuff.java} | 4 +- .../examples/buffs/ExampleArrowBuff.java | 52 ++++++ .../examples/items/ammo/ExampleArrowItem.java | 37 +++++ ...apon.java => ExampleMagicStaffWeapon.java} | 6 +- .../items/tools/ExampleRangedBowWeapon.java | 31 ++++ .../projectiles/ExampleArrowProjectile.java | 157 ++++++++++++++++++ .../{ => projectiles}/ExampleProjectile.java | 2 +- src/main/resources/items/examplearrow.png | Bin 0 -> 464 bytes src/main/resources/items/examplerangedbow.png | Bin 0 -> 418 bytes src/main/resources/locale/en.lang | 8 +- .../player/weapons/examplerangedbow.png | Bin 0 -> 423 bytes .../projectiles/examplearrowprojectile.png | Bin 0 -> 448 bytes 15 files changed, 303 insertions(+), 13 deletions(-) rename src/main/java/examplemod/examples/buffs/{ExampleArmorSetBonusBuff.java => ExampleArmorSetBuff.java} (87%) create mode 100644 src/main/java/examplemod/examples/buffs/ExampleArrowBuff.java create mode 100644 src/main/java/examplemod/examples/items/ammo/ExampleArrowItem.java rename src/main/java/examplemod/examples/items/tools/{ExampleMagicProjectileWeapon.java => ExampleMagicStaffWeapon.java} (96%) create mode 100644 src/main/java/examplemod/examples/items/tools/ExampleRangedBowWeapon.java create mode 100644 src/main/java/examplemod/examples/projectiles/ExampleArrowProjectile.java rename src/main/java/examplemod/examples/{ => projectiles}/ExampleProjectile.java (98%) create mode 100644 src/main/resources/items/examplearrow.png create mode 100644 src/main/resources/items/examplerangedbow.png create mode 100644 src/main/resources/player/weapons/examplerangedbow.png create mode 100644 src/main/resources/projectiles/examplearrowprojectile.png diff --git a/src/main/java/examplemod/Loaders/ExampleModBuffs.java b/src/main/java/examplemod/Loaders/ExampleModBuffs.java index 45d4f3b..f7a5ce7 100644 --- a/src/main/java/examplemod/Loaders/ExampleModBuffs.java +++ b/src/main/java/examplemod/Loaders/ExampleModBuffs.java @@ -7,6 +7,7 @@ public class ExampleModBuffs { public static void load(){ // Register our buff BuffRegistry.registerBuff("examplebuff", new ExampleBuff()); - BuffRegistry.registerBuff("examplearmorsetbonus", new ExampleArmorSetBonusBuff()); + BuffRegistry.registerBuff("examplearmorsetbonus", new ExampleArmorSetBuff()); + BuffRegistry.registerBuff("examplearrowbuff", new ExampleArrowBuff()); } } diff --git a/src/main/java/examplemod/Loaders/ExampleModItems.java b/src/main/java/examplemod/Loaders/ExampleModItems.java index cf31ab5..2b37fda 100644 --- a/src/main/java/examplemod/Loaders/ExampleModItems.java +++ b/src/main/java/examplemod/Loaders/ExampleModItems.java @@ -1,13 +1,15 @@ package examplemod.Loaders; +import examplemod.examples.items.ammo.ExampleArrowItem; import examplemod.examples.items.armor.ExampleBootsArmorItem; import examplemod.examples.items.armor.ExampleChestArmorItem; import examplemod.examples.items.armor.ExampleHelmetArmorItem; import examplemod.examples.items.consumable.ExampleFoodItem; import examplemod.examples.items.consumable.ExamplePotionItem; import examplemod.examples.items.materials.*; -import examplemod.examples.items.tools.ExampleMagicProjectileWeapon; +import examplemod.examples.items.tools.ExampleMagicStaffWeapon; import examplemod.examples.items.tools.ExampleMeleeSwordWeapon; +import examplemod.examples.items.tools.ExampleRangedBowWeapon; import examplemod.examples.items.tools.ExampleSummonOrbWeapon; import necesse.engine.registries.ItemRegistry; @@ -25,8 +27,9 @@ public static void load(){ // Tools ItemRegistry.registerItem("examplemeleesword", new ExampleMeleeSwordWeapon(), 20, true); - ItemRegistry.registerItem("examplemagicstaff", new ExampleMagicProjectileWeapon(), 30, true); + ItemRegistry.registerItem("examplemagicstaff", new ExampleMagicStaffWeapon(), 30, true); ItemRegistry.registerItem("examplesummonorb", new ExampleSummonOrbWeapon(),40,true); + ItemRegistry.registerItem("examplerangedbow", new ExampleRangedBowWeapon(),10,true); // Armor ItemRegistry.registerItem("examplehelmet", new ExampleHelmetArmorItem(), 200f, true); @@ -36,5 +39,8 @@ public static void load(){ // Consumables ItemRegistry.registerItem("examplepotion", new ExamplePotionItem(), 10, true); ItemRegistry.registerItem("examplefood", new ExampleFoodItem(), 15, true); + + // Ammo + ItemRegistry.registerItem("examplearrow", new ExampleArrowItem(),5,true); } } diff --git a/src/main/java/examplemod/Loaders/ExampleModProjectiles.java b/src/main/java/examplemod/Loaders/ExampleModProjectiles.java index c910ab9..a6c190d 100644 --- a/src/main/java/examplemod/Loaders/ExampleModProjectiles.java +++ b/src/main/java/examplemod/Loaders/ExampleModProjectiles.java @@ -1,11 +1,15 @@ package examplemod.Loaders; -import examplemod.examples.ExampleProjectile; +import examplemod.examples.projectiles.ExampleArrowProjectile; +import examplemod.examples.projectiles.ExampleProjectile; import necesse.engine.registries.ProjectileRegistry; public class ExampleModProjectiles { public static void load(){ // Register our projectile ProjectileRegistry.registerProjectile("exampleprojectile", ExampleProjectile.class, "exampleprojectile", "exampleprojectile_shadow"); + + // Register our arrow projectile + ProjectileRegistry.registerProjectile("examplearrowprojectile", ExampleArrowProjectile.class, "examplearrowprojectile","arrow_shadow"); } } diff --git a/src/main/java/examplemod/examples/buffs/ExampleArmorSetBonusBuff.java b/src/main/java/examplemod/examples/buffs/ExampleArmorSetBuff.java similarity index 87% rename from src/main/java/examplemod/examples/buffs/ExampleArmorSetBonusBuff.java rename to src/main/java/examplemod/examples/buffs/ExampleArmorSetBuff.java index 65df04f..ec17866 100644 --- a/src/main/java/examplemod/examples/buffs/ExampleArmorSetBonusBuff.java +++ b/src/main/java/examplemod/examples/buffs/ExampleArmorSetBuff.java @@ -6,8 +6,8 @@ import necesse.entity.mobs.buffs.BuffModifiers; import necesse.entity.mobs.buffs.staticBuffs.armorBuffs.setBonusBuffs.SimpleSetBonusBuff; -public class ExampleArmorSetBonusBuff extends SimpleSetBonusBuff { - public ExampleArmorSetBonusBuff() { +public class ExampleArmorSetBuff extends SimpleSetBonusBuff { + public ExampleArmorSetBuff() { super( new ModifierValue<>(BuffModifiers.ALL_DAMAGE, 0.10f), new ModifierValue<>(BuffModifiers.SPEED, 0.10f) diff --git a/src/main/java/examplemod/examples/buffs/ExampleArrowBuff.java b/src/main/java/examplemod/examples/buffs/ExampleArrowBuff.java new file mode 100644 index 0000000..cedb0e2 --- /dev/null +++ b/src/main/java/examplemod/examples/buffs/ExampleArrowBuff.java @@ -0,0 +1,52 @@ +package examplemod.examples.buffs; + +import necesse.entity.levelEvent.mobAbilityLevelEvent.MobHealthChangeEvent; +import necesse.entity.mobs.Mob; +import necesse.entity.mobs.buffs.ActiveBuff; +import necesse.entity.mobs.buffs.BuffEventSubscriber; +import necesse.entity.mobs.buffs.staticBuffs.Buff; + +public class ExampleArrowBuff extends Buff { + + // Heal interval in milliseconds + private static final int HEAL_INTERVAL_MS = 250; + + public ExampleArrowBuff() { + this.canCancel = false; + this.isVisible = false; + this.isPassive = false; + this.shouldSave = false; + } + + @Override + public void init(ActiveBuff buff, BuffEventSubscriber eventSubscriber) { + buff.getGndData().setInt("timePassed", 0); + } + @Override + public void serverTick(ActiveBuff buff) { + Mob m = buff.owner; + if (m == null) return; + + int heal = buff.getGndData().getInt("healPerTick"); + if (heal <= 0) return; + + int accum = buff.getGndData().getInt("timePassed"); + accum += 50; + + if (accum < HEAL_INTERVAL_MS) { + buff.getGndData().setInt("timePassed", accum); + return; + } + + accum -= HEAL_INTERVAL_MS; + buff.getGndData().setInt("timePassed", accum); + + int before = m.getHealth(); + int finalHealth = Math.min(m.getMaxHealth(), before + heal); + int applied = finalHealth - before; + + if (applied > 0) { + m.getLevel().entityManager.events.add(new MobHealthChangeEvent(m, finalHealth, applied)); + } + } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/items/ammo/ExampleArrowItem.java b/src/main/java/examplemod/examples/items/ammo/ExampleArrowItem.java new file mode 100644 index 0000000..058b331 --- /dev/null +++ b/src/main/java/examplemod/examples/items/ammo/ExampleArrowItem.java @@ -0,0 +1,37 @@ +package examplemod.examples.items.ammo; + +import necesse.engine.registries.ProjectileRegistry; +import necesse.entity.mobs.GameDamage; +import necesse.entity.mobs.Mob; +import necesse.entity.mobs.itemAttacker.ItemAttackerMob; +import necesse.entity.projectile.Projectile; +import necesse.inventory.item.arrowItem.ArrowItem; + +public class ExampleArrowItem extends ArrowItem { + + public ExampleArrowItem() { + super(5000); // stack size like vanilla arrows + + // These fields are on ArrowItem (public), and BowProjectileToolItem uses them via modDamage/modVelocity. + this.damage = 8; // adds +8 damage to the bow’s base damage + this.armorPen = 2; // adds armor pen + this.critChance = 0.05f; // +5% base crit + this.speedMod = 1.10f; // 10% faster arrow velocity + } + + @Override + public Projectile getProjectile(float x, float y, float targetX, float targetY, + float velocity, int range, GameDamage damage, int knockback, + ItemAttackerMob owner) { + + // Same exact pattern as StoneArrowItem / IronArrowItem, etc. + return ProjectileRegistry.getProjectile( + "examplearrowprojectile", // your projectile stringID + owner.getLevel(), + x, y, targetX, targetY, + velocity, range, + damage, knockback, + (Mob) owner + ); + } +} diff --git a/src/main/java/examplemod/examples/items/tools/ExampleMagicProjectileWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleMagicStaffWeapon.java similarity index 96% rename from src/main/java/examplemod/examples/items/tools/ExampleMagicProjectileWeapon.java rename to src/main/java/examplemod/examples/items/tools/ExampleMagicStaffWeapon.java index f2440ae..b604b4e 100644 --- a/src/main/java/examplemod/examples/items/tools/ExampleMagicProjectileWeapon.java +++ b/src/main/java/examplemod/examples/items/tools/ExampleMagicStaffWeapon.java @@ -1,6 +1,6 @@ package examplemod.examples.items.tools; -import examplemod.examples.ExampleProjectile; +import examplemod.examples.projectiles.ExampleProjectile; import necesse.engine.localization.Localization; import necesse.engine.network.gameNetworkData.GNDItemMap; import necesse.engine.sound.SoundEffect; @@ -18,13 +18,13 @@ import necesse.level.maps.Level; // Extends MagicProjectileToolItem -public class ExampleMagicProjectileWeapon extends MagicProjectileToolItem { +public class ExampleMagicStaffWeapon extends MagicProjectileToolItem { // This weapon will shoot out some projectiles. // Different classes for specific projectile weapon are already in place that you can use: // GunProjectileToolItem, BowProjectileToolItem, BoomerangToolItem, etc. - public ExampleMagicProjectileWeapon() { + public ExampleMagicStaffWeapon() { super(400, null); rarity = Rarity.RARE; attackAnimTime.setBaseValue(300); diff --git a/src/main/java/examplemod/examples/items/tools/ExampleRangedBowWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleRangedBowWeapon.java new file mode 100644 index 0000000..16740cd --- /dev/null +++ b/src/main/java/examplemod/examples/items/tools/ExampleRangedBowWeapon.java @@ -0,0 +1,31 @@ +package examplemod.examples.items.tools; + +import necesse.inventory.item.Item; +import necesse.inventory.item.toolItem.projectileToolItem.bowProjectileToolItem.BowProjectileToolItem; +import necesse.inventory.lootTable.presets.BowWeaponsLootTable; + +public class ExampleRangedBowWeapon extends BowProjectileToolItem { + public ExampleRangedBowWeapon() { + // (enchantCost, lootTableCategory) + super(100, BowWeaponsLootTable.bowWeapons); + + this.rarity = Item.Rarity.NORMAL; + + // Core stats + this.attackAnimTime.setBaseValue(800); // ms per shot + this.attackDamage.setBaseValue(12.0F); // base bow damage (arrows further modify) + this.attackRange.setBaseValue(600); // tiles-ish range value used by bows + this.velocity.setBaseValue(100); // base projectile velocity (arrows further modify) + this.knockback.setBaseValue(25); + + // Sprite offsets (tune until it looks right in-hand) + this.attackXOffset = 8; + this.attackYOffset = 20; + + // How much the bow sprite “stretches” while charging + this.attackSpriteStretch = 4; + + // Optional + this.canBeUsedForRaids = true; + } +} diff --git a/src/main/java/examplemod/examples/projectiles/ExampleArrowProjectile.java b/src/main/java/examplemod/examples/projectiles/ExampleArrowProjectile.java new file mode 100644 index 0000000..852c929 --- /dev/null +++ b/src/main/java/examplemod/examples/projectiles/ExampleArrowProjectile.java @@ -0,0 +1,157 @@ +package examplemod.examples.projectiles; + +import java.awt.*; +import java.util.List; +import java.util.stream.Stream; + +import necesse.engine.gameLoop.tickManager.TickManager; +import necesse.engine.network.NetworkClient; +import necesse.engine.util.GameRandom; +import necesse.engine.util.GameUtils; +import necesse.entity.mobs.Mob; +import necesse.entity.mobs.PlayerMob; +import necesse.entity.mobs.buffs.ActiveBuff; +import necesse.entity.projectile.Projectile; +import necesse.gfx.camera.GameCamera; +import necesse.gfx.drawOptions.texture.TextureDrawOptionsEnd; +import necesse.gfx.drawables.EntityDrawable; +import necesse.gfx.drawables.LevelSortedDrawable; +import necesse.gfx.drawables.OrderableDrawables; +import necesse.inventory.InventoryItem; +import necesse.level.maps.Level; +import necesse.level.maps.LevelObjectHit; +import necesse.level.maps.light.GameLight; + +public class ExampleArrowProjectile extends Projectile { + + + @Override + public void init() { + super.init(); + this.height = 18.0F; + this.heightBasedOnDistance = true; + setWidth(8.0F); + this.doesImpactDamage = false; + } + + @Override + public void addDrawables(List list, + OrderableDrawables tileList, OrderableDrawables topList, OrderableDrawables overlayList, + Level level, TickManager tickManager, GameCamera camera, PlayerMob perspective) { + if (removed()) return; + + GameLight light = level.getLightLevel(this); + int drawX = camera.getDrawX(this.x) - this.texture.getWidth() / 2; + int drawY = camera.getDrawY(this.y); + + final TextureDrawOptionsEnd options = this.texture.initDraw() + .light(light) + .rotate(getAngle(), this.texture.getWidth() / 2, 0) + .pos(drawX, drawY - (int)getHeight()); + + list.add(new EntityDrawable(this) { + @Override + public void draw(TickManager tickManager) { + options.draw(); + } + }); + + // Shadow + addShadowDrawables(tileList, drawX, drawY, light, getAngle(), 0); + } + @Override + protected Stream streamTargets(Mob owner, Shape hitBounds) { + // The projectile calls this to ask: “Which mobs should I check collisions against this tick?” + + Level level = getLevel(); + // If we don’t have a level there’s nothing to test. + if (level == null || hitBounds == null) return Stream.empty(); + + // collect non player mobs + NetworkClient attackerClient = (owner == null) ? null : GameUtils.getAttackerClient(owner); + + // enemies/settlers/animals/etc) that are inside the projectile_attach hit area. + Stream mobs = level.entityManager.mobs + .streamInRegionsShape(hitBounds, 1) //query mobs in nearby regions + + // Is valid target logic + .filter(m -> owner == null || m.canBeTargeted(owner, attackerClient)); + + + // Collect players even if pvp is off + Stream players = level.entityManager.players + .streamInRegionsShape(hitBounds, 1) + + // Ignore null, the shooter, and players that were removed from the world. + .filter(p -> p != null && p != owner && !p.removed()) + + /* this makes sure the player has a network client + * the client has spawned and is not dead + * the playerMob exists and has a valid Level reference + */ + .filter(p -> { + NetworkClient c = p.getNetworkClient(); + return (c != null + && !c.isDead() + && c.hasSpawned() + && c.playerMob != null + && c.playerMob.getLevel() != null); + }); + + // Combine the two streams into one “things we can collide with” stream. + // PlayerMob is a subclass of Mob + return Stream.concat(mobs, players.map(p -> (Mob) p)); + } + + @Override + public boolean canHit(Mob mob) { + Mob owner = getOwner(); + + // Allow hitting allies + if (owner != null && (mob == owner || mob.isSameTeam(owner))) { + return true; + } + + // Otherwise use normal rules (enemies, etc.) + return super.canHit(mob); + } + + @Override + public void doHitLogic(Mob mob, LevelObjectHit object, float x, float y) { + super.doHitLogic(mob, object, x, y); + + if (!isServer() || mob == null) return; + + int durationMs = 4000; // 4 seconds regen + int healPerTick = 5; // healed every 250ms in the buff = 20 HP/sec + + // Try get existing buff + ActiveBuff existing = mob.buffManager.getBuff("examplearrowbuff"); + if (existing != null) { + // refresh duration + existing.setDurationLeft(durationMs); + + // optionally stack strength + int current = existing.getGndData().getInt("healPerTick"); + existing.getGndData().setInt("healPerTick", Math.max(current, healPerTick)); + + return; + } + + // create new + ActiveBuff regen = new ActiveBuff("examplearrowbuff", mob, durationMs, getOwner()); + regen.getGndData().setInt("healPerTick", healPerTick); + + // add the buff, send an update packet to clients and force update the buff + mob.buffManager.addBuff(regen, true,true,true); + } + + @Override + public void dropItem() { + // Optional: drop your arrow item sometimes, like vanilla StoneArrowProjectile does. + if (GameRandom.globalRandom.getChance(0.5F)) { + getLevel().entityManager.pickups.add(new InventoryItem("examplearrow").getPickupEntity(getLevel(), this.x, this.y) + ); + } + } +} diff --git a/src/main/java/examplemod/examples/ExampleProjectile.java b/src/main/java/examplemod/examples/projectiles/ExampleProjectile.java similarity index 98% rename from src/main/java/examplemod/examples/ExampleProjectile.java rename to src/main/java/examplemod/examples/projectiles/ExampleProjectile.java index 8e46ce8..3b247c0 100644 --- a/src/main/java/examplemod/examples/ExampleProjectile.java +++ b/src/main/java/examplemod/examples/projectiles/ExampleProjectile.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.projectiles; import necesse.engine.gameLoop.tickManager.TickManager; import necesse.entity.mobs.GameDamage; diff --git a/src/main/resources/items/examplearrow.png b/src/main/resources/items/examplearrow.png new file mode 100644 index 0000000000000000000000000000000000000000..8ad20938cd07694ec04cfa6e517c5f43bd89b54f GIT binary patch literal 464 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVq#;|wtgh}pb6*#5lPl*w+Q4Xrpq>|M_gce zYmzH5+vd*P(vD5qvt$3BntA-)pZnFu1<$WlUJ#Emxw`TYXK;M3#FBL%FM3y)qTS-+Vb_U`siBqiPMq zt^FstcbYJK-oB^l0Z?VZ$Cs)*XK+{ve>7HTNmbg%#xl9H_40=+U)#Q&tar^j@Plto z_m>#+z*9Di0h-sBgf!lF`NPX`sn~Pg-Mvf?YD5`d?6L7)dEBLl2PC+!{yjrN_I4+a uKE~@M+7{npCaHgyy>QX@kLwe*cQOoNjnV9lIq{%yWbkzLb6Mw<&;$TcFs|eP literal 0 HcmV?d00001 diff --git a/src/main/resources/items/examplerangedbow.png b/src/main/resources/items/examplerangedbow.png new file mode 100644 index 0000000000000000000000000000000000000000..0310d0fcbdd2373d3bf3b6477afd1708fcba6803 GIT binary patch literal 418 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%C&rs6b?Si}mUKs7M+SzC z{oH>NSwSk3J%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f-TA0 z-G$*l2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3bL1Y`ns~eVq#;oG~l)Oya^O~=IP=X z649ERAn~$cVi5ByA=dwoC+E-lFSlOSXNpxr&;h4)ZZpoy_C`ob072ceb50ZOr_Jz~ zV#zGWq_Ui`Wd3{m@6xNSoC^7wfg&!;8E2eNzGDeeD>3cOpMwVuG`tLU+Q}r#GgqLY zv0Cu5B9BtArJ}(PX9neWk_vX}W>bZkn(bK<8g4;MSj?wj#jCOQL`nh>SZtfTi%;T@ zV#A5|6Fwct`d7mS1Z}&H9QnWE)04)7@=PxpZe=FEXkn;|YS`M5P*ZfUp`GET=8PL* pA3wYmU$C6BDElNMe`6p6L+T&r^N;uQ%mezE!PC{xWt~$(69B{*jUE61 literal 0 HcmV?d00001 diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index 7c67aea..57bb308 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -25,9 +25,11 @@ examplegrassseed=Example Grass Seed examplehuntincursionmaterial=Example Hunt Incursion Material examplepotion=Example Potion examplefood=Example Food -examplemeleesword=Example Melee Sword -examplemagicstaff=Example Magic Staff -examplesummonorb=Example Summon Orb +examplemeleesword=Example Melee Weapon +examplemagicstaff=Example Magic Weapon +examplerangedbow= Example Ranged Weapon +examplesummonorb=Example Summon Weapon +examplearrow=Example Arrow examplehelmet=Example Helmet examplechestplate=Example Chestplate exampleboots=Example Boots diff --git a/src/main/resources/player/weapons/examplerangedbow.png b/src/main/resources/player/weapons/examplerangedbow.png new file mode 100644 index 0000000000000000000000000000000000000000..09a87a4401178895117fe0a374dddb366c98edb0 GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^B0#Ld!3HEdKT9nDQjEnx?oNz1PwLbIIV|apzK#qG z8~eHcB(ehe3dtTpz6=aiY77hwEes65fI|H(?D8gCb5n0T@z%2~Ij105pNB{-dOFVsD*A3cdDp zaSZY3JUdO1?~s8=+kQch4Ka<%3eN)~RG5N0A8-lBG_eFXJ0vYHm@Lz%ouJsEI88ib z`^6<|&emOB7W?_>>Fn#%Uj#`VQc%bgWx zpLb)h+0NeVbI0?M<(89r*TY?p7%`qOn0riFWYgCqo&$#UAt}>VZ#z<45&d>`V`%!i z7UeZN7wWGgtNdSvY3H^TNs2H8D`Cq01C2~c>21szhYuzR8zUNf3g+O1uUK} zjv*eMZ?CNPVhI#E{;^-_2V2uo1-4e!!`mEPn)?-gGxa69ZkTaKt@+Ro8ln}vUc)5Kc;PCdQz`Mvvp^6_AbU7dAu)3CMn%->BnAq2+o`*^Vp65D{ znXJ?HGeK<5-1z3BUzgj3>#qNO`$f20s~~R=o15#4;QKlGYM(Qf8L?_Du2!hJ%V2&{ z_QoyEYPqE<4_|RryeOG;BzcpMk6-i71jW5V=_gOU|99=8VZ^$!YUHJO_ jL2XdoRQ)+G-oNBciuCqB8aF!>7(@)7u6{1-oD!M