#How to make EntityJS Mob AI
287 messages · Page 1 of 1 (latest)
Once your ticket has been resolved, please close it with </ticket close:1054771505520717835> command!
it might be a bit daunting at first but after you understand the basics of goals you'll start to figure things out
Couple examples of adding goals to your entity js EntityJSEvents.addGoalSelectors("kubejs:wyrm", event => { // Enables the entity to walk around event.randomStroll(1, 0.3, 12, false) // Enables the entity to attack, essential for all other attack actions event.meleeAttack(2, 0.4, true) }) let Player = Java.loadClass('net.minecraft.world.entity.player.Player') EntityJSEvents.addGoals("kubejs:wyrm", event => { // Goal which enables the entity to fight back when hurt event.hurtByTarget(1, [], true, []) // Nearest Attackable Entity Goal event.nearestAttackableTarget(2, Player, 5, false, false, entity => { // Predicate of whether the entity can target the target, for example only let the adults attack return !entity.baby }) })
ofcourse if you're trying to replicate a slime goal where it jumps around you will have to create a custom goal with the event.customGoal method and take a look at the slime goals and replicate them by looking at the slime class near the bottom where the goals are
Paste version of Slime.java from @mellow girder
although i might add slime to the default builder list tomorrow as an option so you dont have to recreate the goals
cause we currently have roughly 20-30 default mob builders and slimes arent one of them it looks like https://github.com/liopyu/EntityJS/wiki/Default-Minecraft-Entity-Builders
so if you want you can also wait for me to put out an update for that
np! the new version should be up on curseforge/modrinth now which will allow you to do this js event.create("slime", "minecraft:slime")
im not sure if you already have a model or not but i also went ahead and used blockbench's Minecraft Entity Wizard to download and package the default slime texture/model, the animations are handled i believe by custom rendering though it should also be doable inside the entity builder with the .render() method
Paste version of Slime_Pack.zip from @mellow girder
something like this should do it for the way they stretch when jumping around, basically mimicing the way vanilla does it in their own render method ```js
StartupEvents.registry("entity_type", event => {
event.create("slime", "minecraft:slime")
.scaleModelForRender(context => global.slimeRender(context));
});
/**
*
-
@param {Internal.ContextUtils$ScaleModelRenderContext<Internal.SlimeEntityJS>} context
*/
global.slimeRender = context => {
const { entity, poseStack } = context;
try {
let slime = entity;let size = slime.getSize(); let partialTick = context.partialTick; let squish = slime.squish * 3; let prevSquish = slime.oSquish * 3; let lerpedSquish = (partialTick * (squish - prevSquish)) + prevSquish; let squishFactor = lerpedSquish / (size * 0.5 + 1.0); let inverseSquishFactor = 1.0 / (squishFactor + 1.0); // Scale the entity based on the squish factor poseStack.scale(inverseSquishFactor * size, 1.0 / inverseSquishFactor * size, inverseSquishFactor * size); poseStack.translate(0.0, 0.001, 0.0); // Slight translation to avoid Z-fighting} catch (error) {
console.log(error);
}
}```
thank you so much!! i was able to get it working using the new update
i do already have a custom model and i was able to get it working
also, would it be possible for me to change the texture of the particles when it lands?
it's currently just using the default slime particles, but i want to make blue particles to go with the color of the mob
.setParticleType("crit")```
you also have the js .setSquishSound("block.azalea.hit")
for example
thank you again!!
my only other problems are i can't get it to attack me or to drop anything
it tries jumping towards me, but it does no damage

why does it do damage to other mobs like iron golems but not the player
i mightve figured out why, gimme a bit
alright so this seems to be lack of implimentation on my end where playertouch isnt calling the super method by default so you'll have to for now do this js .playerTouch(context => { let { entity, player } = context if (entity.isDealsDamage()) { entity.dealDamage(player) } })
yea
it does this automatically but you can effectively overwrite it with the js .onAddedToWorld(entity => { entity.modifyAttribute("minecraft:generic.attack_damage", "uniqueid", 5, "addition") }) method
you get the size with entity.size
alr, will test it now
also i don't know if i'm doing something wrong, but even with the the playerTouch, im not taking damage from the slimes
did you restart your game
yes
You can find your KubeJS startup log in /minecraft/logs/kubejs/startup.log.
If you are on 1.18 or 1.16 it will be called startup.txt.
Please send it if asked, as it contains helpful information.
also send your script
actually sec
no need for any of that, i know whats going on here
StartupEvents.registry('entity_type', event => {
event.create('dungeon_slime', 'minecraft:slime')
.scaleModelForRender(context => global.slimeRender(context))
.setRenderType("translucent")
.sized(3.0, 2.8)
.setParticleType("crit")
.eggItem(item => {
item.backgroundColor(4492532)
item.highlightColor(10209279)
})
.playerTouch(context => {
let { entity, player } = context
if (entity.isDealsDamage()) {
entity.dealDamage(player)
}
})
})
/**
*
* @param {Internal.ContextUtils$ScaleModelRenderContext<Internal.SlimeEntityJS>} context
*/
global.slimeRender = context => {
const { entity, poseStack } = context;
try {
let slime = entity;
let size = slime.getSize();
let partialTick = context.partialTick;
let squish = slime.squish * 3;
let prevSquish = slime.oSquish * 3;
let lerpedSquish = (partialTick * (squish - prevSquish)) + prevSquish;
let squishFactor = lerpedSquish / (size * 0.5 + 1.0);
let inverseSquishFactor = 1.0 / (squishFactor + 1.0);
// Scale the entity based on the squish factor
poseStack.scale(inverseSquishFactor * size, 1.0 / inverseSquishFactor * size, inverseSquishFactor * size);
poseStack.translate(0.0, 0.001, 0.0); // Slight translation to avoid Z-fighting
} catch (error) {
console.log(error);
}
}
Paste version of startup.log from @analog tendon
yeah its being weird, for some reason when i directly add the super method to the actual method override it works but doing it manually through the builder it isnt
ahhhh [20:59:15] [ERROR] ! entities.js#13: [EntityJS]: Error in entity.kubejs.dungeon_slimebuilder for field: playerTouch.: TypeError: Cannot find function isDealsDamage in object SlimeEntityJS['entity.kubejs.dungeon_slime'/180, l='ServerLevel[New World]', x=46.01, y=57.02, z=-86.30].
why is that not being found 

crap i just noticed these methods are protected
i honestly just might push another update sorting all this out
nah if you hadnt wanted it then someone else wouldve lol i'll have a new version out by tomorrow, i can already confirm it works with the new update
sure thing!
alright @analog tendon newest version should be up now, you'll also need to remove the .playerTouch implimentation you have for it to work
thank you so much! it's now attacking me
Please close your ticket (with </ticket close:1054771505520717835> or the button atop this thread) once you resolved your issue! This also helps others that would like to help out, as they don't have to look into this thread to check if it has been resolved by now.
Do you have any other questions regarding your issue? Feel free to ask!
Note: You should generally create a new post for unrelated issues.
Ticket re-opened!
when the entity spawns, the health isn't set to its max health. so the slime will show up with 16 max health, but only spawn on 1 health.
is there a way to change the starting health to be the same as its max health?
🗒️**Send the code!**🗒️
You may have an issue with a KubeJS script and you explain it to the best of your ability yet without the actual code in question we have very little to go off of in trying to assist you.
StartupEvents.registry('entity_type', event => {
event.create('dungeon_slime', 'minecraft:slime')
.scaleModelForRender(context => global.slimeRender(context))
.setRenderType("translucent")
.sized(3.0, 2.8)
.setParticleType("dripping_dripstone_water")
.canBreatheUnderwater(true)
.eggItem(item => {
item.backgroundColor(4492532)
item.highlightColor(10209279)
})
.onAddedToWorld(entity => {
entity.modifyAttribute("minecraft:generic.max_health", "health_mb", 16, "multiply_base")
entity.modifyAttribute("minecraft:generic.attack_damage", "attack_mb", 2, "multiply_base")
entity.modifyAttribute("minecraft:generic.attack_damage", "attack_a", 8, "addition")
})
})
/**
*
* @param {Internal.ContextUtils$ScaleModelRenderContext<Internal.SlimeEntityJS>} context
*/
global.slimeRender = context => {
const { entity, poseStack } = context;
try {
let slime = entity;
let size = slime.getSize();
let partialTick = context.partialTick;
let squish = slime.squish * 3;
let prevSquish = slime.oSquish * 3;
let lerpedSquish = (partialTick * (squish - prevSquish)) + prevSquish;
let squishFactor = lerpedSquish / (size * 0.5 + 1.0);
let inverseSquishFactor = 1.0 / (squishFactor + 1.0);
// Scale the entity based on the squish factor
poseStack.scale(inverseSquishFactor * size, 1.0 / inverseSquishFactor * size, inverseSquishFactor * size);
poseStack.translate(0.0, 0.001, 0.0); // Slight translation to avoid Z-fighting
} catch (error) {
console.log(error);
}
}
ahh ok
i was confused, ok so whats happening is you're setting the attribute to 16 for every one regardless of size? @analog tendon
yes, but i also tried setting the attribute for each size as well, and while the attributes would still set correctly, the starting health would still be wrong
so both ways didnt work properly for me
alright sec
uh
do you want the health to depend on size or do you want it to be constant
because there are 2 ways we can do this
i want it to depend on size
either setting it manually per size, or just multiplying the default slime health by x amount for all sizes
both ways work for me
alright
method 1:
when dealing with/modifying max health you'll need to heal the mob if the new value is greater than its default value js .onAddedToWorld(entity => { entity.modifyAttribute("minecraft:generic.max_health", "health_mb", 16, "multiply_base") entity.setHealth(entity.maxHealth) entity.modifyAttribute("minecraft:generic.attack_damage", "attack_mb", 2, "multiply_base") entity.modifyAttribute("minecraft:generic.attack_damage", "attack_a", 8, "addition") })
method 2:
you can also set the default values in the attribute modification startup event like so which will modify its health universally which will then get auto calculated by the default mobs health implimentation based on size, useful if you just want to make all the health values bigger or lowerjs EntityJSEvents.attributes(event => { event.modify('kubejs:dungeon_slime', attribute => { attribute.add("minecraft:generic.max_health", 16) }) })
also to detect what size they are you can do something like js switch (entity.size) { case 1: entity.modifyAttribute("minecraft:generic.attack_damage", "uniqueid", 5, "addition") break; case 2: entity.modifyAttribute("minecraft:generic.attack_damage", "uniqueid", 2, "addition") break; case 3: entity.modifyAttribute("minecraft:generic.attack_damage", "uniqueid", 1, "addition") break; default: entity.modifyAttribute("minecraft:generic.attack_damage", "uniqueid", 15, "addition") break; }
i'm using method 1, and it works now!
also, is there a way to get them to drop items when killed?
as well as make the drops depend on the size
the slimes also just show up as entity.kubejs.dungeon_slime when looking at them using Jade, so is there a way to set the mob name?
you can set the name with js .setCustomName("name") i believe
as for the death loot i would either use lootjs or the EntityEvents.death
the custom name always shows up above the entity when hovering over the slimes, how do i get the name to not show up?
alr
didn't work
StartupEvents.registry('entity_type', event => {
event.create('dungeon_slime', 'minecraft:slime')
.scaleModelForRender(context => global.slimeRender(context))
.setRenderType("translucent")
.sized(1.6, 1.4)
.setParticleType("rain")
.canBreatheUnderwater(true)
.eggItem(item => {
item.backgroundColor(4492532)
item.highlightColor(10209279)
})
.onAddedToWorld(entity => {
entity.modifyAttribute("minecraft:generic.max_health", "health_mb", 3, "multiply_base")
entity.modifyAttribute("minecraft:generic.attack_damage", "attack_mb", 3, "multiply_base")
entity.setHealth(entity.maxHealth)
})
.displayName("Dungeon Slime")
})
/**
*
* @param {Internal.ContextUtils$ScaleModelRenderContext<Internal.SlimeEntityJS>} context
*/
global.slimeRender = context => {
const { entity, poseStack } = context;
try {
let slime = entity;
let size = slime.getSize();
let partialTick = context.partialTick;
let squish = slime.squish * 3;
let prevSquish = slime.oSquish * 3;
let lerpedSquish = (partialTick * (squish - prevSquish)) + prevSquish;
let squishFactor = lerpedSquish / (size * 0.5 + 1.0);
let inverseSquishFactor = 1.0 / (squishFactor + 1.0);
// Scale the entity based on the squish factor
poseStack.scale(inverseSquishFactor * size, 1.0 / inverseSquishFactor * size, inverseSquishFactor * size);
poseStack.translate(0.0, 0.001, 0.0); // Slight translation to avoid Z-fighting
} catch (error) {
console.log(error);
}
}
uh
try this with the customname js entity.setCustomNameVisible(false)
although 
let me see if i can find how someone else solved this, im pretty sure it wasnt to do with custom name, sec
alright
add this in client scripts js ClientEvents.lang('en_us', (e) => { e.add("entity.kubejs.dungeon_slime", "Dungeon Slime"); })
then do /kubejs reload client_scripts and f3+t
nice
ok, so the name works
i got the drops to work
i got the ai to work
model to work
is there anything else......
biome spawns?
i dont want to forget anything lol
the mobs i'm making don't need to spawn naturally
so i can skip that
ah ok
hmm, one thing i think would be cool, but i don't know if it's too complicated, would be to make it so that the small slimes will try to merge together to make bigger slimes
but if that's too difficult then i can't think of anything else that needs to be done
i mean
try this ```js
.tick(entity => {
if (entity.age % 20 != 0) return
if (entity.getSize() <= 1) {
let slimes = entity.level.getEntitiesWithin(entity.getBoundingBox().grow(1.0))
slimes.forEach(otherSlime => {
if (otherSlime.type != entity.type) return
if (otherSlime !== entity && otherSlime.size <= 1 && entity.getBoundingBox().intersects(otherSlime.getBoundingBox())) {
entity.setSize(entity.size + 1)
otherSlime.remove("discarded")
}
});
}
})```
they dont seem to combine
You can find your KubeJS startup log in /minecraft/logs/kubejs/startup.log.
If you are on 1.18 or 1.16 it will be called startup.txt.
Please send it if asked, as it contains helpful information.
[22:22:42] [ERROR] ! entities.js#20: [EntityJS]: Error in entity.kubejs.dungeon_slimebuilder for field: tick.: TypeError: Cannot find function grow in object AABB[42.22271252781593, 56.0, -86.48687719854479] -> [42.630712520186535, 56.35699999332428, -86.07887720617418].
alright
also, is it possible to store a variable in an entity? so that way i could keep track of the slimes absorbed as something like entity.slimesAbsorbed and only have the slimes grow in size after a certain number have been absorbed
sure, with persistent datajs entity.persistentData.MyVariable = 0
its basically an nbt tag
nice
so that would be a global variable not specific to the entity?
yea
stored in the server itself
though for entity stuff its recommended to store it in the entity so its garbage collected when the entity disappears
server persistent data is for when you really need to store something static
does this work for players too?
yup
amazing
any entitys
Paste version of startup.log from @analog tendon
kubejs is having a stroke i believe
oh uh
apparently setsize takes in a number and a boolean
replace what you have with ```js
.tick(entity => {
try {
if (entity.age % 20 != 0) return
if (entity.getSize() <= 1) {
let slimes = entity.level.getEntitiesWithin(entity.getBoundingBox().inflate(2))
slimes.forEach(otherSlime => {
if (otherSlime.type != entity.type) return
if (otherSlime !== entity && otherSlime.size <= 1 && entity.getBoundingBox().intersects(otherSlime.getBoundingBox())) {
entity.setSize(entity.size + 1,true)
otherSlime.remove("discarded")
}
});
}
} catch (error) {
console.log(error)
}
})```
that try catch function will also prevent you from crashing anymore as well ™️
for most errors anyways
crap wait
i forgot to change out the grow
there
i closed out of minecraft too quickly and my game had a stroke on the main menu when i relaunched
relaunching again seemed to fix it
and for the moment of truth
did you copy the edited version of the script?
i copied this, then changed .grow(1.0) to .inflate(2), but i didn't copy it the second time if there were other changes
increase the inflate
alright
its on a 1 second interval too for performance sake so if you want you could also change that around
that being this js if (entity.age % 20 != 0) return
i was about to ask, how bad would the performance impact be if it was every tick instead of every 20?
i mean probably negligible though its a good habit to have especially for things like this where you're dealing with bounding boxes, though it shouldnt be an issue since the bounding box is so small
if it were minecraft then they'd 100% do this without the 1 second interval check
so its up to you
well actually
theres this you could also do if you wanted js .onEntityCollision(ctx => { const {entity, target} = ctx })
though it'll only check if the entity is touching the other entity and wont be configurable to be lets say 1 block away
if you want them to combine 1 or 2 blocks away id stick with the tick method
ill try the collision method and if the results are bad ill do tick
sounds good
.onEntityCollision(ctx => {
const {entity, target} = ctx
if (entity.getSize() <= 1) {
if (target.type != entity.type) return
if (target !== entity && target.size <= 1 && entity.getBoundingBox().intersects(target.getBoundingBox())) {
entity.setSize(entity.size + 1,true)
target.remove("discarded")
}
}
})
would this work
actually would the if statement even be needed
it wouldn't
yeah id actually go the opposite to set a maximum standard tbh, like entity.getSize() <= 4 just so slimes cant create massive 50 sized slimes or something
.onEntityCollision(ctx => {
const {entity, target} = ctx
if (entity.getSize() <= 1) {
if (target.type != entity.type) return
entity.setSize(entity.size + 1,true)
target.remove("discarded")
}
})
that works
nice
i see nothing wrong with that :)
tho yeah a limit would probably be a good idea
lol fair
im just saying if they're in spawners and someone makes a mob farm outta it
could cause lag
what i might do is have a variable set when the slime first spawns for what its max size can be
nvm
the idea i had wouldn't work
i'll just set a limit to a size of 4 or 8
can i get an entity's health with entity.health?
thats current health
wha
did i put .onEntityCollision() in the wrong spot
or is it just being stupid
StartupEvents.registry('entity_type', event => {
event.create('dungeon_slime', 'minecraft:slime')
.scaleModelForRender(context => global.slimeRender(context))
.setRenderType("translucent")
.sized(1.6, 1.4)
.setParticleType("rain")
.canBreatheUnderwater(true)
.eggItem(item => {
item.backgroundColor(4492532)
item.highlightColor(10209279)
})
.onAddedToWorld(entity => {
entity.modifyAttribute("minecraft:generic.max_health", "health_mb", 3, "multiply_base")
entity.modifyAttribute("minecraft:generic.attack_damage", "attack_mb", 3, "multiply_base")
entity.setHealth(entity.maxHealth)
})
.onEntityCollision(ctx => {
const {entity, target} = ctx
if (entity.getSize() <= 1) {
if (target.type != entity.type) return
entity.setSize(entity.size + 1,true)
target.remove("discarded")
}
})
})
/**
*
* @param {Internal.ContextUtils$ScaleModelRenderContext<Internal.SlimeEntityJS>} context
*/
global.slimeRender = context => {
const { entity, poseStack } = context;
try {
let slime = entity;
let size = slime.getSize();
let partialTick = context.partialTick;
let squish = slime.squish * 3;
let prevSquish = slime.oSquish * 3;
let lerpedSquish = (partialTick * (squish - prevSquish)) + prevSquish;
let squishFactor = lerpedSquish / (size * 0.5 + 1.0);
let inverseSquishFactor = 1.0 / (squishFactor + 1.0);
// Scale the entity based on the squish factor
poseStack.scale(inverseSquishFactor * size, 1.0 / inverseSquishFactor * size, inverseSquishFactor * size);
poseStack.translate(0.0, 0.001, 0.0); // Slight translation to avoid Z-fighting
} catch (error) {
console.log(error);
}
}
is probejs messing with me or is it grabbing the typings for the projectile builder
ok so apparently my typings are messed up because i didnt update
and i didnt impliment onEntityCollision for the other builders yet so just use this instead js .canCollideWith(ctx => { const {entity,collidingEntity} = ctx if (entity.getSize() <= 1) { if (collidingEntity.type == entity.type) { entity.setSize(entity.size + 1, true) collidingEntity.remove("discarded")} } return collidingEntity.canBeCollidedWith() && !entity.isPassengerOfSameVehicle(collidingEntity) })
here we have to return a boolean but its pretty much the super method
testing now
i mean as long as you're returning a boolean it should be fine
it did seem to work
the new version did an oopsie
but i did change something so ima unchange it
and see if it was my change or the code that crapped itself
hold on
you could also use global functions like how we're doing with the render method so you dont have to keep restarting your game to refresh js .canCollideWith(ctx => global.collidefunction(ctx))
/**
*
* @param {Internal.ContextUtils$CollidingEntityContext} ctx
* @returns
*/
global.collidefunction = ctx => {
const { entity, collidingEntity } = ctx
if (entity.getSize() <= 1) {
if (collidingEntity.type == entity.type) {
entity.setSize(entity.size + 1, true)
collidingEntity.remove("discarded")
}
}
return collidingEntity.canBeCollidedWith() && !entity.isPassengerOfSameVehicle(collidingEntity)
}
this way instead you can do /kubejs reload startup_scripts to reload the global function
alr
hmm
i want to change the script to this
global.collidefunction = ctx => {
const { entity, collidingEntity } = ctx
if (collidingEntity.getSize() <= 1) {
if (collidingEntity.type == entity.type) {
entity.setSize(entity.size + 1, true)
collidingEntity.remove("discarded")
}
}
return collidingEntity.canBeCollidedWith() && !entity.isPassengerOfSameVehicle(collidingEntity)
}
because right now the bigger slime disappears instead of the other way around
but when i change it to this
the game crashes
how could i make it so the small slime is absorbed into the big slime but the game doesnt crash?
also, something is wrong with one of my slimes
just that one though
that looks like a case of client death desync, not an issue on entityjs' end
yeah i figured the slime death thing wasn't too big a deal, thought i would mention it anyways tho
but do you have any idea what causes the game to crash when i change the script?
always check your logs when it crashes
the error message tells you exactly what line is erroring and whats wrong
kubejs logs
Paste version of client.log, server.log, startup.log from @analog tendon
game log
Paste version of message.txt from @analog tendon
yup
global.collidefunction = ctx => {
const { entity, collidingEntity } = ctx
if (collidingEntity.type == entity.type) {
if (collidingEntity.getSize() <= 1) {
entity.setSize(entity.size + 1, true)
collidingEntity.remove("discarded")
}
}
return collidingEntity.canBeCollidedWith() && !entity.isPassengerOfSameVehicle(collidingEntity)
}
see how useful checking the error messages are
this should work?
??tryitandsee
sorry im blind and didnt see it
will do
also
you didnt change the issue i will say that much
er wait a second

it should be filtering out by entity type
so yeah nvm try it
i was seeing ghosts
also
you might want to put your code in a try catch and console.log(error) the output
to prevent crashes though i guess it should default catch anyways i just realized
awesome
also, i hate to ask something else, but would it be possible to get the small slimes to pathfind to other slimes to merge?
since it currently only happens by chance
