[DOCS] How to make mobkit mobs

Post Reply
User avatar
MisterE
Member
Posts: 693
Joined: Sun Feb 16, 2020 21:06
GitHub: MisterE123
IRC: MisterE
In-game: MisterE

[DOCS] How to make mobkit mobs

by MisterE » Post

ok, So I am starting this thread to give myself and others a place to post user-made documentation on how to mod with mobkit. Using mob_core and water_life is welcome too. there seems to be a dearth of information available for it, so post away! Some rules to keep in mind:

1) Explain what you are doing thoroughly.
2) Give access to all the code you use. Post it, or link to it.
3) feel free to criticize, update, and improve on others code here. Posting here means you want to be criticized.

The purpose of this thread is to provide a community resource for beginners learning to use mobkit
Last edited by MisterE on Tue Jan 19, 2021 02:45, edited 1 time in total.

User avatar
MisterE
Member
Posts: 693
Joined: Sun Feb 16, 2020 21:06
GitHub: MisterE123
IRC: MisterE
In-game: MisterE

Re: How to make mobkit mobs

by MisterE » Post

What mobkit is:

according to Termos, the author of mobkit:
"mobkit is a library for coding any entities possible"

here are some links:
mobkit:
viewtopic.php?t=22112

mob_core:
viewtopic.php?t=25568&p=383132

mobkit mobs, unlike mobs_redo mobs use the regular minetest.register_entity()
https://dev.minetest.net/minetest.register_entity
https://minetest.gitlab.io/minetest/def ... definition

All regular entity definition properties are required for any entity to work. Mobkit also requires additional properties, and any additional functions added by yourself or an api that extends mobkit (such as mob_core or water_life) may require other properties in the entity definition.

The basic theory, as I understand it, behind mobkit, is that every entity uses mobkit's step function, activate function, and get_staticdata function, or other functions that call those. In addition, every entity has a logic function, aka brain_func. It also has a high-level queue of behavioral functions (hq) and a low-level queue of behavioral functions (lq). These structures determine how the mob will behave in specific situations.

Every mob has a 'brain'. By default, that brain has a structure, but is completely empty. You need to understand the brain's structure to make it work for you.

The first, highest level, structure is the logic function (aka brainfunc). It is defined in the entity definition. Its job is to call hq behavior functions and to call entity damage and death functions if necessary. It contains simple logic that controls the most abstract behavior of the mob.

example logic/brainfunc:

Code: Select all



local monster_brain = function(self)

    --get important info
    local pos = mobkit.get_stand_pos(self)
    local prty = mobkit.get_queue_priority(self)
    local player = mobkit.get_nearby_player(self)

    mob_core.random_sound(self, 16/self.dtime)
    --death handling
    if self.hp <= 0 then    
            mob_core.on_die(self) --clears hq and lq queues, handles drops
            
            return
    end

    --decision_making, every second
    if mobkit.timer(self,1) then

            mob_core.vitals(self) --applies water and lava damage
            

            status = self.status --status is an entity property that is user defined

            if not status then --if its not defined, we will define it now
                    mobkit.remember(self, "status", "")
                    status = ''
                    self.status = ''
            end


            if status == 'hunting' then

                    if prty < 50 and self.isinliquid then
                            mobkit.hq_liquid_recovery(self, prty+1) --makes mob decide to get out of the water with a priority greater than what it was doing before
                    end


            else --status == ''

                    if prty < 70 and player then
                            mob_core.hq_hunt(self, math.random(41,69) , player) --hunts player with a random priority between 41 and 69 
                    end
                    if prty < 40 and self.isinliquid then
                            mobkit.hq_liquid_recovery(self,math.random(41,55)) --makes mob get out of water, with a priority of 41 to 55
                    end
                    


            end

            if mobkit.is_queue_empty_high(self) then
                    mob_core.hq_roam(self, 0, 1) --starts roaming around with a priority of 0
                    self.status = mobkit.remember(self,'status','')
            end        
    
    end
end



Every entity has a high-level queue and a low-level queue. These are stacks of functions that control behavior. The queue is a table, and that table can contain functions. Mobkit calls the function on each queue that has the highest priority, every server step. Sometimes a queue may be empty, which will be described below.

When the logic function calls an hq behavior function, it is the job of the hq behavior function to place a function on the high-level queue. when it does so, it assigns a priority to it. The priority determines whether other behaviors can supersede that behavior. That function is defined in the hq-behavior function, and the only thing that the hq behavior function does is: 1) define the behavior with a function, and 2) place that behavior on the high-level queue, with its priority.

when it defines the behavior with a function, that behavior can do one or many of the following:

1) set or reset an animation
2) call a low-level behavior function
3) return the boolean true -- this indicates that this high-level behavior should cease, whether because it no longer makes sense to continue it, or because the behavior's intention has been completed.

an example high-level behavior function:

Code: Select all

function mob_core.hq_hunt(self, prty, target) --hunts a target object
    local scan_pos = target:get_pos()
    scan_pos.y = scan_pos.y + 1
    if not line_of_sight(self.object:get_pos(), scan_pos) then return true end --exit if we can't see teh target, dont place anything on the queue
    
    
    --define the behavior that will be placed on the queue
    local func = function(self)
        --stop behavior if self is dead
        if not mobkit.is_alive(target) then
            mobkit.clear_queue_high(self)
            return true
        end

        --get important info about self and target
        local pos = mobkit.get_stand_pos(self)
        local tpos = target:get_pos()


        mob_core.punch_timer(self) --updates time since last punch

        -- if self is not already performing a low level behavior, ie, if the last low level behavior is complete, then
        if mobkit.is_queue_empty_low(self) then

            --update status for future reference
            self.status = mobkit.remember(self, "status", "hunting")
            
            --get the distance between enemy and self
            local dist = vec_dist(pos, tpos)

            --get look direction
            local yaw = self.object:get_yaw()
            local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
            if abs(tyaw - yaw) > 0.1 then
                --if we arent looking at the target, then the most important thing to do is to turn to look at it. Put that on the low queue
                mobkit.lq_turn2pos(self, tpos)
            end
            if dist > self.view_range then
                --if the enemy is out of sight, then we are finished here, stop this behavior.
                self.status = mobkit.remember(self, "status", "")
                return true
            end

            local target_side = abs(target:get_properties().collisionbox[4])
            mob_core.goto_next_waypoint(self, tpos) --this will add a lq behavior to the lq queue that takes a step towards the enemy

            --if the enemy is within reach, then we will place a punch action on the lq queue
            if vec_dist(pos, tpos) < self.reach + target_side then
                self.status = mobkit.remember(self, "status", "")
                mob_core.lq_dumb_punch(self, target, "stand")
            end

            --notice that this function does not have to return true. Iff it does, then this high-level behavior ends. If it doesnt, then this high level behavior will run again next server step.
        end
    end

    --alright, we defined the behavior, place the behavior on the high-level queue
    mobkit.queue_high(self, func, prty)
end

Low-level behavior functions are very similar to high-level behavior functions, but they serve the purpose of performing short, stupid actions, such as moving a short distance toward a target, jumping, etc. They should take direct control of the mob's physical activities. If I understand correctly, functions on the low level queue ARE performed, in the sequence that they were stacked up. They do not receive a priority, but you can stop all lowlevel behaviors with

Code: Select all

mobkit.clear_queue_low(self)
an example low-level behavior function:
(from mob_core, GPL3, author ElCeejo)

Code: Select all


function mob_core.lq_dumbwalk(self,dest,speed_factor)
	local timer = 3			-- failsafe
	speed_factor = speed_factor or 1
	local func = function(self)
		mobkit.animate(self, "walk")
		timer = timer - self.dtime
		if timer < 0 then return true end
		
		local pos = mobkit.get_stand_pos(self)
		local y = self.object:get_velocity().y

		if mobkit.is_there_yet2d(pos,minetest.yaw_to_dir(self.object:get_yaw()), dest) then
            if (not self.isonground
            and not self.isinliquid)
            or abs(dest.y-pos.y) > 0.1 then		-- prevent uncontrolled fall when velocity too high
				self.object:set_velocity({x=0,y=y,z=0})
			end
			return true 
		end

        if self.isonground
        or self.isinliquid then
			local dir = vector.normalize(vector.direction({x=pos.x,y=0,z=pos.z},
														{x=dest.x,y=0,z=dest.z}))
			dir = vector.multiply(dir,self.max_speed*speed_factor)
--			self.object:set_yaw(minetest.dir_to_yaw(dir))
			mobkit.turn2yaw(self,minetest.dir_to_yaw(dir))
			dir.y = y
			self.object:set_velocity(dir)
		end
	end
	mobkit.queue_low(self, func)
end



All behavior functions are passed the entity's "self" parameter, so they have direct access to the mob's pertinent information.


at any time within any of the functions, you may use:

Code: Select all

mobkit.remember(self, [key], [val])   -- [key] - string. [val] - any value to remember, may even be a table
-- stores [val] in mob's unique memory
--------------------------------------------------------
local value = mobkit.recall(self, [key])  --recalls [key] and from mob's memory and saves to value

for both high and low level queue functions, returning true removes the function from the queue, indicating completion (not necessarily success, mind you)

to recap:

Mobkit's step function calls the mob's logic function (which places behaviors on the hq queue, which in turn place low level behaviors on the lq queue), and it executes the highest priority hq function on the hq queue and it executes the oldest function in the lq queue. If either of these functions return true, they are removed from their queue.

While you should understand the theory of how mobkit mobs work, thankfully, you do not have to understand all that in order to get started. mobkit provides some example behaviors, and mob_core provides many behaviors. If you use these libraries, all you have to do is write a brain function and an entity definition.

Of course, a library will never do things exactly the way you want. that is when you should start getting into writing your own custom hq functions.

Next up: defining a simple mob, using mobkit and mob_core. Lets start at the basics.

User avatar
MisterE
Member
Posts: 693
Joined: Sun Feb 16, 2020 21:06
GitHub: MisterE123
IRC: MisterE
In-game: MisterE

Re: How to make mobkit mobs

by MisterE » Post

In this post, we will define a simple mob using mobkit and the mob_core library.
============================================================

lets convert the rabbit from here:
https://github.com/MisterE123/mobs_crea ... t_redo.lua

follow along in the code as we convert it, step by step. We will not implement the special spawning features in this post, as that is a bit more advanced. That doesnt mean it isnt doable, or easy, just, lets focus on basic conversion.

The first thing to do is to write a brain function. This can be copied and adjusted from other mob_core mobs.

Here is how to start writing the brain func:

Code: Select all

rabbit_logic = function(self)
	--logical stuff :)
end
A brain function must handle the death of the mob. that is easy to do:

Code: Select all


	if self.hp <= 0 then
		mob_core.on_die(self)
		return
	end
Next, we should know the current priority of the high-level queue. That will allow us to decide whether to change our actions. Lets also find out if there are any players within view range:

Code: Select all

	local prty = mobkit.get_queue_priority(self)
	local player = mobkit.get_nearby_player(self)

Great! now, we have all the information we need to make decisions!
Decision making needn't happen every server step. Really, we only need to make important decisions every second or so. Also, we only should apply drowning and lava damage every second or so. Therefore, we will put everything else into an if block that runs only once per second. mobkit makes that easy:

Code: Select all

	if mobkit.timer(self,1) then  --this will only run about once per second

apply lava damage and drowning damage:

Code: Select all

		mob_core.vitals(self)
haha, lets make it poop every so often:

Code: Select all

		mob_core.random_drop(self, 10, 1800, "mobs_creatures:poop_turd")

to make it more alive, we want to make it make sounds randomly. This pulls a sound from the mob's 'random' sound register:

Code: Select all

		mob_core.random_sound(self, 8)
remember how we got the priority of the high-level queue? well, lets make decisions now!
Lets come up with a scheme for priority.
the priority will normally range from 0-10
(I just came up with this, it could be 0-100, or 0-65, who cares? you program priorities based on the range you set. Some mob_core functions, such as on_punch_runaway, set the priority at a certain level, so you have to work with that)

Lets say that the rabbit should runaway from a player if its priority is less than 9. Than means that if it is not doing
anything more important than level 9, it will runaway from the player. When it runs away, it will do so with a priority of 10

Lets also say that if the rabbit is in water, it will try to get out of the water, with a priority level 9, if it is not doing anything more important then level 9. I guess it really hates water.

Finally, if it isn't doing anything else, then it will just roam around doing nothing much.

Please note, that, later on, we will make it so if punched, the rabbit will run away with priority 10, and cause other rabbits in the area to run away too.

here is how that looks in code:

Code: Select all


        if prty < 9 and player then
			if self.isinliquid then --if for some reason we have been knocked into water, then we will swim away from the player
				mob_core.hq_swimfrom(self, 10, player, 1)  --mob_core.hq_swimfrom(self, prty, target, speed), speed is a multiplier on normal speed
			else
				mob_core.hq_runfrom(self, 10, player)   --mob_core.hq_runfrom(self, prty, tgtobj)
			end
		end

		if prty < 9 then
			if self.is_in_liquid then
				mob_core.hq_liquid_recovery(self, 9, 'walk') --mob_core.hq_liquid_recovery(self, prty, anim)
			end
		end
		
		if mobkit.is_queue_empty_high(self) then
			mob_core.hq_roam(self, 0)
		end
Yay! we are done with the logic!

Here is our completed logic function:

Code: Select all


local function rabbit_logic(self)

	if self.hp <= 0 then
		mob_core.on_die(self)
		return
	end
	local prty = mobkit.get_queue_priority(self)
	local player = mobkit.get_nearby_player(self)

	if mobkit.timer(self,1) then

		mob_core.vitals(self)
		mob_core.random_drop(self, 10, 1800, "mobs_creatures:poop_turd")
		mob_core.random_sound(self, 8)


		
        if prty < 9 and player then
			if self.isinliquid then --if for some reason we have been knocked into water, then we will swim away from the player
				mob_core.hq_swimfrom(self, 10, player, 1)  --mob_core.hq_swimfrom(self, prty, target, speed), speed is a multiplier on normal speed
			else
				mob_core.hq_runfrom(self, 10, player)   --mob_core.hq_runfrom(self, prty, tgtobj)
			end
		end

		if prty < 9 then
			if self.is_in_liquid then
				mob_core.hq_liquid_recovery(self, 9, 'walk') --mob_core.hq_liquid_recovery(self, prty, anim)
			end
		end

		if mobkit.is_queue_empty_high(self) then
			mob_core.hq_roam(self, 0)
		end
	end
end


===================================================================================
Now, we need to define the rabbit. My mod is called 'mobs:creatures' you will of course have to change the name to your mod's name accordingly:
CAUTION: we do NOT use mobs:register_mob("mobs_creatures:rabbit",{}. We are not using mobs_redo, so we cannot call its functions.

Code: Select all

minetest.register_entity("mobs_creatures:rabbit",{
minetest entity definition requires certain properties, mobkit requires others, and mob_core wants even more for its functions. Some of these we can pull the values directly from the mobs_redo definition. Others, we have to translate or guess at.

here are the properties required for any entity:

Code: Select all

	physical = true, 
	collide_with_objects = true,
	collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.49, 0.2}, --pulled from mobs_redo definition verbatim
	visual_size = {x=1.5, y=1.5},						--pulled from mobs_redo definition verbatim, if present
	visual = "mesh",                                    --pulled from mobs_redo definition verbatim
	mesh = "mobs_creatures_rabbit.b3d",                 --pulled from mobs_redo definition verbatim
	textures = {"mobs_creatures_rabbit_brown.png",
				"mobs_creatures_rabbit_gold.png",
				"mobs_creatures_rabbit_white.png",
				"mobs_creatures_rabbit_white_splotched.png",
				"mobs_creatures_rabbit_salt.png",
				"mobs_creatures_rabbit_black.png",
				"mobs_creatures_rabbit_toast.png"
			},

			--Woah, that is different from mobs_redo!
CAUTION! Textures is different from mobs_redo. If you get this wrong, you will have a mob that is see-through and assumes random textures from the environment. Mobkit texture definition is a table of strings, while mobs_redo texture definition is a table of tables of strings.

CORRECT:

Code: Select all

	textures = {"mobs_creatures_rabbit_brown.png",
				"mobs_creatures_rabbit_gold.png",
				"mobs_creatures_rabbit_white.png",
				"mobs_creatures_rabbit_white_splotched.png",
				"mobs_creatures_rabbit_salt.png",
				"mobs_creatures_rabbit_black.png",
				"mobs_creatures_rabbit_toast.png"
			},
INCORRECT:

Code: Select all

	textures = {
        {"mobs_creatures_rabbit_brown.png"},
        {"mobs_creatures_rabbit_gold.png"},
        {"mobs_creatures_rabbit_white.png"},
        {"mobs_creatures_rabbit_white_splotched.png"},
        {"mobs_creatures_rabbit_salt.png"},
        {"mobs_creatures_rabbit_black.png"},
	{"mobs_creatures_rabbit_toast.png"},
	},
===========
now, we need mobkit's required properties:
here is what mobkit's api says is required:

Code: Select all

	timeout = [num],			-- entities are removed after this many seconds inactive
								-- 0 is never
								-- mobs having memory entries are not affected

	buoyancy = [num],			-- (0,1) - portion of collisionbox submerged
								-- = 1 - controlled buoyancy (fish, submarine)
								-- > 1 - drowns
								-- < 0 - MC like water trampolining

	lung_capacity = [num], 		-- seconds
	max_hp = [num],
	on_step = mobkit.stepfunc,
	on_activate = mobkit.actfunc,
	get_staticdata = mobkit.statfunc,
	logic = [function user defined],		-- older 'brainfunc' name works as well.

				-- optional mobkit props
				-- or used by built in behaviors
	physics = [function user defined] 		-- optional, overrides built in physics
	animation = {
		[name]={range={x=[num],y=[num]},speed=[num],loop=[bool]},		-- single

		[name]={														-- variant, animations are chosen randomly.
				{range={x=[num],y=[num]},speed=[num],loop=[bool]},
				{range={x=[num],y=[num]},speed=[num],loop=[bool]},
				...
			}
		...
		}
	sounds = {
		[name] = [string filename],				--single, simple,

		[name] = {								--single, more powerful. All fields but 'name' are optional
			name = [string filename],
			gain=[num or range],				--range is a table of the format {left_bound, right_bound}
			fade=[num or range],
			pitch=[num or range],
			},

		[name] = {								--variant, sound is chosen randomly
			{
			name = [string filename],
			gain=[num or range],
			fade=[num or range],
			pitch=[num or range],
			},
			{
			name = [string filename],
			gain=[num or range],
			fade=[num or range],
			pitch=[num or range],
			},
			...
		},
		...
	},
	max_speed = [num],					-- m/s
	jump_height = [num],				-- nodes/meters
	view_range = [num],					-- nodes/meters
	attack={range=[num],				-- range is distance between attacker's collision box center
		damage_groups={fleshy=[num]}},	-- and the tip of the murder weapon in nodes/meters
	armor_groups = {fleshy=[num]}
I'll go through them one-by-one:

timeout- I'll set at 5 minutes. Caution: this should be set to 0 for tamed mobs, or anything else you dont want to disappear eventually
====================

Code: Select all

timeout = 500, --how long until the mob is removed, 0 for never
====================

buoyancy- 0-1, 0, it sinks, 1 it walks on the water. 0.7 should do it.
====================

Code: Select all

buoyancy = .7, 
====================

Code: Select all

lung_capacity = 5, --how many seconds it can hold its breath before taking water damage
====================

Code: Select all

max_hp = 3, -- we can take this from the mobs_redo def
====================

on_step = func
on_activate = func

CAUTION: if you are using just mobkit, you will use:

Code: Select all

on_step = mobkit.stepfunc,
on_activate = mobkit.actfunc,
HOWEVER, if you are using mob_core, you will use:

Code: Select all

on_step = mob_core.on_step,
on_activate = mob_core.on_activate,
FURTHERMORE, if you want to use better_fauna's api extention, you should use:

Code: Select all

on_step = better_fauna.on_step,
on_activate = better_fauna.on_activate,
Water_life may be different still, or it might use mobkit's functions. All of these functions call mobkit's functions, but may do other things as well. You, as a programmer, could eventually write your own wrapper to use with your api.
Make sure you are using the correct function set for the api that your mob is using.


my rabbit will use mob_core.


you also need:

Code: Select all

get_staticdata = mobkit.statfunc,
====================
you don't need physics, that is an option to override mobkit's physics system.
====================
logic = func. Here is where you define your brain or logic function that you wrote above.
brainfunc = func is the same thing.

Code: Select all

logic = rabbit_logic,
====================
animation
----------------
CAUTION: you can't just copy in a mobs_redo animation definition.
here is the mobs_redo animation definition: (INCORRECT)

Code: Select all

animation = {
		speed_normal = 25,
		speed_run = 50,
		stand_start = 0,
		stand_end = 0,
		walk_start = 0,
		walk_end = 20,
		run_start = 0,
		run_end = 20,
	},
here is the format for the animatin definition for mobkit:

Code: Select all

animation = {
		[name]={range={x=[num],y=[num]},speed=[num],loop=[bool]},
		[name]={range={x=[num],y=[num]},speed=[num],loop=[bool]},
		[name]={range={x=[num],y=[num]},speed=[num],loop=[bool]},
		},
You will notice that there are other ways to define animations for mobkit, detailed in mobkit's api.txt This is the simplest. To translate the animations, copy similar information over. Note that the punch animation of mobs_redo is called the attack animation with mob_core. X and Y are the start and end of the animation. Notethe speed_normal item in the redo definition. This is the speed value for each of the mobkit anim defs, except run, which is 50. loop should be true for animations that loop (run, walk, attack, etc)

Notice that using mobkits animation definition, you can have custom animations, for custom behaviors.

Here is the rabbit's animation def code:

Code: Select all

	animation = {
		walk={range={x=0,y=20},speed=25,loop=true},
		run={range={x=0,y=20},speed=50,loop=true},
		stand={range={x=0,y=0},speed=25,loop=true},
	},
=======================
sounds can just be copied from mobs_redo, but you have to turn 'damage' to 'hurt' for mob_core to use it:

Code: Select all

	sounds = {										--can be copied from redo
		random = "mobs_creatures_rabbit_random",
		jump = "mobs_creatures_rabbit_jump",
		hurt = "mobs_creatures_rabbit_pain",                           --was, damage = "mobs_creatures_rabbit_pain",
		death = "mobs_creatures_rabbit_death",	
	},
=============================
max_speed = num

speed is handled a little differently than mobs_redo. Get this value from mobs-redo's run_velocity.

Code: Select all

max_speed = 4, --taken from mobs_redo's run_velocity
For now, mob_core does not support land roaming at a lower speed than the max speed, which would be how one would implement walk velocity. Hopefully it will soon. It would be easy to copy and edit the hq_roam function to accept a speed factor, as the aerial_roam and aqua_roam functions do, but that is outside the scope of this intro.
=============================
jump_height and step_height.
jump_height is a mobkit required prop. stepheight is a mob_core required prop.
Because of bugs I experienced with mob_core, I would set jump_height and step_height to the same value: 1.1
to fix these bugs, wait until mob_core fixes it, write your own functions, use water_life instead, repair mob_core yourself, or live with it for now.

Code: Select all

	jump_height = 1.1,
	stepheight = 1.1,
I think the bugs arise from mob_core using different movement mechanics than mobkit. Anyhow, the point is, keep then the same for now. Feel free to experiment with different values though... the bugs involved mobs not being able to navigate as well. They may even have been fixed by now.

=============================

Code: Select all

	view_range = 8, --copied from mobs_redo
=============================
attack
the mobkit attack property is used bu mobkit attack hq_functions. Mob_core does not use these; it uses a different way for defining attack properties. You may still wish to include the regular mobkit version, however.

Code: Select all

attack={range=2, damage_groups={fleshy=1}},	
range is mobs_redo's reach. damage_groups is how you define its attack capabilities. Its the same as tool definition.

=============================
armor groups
------

Code: Select all

armor_groups = {fleshy=100},
the groups means the amount of damage it will take from that kind of weapon. fleshy = 100 indicates that the mob should take 100% of damage from tools that deal fleshy damage. 200 would mean that it takes twice as much damage as it is supposed to. 50 would mean that it takes half damage.
==============================
==============================
That it for required mobkit properties. Now, for required mob_core properties:

here is what mob_core requires:
Spoiler

Code: Select all

	-- required mob_core props (refer to mobkits documentation for required mobkit props)

	fall_damage = [bool] If true, the mob will take fall damage (true by default)
	igniter_damage = [bool] If true, the mob will take fire/lava damage (true by default)
	follow = [table/string] Table of items mob should follow (will also accept a single string)
	immune_to = [table] Table of items mob can't take damage from
	reach = [num] The distance from the center of the mobs hitbox in which it can hit another mob
	damage = [num] The amount of damage the mob does in the fleshy group (2 damage = 1 player heart)
	knockback = [num] How much knockback the mob deals to other mobs or players
	defend_owner = [bool] If true, when the mobs owner punches another mob, the mob will be stored in the self.owner_target variable
    	drops = [table] Table of items mob can drop. Example: 
	{
        	{name = "my_mob:meat_raw", chance = 1, min = 1, max = 3}
    	}

	-- Obstacle Avoidance
	obstacle_avoidance_range = [num] Multiplier for how far ahead the mob checks for obstacles.
        surface_avoidance_range = [num] How close (in nodes) from the center of the mobs hitbox it will get to the water surface
        floor_avoidance_range = [num] How close (in nodes) from the center of the mobs hitbox it will get to the water floor

	-- For mobs that use mob_core.growth()
	scale_stage1 = [num] Multiplier for how big the mob will be at growth stage 1 (default: 0.25)
	scale_stage2 = [num] Multiplier for how big the mob will be at growth stage 2 (default: 0.5)
	scale_stage3 = [num] Multiplier for how big the mob will be at growth stage 3 (default: 0.75)

	-- For mobs that have unique textures for males, females, and children
    	female_textures = [table] Texture/Textures for female mobs
    	male_textures = [table] Texture/Textures for male mobs
    	child_textures = [table] Texture/Textures for child mobs
========
not all of these have to be defined, there are presets available so you dont have to worry about all of them. For our rabbit we will do:

Code: Select all


	fall_damage = true,
	
	reach = 2,
	damage = 1,
	knockback = 0,
	defend_owner = false,
	drops = {
		{name = "mobs_creatures:rabbit_raw", chance = 1, min = 0, max = 1},
		{name = "mobs_creatures:rabbit_hide", chance = 1, min = 0, max = 1},
	},


	obstacle_avoidance_range = 5,
	surface_avoidance_range = 0,
	floor_avoidance_range = 0,

note: because mob_core uses a different damage system than mobkit,

Code: Select all

reach = 2,
damage = 1,
is the same as

Code: Select all

attack={range=2, damage_groups={fleshy=1}},	
,
and both should be included if using mob_core

of course, for a rabbit, the attack definition is pointless anyways :)

we could include an on_rightclick = func
that could be used for taming and for capturing mobs. better_fauna has some examples. We'll skip this for now.

We also need an on_punch function. The purpose is to make the rabbit wake up and run if it is punched.
for aggressive mobs, we would call hq_hunt here instead:

Code: Select all

	on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
		if time_from_last_punch < .2 then return end --prevent jitterclicking 
		mobkit.clear_queue_high(self)
		mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) --calls damage functions and flashes red
		mob_core.on_punch_runaway(self, puncher, false, true)   --mob_core.on_punch_runaway(self, puncher, water, group)
	end,
here, we dont do anything if you are punching it too fast. Then, we clear the poor rabbit's queue of intentions, and set its only focus on running away, with priority 10.

We finally have a testable mob!
Lets go test it now...

ok... so it basically works!

Code: Select all


local function rabbit_logic(self)

	if self.hp <= 0 then
		mob_core.on_die(self)
		return
	end
	local prty = mobkit.get_queue_priority(self)
	local player = mobkit.get_nearby_player(self)

	if mobkit.timer(self,1) then

		mob_core.vitals(self)
		mob_core.random_drop(self, 10, 1800, "mobs_creatures:poop_turd")
		mob_core.random_sound(self, 8)


		
        if prty < 9 and player then
			if self.isinliquid then --if for some reason we have been knocked into water, then we will swim away from the player
				mob_core.hq_swimfrom(self, 10, player, 1)  --mob_core.hq_swimfrom(self, prty, target, speed), speed is a multiplier on normal speed
			else
				mob_core.hq_runfrom(self, 10, player)   --mob_core.hq_runfrom(self, prty, tgtobj)
			end
		end

		if prty < 9 then
			if self.is_in_liquid then
				mob_core.hq_liquid_recovery(self, 9, 'walk') --mob_core.hq_liquid_recovery(self, prty, anim)
			end
		end

		if mobkit.is_queue_empty_high(self) then
			mob_core.hq_roam(self, 0)
		end
	end
end


minetest.register_entity("mobs_creatures:rabbit",{
	physical = true, 
	collide_with_objects = true,
	collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.49, 0.2}, --pulled from mobs_redo definition verbatim
	visual_size = {x=1.5, y=1.5},						--pulled from mobs_redo definition verbatim, if present
	visual = "mesh",                                    --pulled from mobs_redo definition verbatim
	mesh = "mobs_creatures_rabbit.b3d",                 --pulled from mobs_redo definition verbatim
	textures = {"mobs_creatures_rabbit_brown.png",
				"mobs_creatures_rabbit_gold.png",
				"mobs_creatures_rabbit_white.png",
				"mobs_creatures_rabbit_white_splotched.png",
				"mobs_creatures_rabbit_salt.png",
				"mobs_creatures_rabbit_black.png",
				"mobs_creatures_rabbit_toast.png"
			},

			--Woah, that is different from mobs_redo!

	timeout = 500, --how long until the mob is removed, 0 for never
	buoyancy = .7, 
	lung_capacity = 5, --how many seconds it can hold its breath before taking water damage
	max_hp = 3, -- we can take this from the mobs_redo def
	on_step = mob_core.on_step,
	on_activate = mob_core.on_activate,
	get_staticdata = mobkit.statfunc,
	logic = rabbit_logic,
	animation = {									-- has to be translated
		walk={range={x=0,y=20},speed=25,loop=true},
		run={range={x=0,y=20},speed=50,loop=true},
		stand={range={x=0,y=0},speed=25,loop=true},
	},
	sounds = {										--can be copied from redo
		random = "mobs_creatures_rabbit_random",
		jump = "mobs_creatures_rabbit_jump",
		damage = "mobs_creatures_rabbit_pain",
		death = "mobs_creatures_rabbit_death",	
	},
	max_speed = 4, --taken from mobs_redo's run_velocity
	jump_height = 1.1,
	stepheight = 1.1,
	view_range = 8, --copied from mobs_redo
	attack={range=2,damage_groups={fleshy=1}},
	armor_groups = {fleshy=100},
		

	fall_damage = true,
	
	reach = 2,
	damage = 1,
	knockback = 0,
	defend_owner = false,
	drops = {
		{name = "mobs_creatures:rabbit_raw", chance = 1, min = 0, max = 1},
		{name = "mobs_creatures:rabbit_hide", chance = 1, min = 0, max = 1},
	},


	obstacle_avoidance_range = 5,
	surface_avoidance_range = 0,
	floor_avoidance_range = 0,
	on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
		if time_from_last_punch < .2 then return end --prevent jitterclicking 
		mobkit.clear_queue_high(self)
		mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) --calls damage functions and flashes red
		mob_core.on_punch_runaway(self, puncher, false, true)
	end,
})
Thats the current state of the code.

Now, we have to add spawning and spawneggs.

mob_core makes spawneggs easy:

Code: Select all

mob_core.register_spawn_egg("mobs_creatures:rabbit", "ab7e35", "26231f")
the first argument is the name of the entity to spawn, the second is the color of the background of the egg, the third is the color of the forground of the egg.
Use the google color picker to get your colors, cpoy the hex value, without the pound sign into the arguments:
https://www.google.com/search?client=fi ... lor+picker

now for in-world spawning:

Code: Select all

 from mob_Core's api:
function mob_core.register_spawn({
	name = [string] mob name
	nodes = [table] list of nodes to spawn mob on
	min_light = [number] minimum light level
	max_light = [number] maximum ligth level
	min_height = [number] minimum world heigh
	max_height = [number] maximum world heigh
	min_rad = [number] minimum radius around player
	max_rad = [number] maximum radius around player
	group = [number] amount of mobs to spawn
	optional = {
		reliability = 3, --the number of tries to find a node, def. 3
	}
}, interval, chance)
	-- interval: how often (in seconds) to attempt spawning
	-- chance: chance to attempt spawning
You can't just copy over all the values from a mobs_redo spawn definition: the chance is a different system

The way it works is that every [interval] seconds, the code tries [reliability] times to choose a random location around the player, and it checks if teh node there is one of [nodes] and is within the other values listed. If everything is peachy, it places [group] number of mobs there.

here is what I am trying for my rabbit:

Code: Select all


mob_core.register_spawn({
	name = "mobs_creatures:rabbit",
	nodes = {"group:crumbly"},
	min_light = 0,
	max_light = 15,
	min_height = -100,
	max_height = 500,
	min_rad = 24,
	max_rad = 256,
	group = 2,
	optional = {
		
		reliability = 3,
	}
}, 16, 6)

==========FINISHED!==============
here is the complete code:

Code: Select all


local function rabbit_logic(self)

	if self.hp <= 0 then
		mob_core.on_die(self)
		return
	end
	local prty = mobkit.get_queue_priority(self)
	local player = mobkit.get_nearby_player(self)

	if mobkit.timer(self,1) then

		mob_core.vitals(self)
		mob_core.random_drop(self, 10, 1800, "mobs_creatures:poop_turd")
		mob_core.random_sound(self, 8)


		
        if prty < 9 and player then
			if self.isinliquid then --if for some reason we have been knocked into water, then we will swim away from the player
				mob_core.hq_swimfrom(self, 10, player, 1)  --mob_core.hq_swimfrom(self, prty, target, speed), speed is a multiplier on normal speed
			else
				mob_core.hq_runfrom(self, 10, player)   --mob_core.hq_runfrom(self, prty, tgtobj)
			end
		end

		if prty < 9 then
			if self.is_in_liquid then
				mob_core.hq_liquid_recovery(self, 9, 'walk') --mob_core.hq_liquid_recovery(self, prty, anim)
			end
		end

		if mobkit.is_queue_empty_high(self) then
			mob_core.hq_roam(self, 0)
		end
	end
end


minetest.register_entity("mobs_creatures:rabbit",{
	physical = true, 
	collide_with_objects = true,
	collisionbox = {-0.2, -0.01, -0.2, 0.2, 0.49, 0.2}, --pulled from mobs_redo definition verbatim
	visual_size = {x=1.5, y=1.5},						--pulled from mobs_redo definition verbatim, if present
	visual = "mesh",                                    --pulled from mobs_redo definition verbatim
	mesh = "mobs_creatures_rabbit.b3d",                 --pulled from mobs_redo definition verbatim
	textures = {"mobs_creatures_rabbit_brown.png",
				"mobs_creatures_rabbit_gold.png",
				"mobs_creatures_rabbit_white.png",
				"mobs_creatures_rabbit_white_splotched.png",
				"mobs_creatures_rabbit_salt.png",
				"mobs_creatures_rabbit_black.png",
				"mobs_creatures_rabbit_toast.png"
			},

			--Woah, that is different from mobs_redo!

	timeout = 500, --how long until the mob is removed, 0 for never
	buoyancy = .7, 
	lung_capacity = 5, --how many seconds it can hold its breath before taking water damage
	max_hp = 3, -- we can take this from the mobs_redo def
	on_step = mob_core.on_step,
	on_activate = mob_core.on_activate,
	get_staticdata = mobkit.statfunc,
	logic = rabbit_logic,
	animation = {									-- has to be translated
		walk={range={x=0,y=20},speed=25,loop=true},
		run={range={x=0,y=20},speed=50,loop=true},
		stand={range={x=0,y=0},speed=25,loop=true},
	},
	sounds = {										--can be copied from redo
		random = "mobs_creatures_rabbit_random",
		jump = "mobs_creatures_rabbit_jump",
		damage = "mobs_creatures_rabbit_pain",
		death = "mobs_creatures_rabbit_death",	
	},
	max_speed = 4, --taken from mobs_redo's run_velocity
	jump_height = 1.1,
	stepheight = 1.1,
	view_range = 8, --copied from mobs_redo
	attack={range=2,damage_groups={fleshy=1}},
	armor_groups = {fleshy=100},
		

	fall_damage = true,
	
	reach = 2,
	damage = 1,
	knockback = 0,
	defend_owner = false,
	drops = {
		{name = "mobs_creatures:rabbit_raw", chance = 1, min = 0, max = 1},
		{name = "mobs_creatures:rabbit_hide", chance = 1, min = 0, max = 1},
	},


	obstacle_avoidance_range = 5,
	surface_avoidance_range = 0,
	floor_avoidance_range = 0,
	on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
		if time_from_last_punch < .2 then return end --prevent jitterclicking 
		mobkit.clear_queue_high(self)
		mob_core.on_punch_basic(self, puncher, tool_capabilities, dir) --calls damage functions and flashes red
		mob_core.on_punch_runaway(self, puncher, false, true)
	end,
})

mob_core.register_spawn_egg("mobs_creatures:rabbit", "ab7e35", "26231f")


mob_core.register_spawn({
	name = "mobs_creatures:rabbit",
	nodes = {"group:crumbly"},
	min_light = 0,
	max_light = 15,
	min_height = -100,
	max_height = 500,
	min_rad = 24,
	max_rad = 256,
	group = 2,
	optional = {
		
		reliability = 3,
	}
}, 16, 6)

If you want to download the nod that contains this, here is the link:
https://github.com/MisterE123/mobs_crea ... rabbit.lua --code
https://github.com/MisterE123/mobs_crea ... e/main.zip --download
It requires mob_redo (for the mobs I haven't converted yet), mobkit, mob_core, and better_fauna. You could avoid all those dependencies, only mob_kit and mob_core, by commenting out all the other file execution calls besides rabbit in init.lua. You would also have to change depends.txt

Adnunano
Member
Posts: 43
Joined: Thu Feb 13, 2020 09:28

Re: [DOCS] How to make mobkit mobs

by Adnunano » Post

I want my aggresive animal to retreat after his hp is below the limit.Because in real life animals don't fight you to death.If they are really hurt they try to retreat.
NfAPhpmGaVphBjK8NIr9Mit5xNW2emD2

u18398

Re: How to make mobkit mobs

by u18398 » Post

MisterE wrote:
Tue Jan 19, 2021 01:50
on_step = func
on_activate = func

CAUTION: if you are using just mobkit, you will use:

Code: Select all

on_step = mobkit.stepfunc,
on_activate = mobkit.actfunc,
[...]

Water_life may be different still, or it might use mobkit's functions.
Water_life is not touching on_step or on_activate or on_whatever :)
I leave it to mobkit.

User avatar
MisterE
Member
Posts: 693
Joined: Sun Feb 16, 2020 21:06
GitHub: MisterE123
IRC: MisterE
In-game: MisterE

Re: [DOCS] How to make mobkit mobs

by MisterE » Post

Adnunano wrote:
Tue Jan 19, 2021 07:18
I want my aggresive animal to retreat after his hp is below the limit.Because in real life animals don't fight you to death.If they are really hurt they try to retreat.

Code: Select all

on_punch = function(self, puncher, _, tool_capabilities, dir)
        if _ < 2 then mobkit.clear_queue_high(self) end --if dtime since last punch is greater than 2 then refocus
        if _ < .3 then return end --if dtime since last punch is > than, continue
        --apply damage
        mob_core.on_punch_basic(self, puncher, tool_capabilities, dir)
        --if hurt, flee
        if self.hp < 5 then --if hp < 5 then flee

                mob_core.on_punch_runaway(self, puncher, true , false)
                
                
        else
                --attack
                mob_core.on_punch_retaliate(self, puncher, true, false)
                
        end
end,

try that

Termos
Member
Posts: 417
Joined: Sun Dec 16, 2018 12:50

Re: [DOCS] How to make mobkit mobs

by Termos » Post

MisterE wrote:
Tue Jan 19, 2021 16:37
if _ < 2 then mobkit.clear_queue_high(self) end --if dtime since last punch is greater than 2 then refocus
I recommend looking into the concept of behavior priority, it's quite an essential feature and simplifies the process a lot.
Normally you would check what's the current queue priority and only queue another behavior if its priority is greater than that.
That allows for some neat tricks for example after the fight is over and if the mob survived it, it can resume whatever they were doing previously on their own, without any additional code.
Erasing the high queue is quite a radical move, so far I've been using it only after a mob has been killed, because dead mobs don't have to behave anymore.

Mob core seems to use hardcoded priority scheme which is not ideal, but knowing what priorities it assigns to its behaviors you can still take advantage of that.

User avatar
MisterE
Member
Posts: 693
Joined: Sun Feb 16, 2020 21:06
GitHub: MisterE123
IRC: MisterE
In-game: MisterE

Re: [DOCS] How to make mobkit mobs

by MisterE » Post

Termos wrote:
Wed Jan 20, 2021 12:19
MisterE wrote:
Tue Jan 19, 2021 16:37
if _ < 2 then mobkit.clear_queue_high(self) end --if dtime since last punch is greater than 2 then refocus
I recommend looking into the concept of behavior priority, it's quite an essential feature and simplifies the process a lot.
Normally you would check what's the current queue priority and only queue another behavior if its priority is greater than that.
That allows for some neat tricks for example after the fight is over and if the mob survived it, it can resume whatever they were doing previously on their own, without any additional code.
Erasing the high queue is quite a radical move, so far I've been using it only after a mob has been killed, because dead mobs don't have to behave anymore.

Mob core seems to use hardcoded priority scheme which is not ideal, but knowing what priorities it assigns to its behaviors you can still take advantage of that.
I was wondering why I saw so much clearing of the high queue... It easy to copy mistakes. Thanks

u18398

Re: [DOCS] How to make mobkit mobs

by u18398 » Post

MisterE wrote:
Wed Jan 20, 2021 13:03

I was wondering why I saw so much clearing of the high queue... It easy to copy mistakes. Thanks
You must be a very good and used programmer to not to do so (clearing the queue).
Depending on the complexity of your behavior, you sometimes forget something and
the return true case is never happening.
For an amateur like me it is a dirty trick but it helps, before doing some important stuff
to clear both queues :)
After some time you will get used to the queue system, but that took a while for me :D

User avatar
MisterE
Member
Posts: 693
Joined: Sun Feb 16, 2020 21:06
GitHub: MisterE123
IRC: MisterE
In-game: MisterE

Re: [DOCS] How to make mobkit mobs

by MisterE » Post

This discussion is good...

As you feel like it,
Termos, could you post more tips of how you intended Mobkit to be used, optimal strategies, etc

Gundul, could you give a simplified example of how to use the water_life api.

Tips, strategies, and simple examples are extremely helpful for beginners like myself and others.

Anyone else who has experience with mobkit, please post what you know, (or think you know!)

Termos
Member
Posts: 417
Joined: Sun Dec 16, 2018 12:50

Re: [DOCS] How to make mobkit mobs

by Termos » Post

Gundul wrote:
Wed Jan 20, 2021 17:03
Depending on the complexity of your behavior, you sometimes forget something and
the return true case is never happening.
Yes, this is actually the tricky part, exit conditions must be well thought out and whenever in doubt add extra safety e.g. timeout.
But notice that even if you end up with an infinite behavior, as soon as you queue a higher priority behavior it still takes over.

See the chapter Priority in mobkit_api.txt.

u18398

Re: [DOCS] How to make mobkit mobs

by u18398 » Post

MisterE wrote:
Wed Jan 20, 2021 18:22

Gundul, could you give a simplified example of how to use the water_life api.
First of all, you might want to set water_life.apionly in the settings to true.
Like this all water_life mobs are off and you can focus on your own thing.

You find an api documentation in the 'doc' folder. This is maybe not complete
but only the newest functions are missing.

If you would like to know how brainfunctions work, take a look in the animals
folder. Each file contains the entity registration and the brainfunc.

Spawn.lua contains the mob spawning. I use my custom functions to do so:
water_life.find_node_under_air(pos,radius,nodename or group) for
spawning on land

and

depth, stype, surface = water_life.water_depth(pos,maxdepth) for water
where the returning depth contains the waterdepth at pos, stype the string of
the water_source and surface the surface position vector

Everything else I would suggest to look at the code, I usually leave some comments
in there.

User avatar
runs
Member
Posts: 3225
Joined: Sat Oct 27, 2018 08:32

Re: [DOCS] How to make mobkit mobs

by runs » Post

MisterE, you resumed perfectly what I approached in 2 years. :-D

Mobkit is not at all easy and not accessible for novice modders. Mobs_redo is much simpler, just fill in a table with the entity definition.

But the complexity of mobkit is precisely what makes it so flexible. There is nothing, or almost nothing, that can't be done with mobkit. The only problem is that you practically have to build everything yourself (behaviours); that, together with the elaboration of the models for the mobs, sounds, etc. makes it very complicated in time.

User avatar
MisterE
Member
Posts: 693
Joined: Sun Feb 16, 2020 21:06
GitHub: MisterE123
IRC: MisterE
In-game: MisterE

Re: [DOCS] How to make mobkit mobs

by MisterE » Post

That is why it is nice to use a library that has a bunch of useful function, so you can focus on the extrodinary behavior, rather than the mundane

Adnunano
Member
Posts: 43
Joined: Thu Feb 13, 2020 09:28

Re: [DOCS] How to make mobkit mobs

by Adnunano » Post

https://github.com/Adnan1091/mobs_zebra
Finally managed to convert this mob,but it retains a issue from mobs redo.It moves backwards(in redo too if someone knows to fix this i added old init.lua) and i couldn't figured out how to add spawn egg to mobkit.I added as a 7z archive as i couldn't upload a folder on my Android phone.
NfAPhpmGaVphBjK8NIr9Mit5xNW2emD2

u18398

Re: [DOCS] How to make mobkit mobs

by u18398 » Post

Adnunano wrote:
Fri Feb 05, 2021 20:41
https://github.com/Adnan1091/mobs_zebra
Finally managed to convert this mob,but it retains a issue from mobs redo.It moves backwards(in redo too if someone knows to fix this i added old init.lua) and i couldn't figured out how to add spawn egg to mobkit.I added as a 7z archive as i couldn't upload a folder on my Android phone.
Open the model in blender and turn it around 180 degrees :)

Termos
Member
Posts: 417
Joined: Sun Dec 16, 2018 12:50

Re: [DOCS] How to make mobkit mobs

by Termos » Post

Gundul wrote:
Sat Feb 06, 2021 08:30
Open the model in blender and turn it around 180 degrees :)
Seriously though, is there an easy way of importing b3d into blender yet by any chance?

u18398

Re: [DOCS] How to make mobkit mobs

by u18398 » Post

Termos wrote:
Sat Feb 06, 2021 13:47
Seriously though, is there an easy way of importing b3d into blender yet by any chance?
No idea, I am absolute blender noob. But often the blend files are included or the owner is
not too far away to ask him for the blend file.

User avatar
runs
Member
Posts: 3225
Joined: Sat Oct 27, 2018 08:32

Re: [DOCS] How to make mobkit mobs

by runs » Post

Termos wrote:
Sat Feb 06, 2021 13:47
Gundul wrote:
Sat Feb 06, 2021 08:30
Open the model in blender and turn it around 180 degrees :)
Seriously though, is there an easy way of importing b3d into blender yet by any chance?
No, you can export but not import. Still not Blender plugin :-(

I.e. the Chinese evil guy do not provide the Blender files, only the b3d files. So his mods are really not Open Source. Not for the models at least...

u18398

Re: [DOCS] How to make mobkit mobs

by u18398 » Post

I found only this:

https://github.com/joric/io_scene_b3d

The readme says it can import and export b3d files.
Anyone who can try this out if it works ?

Adnunano
Member
Posts: 43
Joined: Thu Feb 13, 2020 09:28

Re: [DOCS] How to make mobkit mobs

by Adnunano » Post

I already have the blend file but i don't have pc to rotate the model.Can you guys do it for me?
Attachments
amcaw_zebra.7z
Blend file
(74.15 KiB) Downloaded 36 times
NfAPhpmGaVphBjK8NIr9Mit5xNW2emD2

Post Reply

Who is online

Users browsing this forum: Google [Bot] and 18 guests