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/java/examplemod/ExampleMod.java b/src/main/java/examplemod/ExampleMod.java index 6cc822c..920d719 100644 --- a/src/main/java/examplemod/ExampleMod.java +++ b/src/main/java/examplemod/ExampleMod.java @@ -1,126 +1,76 @@ package examplemod; -import examplemod.examples.*; -import examplemod.examples.items.ExampleFoodItem; -import examplemod.examples.items.ExampleHuntIncursionMaterialItem; -import examplemod.examples.items.ExampleMaterialItem; -import examplemod.examples.items.ExamplePotionItem; -import necesse.engine.commands.CommandsManager; +import examplemod.Loaders.*; +import examplemod.examples.maps.biomes.ExampleBiome; 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.engine.sound.SoundSettings; +import necesse.engine.sound.gameSound.GameSound; import necesse.level.maps.biomes.Biome; @ModEntry public class ExampleMod { + // Global access point for mod settings + public static ExampleModSettings settings; // 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; + + // Load settings for the example mod from the external file defined in ExampleModSettings + public ExampleModSettings initSettings() { + settings = new ExampleModSettings(); + return settings; + } public void init() { System.out.println("Hello world from my example mod!"); + settings.logLoadedSettings(); // log the loaded settings for debug - // 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 categories first: Used by Items/Objects to appear correctly in Creative/crafting trees + ExampleModCategories.load(); - // Register our tiles - TileRegistry.registerTile("exampletile", new ExampleTile(), 1, true); + // Register packets early: Anything networked (mobs, settlers, job UIs, events) can safely reference packet IDs + ExampleModPackets.load(); - // Register our objects - ObjectRegistry.registerObject("exampleobject", new ExampleObject(), 2, true); + // Core content building blocks first: Tiles/Objects/Items are referenced by biomes, incursions, mobs, projectiles, buffs, etc. + ExampleModTiles.load(); + ExampleModObjects.load(); + ExampleModItems.load(); - // Register our items - ItemRegistry.registerItem("exampleitem", new ExampleMaterialItem(), 10, true); - ItemRegistry.registerItem("examplehuntincursionitem", 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); + // Combat + entity registries next: Projectiles and buffs often reference items/mobs, and mobs can reference buffs/projectiles. + ExampleModProjectiles.load(); + ExampleModBuffs.load(); + ExampleModMobs.load(); - // Register our mob - MobRegistry.registerMob("examplemob", ExampleMob.class, true); + // Settlement systems after mobs/items exist: Settlers are mobs; jobs can reference settlers, items, and packets/UI. + ExampleModSettlers.load(); + ExampleModJobs.load(); - // Register our projectile - ProjectileRegistry.registerProjectile("exampleprojectile", ExampleProjectile.class, "exampleprojectile", "exampleprojectile_shadow"); + // World generation last-ish: Biomes/incursions can safely reference all registered tiles/objects/mobs/items now. + ExampleModBiomes.load(); + ExampleModIncursions.load(); - // Register our buff - BuffRegistry.registerBuff("examplebuff", new ExampleBuff()); + // Events after everything is registered: Lets event listeners safely reference IDs and content without ordering surprises. + ExampleModEvents.load(); - // Register our packet - PacketRegistry.registerPacket(ExamplePacket.class); + // Journal last: JournalEntry.addMobEntries() resolves MobRegistry immediately at registration time. + ExampleModJournal.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 - - ExampleMob.texture = GameTexture.fromFile("mobs/examplemob"); + ExampleModResources.load(); } 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 so we can keep this class easy to read + ExampleModRecipes.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/ExampleModSettings.java b/src/main/java/examplemod/ExampleModSettings.java new file mode 100644 index 0000000..9536e18 --- /dev/null +++ b/src/main/java/examplemod/ExampleModSettings.java @@ -0,0 +1,37 @@ +package examplemod; + +import necesse.engine.GameLog; +import necesse.engine.modLoader.ModSettings; +import necesse.engine.save.LoadData; +import necesse.engine.save.SaveData; + +public class ExampleModSettings extends ModSettings { + // Your config values + public boolean exampleBoolean = true; + public int exampleInt = 1; + public String exampleString = "Hello! from the config file "; + + @Override + public void addSaveData(SaveData data) { + // This is what gets written to cfg/mods/.cfg under SETTINGS { ... } + data.addBoolean("exampleBoolean", exampleBoolean); + data.addInt("exampleInt", exampleInt); + data.addSafeString("exampleString", exampleString); + } + + @Override + public void applyLoadData(LoadData data) { + // This is what gets read back from cfg/mods/.cfg + exampleBoolean = data.getBoolean("exampleBoolean", exampleBoolean); + exampleInt = data.getInt("exampleInt", exampleInt); + exampleString = data.getSafeString("exampleString", exampleString, false); + } + + public void logLoadedSettings() { + + GameLog.out.println("[ExampleMod] Settings loaded:"); + GameLog.out.println(" exampleBoolean = " + exampleBoolean); + GameLog.out.println(" exampleInt = " + exampleInt); + GameLog.out.println(" exampleString = \"" + exampleString + "\""); + } +} 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/ExampleModBuffs.java b/src/main/java/examplemod/Loaders/ExampleModBuffs.java new file mode 100644 index 0000000..fc48389 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModBuffs.java @@ -0,0 +1,21 @@ +package examplemod.Loaders; + +import examplemod.examples.buffs.*; +import necesse.engine.registries.BuffRegistry; +import necesse.entity.mobs.buffs.staticBuffs.Buff; + +public class ExampleModBuffs { + public static void load(){ + // Register our buff + BuffRegistry.registerBuff("examplebuff", new ExampleBuff()); + + // Register our Armor Set Bonus + BuffRegistry.registerBuff("examplearmorsetbonusbuff", new ExampleArmorSetBuff()); + + // Register our Arrow Buff + BuffRegistry.registerBuff("examplearrowbuff", new ExampleArrowBuff()); + + // Register our Trinket Buff + BuffRegistry.registerBuff("exampletrinketbuff",new ExampleTrinketBuff()); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModCategories.java b/src/main/java/examplemod/Loaders/ExampleModCategories.java new file mode 100644 index 0000000..65dd8c0 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModCategories.java @@ -0,0 +1,118 @@ +package examplemod.Loaders; + +import necesse.engine.localization.message.LocalMessage; +import necesse.inventory.item.ItemCategory; + +public final class ExampleModCategories { + private ExampleModCategories() {} + + /* + * 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. + * + * Placeables tab roots: tiles / objects / wiring + * Items tab roots: equipment / consumable / materials / misc + * Mobs tab roots: mobs + * + * So: your itemCategoryTree MUST start with one of those roots, otherwise the item/object + * will not appear in Creative even though it is registered. + */ + + // 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"; + + // ===== 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 final String EXAMPLEWOOD = "examplewood"; + + public static void load() { + + // 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", "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-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/ExampleModCommands.java b/src/main/java/examplemod/Loaders/ExampleModCommands.java new file mode 100644 index 0000000..3754142 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModCommands.java @@ -0,0 +1,13 @@ +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/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/ExampleModIncursions.java b/src/main/java/examplemod/Loaders/ExampleModIncursions.java new file mode 100644 index 0000000..6f328d7 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModIncursions.java @@ -0,0 +1,17 @@ +package examplemod.Loaders; + +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 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..2d25289 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModItems.java @@ -0,0 +1,52 @@ +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.ExampleBossSummonItem; +import examplemod.examples.items.consumable.ExampleFoodItem; +import examplemod.examples.items.consumable.ExamplePotionItem; +import examplemod.examples.items.materials.*; +import examplemod.examples.items.tools.ExampleStaffMagicWeapon; +import examplemod.examples.items.tools.ExampleSwordMeleeWeapon; +import examplemod.examples.items.tools.ExampleBowRangedWeapon; +import examplemod.examples.items.tools.ExampleOrbSummonWeapon; +import examplemod.examples.items.trinkets.ExampleTrinketItem; +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); + ItemRegistry.registerItem("examplelog", new ExampleLogItem().setItemCategory("materials","logs"),10,true); + ItemRegistry.registerItem("examplegrassseed", new ExampleGrassSeedItem(),1,true); + + // Tools + ItemRegistry.registerItem("examplemeleesword", new ExampleSwordMeleeWeapon(), 20, true); + ItemRegistry.registerItem("examplemagicstaff", new ExampleStaffMagicWeapon(), 30, true); + ItemRegistry.registerItem("examplesummonorb", new ExampleOrbSummonWeapon(),40,true); + ItemRegistry.registerItem("examplerangedbow", new ExampleBowRangedWeapon(),10,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); + ItemRegistry.registerItem("examplebosssummonitem", new ExampleBossSummonItem(),1,true); + + // Ammo + ItemRegistry.registerItem("examplearrow", new ExampleArrowItem(),5,true); + + // Trinkets + ItemRegistry.registerItem("exampletrinket",new ExampleTrinketItem(),1,true); + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModJobs.java b/src/main/java/examplemod/Loaders/ExampleModJobs.java new file mode 100644 index 0000000..54b6e15 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModJobs.java @@ -0,0 +1,26 @@ +package examplemod.Loaders; + +import examplemod.examples.settlement.jobs.ExampleLevelJob; +import necesse.engine.localization.message.LocalMessage; +import necesse.engine.registries.JobTypeRegistry; +import necesse.engine.registries.LevelJobRegistry; +import necesse.entity.mobs.job.JobType; + +public class ExampleModJobs { + + public static void load(){ + // 1) Register the job type + JobTypeRegistry.registerType("examplejobtype", + new JobType( + true, // canChangePriority (shows in settlement UI) + true, // defaultDisabledBySettler (locked for normal settlers) + new LocalMessage("jobs", "examplejobname"), + new LocalMessage("jobs", "examplejobtip") + ) + ); + + // 2) Register our ExampleLevelJob //DEBUG + LevelJobRegistry.registerJob("examplejob", ExampleLevelJob .class, "examplejobtype"); + } + +} diff --git a/src/main/java/examplemod/Loaders/ExampleModJournal.java b/src/main/java/examplemod/Loaders/ExampleModJournal.java new file mode 100644 index 0000000..cf69481 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModJournal.java @@ -0,0 +1,40 @@ +package examplemod.Loaders; + +import examplemod.ExampleMod; +import necesse.engine.journal.JournalEntry; +import necesse.engine.registries.JournalRegistry; +import necesse.engine.util.LevelIdentifier; + + +public class ExampleModJournal { + + public static void load(){ + // Surface + JournalEntry exampleBiomeJournalSurface = JournalRegistry.registerJournalEntry( + "examplebiomesurface", + new JournalEntry(ExampleMod.EXAMPLE_BIOME, LevelIdentifier.SURFACE_IDENTIFIER) + ); + //content lists inside the journal page + exampleBiomeJournalSurface.addBiomeLootEntry("examplelog"); + exampleBiomeJournalSurface.addMobEntries("examplemob"); + + // Caves + JournalEntry exampleBiomeJournalCave = JournalRegistry.registerJournalEntry( + "examplebiomecave", + new JournalEntry(ExampleMod.EXAMPLE_BIOME, LevelIdentifier.CAVE_IDENTIFIER) + ); + //content lists inside the journal page + exampleBiomeJournalCave.addBiomeLootEntry("exampleore","examplestone"); + exampleBiomeJournalCave.addMobEntries("examplemob"); + + // Deep Caves + JournalEntry exampleBiomeJournalDeepCave = JournalRegistry.registerJournalEntry( + "examplebiomedeepcave", + new JournalEntry(ExampleMod.EXAMPLE_BIOME, LevelIdentifier.DEEP_CAVE_IDENTIFIER) + ); + //content lists inside the journal page + exampleBiomeJournalDeepCave.addBiomeLootEntry("exampleore","examplestone"); + exampleBiomeJournalDeepCave.addMobEntries("examplemob"); + } + +} diff --git a/src/main/java/examplemod/Loaders/ExampleModMobs.java b/src/main/java/examplemod/Loaders/ExampleModMobs.java new file mode 100644 index 0000000..a9cb57e --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModMobs.java @@ -0,0 +1,23 @@ +package examplemod.Loaders; + +import examplemod.examples.mobs.ExampleBossMob; +import examplemod.examples.mobs.ExampleMob; +import examplemod.examples.mobs.ExampleSummonWeaponMob; +import examplemod.examples.mobs.ExampleSettlerMob; +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); + + // Register summon mob + MobRegistry.registerMob("examplesummonmob", ExampleSummonWeaponMob.class,true,false); + + // Register a example mob (ExampleSettlerMob that uses ExampleSettler for settler settings and is capable of our ExampleLevelJob //DEBUG + MobRegistry.registerMob("examplesettlermob", ExampleSettlerMob.class, false, false,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..830fcc3 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModObjects.java @@ -0,0 +1,68 @@ +package examplemod.Loaders; + +import examplemod.examples.objects.*; +import necesse.engine.registries.ObjectRegistry; +import necesse.level.gameObject.WallObject; + +//NOTE item and crafting categories subject to change +public class ExampleModObjects { + + // Expose IDs for other classes (biomes, levels, etc.) + public static int EXAMPLE_BASE_ROCK_ID = -1; + public static int EXAMPLE_ORE_ROCK_ID = -1; + + public static void load(){ + // Register our objects + + ObjectRegistry.registerObject("exampleobject", new ExampleObject() + .setItemCategory(ExampleModCategories.ROOT_OBJECTS,ExampleModCategories.OBJECTS_COLUMNS) + .setCraftingCategory(ExampleModCategories.ROOT_OBJECTS,ExampleModCategories.OBJECTS_COLUMNS), 2, true); + + + // Register a rock object + ExampleBaseRockObject exampleBaseRock = new ExampleBaseRockObject(); + EXAMPLE_BASE_ROCK_ID = ObjectRegistry.registerObject("examplebaserock", exampleBaseRock, -1.0F, true); + + // Register an ore rock object that overlays onto our incursion rock + EXAMPLE_ORE_ROCK_ID = ObjectRegistry.registerObject("exampleorerock", new ExampleOreRockObject(exampleBaseRock), -1.0F, true); + + // 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); + + // 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); + + // Register ExampleJobObject an object that triggers our new job to happen //DEBUG + ObjectRegistry.registerObject("examplejobobject",new ExampleJobObject(),1,true); + + // Register an object that uses the mods config file + ObjectRegistry.registerObject("exampleconfigobject", new ExampleConfigObject(),1,true); + + // Register an example pressure plate object + ObjectRegistry.registerObject("examplepressureplate",new ExamplePressurePlateObject(),1,true); + + // Get the wall object we want this trap to attach to. + // ObjectRegistry stores everything as a generic "GameObject" + // so we fetch by string ID ("examplewall") and cast it to WallObject. + // Takes the texture of the wall object and overlays our "examplewalltrap" + WallObject exampleWall = (WallObject) ObjectRegistry.getObject("examplewall"); + ObjectRegistry.registerObject("examplewalltrap",new ExampleWallTrapObject(exampleWall),1,true); + + + + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModPackets.java b/src/main/java/examplemod/Loaders/ExampleModPackets.java new file mode 100644 index 0000000..1a6aabf --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModPackets.java @@ -0,0 +1,20 @@ +package examplemod.Loaders; + +import examplemod.examples.packets.ExampleConfigInteractPacket; +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); + + // Register our sound playing packet + PacketRegistry.registerPacket(ExamplePlaySoundPacket.class); + + // Register our packet to read config from an object + PacketRegistry.registerPacket(ExampleConfigInteractPacket.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..a6c190d --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModProjectiles.java @@ -0,0 +1,15 @@ +package examplemod.Loaders; + +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/Loaders/ExampleModRecipes.java b/src/main/java/examplemod/Loaders/ExampleModRecipes.java new file mode 100644 index 0000000..deab3f5 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModRecipes.java @@ -0,0 +1,181 @@ +package examplemod.Loaders; + +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 ExampleModRecipes { + + //Put your recipe registrations in here + public static void registerRecipes(){ + + // Example item recipe, crafted in inventory for 2 iron bars + Recipes.registerModRecipe(new Recipe( + "exampleitem", + 1, + RecipeTechRegistry.NONE, + new Ingredient[]{ + new Ingredient("examplebar", 2) + } + ).showAfter("woodboat")); // Show recipe after wood boat recipe + + + //FORGE RECIPES + Recipes.registerModRecipe(new Recipe( + "examplebar", + 1, + RecipeTechRegistry.FORGE, + new Ingredient[]{ + new Ingredient("exampleore",2) + }) + ); + + //IRON ANVIL RECIPES + Recipes.registerModRecipe(new Recipe( + "examplemeleesword", + 1, + RecipeTechRegistry.IRON_ANVIL, + new Ingredient[]{ + new Ingredient("exampleitem", 4), + new Ingredient("examplebar", 5) + } + )); + + Recipes.registerModRecipe(new Recipe( + "examplemagicstaff", + 1, + RecipeTechRegistry.IRON_ANVIL, + new Ingredient[]{ + new Ingredient("exampleitem", 5), + new Ingredient("examplebar", 4) + } + )); + + 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, + 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("examplestone", 7) + } + )); + + 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( + "examplefood", + 1, + RecipeTechRegistry.COOKING_POT, + new Ingredient[]{ + new Ingredient("bread", 1), + new Ingredient("strawberry", 2), + new Ingredient("sugar", 1) + } + )); + + //ALCHEMY RECIPES + Recipes.registerModRecipe(new Recipe( + "examplepotion", + 1, + RecipeTechRegistry.ALCHEMY, + new Ingredient[]{ + 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), + } + )); + + //CARPENTER RECIPES + Recipes.registerModRecipe(new Recipe( + "examplechair", + 1, + RecipeTechRegistry.CARPENTER, + new Ingredient[]{ + new Ingredient("examplelog", 5), + } + )); + + } +} diff --git a/src/main/java/examplemod/Loaders/ExampleModResources.java b/src/main/java/examplemod/Loaders/ExampleModResources.java new file mode 100644 index 0000000..668a259 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModResources.java @@ -0,0 +1,33 @@ +package examplemod.Loaders; + +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; + +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"); + ExampleSummonWeaponMob.texture = GameTexture.fromFile("mobs/examplesummonmob"); + + //initializing 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/ExampleModSettlers.java b/src/main/java/examplemod/Loaders/ExampleModSettlers.java new file mode 100644 index 0000000..67431c8 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModSettlers.java @@ -0,0 +1,14 @@ +package examplemod.Loaders; + +import examplemod.examples.settlement.settlers.ExampleSettler; +import necesse.engine.registries.SettlerRegistry; + + +public class ExampleModSettlers { + + public static void load(){ + // Register our settler ExampleSettler used by ExampleSettlerMob //DEBUG + SettlerRegistry.registerSettler("examplesettler", new ExampleSettler()); + } + +} diff --git a/src/main/java/examplemod/Loaders/ExampleModTiles.java b/src/main/java/examplemod/Loaders/ExampleModTiles.java new file mode 100644 index 0000000..b4d5769 --- /dev/null +++ b/src/main/java/examplemod/Loaders/ExampleModTiles.java @@ -0,0 +1,16 @@ +package examplemod.Loaders; + +import examplemod.examples.tiles.ExampleGrassTile; +import examplemod.examples.tiles.ExampleTile; +import necesse.engine.registries.TileRegistry; + +public class ExampleModTiles { + + public static int EXAMPLE_TILE_ID = -1; + + public static void load(){ + // Register our tiles + EXAMPLE_TILE_ID = TileRegistry.registerTile("exampletile", new ExampleTile(), 1, true); + TileRegistry.registerTile("examplegrasstile", new ExampleGrassTile(),1,false,false,true); + } +} diff --git a/src/main/java/examplemod/examples/ExampleBiome.java b/src/main/java/examplemod/examples/ExampleBiome.java deleted file mode 100644 index a6ae51e..0000000 --- a/src/main/java/examplemod/examples/ExampleBiome.java +++ /dev/null @@ -1,36 +0,0 @@ -package examplemod.examples; - -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/ExampleLootTable.java b/src/main/java/examplemod/examples/ExampleLootTable.java new file mode 100644 index 0000000..9f397d2 --- /dev/null +++ b/src/main/java/examplemod/examples/ExampleLootTable.java @@ -0,0 +1,64 @@ +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; +import necesse.inventory.lootTable.lootItem.RotationLootItem; + +/** + * 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( + + // Rotating entries: + // This uses the (level + AtomicInteger lootRotation) arguments that chest rooms pass in. + // Position 0 = first item, position 1 = second item, etc. + RotationLootItem.presetRotation( + new LootItem("exampletrinket"), // position 0 + new LootItem("examplehelmet"), // position 1 + new LootItem("examplechestplate"),// position 2 (example) + new LootItem("examplefood") // position 3 (example) + ), + // 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, "examplemeleesword"), + new ChanceLootItem(0.60f, "examplemagicstaff"), + new ChanceLootItem(0.60f, "examplesummonorb"), + new ChanceLootItem(0.60f, "examplerangedbow") + ) + ); + + /** + * 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/ExampleSwordItem.java b/src/main/java/examplemod/examples/ExampleSwordItem.java deleted file mode 100644 index 68fc649..0000000 --- a/src/main/java/examplemod/examples/ExampleSwordItem.java +++ /dev/null @@ -1,21 +0,0 @@ -package examplemod.examples; - -import necesse.inventory.item.Item; -import necesse.inventory.item.toolItem.swordToolItem.SwordToolItem; - -// Extends SwordToolItem -public class ExampleSwordItem extends SwordToolItem { - - // Weapon attack textures are loaded from resources/player/weapons/ - - public ExampleSwordItem() { - super(400, null); - rarity = Item.Rarity.UNCOMMON; - attackAnimTime.setBaseValue(300); // 300 ms attack time - attackDamage.setBaseValue(20) // Base sword damage - .setUpgradedValue(1, 95); // Upgraded tier 1 damage - attackRange.setBaseValue(120); // 120 range - knockback.setBaseValue(100); // 100 knockback - } - -} 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..f078f6f --- /dev/null +++ b/src/main/java/examplemod/examples/ai/ExampleAI.java @@ -0,0 +1,65 @@ +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 { + + // Plays a sound when then boss appears + public final ExampleAILeaf soundPlay; + + // 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; + + // “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 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.soundPlay = new ExampleAILeaf<>(); + addChild(this.soundPlay); + + // 2) Chase + attack (second priority). + this.chaser = new CollisionPlayerChaserAI(searchDistance, damage, knockback) { + + + + // 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) { + return ExampleAI.this.attackTarget(mob, target); + } + }; + addChild(this.chaser); + + // 3) Wander around if we aren’t teleporting, and we aren’t chasing anyone. + this.wanderer = new WandererAINode<>(wanderFrequency); + addChild(this.wanderer); + } + + // 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 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, + 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 new file mode 100644 index 0000000..4a56704 --- /dev/null +++ b/src/main/java/examplemod/examples/ai/ExampleAILeaf.java @@ -0,0 +1,47 @@ +package examplemod.examples.ai; + +import examplemod.examples.packets.ExamplePlaySoundPacket; +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; + +/** + * Runs exactly once, server-side, and tells nearby clients to play a sound + * at the mob's current position via ExamplePlaySoundPacket. + * Returns FAILURE so the parent tree continues normally (this leaf never "takes over"). + */ +public class ExampleAILeaf extends AINode { + + // Ensure this only fires once per mob instance. + private boolean didRun = false; + + @Override + protected void onRootSet(AINode root, T mob, Blackboard blackboard) { + // No setup needed. + } + + @Override + public void init(T mob, Blackboard blackboard) { + // No init needed. + } + + @Override + public AINodeResult tick(T mob, Blackboard blackboard) { + // Run once. + if (didRun) return AINodeResult.FAILURE; + didRun = true; + + // Only the server should broadcast packets to clients. + if (mob == null || !mob.isServer()) return AINodeResult.FAILURE; + + if (mob.getLevel() != null && mob.getLevel().getServer() != null) { + mob.getLevel().getServer().network.sendToClientsWithEntity( + new ExamplePlaySoundPacket(mob.x, mob.y), + mob + ); + } + + return AINodeResult.FAILURE; + } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/buffs/ExampleArmorSetBuff.java b/src/main/java/examplemod/examples/buffs/ExampleArmorSetBuff.java new file mode 100644 index 0000000..d5d3548 --- /dev/null +++ b/src/main/java/examplemod/examples/buffs/ExampleArmorSetBuff.java @@ -0,0 +1,38 @@ +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; + +/** + * Set bonus buff: + * When a player wears the full armor set, this buff is applied. + * It gives +10% damage and +10% movement speed. + */ +public class ExampleArmorSetBuff extends SimpleSetBonusBuff { + + public ExampleArmorSetBuff() { + // The parent class (SimpleSetBonusBuff) takes the stat boosts here. + super( + new ModifierValue<>(BuffModifiers.ALL_DAMAGE, 0.10f), // +10% damage + new ModifierValue<>(BuffModifiers.SPEED, 0.10f) // +10% speed + ); + } + + @Override + public void init(ActiveBuff buff, BuffEventSubscriber eventSubscriber) { + // Set bonuses should not be removable by the player. + this.canCancel = false; + + // Mark it as a passive buff (always active while wearing the set). + this.isPassive = true; + + // Show it in the buff UI. + this.isVisible = true; + + // Let the parent class finish setup (applies the modifiers). + super.init(buff, eventSubscriber); + } +} \ No newline at end of file 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..5203dbd --- /dev/null +++ b/src/main/java/examplemod/examples/buffs/ExampleArrowBuff.java @@ -0,0 +1,63 @@ +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 every 250ms (about 1/4 second) + private static final int HEAL_INTERVAL_MS = 250; + + public ExampleArrowBuff() { + // Keep this buff hidden + temporary + this.canCancel = false; + this.isVisible = false; + this.isPassive = false; + this.shouldSave = false; + } + + @Override + public void init(ActiveBuff buff, BuffEventSubscriber eventSubscriber) { + // Timer for this buff instance + buff.getGndData().setInt("timePassed", 0); + + // "healPerTick" is set by whoever applies the buff + // buff.getGndData().setInt("healPerTick", 2); + } + + @Override + public void serverTick(ActiveBuff buff) { + Mob mob = buff.owner; + if (mob == null) return; + + // How much to heal each time (0 = no healing) + int healPerTick = buff.getGndData().getInt("healPerTick"); + if (healPerTick <= 0) return; + + // Add ~50ms per server tick + int time = buff.getGndData().getInt("timePassed") + 50; + + // Not ready to heal yet + if (time < HEAL_INTERVAL_MS) { + buff.getGndData().setInt("timePassed", time); + return; + } + + // Ready: keep leftover time and heal once + buff.getGndData().setInt("timePassed", time - HEAL_INTERVAL_MS); + + // Heal, but don't go past max health + int newHealth = Math.min(mob.getMaxHealth(), mob.getHealth() + healPerTick); + + // If health actually changed, tell the game with an event (sync + floating numbers) + if (newHealth != mob.getHealth()) { + int amountHealed = newHealth - mob.getHealth(); + mob.getLevel().entityManager.events.add( + new MobHealthChangeEvent(mob, newHealth, amountHealed) + ); + } + } +} \ No newline at end of file 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/buffs/ExampleTrinketBuff.java b/src/main/java/examplemod/examples/buffs/ExampleTrinketBuff.java new file mode 100644 index 0000000..032e5ef --- /dev/null +++ b/src/main/java/examplemod/examples/buffs/ExampleTrinketBuff.java @@ -0,0 +1,29 @@ +package examplemod.examples.buffs; + +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.trinketBuffs.SimpleTrinketBuff; + +public class ExampleTrinketBuff extends SimpleTrinketBuff { + public ExampleTrinketBuff(){ + + } + + @Override + public void init(ActiveBuff activeBuff, BuffEventSubscriber buffEventSubscriber) { + // Apply modifiers here + activeBuff.setModifier(BuffModifiers.SPELUNKER,true); // +50% speed + } + + @Override + public void serverTick(ActiveBuff buff) { + // You can do server ticks here + } + + @Override + public void clientTick(ActiveBuff buff) { + // You can do client ticks here, like adding particles to buff.owner + } + +} \ No newline at end of file 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..4c4cc7e --- /dev/null +++ b/src/main/java/examplemod/examples/events/ExampleEvent.java @@ -0,0 +1,35 @@ +package examplemod.examples.events; + +import necesse.engine.events.GameEvent; +import necesse.level.maps.Level; + +/* + * ExampleEvent is a small "notification" object for our mod. + * + * Compared to a LevelEvent + * it does not exist in the world + * it does not tick + * it does not draw anything + * + * 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 event happened + public final Level level; + + // The slot id of the player this event relates to + public final int clientSlot; + + // Simple data payload for the demo + public final String message; + + public ExampleEvent(Level level, int clientSlot) { + this.level = level; + this.clientSlot = clientSlot; + + // 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 new file mode 100644 index 0000000..82fefe1 --- /dev/null +++ b/src/main/java/examplemod/examples/events/ExampleLevelEvent.java @@ -0,0 +1,176 @@ +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; +import necesse.entity.levelEvent.mobAbilityLevelEvent.MobHealthChangeEvent; +import necesse.entity.mobs.GameDamage; +import necesse.entity.particle.Particle; + +import java.awt.Color; + +/** + * 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. + 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() { + } + + /** + * 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"; + } + + @Override + public void init() { + super.init(); + + // 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); + } + + // 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); + + /* + * 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); + + /* + * 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/ExamplePotionItem.java b/src/main/java/examplemod/examples/items/ExamplePotionItem.java deleted file mode 100644 index 17bdcdc..0000000 --- a/src/main/java/examplemod/examples/items/ExamplePotionItem.java +++ /dev/null @@ -1,11 +0,0 @@ -package examplemod.examples.items; - -import necesse.inventory.item.placeableItem.consumableItem.potionConsumableItem.SimplePotionItem; - -public class ExamplePotionItem extends SimplePotionItem { - - public ExamplePotionItem() { - super(100,Rarity.COMMON,"examplebuff",100, "examplepotionitemtip"); - } - -} \ 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..3d8d6b0 --- /dev/null +++ b/src/main/java/examplemod/examples/items/ammo/ExampleArrowItem.java @@ -0,0 +1,35 @@ +package examplemod.examples.items.ammo; + +import necesse.engine.registries.ProjectileRegistry; +import necesse.entity.mobs.GameDamage; +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) { + + return ProjectileRegistry.getProjectile( + "examplearrowprojectile", // your projectile stringID + owner.getLevel(), + x, y, targetX, targetY, + velocity, range, + damage, knockback, + owner + ); + } +} 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..1ce4bb1 --- /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 + "examplearmorsetbonusbuff" //buff STRING ID + ); + } +} diff --git a/src/main/java/examplemod/examples/items/consumable/ExampleBossSummonItem.java b/src/main/java/examplemod/examples/items/consumable/ExampleBossSummonItem.java new file mode 100644 index 0000000..bb0f2d4 --- /dev/null +++ b/src/main/java/examplemod/examples/items/consumable/ExampleBossSummonItem.java @@ -0,0 +1,189 @@ +package examplemod.examples.items.consumable; + +import java.awt.geom.Line2D; + +import examplemod.examples.maps.biomes.ExampleBiome; +import necesse.engine.localization.Localization; +import necesse.engine.localization.message.GameMessage; +import necesse.engine.localization.message.LocalMessage; +import necesse.engine.network.gameNetworkData.GNDItemMap; +import necesse.engine.network.packet.PacketChatMessage; +import necesse.engine.registries.MobRegistry; +import necesse.engine.util.GameBlackboard; +import necesse.engine.util.GameMath; +import necesse.engine.util.GameRandom; +import necesse.engine.util.LevelIdentifier; +import necesse.entity.mobs.Mob; +import necesse.entity.mobs.PlayerMob; +import necesse.gfx.gameTooltips.ListGameTooltips; +import necesse.inventory.InventoryItem; +import necesse.inventory.item.Item; +import necesse.inventory.item.placeableItem.consumableItem.ConsumableItem; +import necesse.level.maps.IncursionLevel; +import necesse.level.maps.Level; + +/** + * A consumable item that summons our boss mob. + */ +public class ExampleBossSummonItem extends ConsumableItem { + + public ExampleBossSummonItem() { + // Stack size 1, is "single use" consumable behaviour + super(1, true); + + // Cooldown (ms) before you can use it again + this.itemCooldownTime.setBaseValue(2000); + + // Where it appears in the creative menu + setItemCategory("consumable", "bossitems"); + + // If the player dies, drop this like a material (depending on death penalty rules) + this.dropsAsMatDeathPenalty = true; + + // Search keywords (helps with the in-game search) + this.keyWords.add("boss"); + + // Item rarity colour / tier + this.rarity = Item.Rarity.LEGENDARY; + + // How big the item sprite is when dropped in the world + this.worldDrawSize = 32; + + // How long before the item burns up in fire/lava (30 seconds) + this.incinerationTimeMillis = 30000; + } + + /** + * Checks if the item is allowed to be used here. + */ + public String canPlace(Level level, int x, int y, PlayerMob player, + Line2D playerPositionLine, InventoryItem item, GNDItemMap mapContent) { + + int tileX; + int tileY; + + // Don't allow boss summoning inside an incursion (special dungeon-like levels) + if (level instanceof IncursionLevel) + return "inincursion"; + + // Only allow use in caves (not surface) + if (!level.isCave) + return "notcave"; + + // Figure out which tile we should check. + // If we have a player, use the player's tile. + // If not (rare cases), convert the clicked pixel coords into tile coords. + if (player == null) { + tileX = GameMath.getTileCoordinate(x); + tileY = GameMath.getTileCoordinate(y); + } else { + tileX = player.getTileX(); + tileY = player.getTileY(); + } + + // Only allow in *cave identifier* AND only if the biome at that tile is our ExampleBiome. + // This prevents using the item in other cave biomes. + if (!level.getIdentifier().equals(LevelIdentifier.CAVE_IDENTIFIER) + || !(level.getBiome(tileX, tileY) instanceof ExampleBiome)) + return "notexamplebiome"; + + // Allowed + return null; + } + + /** + * Runs when the player tries to use the item but canPlace(...) returned an error. + * This is where we can send a nicer message to the player. + */ + public InventoryItem onAttemptPlace(Level level, int x, int y, PlayerMob player, + InventoryItem item, GNDItemMap mapContent, String error) { + + // Only do chat messages on the server, and only if we have a real server client player + if (level.isServer() && player != null && player.isServerClient() && error.equals("inincursion")) + player.getServerClient().sendChatMessage(new LocalMessage("misc", "cannotsummoninincursion")); + + // Let vanilla handle the rest (cooldowns, failure behaviour, etc.) + return super.onAttemptPlace(level, x, y, player, item, mapContent, error); + } + + /** + * Runs when the item is successfully used. + * This is where we actually spawn the boss. + */ + public InventoryItem onPlace(Level level, int x, int y, PlayerMob player, + int seed, InventoryItem item, GNDItemMap mapContent) { + + // Only spawn mobs on the server (clients are just visuals) + if (level.isServer()) { + + // If we ARE in an incursion, ask the incursion system if boss summoning is allowed. + // (This also supports the game’s built-in "one boss at a time" rules for incursions.) + if (level instanceof IncursionLevel) { + GameMessage summonError = ((IncursionLevel) level).canSummonBoss("examplebossmob"); + if (summonError != null) { + // Tell the player why it failed + if (player != null && player.isServerClient()) + player.getServerClient().sendChatMessage(summonError); + + // Do NOT consume the item if summoning failed + return item; + } + } + + // Simple debug log + System.out.println("Example Boss Mob has been summoned at " + level.getIdentifier() + "."); + + // Pick a random direction (angle 0-359 degrees) + float angle = GameRandom.globalRandom.nextInt(360); + + // Turn that angle into a unit direction vector (nx, ny) + float nx = GameMath.cos(angle); + float ny = GameMath.sin(angle); + + // How far away from the player the boss should appear (in pixels) + float distance = 460F; + + // Create the boss mob instance + Mob mob = MobRegistry.getMob("examplebossmob", level); + + // Spawn it near the player, at a random offset + level.entityManager.addMob( + mob, + (player.getX() + (int) (nx * distance)), + (player.getY() + (int) (ny * distance)) + ); + + // Tell nearby clients (chat message) that the boss was summoned + level.getServer().network.sendToClientsWithEntity( + new PacketChatMessage(new LocalMessage("misc", "bosssummon", "name", mob.getLocalization())), + mob + ); + + // Let the incursion know a boss was summoned (so it can track / handle rules) + if (level instanceof IncursionLevel) + ((IncursionLevel) level).onBossSummoned(mob); + } + + // If this item is single-use, consume 1 from the stack + if (isSingleUse(player)) + item.setAmount(item.getAmount() - 1); + + return item; + } + + /** + * Extra tooltip line shown on the item. + */ + public ListGameTooltips getTooltips(InventoryItem item, PlayerMob perspective, GameBlackboard blackboard) { + ListGameTooltips tooltips = super.getTooltips(item, perspective, blackboard); + tooltips.add(Localization.translate("itemtooltip", "examplebosssummontip")); + return tooltips; + } + + /** + * The "type name" shown in the UI (e.g. Relic). + */ + public String getTranslatedTypeName() { + return Localization.translate("item", "relic"); + } +} \ No newline at end of file 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/consumable/ExamplePotionItem.java b/src/main/java/examplemod/examples/items/consumable/ExamplePotionItem.java new file mode 100644 index 0000000..d9fe2b7 --- /dev/null +++ b/src/main/java/examplemod/examples/items/consumable/ExamplePotionItem.java @@ -0,0 +1,15 @@ +package examplemod.examples.items.consumable; + +import necesse.inventory.item.placeableItem.consumableItem.potionConsumableItem.SimplePotionItem; + +public class ExamplePotionItem extends SimplePotionItem { + + public ExamplePotionItem() { + super(100, // Max Stack Size + Rarity.COMMON, // Item Rarity + "examplebuff", // Buff to apply + 100, // Buff Duration in seconds + "examplepotionitemtip"); // Localization text to load and display + } + +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/items/materials/ExampleBarItem.java b/src/main/java/examplemod/examples/items/materials/ExampleBarItem.java new file mode 100644 index 0000000..83e47a3 --- /dev/null +++ b/src/main/java/examplemod/examples/items/materials/ExampleBarItem.java @@ -0,0 +1,13 @@ +package examplemod.examples.items.materials; + +import necesse.inventory.item.Item; +import necesse.inventory.item.matItem.MatItem; + +public class ExampleBarItem extends MatItem { + + public ExampleBarItem() { + super(500, // Max Stack Size + Item.Rarity.UNCOMMON); // Rarity + + } +} 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..d2e9b63 --- /dev/null +++ b/src/main/java/examplemod/examples/items/materials/ExampleGrassSeedItem.java @@ -0,0 +1,19 @@ +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. + * 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 + super("examplegrasstile"); + } +} diff --git a/src/main/java/examplemod/examples/items/ExampleHuntIncursionMaterialItem.java b/src/main/java/examplemod/examples/items/materials/ExampleHuntIncursionMaterialItem.java similarity index 56% rename from src/main/java/examplemod/examples/items/ExampleHuntIncursionMaterialItem.java rename to src/main/java/examplemod/examples/items/materials/ExampleHuntIncursionMaterialItem.java index 8c15c37..7796e8d 100644 --- a/src/main/java/examplemod/examples/items/ExampleHuntIncursionMaterialItem.java +++ b/src/main/java/examplemod/examples/items/materials/ExampleHuntIncursionMaterialItem.java @@ -1,11 +1,12 @@ -package examplemod.examples.items; +package examplemod.examples.items.materials; import necesse.inventory.item.matItem.MatItem; public class ExampleHuntIncursionMaterialItem extends MatItem { public ExampleHuntIncursionMaterialItem() { - super(100, Rarity.RARE); + super(100, // Max Stack Size + Rarity.RARE); // Rarity } } 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..ed190f3 --- /dev/null +++ b/src/main/java/examplemod/examples/items/materials/ExampleLogItem.java @@ -0,0 +1,13 @@ +package examplemod.examples.items.materials; + +import necesse.inventory.item.matItem.MatItem; + +public class ExampleLogItem extends MatItem { + + public ExampleLogItem() { + super(500, // Max Stack Size + Rarity.UNCOMMON, // Rarity + new String[]{"anylog"}); // Global Ingrediants + + } +} diff --git a/src/main/java/examplemod/examples/items/ExampleMaterialItem.java b/src/main/java/examplemod/examples/items/materials/ExampleMaterialItem.java similarity index 51% rename from src/main/java/examplemod/examples/items/ExampleMaterialItem.java rename to src/main/java/examplemod/examples/items/materials/ExampleMaterialItem.java index a20ddb6..558a20a 100644 --- a/src/main/java/examplemod/examples/items/ExampleMaterialItem.java +++ b/src/main/java/examplemod/examples/items/materials/ExampleMaterialItem.java @@ -1,11 +1,12 @@ -package examplemod.examples.items; +package examplemod.examples.items.materials; import necesse.inventory.item.matItem.MatItem; public class ExampleMaterialItem extends MatItem { public ExampleMaterialItem() { - super(100, Rarity.UNCOMMON); + super(100, // Max Stack Size + Rarity.UNCOMMON); // Rarity } } diff --git a/src/main/java/examplemod/examples/items/materials/ExampleOreItem.java b/src/main/java/examplemod/examples/items/materials/ExampleOreItem.java new file mode 100644 index 0000000..4b1ff07 --- /dev/null +++ b/src/main/java/examplemod/examples/items/materials/ExampleOreItem.java @@ -0,0 +1,13 @@ +package examplemod.examples.items.materials; + +import necesse.inventory.item.Item; +import necesse.inventory.item.matItem.MatItem; + +public class ExampleOreItem extends MatItem { + + public ExampleOreItem() { + super(500, // Max Stack Size + Item.Rarity.UNCOMMON); // Rarity + + } +} diff --git a/src/main/java/examplemod/examples/items/materials/ExampleStoneItem.java b/src/main/java/examplemod/examples/items/materials/ExampleStoneItem.java new file mode 100644 index 0000000..720f8c8 --- /dev/null +++ b/src/main/java/examplemod/examples/items/materials/ExampleStoneItem.java @@ -0,0 +1,9 @@ +package examplemod.examples.items.materials; + +import necesse.inventory.item.placeableItem.StonePlaceableItem; + +public class ExampleStoneItem extends StonePlaceableItem { + public ExampleStoneItem(){ + super(100); // Max Stack Size + } +} diff --git a/src/main/java/examplemod/examples/items/tools/ExampleBowRangedWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleBowRangedWeapon.java new file mode 100644 index 0000000..46c8574 --- /dev/null +++ b/src/main/java/examplemod/examples/items/tools/ExampleBowRangedWeapon.java @@ -0,0 +1,32 @@ +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 ExampleBowRangedWeapon extends BowProjectileToolItem { + public ExampleBowRangedWeapon() { + // (enchantCost, lootTableCategory) + super(100, // Enchant Cost + BowWeaponsLootTable.bowWeapons); // Loot Table Category + + this.rarity = Item.Rarity.NORMAL; + + // Core stats + this.attackAnimTime.setBaseValue(800); // Ms Per Shot + this.attackDamage.setBaseValue(12.0F); // Base Bow Damage + this.attackRange.setBaseValue(600); // Range In Tiles (Ish) + this.velocity.setBaseValue(100); // Base Projectile Velocity + this.knockback.setBaseValue(25); // Base Knockback + + // 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/items/tools/ExampleOrbSummonWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleOrbSummonWeapon.java new file mode 100644 index 0000000..6df908b --- /dev/null +++ b/src/main/java/examplemod/examples/items/tools/ExampleOrbSummonWeapon.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 ExampleOrbSummonWeapon extends SummonToolItem { + public ExampleOrbSummonWeapon() { + // , followPosition, summonSpaceTaken, enchantCost, lootTableCategory + super("examplesummonmob", // Mob String ID + FollowPosition.PYRAMID, // Follow Position + 1.0F, // Summon Space Taken + 400, // Enchant Cost + SummonWeaponsLootTable.summonWeapons); // Loot Table Category + + 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/ExampleProjectileWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleStaffMagicWeapon.java similarity index 93% rename from src/main/java/examplemod/examples/ExampleProjectileWeapon.java rename to src/main/java/examplemod/examples/items/tools/ExampleStaffMagicWeapon.java index 31aff93..ea31f86 100644 --- a/src/main/java/examplemod/examples/ExampleProjectileWeapon.java +++ b/src/main/java/examplemod/examples/items/tools/ExampleStaffMagicWeapon.java @@ -1,5 +1,6 @@ -package examplemod.examples; +package examplemod.examples.items.tools; +import examplemod.examples.projectiles.ExampleProjectile; import necesse.engine.localization.Localization; import necesse.engine.network.gameNetworkData.GNDItemMap; import necesse.engine.sound.SoundEffect; @@ -17,13 +18,13 @@ import necesse.level.maps.Level; // Extends MagicProjectileToolItem -public class ExampleProjectileWeapon extends MagicProjectileToolItem { +public class ExampleStaffMagicWeapon 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 ExampleStaffMagicWeapon() { super(400, null); rarity = Rarity.RARE; attackAnimTime.setBaseValue(300); @@ -41,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; } @@ -57,7 +58,7 @@ public void showAttack(Level level, int x, int y, ItemAttackerMob attackerMob, i @Override public InventoryItem onAttack(Level level, int x, int y, ItemAttackerMob attackerMob, int attackHeight, InventoryItem item, ItemAttackSlot slot, int animAttack, int seed, GNDItemMap mapContent) { - // This method is ran on the attacking client and on the server. + // This method is run on the attacking client and on the server. // This means we need to tell other clients that a projectile is being added. // Every projectile weapon is set to include an integer seed used to make sure that the attacking client // and the server gives the projectiles added the same uniqueID. diff --git a/src/main/java/examplemod/examples/items/tools/ExampleSwordMeleeWeapon.java b/src/main/java/examplemod/examples/items/tools/ExampleSwordMeleeWeapon.java new file mode 100644 index 0000000..c62d569 --- /dev/null +++ b/src/main/java/examplemod/examples/items/tools/ExampleSwordMeleeWeapon.java @@ -0,0 +1,23 @@ +package examplemod.examples.items.tools; + +import necesse.inventory.item.Item; +import necesse.inventory.item.toolItem.swordToolItem.SwordToolItem; + +// Extends SwordToolItem +public class ExampleSwordMeleeWeapon extends SwordToolItem { + + // Weapon attack textures are loaded from resources/player/weapons/ + + public ExampleSwordMeleeWeapon() { + super(400, // Enchant Cost + null); // Loot Table Category (there isn't a built-in one for melee + rarity = Item.Rarity.UNCOMMON; // Rarity + attackAnimTime.setBaseValue(300); // 300 ms attack time + attackDamage.setBaseValue(20) // Base Sword damage + .setUpgradedValue(1, 95); // Upgraded Tier 1 Damage + attackRange.setBaseValue(120); // 120 Range + knockback.setBaseValue(100); // 100 Knockback + + } + +} diff --git a/src/main/java/examplemod/examples/items/trinkets/ExampleTrinketItem.java b/src/main/java/examplemod/examples/items/trinkets/ExampleTrinketItem.java new file mode 100644 index 0000000..a8314e4 --- /dev/null +++ b/src/main/java/examplemod/examples/items/trinkets/ExampleTrinketItem.java @@ -0,0 +1,36 @@ +package examplemod.examples.items.trinkets; + +import necesse.engine.localization.Localization; +import necesse.engine.registries.BuffRegistry; +import necesse.engine.util.GameBlackboard; +import necesse.entity.mobs.PlayerMob; +import necesse.entity.mobs.buffs.staticBuffs.armorBuffs.trinketBuffs.TrinketBuff; +import necesse.gfx.gameTooltips.ListGameTooltips; +import necesse.inventory.InventoryItem; +import necesse.inventory.item.trinketItem.TrinketItem; +import necesse.inventory.lootTable.presets.TrinketsLootTable; + +public class ExampleTrinketItem extends TrinketItem { + + // What buff this trinket gives when equipped + private static final String BUFF_ID = "exampletrinketbuff"; + + public ExampleTrinketItem() { + // Basic trinket settings (rarity, enchant cost, loot group) + super(Rarity.UNCOMMON, 400, TrinketsLootTable.trinkets); + } + + @Override + public TrinketBuff[] getBuffs(InventoryItem inventoryItem) { + // Give the player our buff while the trinket is equipped + return new TrinketBuff[] { (TrinketBuff) BuffRegistry.getBuff(BUFF_ID) }; + } + + @Override + public ListGameTooltips getPreEnchantmentTooltips(InventoryItem item, PlayerMob perspective, GameBlackboard blackboard) { + // Start with normal tooltip, then add 1 extra line + ListGameTooltips t = super.getPreEnchantmentTooltips(item, perspective, blackboard); + t.add(Localization.translate("itemtooltip", "exampletrinkettip")); + return t; + } +} \ No newline at end of file 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..12fb827 --- /dev/null +++ b/src/main/java/examplemod/examples/maps/biomes/ExampleBiome.java @@ -0,0 +1,302 @@ +package examplemod.examples.maps.biomes; + +import examplemod.Loaders.ExampleModObjects; +import examplemod.Loaders.ExampleModTiles; +import examplemod.examples.ExampleLootTable; +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.util.LevelIdentifier; +import necesse.engine.world.biomeGenerator.BiomeGeneratorStack; +import necesse.entity.mobs.Mob; +import necesse.entity.mobs.PlayerMob; +import necesse.inventory.lootTable.LootItemInterface; +import necesse.inventory.lootTable.LootTable; +import necesse.inventory.lootTable.lootItem.ChanceLootItem; +import necesse.inventory.lootTable.lootItem.LootItemList; +import necesse.level.maps.Level; +import necesse.level.maps.biomes.Biome; +import necesse.level.maps.biomes.MobSpawnTable; +import necesse.level.maps.presets.RandomCaveChestRoom; +import necesse.level.maps.presets.caveRooms.CaveRuins; +import necesse.level.maps.presets.set.ChestRoomSet; +import necesse.level.maps.presets.set.ColumnSet; +import necesse.level.maps.presets.set.WallSet; +import necesse.level.maps.regionSystem.Region; + +import java.awt.Color; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Controls: + * - surface ground tile + * - base cave/deep cave tiles + rock objects + * - region decoration passes (surface / cave / deep cave) + * - spawns, music, and structure hooks + */ +public class ExampleBiome extends Biome { + + // ========================================================================= + // Spawns + // ========================================================================= + + public static final MobSpawnTable surfaceCritters = new MobSpawnTable() + .include(Biome.defaultSurfaceCritters); + + public static final MobSpawnTable caveCritters = new MobSpawnTable() + .include(Biome.defaultCaveCritters); + + public static final MobSpawnTable surfaceMobs = new MobSpawnTable() + .add(30, "examplemob"); + + public static final MobSpawnTable caveMobs = new MobSpawnTable() + .add(100, "examplemob"); + + public static final MobSpawnTable deepCaveMobs = new MobSpawnTable() + .add(100, "examplemob"); + + public ExampleBiome() { + super(); + this.setGenerationWeight(1.0F); + } + + // ========================================================================= + // Base tiles / rocks + // ========================================================================= + + /** + * Surface ground tile. + * If it can't be found, we fall back to grass so worlds still load. + */ + @Override + public int getGenerationTerrainTileID() { + int exampleGrass = TileRegistry.getTileID("examplegrasstile"); + return (exampleGrass == -1) ? TileRegistry.grassID : exampleGrass; + } + + /** + * Cave floor tile used in this biome. + */ + @Override + public int getGenerationCaveTileID() { + return ExampleModTiles.EXAMPLE_TILE_ID; + } + + /** + * Cave rock object used in this biome. + */ + @Override + public int getGenerationCaveRockObjectID() { + return ExampleModObjects.EXAMPLE_BASE_ROCK_ID; + } + + /** + * Deep cave floor tile used in this biome. + */ + @Override + public int getGenerationDeepCaveTileID() { + return ExampleModTiles.EXAMPLE_TILE_ID; + } + + /** + * Deep cave rock object used in this biome. + * If you ever add a separate deep version, swap it in here. + */ + @Override + public int getGenerationDeepCaveRockObjectID() { + return ExampleModObjects.EXAMPLE_BASE_ROCK_ID; + } + + // Set up the loot interface for our boss summon extra drop + public static LootItemInterface randomExampleBossSummonDrop = new LootItemList(new ChanceLootItem(1.00F, "examplebosssummonitem")); + + // ========================================================================= + // Generator setup (veins) + // ========================================================================= + + @Override + public void initializeGeneratorStack(BiomeGeneratorStack stack) { + super.initializeGeneratorStack(stack); + + // Trees on the surface + stack.addRandomSimplexVeinsBranch("exampleTrees", 2.0F, 0.2F, 1.0F, 0); + + // Ore veins underground + stack.addRandomVeinsBranch("exampleCaveOre", 0.60F, 3, 6, 0.4F, 2, false); + stack.addRandomVeinsBranch("exampleDeepCaveOre", 0.60F, 3, 6, 0.4F, 2, false); + } + + // ========================================================================= + // Region passes + // ========================================================================= + + @Override + public void generateRegionSurfaceTerrain(Region region, BiomeGeneratorStack stack, GameRandom random) { + super.generateRegionSurfaceTerrain(region, stack, random); + + final int grassTile = getGenerationTerrainTileID(); + + stack.startPlaceOnVein(this, region, random, "exampleTrees") + .onlyOnTile(grassTile) + .chance(0.10D) + .placeObject("exampletree"); + + stack.startPlace(this, region, random) + .chance(0.40D) + .onlyOnTile(grassTile) + .placeObject("examplegrass"); + } + + @Override + public void generateRegionCaveTerrain(Region region, BiomeGeneratorStack stack, GameRandom random) { + super.generateRegionCaveTerrain(region, stack, random); + + // Ore veins: place on our cave rock object + stack.startPlaceOnVein(this, region, random, "exampleCaveOre") + .onlyOnObject(ExampleModObjects.EXAMPLE_BASE_ROCK_ID) + .placeObjectForced("exampleorerock"); + + // If you want crates / small rocks etc, add them here. + // region.updateLiquidManager(); // only needed if you place/edit liquids + } + + @Override + public void generateRegionDeepCaveTerrain(Region region, BiomeGeneratorStack stack, GameRandom random) { + super.generateRegionDeepCaveTerrain(region, stack, random); + + // Ore veins: place on our deep cave rock object (same as base for now) + stack.startPlaceOnVein(this, region, random, "exampleDeepCaveOre") + .onlyOnObject(ExampleModObjects.EXAMPLE_BASE_ROCK_ID) + .placeObjectForced("exampleorerock"); + + // region.updateLiquidManager(); // only needed if you place/edit liquids + } + + // ========================================================================= + // Debug + music + // ========================================================================= + + @Override + public Color getDebugBiomeColor() { + return new Color(128, 0, 128); + } + + @Override + public AbstractMusicList getLevelMusic(Level level, PlayerMob perspective) { + return new MusicList(MusicRegistry.ForestPath); + } + // ========================================================================= + // Boss Summon Drop + // ========================================================================= + + // Add drop to journal + @Override + public LootTable getExtraBiomeMobDrops(LevelIdentifier levelIdentifier) { + if (levelIdentifier == null) + return new LootTable(); + if (levelIdentifier.equals(LevelIdentifier.CAVE_IDENTIFIER)) + return new LootTable(randomExampleBossSummonDrop); + return new LootTable(); + } + + // Add Example Boss Summon Item + @Override + public LootTable getExtraMobDrops(Mob mob) { + if (mob == null || mob.getLevel() == null) return super.getExtraMobDrops(mob); + + // Only in caves (or include deep caves too) + LevelIdentifier id = mob.getLevel().getIdentifier(); + boolean isCave = LevelIdentifier.CAVE_IDENTIFIER.equals(id); + // boolean isDeepCave = LevelIdentifier.DEEP_CAVE_IDENTIFIER.equals(id); + + if (isCave) { + if (mob.isHostile && !mob.isSummoned) return new LootTable(randomExampleBossSummonDrop); + } + return super.getExtraMobDrops(mob); + } + + // ========================================================================= + // Spawn selection + // ========================================================================= + + @Override + public MobSpawnTable getCritterSpawnTable(Level level) { + return level.isCave ? caveCritters : surfaceCritters; + } + + @Override + public MobSpawnTable getMobSpawnTable(Level level) { + if (!level.isCave) return surfaceMobs; + if (LevelIdentifier.DEEP_CAVE_IDENTIFIER.equals(level.getIdentifier())) return deepCaveMobs; + return caveMobs; + } + + // ========================================================================= + // Structures / presets + // ========================================================================= + + public RandomCaveChestRoom getNewCaveChestRoomPreset(GameRandom random, AtomicInteger lootRotation) { + + // WallSet("example") will look for: + // examplewall, exampledoor, examplearrowtrap, exampleflametrap, etc. + WallSet exampleWalls = new WallSet("example"); + + // Keep columns the same as stone (optional) + ColumnSet columns = ColumnSet.stone; + +// This "set" is just a bundle of IDs that tells the chest-room preset +// which tiles/objects to use when it builds the room. + ChestRoomSet exampleSet = new ChestRoomSet( + "exampletile", // The floor tile name the preset should use + "examplepressureplate", // The pressure plate object to place in the room + exampleWalls, // WallSet we made earlier. It supplies the wall + door + trap object by looking up IDs that start with "example" + columns, // Just the column style (visual decoration) + "storagebox", // The chest object that will be placed in the room + "examplewalltrap" // A trap object ID to use (this must be a real registered object ID) + ); + +// Now we build the actual room preset using that set. + return getRandomCaveChestRoom(random, lootRotation, exampleSet); + } + + private static RandomCaveChestRoom getRandomCaveChestRoom( + GameRandom random, + AtomicInteger lootRotation, + ChestRoomSet exampleSet + ) { + // Create the chest room preset. + // - ExampleLootTable.exampleloottable = what items go in the chest + // - lootRotation = the counter used for "rotating" loot between different chest rooms + // - exampleSet = which walls/door/plate/traps/chest to place + RandomCaveChestRoom chestRoom = new RandomCaveChestRoom( + random, + ExampleLootTable.exampleloottable, + lootRotation, + exampleSet + ); + + // The base preset normally uses stone floor tiles. + // This line swaps any stone floor the preset would place into our custom cave tile instead. + chestRoom.replaceTile(TileRegistry.stoneFloorID, ExampleModTiles.EXAMPLE_TILE_ID); + + // Give the preset back to world gen. It will be placed later when the generator picks a location. + return chestRoom; + } + + + public RandomCaveChestRoom getNewDeepCaveChestRoomPreset(GameRandom random, AtomicInteger unique) { + return null; + } + + + public CaveRuins getNewCaveRuinsPreset(GameRandom random, AtomicInteger unique) { + return null; + } + + + public CaveRuins getNewDeepCaveRuinsPreset(GameRandom random, AtomicInteger unique) { + return null; + } +} diff --git a/src/main/java/examplemod/examples/ExampleIncursionBiome.java b/src/main/java/examplemod/examples/maps/incursion/ExampleIncursionBiome.java similarity index 89% rename from src/main/java/examplemod/examples/ExampleIncursionBiome.java rename to src/main/java/examplemod/examples/maps/incursion/ExampleIncursionBiome.java index 6671f98..b410505 100644 --- a/src/main/java/examplemod/examples/ExampleIncursionBiome.java +++ b/src/main/java/examplemod/examples/maps/incursion/ExampleIncursionBiome.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.maps.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")); } /** @@ -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") ); } @@ -77,13 +77,13 @@ public IncursionLevel getNewIncursionLevel(FallenAltarObjectEntity altar, LevelI } /** - * Colors used for the glowing gateway lights on the fallen altar. - * IncursionBiome requires this method; expected to return list of 6 colors. + * Colours used for the glowing gateway lights on the fallen altar. + * IncursionBiome requires this method; expected to return list of 6 colours. */ @Override public ArrayList getFallenAltarGatewayColorsForBiome() { ArrayList colors = new ArrayList<>(); - // Repeat colors to satisfy the altar rendering requirements + // Repeat colours to satisfy the altar rendering requirements colors.add(new Color(181, 80, 120)); colors.add(new Color(215, 42, 52)); colors.add(new Color(181, 92, 59)); diff --git a/src/main/java/examplemod/examples/ExampleIncursionLevel.java b/src/main/java/examplemod/examples/maps/incursion/ExampleIncursionLevel.java similarity index 62% rename from src/main/java/examplemod/examples/ExampleIncursionLevel.java rename to src/main/java/examplemod/examples/maps/incursion/ExampleIncursionLevel.java index bfcecef..ea8c555 100644 --- a/src/main/java/examplemod/examples/ExampleIncursionLevel.java +++ b/src/main/java/examplemod/examples/maps/incursion/ExampleIncursionLevel.java @@ -1,6 +1,7 @@ -package examplemod.examples; +package examplemod.examples.maps.incursion; import examplemod.ExampleMod; +import examplemod.examples.presets.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,46 +48,69 @@ 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"); - - // Seed the generator so this incursion layout is deterministic per mission + CaveGeneration cg = new CaveGeneration(this, "deeprocktile", "examplebaserock"); 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("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/mobs/ExampleBossMob.java b/src/main/java/examplemod/examples/mobs/ExampleBossMob.java new file mode 100644 index 0000000..8f0455d --- /dev/null +++ b/src/main/java/examplemod/examples/mobs/ExampleBossMob.java @@ -0,0 +1,161 @@ +package examplemod.examples.mobs; + +import examplemod.examples.ai.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.inventory.lootTable.lootItem.LootItem; +import necesse.inventory.lootTable.lootItem.RotationLootItem; +import necesse.level.maps.CollisionFilter; +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; + + // Our regular loot table with a chance item + public static LootTable lootTable = new LootTable( + ChanceLootItem.between(0.5f, "exampleitem", 1, 3) + ); + + // Indivitual boss unique loot rotation + public static RotationLootItem uniqueDrops = RotationLootItem.privateLootRotation( + new LootItem("examplemeleesword"), + new LootItem("examplemagicstaff"), + new LootItem("examplesummonorb"), + new LootItem("examplerangedbow")); + + // LootTable for unique drops + public static LootTable privateLootTable = new LootTable( + uniqueDrops); + + // 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); + } + + //ignore level collisions (this will still collide with the player + @Override + public CollisionFilter getLevelCollisionFilter() { + return null; // Level.collides(..., null) returns false in source + } + + @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 + ) + ); + } + // Our regular LootTable + @Override + public LootTable getLootTable() { + return lootTable; + } + + // The boss's Rotating loot table unique to each player + @Override + public LootTable getPrivateLootTable() { + return privateLootTable; + } + + @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/mobs/ExampleSettlerMob.java b/src/main/java/examplemod/examples/mobs/ExampleSettlerMob.java new file mode 100644 index 0000000..933a8f6 --- /dev/null +++ b/src/main/java/examplemod/examples/mobs/ExampleSettlerMob.java @@ -0,0 +1,46 @@ +package examplemod.examples.mobs; + +import examplemod.examples.settlement.jobs.ExampleLevelJob; +import necesse.engine.network.server.ServerClient; +import necesse.entity.mobs.friendly.human.humanShop.HumanShop; +import necesse.inventory.InventoryItem; + +import java.util.Collections; +import java.util.List; + +public class ExampleSettlerMob extends HumanShop { + + public ExampleSettlerMob() { + // MUST pass 3 args (same pattern as FarmerHumanMob) + super(500, 200, "examplesettler"); + + // Unlock the job type for THIS settler only + this.jobTypeHandler.getPriority("examplejobtype").disabledBySettler = false; + + // Give them a tool to clear grass (optional, but nice) + this.equipmentInventory.setItem(6, new necesse.inventory.InventoryItem("farmingscythe")); + + // Register handler so they can actually perform the job + this.jobTypeHandler.setJobHandler( + ExampleLevelJob.class, + 0, 0, // cooldown min/max + 0, 4000, // work-break buffer usage min/max (matches Forestry) + (handler, worker) -> + !isOnWorkBreak() + && !isOnStrike() + && !hasCompletedMission() + && (!isSettler() || isSettlerWithinSettlement()) + && !isInventoryFull(true), + foundJob -> ExampleLevelJob.getJobSequence(this, isSettler(), foundJob) + ); + } + + @Override + public List getRecruitItems(ServerClient client) { + // Optional: if trapped, don’t allow recruiting + if (isTrapped()) return Collections.emptyList(); + + // Simple recruit cost (you can make this random like vanilla does) + return Collections.singletonList(new InventoryItem("exampleitem", 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/objectentity/ExampleJobObjectEntity.java b/src/main/java/examplemod/examples/objectentity/ExampleJobObjectEntity.java new file mode 100644 index 0000000..bf639e6 --- /dev/null +++ b/src/main/java/examplemod/examples/objectentity/ExampleJobObjectEntity.java @@ -0,0 +1,79 @@ +package examplemod.examples.objectentity; + +import necesse.entity.objectEntity.ObjectEntity; +import necesse.level.maps.Level; +import necesse.level.maps.LevelObject; + +import examplemod.examples.settlement.jobs.ExampleLevelJob; + +public class ExampleJobObjectEntity extends ObjectEntity { + + // Config + private final int radiusTiles = 25; + + // State + private long lastScanTime; + private int scanDX; + private int scanDY; + + public ExampleJobObjectEntity(Level level, int tileX, int tileY) { + // NOTE: ObjectEntity constructor needs (level, type, x, y) + super(level, "examplejobobjectentity", tileX, tileY); + this.shouldSave = true; + + this.scanDX = -radiusTiles; + this.scanDY = -radiusTiles; + this.lastScanTime = 0L; + } + + @Override + public void init() { + super.init(); + if (this.lastScanTime <= 0L) { + this.lastScanTime = getWorldEntity().getWorldTime(); + } + } + + @Override + public void serverTick() { + super.serverTick(); + + long now = getWorldEntity().getWorldTime(); + // run once per second (worldTime is ms) + long scanIntervalMs = 1000; + if (now < this.lastScanTime + scanIntervalMs) { + return; + } + this.lastScanTime = now; + + Level level = getLevel(); + + // how many tiles we check each scan burst + int tilesPerRun = 120; + for (int i = 0; i < tilesPerRun; i++) { + int x = this.tileX + scanDX; + int y = this.tileY + scanDY; + + // advance scan cursor (square spiral-ish over the area) + scanDX++; + if (scanDX > radiusTiles) { + scanDX = -radiusTiles; + scanDY++; + if (scanDY > radiusTiles) { + scanDY = -radiusTiles; + } + } + + if (!level.isTileWithinBounds(x, y)) continue; + + // Don’t clear decorative / player-placed grass + if (level.objectLayer.isPlayerPlaced(x, y)) continue; + + LevelObject lo = level.getLevelObject(x, y); + if (lo.object == null || !lo.object.isGrass) continue; + + // Add your example job + level.jobsLayer.addJob(new ExampleLevelJob(x,y)); + } + } +} diff --git a/src/main/java/examplemod/examples/objectentity/ExampleObjectEntity.java b/src/main/java/examplemod/examples/objectentity/ExampleObjectEntity.java new file mode 100644 index 0000000..96768ec --- /dev/null +++ b/src/main/java/examplemod/examples/objectentity/ExampleObjectEntity.java @@ -0,0 +1,97 @@ +package examplemod.examples.objectentity; + +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/objectentity/ExampleTrapObjectEntity.java b/src/main/java/examplemod/examples/objectentity/ExampleTrapObjectEntity.java new file mode 100644 index 0000000..2317fc1 --- /dev/null +++ b/src/main/java/examplemod/examples/objectentity/ExampleTrapObjectEntity.java @@ -0,0 +1,77 @@ +package examplemod.examples.objectentity; + +import java.awt.Point; + +import necesse.entity.mobs.GameDamage; +import necesse.entity.objectEntity.TrapObjectEntity; +import necesse.entity.projectile.TrapArrowProjectile; +import necesse.level.maps.Level; + +/* + * Arrow trap logic. + * When this trap is triggered by a wire, it shoots an arrow in the direction it faces. + */ +public class ExampleTrapObjectEntity extends TrapObjectEntity { + + // The damage the arrow will deal when it hits something. + public static final GameDamage DAMAGE = new GameDamage(40.0F, 100.0F, 0.0F, 2.0F, 1.0F); + + public ExampleTrapObjectEntity(Level level, int x, int y) { + // Cooldown in milliseconds (1000ms = 1 second). + super(level, x, y, 1000L); + + // This object entity is meant to be recreated, not saved. + this.shouldSave = false; + } + + @Override + public void triggerTrap(int wireID, int dir) { + // Only the server should spawn projectiles. + // Also, don't fire again while we're still on cooldown. + if (isClient() || onCooldown()) return; + + // If a different wire is active at the same time, ignore this trigger. + if (otherWireActive(wireID)) return; + + // Find the tile position the trap should fire from. + Point tilePos = getPos(this.tileX, this.tileY, dir); + + // Turn the direction number (0..3) into a simple (x,y) direction. + Point d = getDir(dir); + + // Convert tile coordinates into pixel coordinates (32 pixels per tile). + int xPos = tilePos.x * 32; + int yPos = tilePos.y * 32; + + // Shift the spawn position a bit so the arrow looks like it comes from the correct side. + if (d.x == 0) xPos += 16; // shooting up/down: centre of the tile + else if (d.x == -1) xPos += 30; // shooting left: near the left edge + else if (d.x == 1) xPos += 2; // shooting right: near the right edge + + if (d.y == 0) yPos += 16; // shooting left/right: centre of the tile + else if (d.y == -1) yPos += 30; // shooting up: near the top edge + else if (d.y == 1) yPos += 2; // shooting down: near the bottom edge + + // Create and spawn the projectile. + // The "target" is just one step in the direction we're firing. + getLevel().entityManager.projectiles.add(new TrapArrowProjectile( + xPos, yPos, + xPos + d.x, + yPos + d.y, + DAMAGE, + null + )); + + // Start the cooldown so it can't fire again instantly. + startCooldown(); + } + + // Converts 0..3 into up/right/down/left. + private Point getDir(int dir) { + if (dir == 0) return new Point(0, -1); // up + if (dir == 1) return new Point(1, 0); // right + if (dir == 2) return new Point(0, 1); // down + if (dir == 3) return new Point(-1, 0); // left + return new Point(0, 0); + } +} \ No newline at end of file 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..b1e6290 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleBaseRockObject.java @@ -0,0 +1,16 @@ +package examplemod.examples.objects; +import necesse.level.gameObject.RockObject; + +import java.awt.Color; + +public class ExampleBaseRockObject extends RockObject { + + public ExampleBaseRockObject() { + super("examplebaserock", // Texture for the base rock + new Color(92, 37, 23), // Mini Map Pixel Colour + "examplestone", // Dropped Stone + "objects", "landscaping"); // Item Categories + + this.toolTier = 0.0F; // Tier of pickaxe required to mine this rock + } +} diff --git a/src/main/java/examplemod/examples/objects/ExampleConfigObject.java b/src/main/java/examplemod/examples/objects/ExampleConfigObject.java new file mode 100644 index 0000000..abbd329 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleConfigObject.java @@ -0,0 +1,86 @@ +package examplemod.examples.objects; + +import examplemod.examples.packets.ExampleConfigInteractPacket; +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; + +public class ExampleConfigObject extends GameObject { + // Loaded once from mod resources in loadTextures() + private GameTexture texture; + + public ExampleConfigObject() { + super(new Rectangle(32, 32)); + this.isSolid = true; + } + + @Override + public void loadTextures() { + super.loadTextures(); + + // Loads: src/main/resources/objects/exampleleveleventobject.png + // (no ".png" in the string) + this.texture = GameTexture.fromFile("objects/exampleconfigobject"); + } + + @Override + public void addDrawables(List list, OrderableDrawables tileList, + Level level, int tileX, int tileY, TickManager tickManager, + GameCamera camera, PlayerMob perspective) { + + // Match sprite lighting to the level light at this tile + GameLight light = level.getLightLevel(tileX, tileY); + + // Convert tile coordinates to screen draw coordinates + int drawX = camera.getTileDrawX(tileX); + int drawY = camera.getTileDrawY(tileY); + + // Build draw options once (sprite + lighting + position) + final TextureDrawOptionsEnd opts = this.texture.initDraw() + .sprite(0, 0, 32) // sprite index (0,0), size 32 + .light(light) + .pos(drawX, drawY); + + /* + */ + tileList.add(tm -> opts.draw()); + } + + @Override + public void drawPreview(Level level, int tileX, int tileY, int rotation, float alpha, + PlayerMob player, GameCamera camera) { + + // Placement preview ("ghost" sprite) while holding the item + 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) { + if (level.isClient() && level.getClient() != null) { + level.getClient().network.sendPacket(new ExampleConfigInteractPacket(x, y)); + } + } +} \ No newline at end of file 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/objects/ExampleJobObject.java b/src/main/java/examplemod/examples/objects/ExampleJobObject.java new file mode 100644 index 0000000..8558d8a --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleJobObject.java @@ -0,0 +1,79 @@ +package examplemod.examples.objects; + +import java.awt.Rectangle; +import java.util.List; + +import examplemod.examples.objectentity.ExampleJobObjectEntity; +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; +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; + +public class ExampleJobObject extends GameObject { + // Loaded once from mod resources in loadTextures() + private GameTexture texture; + + public ExampleJobObject() { + super(new Rectangle(32, 32)); + this.isSolid = true; + this.mapColor = new java.awt.Color(120, 170, 120); + } + @Override + public void loadTextures() { + super.loadTextures(); + + // Loads: src/main/resources/objects/exampleleveleventobject.png + // (no ".png" in the string) + this.texture = GameTexture.fromFile("objects/examplejobobject"); + } + + @Override + public void addDrawables(List list, OrderableDrawables tileList, + Level level, int tileX, int tileY, TickManager tickManager, + GameCamera camera, PlayerMob perspective) { + + // Match sprite lighting to the level light at this tile + GameLight light = level.getLightLevel(tileX, tileY); + + // Convert tile coordinates to screen draw coordinates + int drawX = camera.getTileDrawX(tileX); + int drawY = camera.getTileDrawY(tileY); + + // Build draw options once (sprite + lighting + position) + final TextureDrawOptionsEnd opts = this.texture.initDraw() + .sprite(0, 0, 32) // sprite index (0,0), size 32 + .light(light) + .pos(drawX, drawY); + + /* + */ + tileList.add(tm -> opts.draw()); + } + + @Override + public void drawPreview(Level level, int tileX, int tileY, int rotation, float alpha, + PlayerMob player, GameCamera camera) { + + // Placement preview ("ghost" sprite) while holding the item + 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 ObjectEntity getNewObjectEntity(Level level, int x, int y) { + return new ExampleJobObjectEntity(level, x, y); + } +} 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..cf624fd --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleLevelEventObject.java @@ -0,0 +1,89 @@ +package examplemod.examples.objects; + +import examplemod.examples.objectentity.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; +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 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 mod resources in loadTextures() + private GameTexture texture; + + public ExampleLevelEventObject() { + //no physics shape + super(new Rectangle()); + this.isSolid = false; + } + + @Override + public void loadTextures() { + super.loadTextures(); + + // Loads: src/main/resources/objects/exampleleveleventobject.png + // (no ".png" in the string) + 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) { + + // Match sprite lighting to the level light at this tile + GameLight light = level.getLightLevel(tileX, tileY); + + // Convert tile coordinates to screen draw coordinates + int drawX = camera.getTileDrawX(tileX); + int drawY = camera.getTileDrawY(tileY); + + // Build draw options once (sprite + lighting + position) + final TextureDrawOptionsEnd opts = this.texture.initDraw() + .sprite(0, 0, 32) // sprite index (0,0), size 32 + .light(light) + .pos(drawX, drawY); + + /* + */ + tileList.add(tm -> opts.draw()); + } + + + @Override + public void drawPreview(Level level, int tileX, int tileY, int rotation, float alpha, + PlayerMob player, GameCamera camera) { + + // Placement preview ("ghost" sprite) while holding the item + 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 ObjectEntity getNewObjectEntity(Level level, int x, int y) { + return new ExampleObjectEntity(level, x, y); + } +} 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..340846b --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleOreRockObject.java @@ -0,0 +1,25 @@ +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 ExampleOreRockObject(RockObject parentRock) { + + super(parentRock, + "oremask", // Ore Mask Image + "exampleore", // Ore Texture Name + new Color(90, 40, 160), // Mini Map Color + "exampleore", // Dropped Ore + 1, // Min Drop + 3, // Max Drop + 2, // Placed Dropped Ore + true, // Is Incrustion Extraction Object + "objects", "landscaping"); // Categories + } +} diff --git a/src/main/java/examplemod/examples/objects/ExamplePressurePlateObject.java b/src/main/java/examplemod/examples/objects/ExamplePressurePlateObject.java new file mode 100644 index 0000000..cb71521 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExamplePressurePlateObject.java @@ -0,0 +1,18 @@ +package examplemod.examples.objects; + +import java.awt.Color; + +import necesse.level.gameObject.MaskedPressurePlateObject; + +public class ExamplePressurePlateObject extends MaskedPressurePlateObject { + + public ExamplePressurePlateObject() { + // Map colour used on the minimap. + super("pressureplatemask", // Textue Mask + "exampletile", // Tile Texture + new Color(120, 80, 200)); // Mini Map Color + + // MaskedPressurePlateObject sets the important flags internally (including isPressurePlate) + // and uses a default trigger hitbox through its object entity. + } +} \ No newline at end of file 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..dd897cc --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleTreeSaplingObject.java @@ -0,0 +1,17 @@ +package examplemod.examples.objects; + +import necesse.level.gameObject.TreeSaplingObject; + +public class ExampleTreeSaplingObject extends TreeSaplingObject { + + public ExampleTreeSaplingObject(){ + // Add To Any Sapling Global Ingrediant + super("examplesapling", // Texture Name + "exampletree", // Resulting Object String ID + 1800, // Min Grow Time In Seconds + 2700, // Max Grow Time In Seconds + true); // Add To Any Sapling Global Ingrediant + + } + +} diff --git a/src/main/java/examplemod/examples/objects/ExampleWallTrapObject.java b/src/main/java/examplemod/examples/objects/ExampleWallTrapObject.java new file mode 100644 index 0000000..be57596 --- /dev/null +++ b/src/main/java/examplemod/examples/objects/ExampleWallTrapObject.java @@ -0,0 +1,25 @@ +package examplemod.examples.objects; + +import examplemod.examples.objectentity.ExampleTrapObjectEntity; +import necesse.entity.objectEntity.ObjectEntity; +import necesse.level.gameObject.WallObject; +import necesse.level.gameObject.WallTrapObject; +import necesse.level.maps.Level; + +/* + * A wall trap you can place in the world. + * It uses "examplearrowtrap" as its texture name. + */ +public class ExampleWallTrapObject extends WallTrapObject { + + public ExampleWallTrapObject(WallObject wallObject) { + // Tells the game which texture to use (objects/examplearrowtrap.png) + super(wallObject, "examplewalltrap"); + } + + @Override + public ObjectEntity getNewObjectEntity(Level level, int x, int y) { + // Creates the object entity that handles the trap behaviour. + return new ExampleTrapObjectEntity(level, x, y); + } +} \ No newline at end of file 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/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/java/examplemod/examples/packets/ExampleConfigInteractPacket.java b/src/main/java/examplemod/examples/packets/ExampleConfigInteractPacket.java new file mode 100644 index 0000000..4c1d5c3 --- /dev/null +++ b/src/main/java/examplemod/examples/packets/ExampleConfigInteractPacket.java @@ -0,0 +1,64 @@ +package examplemod.examples.packets; + +import examplemod.ExampleMod; +import necesse.engine.Settings; +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.network.server.Server; +import necesse.engine.network.server.ServerClient; + +public class ExampleConfigInteractPacket extends Packet { + + public final int tileX; + public final int tileY; + + // requred + public ExampleConfigInteractPacket(byte[] data) { + super(data); + PacketReader r = new PacketReader(this); + this.tileX = r.getNextInt(); + this.tileY = r.getNextInt(); + } + + // Convenience constructor when sending + public ExampleConfigInteractPacket(int tileX, int tileY) { + this.tileX = tileX; + this.tileY = tileY; + + PacketWriter w = new PacketWriter(this); + w.putNextInt(tileX); + w.putNextInt(tileY); + } + + @Override + public void processServer(NetworkPacket packet, Server server, ServerClient client) { + if (ExampleMod.settings == null) { + client.sendChatMessage("[ExampleMod] Settings missing on server."); + return; + } + + // 1) Increment server value + ExampleMod.settings.exampleInt += 1; + + // 2) Save server config back to disk (server.cfg + cfg/mods/.cfg) + Settings.saveServerSettings(); + + // 3) Display AFTER increment+save + boolean enabled = ExampleMod.settings.exampleBoolean; + int amount = ExampleMod.settings.exampleInt; + String msg = ExampleMod.settings.exampleString; + + client.sendChatMessage("[ExampleMod] Server config updated (saved):"); + client.sendChatMessage("exampleBoolean: " + enabled); + client.sendChatMessage("exampleInt: " + amount); + client.sendChatMessage("exampleString: " + msg); + } + + @Override + public void processClient(NetworkPacket packet, Client client) { + // Nothing needed. Server sends chat messages to the client. + } +} 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/java/examplemod/examples/ExampleConstructorPatch.java b/src/main/java/examplemod/examples/patches/ExampleConstructorPatch.java similarity index 95% rename from src/main/java/examplemod/examples/ExampleConstructorPatch.java rename to src/main/java/examplemod/examples/patches/ExampleConstructorPatch.java index 7b48c45..eb20d2f 100644 --- a/src/main/java/examplemod/examples/ExampleConstructorPatch.java +++ b/src/main/java/examplemod/examples/patches/ExampleConstructorPatch.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.patches; import necesse.engine.modLoader.annotations.ModConstructorPatch; import necesse.entity.mobs.friendly.critters.RabbitMob; @@ -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/ExampleMethodPatch.java b/src/main/java/examplemod/examples/patches/ExampleMethodPatch.java similarity index 99% rename from src/main/java/examplemod/examples/ExampleMethodPatch.java rename to src/main/java/examplemod/examples/patches/ExampleMethodPatch.java index 00c5082..6bc6809 100644 --- a/src/main/java/examplemod/examples/ExampleMethodPatch.java +++ b/src/main/java/examplemod/examples/patches/ExampleMethodPatch.java @@ -1,4 +1,4 @@ -package examplemod.examples; +package examplemod.examples.patches; import necesse.engine.modLoader.annotations.ModMethodPatch; import necesse.inventory.lootTable.LootTable; diff --git a/src/main/java/examplemod/examples/patches/JobFinderSafePatch.java b/src/main/java/examplemod/examples/patches/JobFinderSafePatch.java new file mode 100644 index 0000000..407bcf1 --- /dev/null +++ b/src/main/java/examplemod/examples/patches/JobFinderSafePatch.java @@ -0,0 +1,36 @@ +package examplemod.examples.patches; + +import java.util.Collection; +import java.util.Iterator; +import java.util.stream.Stream; +import java.util.stream.Stream.Builder; + +import necesse.entity.mobs.job.EntityJobWorker; +import necesse.entity.mobs.job.FoundJob; +import necesse.entity.mobs.job.JobTypeHandler; + +public final class JobFinderSafePatch { + private JobFinderSafePatch() {} + + @SuppressWarnings({"rawtypes"}) + public static Stream safeStreamFoundJobs(JobTypeHandler handler, EntityJobWorker worker) { + if (handler == null || worker == null) return Stream.empty(); + + Builder b = Stream.builder(); + + Collection subs = handler.getJobHandlers(); + + for (Object o : subs) { + if (!(o instanceof JobTypeHandler.SubHandler)) continue; + + JobTypeHandler.SubHandler sub = (JobTypeHandler.SubHandler) o; + + Iterator it = sub.streamFoundJobsFiltered(worker).iterator(); + while (it.hasNext()) { + b.add((FoundJob) it.next()); + } + } + + return b.build(); + } +} diff --git a/src/main/java/examplemod/examples/patches/JobFinderStreamFoundJobsHandlersPatch.java b/src/main/java/examplemod/examples/patches/JobFinderStreamFoundJobsHandlersPatch.java new file mode 100644 index 0000000..d5e7499 --- /dev/null +++ b/src/main/java/examplemod/examples/patches/JobFinderStreamFoundJobsHandlersPatch.java @@ -0,0 +1,30 @@ +package examplemod.examples.patches; + +import java.util.stream.Stream; + +import necesse.engine.modLoader.annotations.ModMethodPatch; +import necesse.entity.mobs.job.EntityJobWorker; +import necesse.entity.mobs.job.FoundJob; +import necesse.entity.mobs.job.JobFinder; +import necesse.entity.mobs.job.JobTypeHandler; +import net.bytebuddy.asm.Advice; + +@ModMethodPatch(target = JobFinder.class, name = "streamFoundJobs", arguments = {}) +public class JobFinderStreamFoundJobsHandlersPatch { + + @Advice.OnMethodEnter(skipOn = Advice.OnNonDefaultValue.class) + static boolean onEnter( + @Advice.FieldValue("handler") JobTypeHandler handler, + @Advice.FieldValue("worker") EntityJobWorker worker, + @Advice.Local("out") Stream out + ) { + out = JobFinderSafePatch.safeStreamFoundJobs(handler, worker); + return true; // skip vanilla method body + } + + @Advice.OnMethodExit + static void onExit(@Advice.Local("out") Stream out, + @Advice.Return(readOnly = false) Stream ret) { + ret = out; + } +} diff --git a/src/main/java/examplemod/examples/presets/ExampleCodePreset.java b/src/main/java/examplemod/examples/presets/ExampleCodePreset.java new file mode 100644 index 0000000..7d805f4 --- /dev/null +++ b/src/main/java/examplemod/examples/presets/ExampleCodePreset.java @@ -0,0 +1,128 @@ +package examplemod.examples.presets; + +import examplemod.examples.ExampleLootTable; +import necesse.engine.registries.ObjectRegistry; +import necesse.engine.registries.TileRegistry; +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 ExampleCodePreset 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 ExampleCodePreset(GameRandom random) { + + // 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); + + /* + * 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 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) + } + } + + /* + * 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); // 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); // left edge + setObject(width - 1, y, wall); // right edge + } + + /* + * 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; + + /* + * Place the storage box object at the centre. + * + * 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 SAFETY RULE (CanApply predicate): + * + * "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 + * - If the tile is NOT a floor, then this returns true + * + * 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 + ); + } +} diff --git a/src/main/java/examplemod/examples/presets/ExamplePreset.java b/src/main/java/examplemod/examples/presets/ExamplePreset.java new file mode 100644 index 0000000..e19d01b --- /dev/null +++ b/src/main/java/examplemod/examples/presets/ExamplePreset.java @@ -0,0 +1,114 @@ +package examplemod.examples.presets; + +import examplemod.examples.ExampleLootTable; +import necesse.engine.util.GameRandom; +import necesse.level.maps.presets.Preset; + +/** + * ExamplePreset (Script-based) + * This preset is the same idea as the code-built room, but it is created using a big text string + * in Necesse's "PRESET script" format. + */ +public class ExamplePreset extends Preset { + + /** + * 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) { + + // 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); + + /* + * 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 + * + * width / height + * - Size of the structure. + * + * tileIDs + tiles + * - "tileIDs" is a list 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 + * - "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, 1436, examplewall, 298, walltorch]," + + "objects=[1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 298, 0, 0, 0, 0, 0, 0, 0, 298, 1436, 1436, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1436, 1436, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1436, 1436, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1436, 1436, 0, 0, 0, 0, 290, 0, 0, 0, 0, 1436, 1436, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1436, 1436, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1436, 1436, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1436, 1436, 298, 0, 0, 0, 0, 0, 0, 0, 298, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436, 1436]," + + "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"; + + /* + * 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); + + /* + * Add loot into the storage box inside the preset. + * + * The idea here is: + * 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 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 then placement fails + * - if isFloor is false, !isFloor is true then 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/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 96% rename from src/main/java/examplemod/examples/ExampleProjectile.java rename to src/main/java/examplemod/examples/projectiles/ExampleProjectile.java index 8e46ce8..6ce8da7 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; @@ -65,7 +65,7 @@ public Trail getTrail() { @Override public void updateTarget() { - // When we have traveled longer than 20 distance, start to find and update the target + // When we have travelled longer than 20 distance, start to find and update the target if (traveledDistance > 20) { findTarget( m -> m.isHostile, // Filter all non hostile diff --git a/src/main/java/examplemod/examples/settlement/jobs/ExampleLevelJob.java b/src/main/java/examplemod/examples/settlement/jobs/ExampleLevelJob.java new file mode 100644 index 0000000..c7f7dc2 --- /dev/null +++ b/src/main/java/examplemod/examples/settlement/jobs/ExampleLevelJob.java @@ -0,0 +1,123 @@ +package examplemod.examples.settlement.jobs; + +import necesse.engine.localization.message.LocalMessage; +import necesse.engine.save.LoadData; +import necesse.entity.ObjectDamageResult; +import necesse.entity.mobs.job.EntityJobWorker; +import necesse.entity.mobs.job.FoundJob; +import necesse.entity.mobs.job.GameLinkedListJobSequence; +import necesse.entity.mobs.job.JobSequence; +import necesse.entity.mobs.job.activeJob.MineObjectActiveJob; +import necesse.level.maps.LevelObject; +import necesse.level.maps.levelData.jobs.LevelJob; +import necesse.level.maps.levelData.jobs.MineObjectLevelJob; + +/** + * A simple settlement job: + * "Go to this tile and clear the grass object there." + * We extend MineObjectLevelJob because Necesse already has a job type for + * destroying an object at a tile. + */ +public class ExampleLevelJob extends MineObjectLevelJob { + + // Create a new job at a tile position + public ExampleLevelJob(int tileX, int tileY) { + super(tileX, tileY); + } + + // Create a job from saved data (not used if shouldSave() returns false) + public ExampleLevelJob(LoadData save) { + super(save); + } + + @Override + public boolean isValid() { + // Use the base checks (it will call isValidObject on the current object) + return super.isValid(); + } + + @Override + public boolean isValidObject(LevelObject object) { + // Do NOT let settlers clear objects that a player placed. + if (getLevel().objectLayer.isPlayerPlaced(this.tileX, this.tileY)) return false; + + // Only allow this job to target grass objects. + return object.object != null && object.object.isGrass; + } + + @Override + public boolean isSameJob(LevelJob other) { + // Jobs system uses this to avoid duplicates. + // If another ExampleLevelJob exists at the same tile, treat it as the same job. + return other instanceof ExampleLevelJob + && other.tileX == this.tileX + && other.tileY == this.tileY; + } + + @Override + public boolean shouldSave() { + // Don't save this job. The settlement can recreate it later if needed. + return false; + } + + /** + * This builds the actual steps the settler will do. + * Here we only add ONE step: mine/destroy the grass object. + */ + public static JobSequence getJobSequence( + EntityJobWorker worker, final boolean useItem, final FoundJob foundJob + ) { + // Get the current object at the job tile (might be null if it changed) + LevelObject target = foundJob.job.getObject(); + + // Message shown for the job (in settlement UI) + LocalMessage msg = new LocalMessage( + "activities", + "examplejob", + "target", + (target != null && target.object != null) + ? target.object.getLocalization() + : new LocalMessage("ui", "unknown") + ); + + // A list of work steps + final GameLinkedListJobSequence seq = new GameLinkedListJobSequence(msg); + + // Add the work step: go to tile + hit the object until it breaks + seq.add(new MineObjectActiveJob( + worker, + foundJob.priority, + foundJob.job.tileX, + foundJob.job.tileY, + + // Keep working only while the job still exists AND the object is still valid grass + lo -> (!foundJob.job.isRemoved() && foundJob.job.isValidObject(lo)), + + // Reservation (stops 2 settlers trying to do the same tile) + foundJob.job.reservable, + + // Item used for the "swing" animation (visual only) + "farmingscythe", + + // Damage per hit to the object + 5, + + // Time per swing (ms) + 250, + + // Extra delay between swings (ms) + 0 + ) { + @Override + public void onObjectDestroyed(ObjectDamageResult result) { + // Make pickup jobs for any drops + addItemPickupJobs(foundJob.priority, result, seq); + + // Remove the job so it doesn't stay posted + foundJob.job.remove(); + } + }); + + return seq; + } +} \ No newline at end of file diff --git a/src/main/java/examplemod/examples/settlement/settlers/ExampleSettler.java b/src/main/java/examplemod/examples/settlement/settlers/ExampleSettler.java new file mode 100644 index 0000000..2512e79 --- /dev/null +++ b/src/main/java/examplemod/examples/settlement/settlers/ExampleSettler.java @@ -0,0 +1,36 @@ +package examplemod.examples.settlement.settlers; + +import java.util.function.Supplier; +import necesse.engine.localization.message.GameMessage; +import necesse.engine.localization.message.LocalMessage; +import necesse.engine.util.TicketSystemList; +import necesse.entity.mobs.friendly.human.HumanMob; +import necesse.gfx.gameTexture.GameTexture; +import necesse.level.maps.levelData.settlementData.ServerSettlementData; +import necesse.level.maps.levelData.settlementData.settler.Settler; + +public class ExampleSettler extends Settler { + + public ExampleSettler() { + // MUST match your registered mob stringID + super("examplesettlermob"); + } + + @Override + public void loadTextures() { + // Use an existing icon for now, or add your own under mobs/icons/ + this.texture = GameTexture.fromFile("mobs/icons/human"); + } + + @Override + public GameMessage getAcquireTip() { + return new LocalMessage("settlement", "foundinvillagetip"); + } + + @Override + public void addNewRecruitSettler(ServerSettlementData data, boolean isRandomEvent, + TicketSystemList> ticketSystem) { + // Weight controls how often they appear as recruits + ticketSystem.addObject(isRandomEvent ? 50 : 25, getNewRecruitMob(data)); + } +} 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..46a5f5a --- /dev/null +++ b/src/main/java/examplemod/examples/tiles/ExampleGrassTile.java @@ -0,0 +1,125 @@ +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.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; + +/** + * ExampleGrassTile + * This is a ground tile. + * It does 3 main things: + * 1) Drops a seed sometimes when mined. + * 2) Can grow a grass object on top of it ("examplegrass"). + * 3) Can spread onto nearby dirt tiles. + */ +public class ExampleGrassTile extends TerrainSplatterTile { + + // How often the grass OBJECT should grow on this tile + public static double growChance = GameMath.getAverageSuccessRuns(7000.0D); + + // How often this TILE should spread onto dirt next to it + public static double spreadChance = GameMath.getAverageSuccessRuns(850.0D); + + // Used only for picking a random sprite row (visual variation) + private final GameRandom drawRandom = new GameRandom(); + + public ExampleGrassTile() { + // Texture file: resources/tiles/examplegrasstile.png + super(false, "examplegrasstile"); + + this.mapColor = new Color(70, 120, 40); // minimap colour + this.canBeMined = true; // player can mine/remove it + this.isOrganic = true; // marks it as organic + } + + @Override + public LootTable getLootTable(Level level, int tileX, int tileY) { + // 4% chance to drop a grass seed when mined + return new LootTable(new ChanceLootItem(0.04F, "examplegrassseed")); + } + + @Override + public void addSimulateLogic(Level level, int x, int y, long ticks, + SimulatePriorityList list, boolean sendChanges) { + // Off-screen simulation: schedule growth while the chunk is not actively ticking + addSimulateGrow(level, x, y, growChance, ticks, "examplegrass", list, sendChanges); + } + + /** + * Off-screen growth: schedule placing the grass object after enough simulated time passes. + */ + public static void addSimulateGrow(Level level, int tileX, int tileY, double chance, long ticks, + String growObjectID, SimulatePriorityList list, boolean sendChanges) { + + // Only grow if there is no object on this tile + if (level.getObjectID(tileX, tileY) != 0) return; + + // Convert the chance into a rough amount of time before it should succeed + double runs = Math.max(1.0D, GameMath.getRunsForSuccess(chance, GameRandom.globalRandom.nextDouble())); + long remainingTicks = (long) (ticks - runs); + if (remainingTicks <= 0L) return; + + GameObject obj = ObjectRegistry.getObject(ObjectRegistry.getObjectID(growObjectID)); + + // canPlace == null means it's allowed to place here + if (obj.canPlace(level, tileX, tileY, 0, false) != null) return; + + // Add a delayed task to place the object later + 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); // natural growth + if (sendChanges) level.sendObjectUpdatePacket(tileX, tileY); + } + }); + } + + @Override + public double spreadToDirtChance() { + // Controls how fast dirt turns into this grass tile when nearby + return spreadChance; + } + + @Override + public void tick(Level level, int x, int y) { + // Only the server should change the world + if (!level.isServer()) return; + + // Grow the grass OBJECT on empty tiles sometimes + 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) { + // Pick a random row for the sprite, but keep it consistent per tile position + int row; + synchronized (drawRandom) { + row = drawRandom.seeded(getTileSeed(tileX, tileY)) + .nextInt(terrainTexture.getHeight() / 32); + } + return new Point(0, row); // column 0, chosen row + } + + @Override + public int getTerrainPriority() { + // Used when tiles overlap/compete in drawing/spreading rules + return 100; + } +} \ 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/buffs/examplearmorsetbonusbuff.png b/src/main/resources/buffs/examplearmorsetbonusbuff.png new file mode 100644 index 0000000..b4edeee Binary files /dev/null and b/src/main/resources/buffs/examplearmorsetbonusbuff.png differ diff --git a/src/main/resources/items/examplearrow.png b/src/main/resources/items/examplearrow.png new file mode 100644 index 0000000..8ad2093 Binary files /dev/null and b/src/main/resources/items/examplearrow.png differ diff --git a/src/main/resources/items/examplebar.png b/src/main/resources/items/examplebar.png new file mode 100644 index 0000000..f23f5a8 Binary files /dev/null and b/src/main/resources/items/examplebar.png differ diff --git a/src/main/resources/items/examplebaserock.png b/src/main/resources/items/examplebaserock.png new file mode 100644 index 0000000..358c39d Binary files /dev/null and b/src/main/resources/items/examplebaserock.png differ diff --git a/src/main/resources/items/exampleboots.png b/src/main/resources/items/exampleboots.png new file mode 100644 index 0000000..4f01a02 Binary files /dev/null and b/src/main/resources/items/exampleboots.png differ diff --git a/src/main/resources/items/examplebosssummonitem.png b/src/main/resources/items/examplebosssummonitem.png new file mode 100644 index 0000000..99c9dd6 Binary files /dev/null and b/src/main/resources/items/examplebosssummonitem.png differ diff --git a/src/main/resources/items/examplechair.png b/src/main/resources/items/examplechair.png new file mode 100644 index 0000000..2c8293e Binary files /dev/null and b/src/main/resources/items/examplechair.png differ diff --git a/src/main/resources/items/examplechestplate.png b/src/main/resources/items/examplechestplate.png new file mode 100644 index 0000000..e129905 Binary files /dev/null and b/src/main/resources/items/examplechestplate.png differ diff --git a/src/main/resources/items/exampleconfigobject.png b/src/main/resources/items/exampleconfigobject.png new file mode 100644 index 0000000..2428534 Binary files /dev/null and b/src/main/resources/items/exampleconfigobject.png differ diff --git a/src/main/resources/items/exampledoor.png b/src/main/resources/items/exampledoor.png new file mode 100644 index 0000000..911d60c Binary files /dev/null and b/src/main/resources/items/exampledoor.png differ diff --git a/src/main/resources/items/examplefood.png b/src/main/resources/items/examplefood.png new file mode 100644 index 0000000..1138bef Binary files /dev/null and b/src/main/resources/items/examplefood.png differ diff --git a/src/main/resources/items/examplefooditem.png b/src/main/resources/items/examplefooditem.png deleted file mode 100644 index d78f1c1..0000000 Binary files a/src/main/resources/items/examplefooditem.png and /dev/null differ diff --git a/src/main/resources/items/examplegrass.png b/src/main/resources/items/examplegrass.png new file mode 100644 index 0000000..1fba177 Binary files /dev/null and b/src/main/resources/items/examplegrass.png differ diff --git a/src/main/resources/items/examplegrassseed.png b/src/main/resources/items/examplegrassseed.png new file mode 100644 index 0000000..35103ba Binary files /dev/null and b/src/main/resources/items/examplegrassseed.png differ diff --git a/src/main/resources/items/examplehelmet.png b/src/main/resources/items/examplehelmet.png new file mode 100644 index 0000000..69341c6 Binary files /dev/null and b/src/main/resources/items/examplehelmet.png differ diff --git a/src/main/resources/items/examplehuntincursionitem.png b/src/main/resources/items/examplehuntincursionitem.png deleted file mode 100644 index d05149e..0000000 Binary files a/src/main/resources/items/examplehuntincursionitem.png and /dev/null differ diff --git a/src/main/resources/items/examplehuntincursionmaterial.png b/src/main/resources/items/examplehuntincursionmaterial.png new file mode 100644 index 0000000..bfd5c94 Binary files /dev/null and b/src/main/resources/items/examplehuntincursionmaterial.png differ diff --git a/src/main/resources/items/exampleincursiontablet.png b/src/main/resources/items/exampleincursiontablet.png index 979cfa6..7f5c3ce 100644 Binary files a/src/main/resources/items/exampleincursiontablet.png and b/src/main/resources/items/exampleincursiontablet.png differ diff --git a/src/main/resources/items/examplejobobject.png b/src/main/resources/items/examplejobobject.png new file mode 100644 index 0000000..214f4fa Binary files /dev/null and b/src/main/resources/items/examplejobobject.png differ diff --git a/src/main/resources/items/exampleleveleventobject.png b/src/main/resources/items/exampleleveleventobject.png new file mode 100644 index 0000000..957d455 Binary files /dev/null and b/src/main/resources/items/exampleleveleventobject.png differ diff --git a/src/main/resources/items/examplelog.png b/src/main/resources/items/examplelog.png new file mode 100644 index 0000000..c0f130b Binary files /dev/null and b/src/main/resources/items/examplelog.png differ diff --git a/src/main/resources/items/examplemagicstaff.png b/src/main/resources/items/examplemagicstaff.png new file mode 100644 index 0000000..eaebb5b Binary files /dev/null and b/src/main/resources/items/examplemagicstaff.png differ diff --git a/src/main/resources/items/examplemeleesword.png b/src/main/resources/items/examplemeleesword.png new file mode 100644 index 0000000..5e8aa5e Binary files /dev/null and b/src/main/resources/items/examplemeleesword.png differ diff --git a/src/main/resources/items/exampleore.png b/src/main/resources/items/exampleore.png new file mode 100644 index 0000000..d9433bd Binary files /dev/null and b/src/main/resources/items/exampleore.png differ diff --git a/src/main/resources/items/exampleorerock.png b/src/main/resources/items/exampleorerock.png new file mode 100644 index 0000000..358c39d Binary files /dev/null and b/src/main/resources/items/exampleorerock.png differ diff --git a/src/main/resources/items/examplepotion.png b/src/main/resources/items/examplepotion.png new file mode 100644 index 0000000..1164dc0 Binary files /dev/null and b/src/main/resources/items/examplepotion.png differ diff --git a/src/main/resources/items/examplepotionitem.png b/src/main/resources/items/examplepotionitem.png deleted file mode 100644 index d06e5b2..0000000 Binary files a/src/main/resources/items/examplepotionitem.png and /dev/null differ diff --git a/src/main/resources/items/examplerangedbow.png b/src/main/resources/items/examplerangedbow.png new file mode 100644 index 0000000..0310d0f Binary files /dev/null and b/src/main/resources/items/examplerangedbow.png differ diff --git a/src/main/resources/items/examplesapling.png b/src/main/resources/items/examplesapling.png new file mode 100644 index 0000000..7ba0488 Binary files /dev/null and b/src/main/resources/items/examplesapling.png differ diff --git a/src/main/resources/items/examplestaff.png b/src/main/resources/items/examplestaff.png deleted file mode 100644 index 40200b4..0000000 Binary files a/src/main/resources/items/examplestaff.png and /dev/null differ diff --git a/src/main/resources/items/examplestone.png b/src/main/resources/items/examplestone.png new file mode 100644 index 0000000..e8ce2a2 Binary files /dev/null and b/src/main/resources/items/examplestone.png differ diff --git a/src/main/resources/items/examplesummonorb.png b/src/main/resources/items/examplesummonorb.png new file mode 100644 index 0000000..d662c70 Binary files /dev/null and b/src/main/resources/items/examplesummonorb.png differ diff --git a/src/main/resources/items/examplesword.png b/src/main/resources/items/examplesword.png deleted file mode 100644 index 57758c1..0000000 Binary files a/src/main/resources/items/examplesword.png and /dev/null differ diff --git a/src/main/resources/items/exampletree.png b/src/main/resources/items/exampletree.png new file mode 100644 index 0000000..772af69 Binary files /dev/null and b/src/main/resources/items/exampletree.png differ diff --git a/src/main/resources/items/exampletrinket.png b/src/main/resources/items/exampletrinket.png new file mode 100644 index 0000000..27a5131 Binary files /dev/null and b/src/main/resources/items/exampletrinket.png differ diff --git a/src/main/resources/items/examplewall.png b/src/main/resources/items/examplewall.png new file mode 100644 index 0000000..2d82ccc Binary files /dev/null and b/src/main/resources/items/examplewall.png differ diff --git a/src/main/resources/locale/en.lang b/src/main/resources/locale/en.lang index 3306996..17f4755 100644 --- a/src/main/resources/locale/en.lang +++ b/src/main/resources/locale/en.lang @@ -1,29 +1,86 @@ [tile] exampletile=Example Tile +examplegrasstile=Example Grass Tile [object] exampleobject=Example Object +examplebaserock=Example Rock +exampleore=Example Ore +examplesapling=Example Sapling +exampletree=Example Tree +examplegrass=Example Grass +examplewall=Example Wall +exampledoor=Example Door +examplechair=Example Chair +exampleleveleventobject=Example Level Event Object +examplejobobject=Example Job Object +exampleconfigobject=Example Config Object +examplepressureplate=Example Pressure Plate +examplewalltrap=Example Wall Trap [item] exampleitem=Example Item -examplehuntincursionitem=Example Hunt Incursion Item -examplepotionitem=Example Potion -examplesword=Example Sword -examplestaff=Example Staff -examplefooditem=Example Food +examplestone=Example Stone +exampleore=Example Ore +examplebar=Example Bar +examplelog=Example Log +examplegrassseed=Example Grass Seed +examplehuntincursionmaterial=Example Hunt Incursion Material +examplepotion=Example Potion +examplefood=Example Food +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 +examplebosssummonitem=Example Boss Summon Item +exampletrinket=Example Trinket + [itemtooltip] -examplestafftip=Shoots a homing, piercing projectile -examplepotionitemtip= An example potion +examplemagicstafftip=Shoots a homing, piercing projectile +examplepotionitemtip=An example potion +examplebosssummontip=Use in the Example Biome Cave to summon the Example Boss Mob +exampletrinkettip=Example Trinket. Acts like a Spelunker Potion [mob] examplemob=Example Mob +examplebossmob=Example Boss +examplesummonmob=Example Summon Mob +examplesettlermob=Example Settler +examplesettlermobname= The Example Settler [buff] examplebuff=Example Buff +examplearmorsetbonusbuff=Example Armor Set Bonus Buff [biome] exampleincursion=Example Incursion [incursion] -exampleincursion=Example Incursion \ No newline at end of file +exampleincursion=Example Incursion + +[itemcategory] +examplemodrootcat=ExampleMod +examplemodobjectsubcat=ExampleMod Objects +examplemodfurnaturesubcat=ExampleMod Furnature + +[jobs] +examplejobname=Example Job +examplejobtip=Keeps grass cleared in assigned zones. + +[ui] +examplejobzone=Example Job Zone +examplejobzonedefname=Example Job Zone {number} + +[activities] +examplejob=Doing Example Job + +[journal] +examplebiomesurface=Example Biome Surface +examplebiomecave=Example Biome Cave +examplebiomedeepcave=Example Biome Deep Cave + diff --git a/src/main/resources/mobs/examplebossmob.png b/src/main/resources/mobs/examplebossmob.png new file mode 100644 index 0000000..e88afcb Binary files /dev/null and b/src/main/resources/mobs/examplebossmob.png differ diff --git a/src/main/resources/mobs/examplesummonmob.png b/src/main/resources/mobs/examplesummonmob.png new file mode 100644 index 0000000..3f85699 Binary files /dev/null and b/src/main/resources/mobs/examplesummonmob.png differ diff --git a/src/main/resources/mobs/icons/examplebossmob.png b/src/main/resources/mobs/icons/examplebossmob.png new file mode 100644 index 0000000..068bd48 Binary files /dev/null and b/src/main/resources/mobs/icons/examplebossmob.png differ diff --git a/src/main/resources/mobs/icons/examplemob.png b/src/main/resources/mobs/icons/examplemob.png new file mode 100644 index 0000000..33fdb61 Binary files /dev/null and b/src/main/resources/mobs/icons/examplemob.png differ diff --git a/src/main/resources/mobs/icons/examplesummonmob.png b/src/main/resources/mobs/icons/examplesummonmob.png new file mode 100644 index 0000000..14ad9f5 Binary files /dev/null and b/src/main/resources/mobs/icons/examplesummonmob.png differ diff --git a/src/main/resources/objects/examplebaserock.png b/src/main/resources/objects/examplebaserock.png new file mode 100644 index 0000000..1dd1765 Binary files /dev/null and b/src/main/resources/objects/examplebaserock.png differ diff --git a/src/main/resources/objects/examplechair.png b/src/main/resources/objects/examplechair.png new file mode 100644 index 0000000..24218ef Binary files /dev/null and b/src/main/resources/objects/examplechair.png differ diff --git a/src/main/resources/objects/exampleconfigobject.png b/src/main/resources/objects/exampleconfigobject.png new file mode 100644 index 0000000..2428534 Binary files /dev/null and b/src/main/resources/objects/exampleconfigobject.png differ diff --git a/src/main/resources/objects/examplegrass.png b/src/main/resources/objects/examplegrass.png new file mode 100644 index 0000000..0d241b8 Binary files /dev/null and b/src/main/resources/objects/examplegrass.png differ diff --git a/src/main/resources/objects/examplejobobject.png b/src/main/resources/objects/examplejobobject.png new file mode 100644 index 0000000..214f4fa Binary files /dev/null and b/src/main/resources/objects/examplejobobject.png differ diff --git a/src/main/resources/objects/exampleleveleventobject.png b/src/main/resources/objects/exampleleveleventobject.png new file mode 100644 index 0000000..957d455 Binary files /dev/null and b/src/main/resources/objects/exampleleveleventobject.png differ diff --git a/src/main/resources/objects/exampleore.png b/src/main/resources/objects/exampleore.png new file mode 100644 index 0000000..d56047f Binary files /dev/null and b/src/main/resources/objects/exampleore.png differ diff --git a/src/main/resources/objects/examplesapling.png b/src/main/resources/objects/examplesapling.png new file mode 100644 index 0000000..25f306d Binary files /dev/null and b/src/main/resources/objects/examplesapling.png differ diff --git a/src/main/resources/objects/exampletree.png b/src/main/resources/objects/exampletree.png new file mode 100644 index 0000000..31c3476 Binary files /dev/null and b/src/main/resources/objects/exampletree.png differ diff --git a/src/main/resources/objects/examplewall.png b/src/main/resources/objects/examplewall.png new file mode 100644 index 0000000..a77d7c6 Binary files /dev/null and b/src/main/resources/objects/examplewall.png differ diff --git a/src/main/resources/objects/examplewalltrap.png b/src/main/resources/objects/examplewalltrap.png new file mode 100644 index 0000000..c99f9b6 Binary files /dev/null and b/src/main/resources/objects/examplewalltrap.png differ diff --git a/src/main/resources/particles/exampleleaves.png b/src/main/resources/particles/exampleleaves.png new file mode 100644 index 0000000..a33976f Binary files /dev/null and b/src/main/resources/particles/exampleleaves.png differ 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 0000000..08da02e Binary files /dev/null and b/src/main/resources/player/armor/examplearms_left.png differ diff --git a/src/main/resources/player/armor/examplearms_right.png b/src/main/resources/player/armor/examplearms_right.png new file mode 100644 index 0000000..ba2aca1 Binary files /dev/null and b/src/main/resources/player/armor/examplearms_right.png differ diff --git a/src/main/resources/player/armor/exampleboots.png b/src/main/resources/player/armor/exampleboots.png new file mode 100644 index 0000000..f731a4f Binary files /dev/null and b/src/main/resources/player/armor/exampleboots.png differ diff --git a/src/main/resources/player/armor/examplechest.png b/src/main/resources/player/armor/examplechest.png new file mode 100644 index 0000000..bd8d737 Binary files /dev/null and b/src/main/resources/player/armor/examplechest.png differ diff --git a/src/main/resources/player/armor/examplehelmet.png b/src/main/resources/player/armor/examplehelmet.png new file mode 100644 index 0000000..bded8d4 Binary files /dev/null and b/src/main/resources/player/armor/examplehelmet.png differ diff --git a/src/main/resources/player/weapons/examplemagicstaff.png b/src/main/resources/player/weapons/examplemagicstaff.png new file mode 100644 index 0000000..0b2ed0c Binary files /dev/null and b/src/main/resources/player/weapons/examplemagicstaff.png differ diff --git a/src/main/resources/player/weapons/examplemeleesword.png b/src/main/resources/player/weapons/examplemeleesword.png new file mode 100644 index 0000000..be64290 Binary files /dev/null and b/src/main/resources/player/weapons/examplemeleesword.png differ diff --git a/src/main/resources/player/weapons/examplerangedbow.png b/src/main/resources/player/weapons/examplerangedbow.png new file mode 100644 index 0000000..09a87a4 Binary files /dev/null and b/src/main/resources/player/weapons/examplerangedbow.png differ diff --git a/src/main/resources/player/weapons/examplestaff.png b/src/main/resources/player/weapons/examplestaff.png deleted file mode 100644 index b277ac2..0000000 Binary files a/src/main/resources/player/weapons/examplestaff.png and /dev/null differ diff --git a/src/main/resources/player/weapons/examplesummonorb.png b/src/main/resources/player/weapons/examplesummonorb.png new file mode 100644 index 0000000..6aa88db Binary files /dev/null and b/src/main/resources/player/weapons/examplesummonorb.png differ diff --git a/src/main/resources/player/weapons/examplesword.png b/src/main/resources/player/weapons/examplesword.png deleted file mode 100644 index d2845b0..0000000 Binary files a/src/main/resources/player/weapons/examplesword.png and /dev/null differ diff --git a/src/main/resources/projectiles/examplearrowprojectile.png b/src/main/resources/projectiles/examplearrowprojectile.png new file mode 100644 index 0000000..8d6ad8e Binary files /dev/null and b/src/main/resources/projectiles/examplearrowprojectile.png differ diff --git a/src/main/resources/sound/examplesound.ogg b/src/main/resources/sound/examplesound.ogg new file mode 100644 index 0000000..08ebdb8 Binary files /dev/null and b/src/main/resources/sound/examplesound.ogg differ diff --git a/src/main/resources/tiles/examplegrasstile_splat.png b/src/main/resources/tiles/examplegrasstile_splat.png new file mode 100644 index 0000000..11981ea Binary files /dev/null and b/src/main/resources/tiles/examplegrasstile_splat.png differ