I have reached a point in a large project I am working on where I haven't got a clue what I'm doing. If anybody could point me to some relevant, up-to-date and good tutorials on these subject matters, that would be great. I need to manipulate the behaviours and properties of mobs. I have found a decent looking tutorial by @TeeePeee on overriding mobs, but I need to have multiple types of mobs (such as a weak zombie, a strong zombie with iron armour, and a boss zombie with diamond armour that fires TNT). These mobs need to be spawned into the game into very selective areas. For example a single building that is infested with spiders. I would want the spiders to be numerous, but not just keep spawning forever. And the spiders would need to 'know' not to stray too far from the building. I understand that these are advanced topics, any help would be awesome. Thanks
Hmm i dont really know any to be honest but with a google search you might find some, Also about fiering tnt's i dont really know but i assume there is a way to disguise a wither to a zombi (as odd as it sounds ) Check out the resource section for more!
@Scullyking I can just say NMS NMS NMS. This is where Bukkit reaches its limits. I know the tutorial of TeeePeee to, I would suggest to develop a manual Spawning System, and spawn entities with World#addEntity(<yourentity>).
@DoppelRR So I could cancel the EntitySpawnEvent and use my own spawning manager. Maybe a hashmap of regions with the type of entity to spawn inside it or something... Not too confident on how to create multiple overwrites of an entity though. TeeePeee's tutorial seems to offer one overwrite for all zombies.
This sounds exactly like a Villager that doesn't stray too far from the Village... Therefore, I would suggest looking at PathfinderGoalMoveThroughVillage... See how they do it... And tweak it to your needs: In this case, something along the lines of a PathfinderGoalMoveThroughArea so-to-speak... where you define the "Area" in question. Then add this pathfinder to your entity. Code:java public class PathfinderGoalMoveThroughVillage extends PathfinderGoal{ private EntityCreature a; private double b; private PathEntity c; private VillageDoor d; private boolean e; private List f = Lists.newArrayList(); public PathfinderGoalMoveThroughVillage(EntityCreature paramEntityCreature, double paramDouble, boolean paramBoolean) { this.a = paramEntityCreature; this.b = paramDouble; this.e = paramBoolean; a(1); if (!(paramEntityCreature.getNavigation() instanceof Navigation)) { throw new IllegalArgumentException("Unsupported mob for MoveThroughVillageGoal"); } } public boolean a() { f(); if ((this.e) && (this.a.world.w())) { return false; } Village localVillage = this.a.world.ae().getClosestVillage(new BlockPosition(this.a), 0); if (localVillage == null) { return false; } this.d = a(localVillage); if (this.d == null) { return false; } Navigation localNavigation = (Navigation)this.a.getNavigation(); boolean bool = localNavigation.g(); localNavigation.b(false); this.c = localNavigation.a(this.d.d()); localNavigation.b(bool); if (this.c != null) { return true; } Vec3D localVec3D = RandomPositionGenerator.a(this.a, 10, 7, new Vec3D(this.d.d().getX(), this.d.d().getY(), this.d.d().getZ())); if (localVec3D == null) { return false; } localNavigation.b(false); this.c = this.a.getNavigation().a(localVec3D.a, localVec3D.b, localVec3D.c); localNavigation.b(bool); return this.c != null; } public boolean b() { if (this.a.getNavigation().m()) { return false; } float f1 = this.a.width + 4.0F; return this.a.b(this.d.d()) > f1 * f1; } public void c() { this.a.getNavigation().a(this.c, this.b); } public void d() { if ((this.a.getNavigation().m()) || (this.a.b(this.d.d()) < 16.0D)) { this.f.add(this.d); } } private VillageDoor a(Village paramVillage) { Object localObject = null; int i = 2147483647; List localList = paramVillage.f(); for (VillageDoor localVillageDoor : localList) { int j = localVillageDoor.b(MathHelper.floor(this.a.locX), MathHelper.floor(this.a.locY), MathHelper.floor(this.a.locZ)); if (j < i) { if (!a(localVillageDoor)) { localObject = localVillageDoor; i = j; } } } return localObject; } private boolean a(VillageDoor paramVillageDoor) { for (VillageDoor localVillageDoor : this.f) { if (paramVillageDoor.d().equals(localVillageDoor.d())) { return true; } } return false; } private void f() { if (this.f.size() > 15) { this.f.remove(0); } }} http://bukkit.org/threads/custom-nms-entity-problem.352387/#post-3073303 This is the solution. Don't write to Map e. EDIT by Moderator: merged posts, please use the edit button instead of double posting.
@Europia79 Interesting. So if I did make my own pathfinder goal e.g PathfinderGoalMoveThroughDungeon, I could presumably use nice variable names and such since I'm not overriding anything. @Europia79 What is Map e doing?
@Scullyking net.minecraft.server.EntityTypes has five Maps: Code:java Map c = Maps.newHashMap(); /** key = entityName , value = entityClass */Map d = Maps.newHashMap(); /** key = entityClass, value = entityName */Map e = Maps.newHashMap(); /** key = entityId, value = entityClass */Map f = Maps.newHashMap(); /** key = entityClass, value = entityId */Map g = Maps.newHashMap(); /** key = entityName, value = entityId */ The server is going to use the class or name to lookup the entityId. The entityId is going to determine what graphic is used to represent the mob: So evenif you extend CraftZombie... if you use entityId of 120, it will look like a Villager. Here are all the valid entityIds: https://github.com/SpigotMC/mc-dev/blob/master/net/minecraft/server/EntityTypes.java#L143-L199 So it sounds like you will want: Code:java public class CustomZombie extends CraftZombie implements Zombie { }public class WeakZombie extends CustomZombie { }public class StrongZombie extends CustomZombie { }public class BossZombie extends CustomZombie { } So if you have any similarities at all (any shared code between them), then it will go in the CustomZombie class. If there are no similarities, then you can just extend CraftZombie directly. I'm thinking at the very least, you could clear all the pathfinders in CustomZombie... then turn around and add back only the ones you want for Weak, Strong, & Boss. The Zombie interface is your own. It is a way you can refer to your zombies in the rest of your code... But it's optional... you could refer directly to CustomZombie... but then, when Minecraft updates, you'll have to update ALL your classes. Instead, if you refer only to your Zombie interface, then you do not have to update any class that only uses this interface. Additionally, the Zombie interface can have multiple implementations... one for each version of Minecraft... so tomorrow, if you update to Minecraft 1.9... your plugin can still be backwards compatible with 1.8.3, 1.8, 1.7.10, 1.7.9, 1.7.2, etc... If you wish to do this, here's the info: http://bukkit.org/threads/support-multiple-minecraft-versions-with-abstraction-maven.115810/ Here are some more examples supporting multiple minecraft versions: https://github.com/Europia79/Extraction https://github.com/Europia79/VirtualPlayers
@Europia79 Wow, I don't think I've ever gotten such a helpful reply, so thank-you. This is very daunting since I've never done any reflection or NMS stuff with my plugins. When you talk about the Zombie interface. Isn't there already a class called Zombie in bukkit? So as well as needing to make CustomZombie, WeakZombie, StrongZombie and BossZombie, I need to make an interface called Zombie? Basically which is the Zombie interface? Thanks again. I just found this excellent tutorial about creating custom goals and entities, for anyone interested. EDIT by Moderator: merged posts, please use the edit button instead of double posting.
Well I just don't like it when I go to a thread and it's already solved, after I read through the entire thing ;3
@nverdier Fair enough. Expect to see many NMS-related questions throughout the night as I try to get custom mobs working
Making your own Zombie interface is 100% optional. Code:java package me.scullyking.mobs.compat.v1_8_R1; import net.minecraft.server.v1_8_R1.EntityZombie; public class CustomZombie extends EntityZombie { } Notice how EntityZombie lives in the v1_8_R1 package... And CustomZombie mirrors this... And you can have other CustomZombies too: Code:java package me.scullyking.mobs.compat.v1_8_R2; import net.minecraft.server.v1_8_R2.EntityZombie; public class CustomZombie extends EntityZombie { } Now... you can no longer refer to CustomZombie in your code... because the question arises: Are you referring to me.scullyking.mobs.compat.v1_8_R1.CustomZombie ? Or me.scullyking.mobs.compat.v1_8_R2.CustomZombie ? Same class, different packages. But they could share a common interface... And you could refer to BOTH of them at the same time thru this shared interface. Here is an example of an abstract factory (where each version of Minecraft has it's own implementation): https://github.com/Europia79/Extrac...rc/mc/euro/extraction/nms/NPCFactory.java#L49 Notice that the factory could return type CraftHostage... but returns the Hostage interface instead. Now, when I code, I always use type Hostage interface... And I don't care which implementation that the factory gives me. Take a look at my imports for my arena: https://github.com/Europia79/Extrac.../src/mc/euro/extraction/HostageArena.java#L23 Notice that there are no NMS or Minecraft-version-dependent imports. So anytime Minecraft updates, I don't have to update the arena class. I only have to supply a new implementation for the Hostage interface (Zombie interface in your case). But like I said: The interface is purely optional. This is my spawn code if you were curious: https://github.com/Europia79/Extrac...c/euro/extraction/HostageArena.java#L106-L109