Mobs: Add fall damage

This commit is contained in:
Wuzzy 2023-11-02 19:40:45 +01:00
parent 19d0875a6f
commit 6e937fee68
5 changed files with 153 additions and 22 deletions

View File

@ -36,7 +36,6 @@ You can use the following template:
drops = { ADD_YOUR_DROPS_HERE },
decider = function(self) --[[ do things ]] end,
entity_definition = {
-- Add
on_activate = function(self)
rp_mobs.init_physics(self)
rp_mobs.init_tasks(self)
@ -110,6 +109,15 @@ functions to learn more about how the node damage mechanic works.
Node damage can be temporarily disabled during the mobs lifetime by setting the
entity field `_get_node_damage` to false.
### Fall damage
Fall damage hurts the mob when it hits the ground too hard.
The fall damage calculation works differently than for players (see
`rp_mobs.handle_fall_damage` for details.
To enable fall damage , add `rp_mobs.init_fall_damage` in `on_activate` and
`rp_mobs.handle_fall_damage` in `on_step`.
### Breath / drowning
Drowning makes mobs take drowning damage when inside a particular node.
@ -124,7 +132,7 @@ by manipulating the drowning fields (see the mob field reference).
### Breeding
Breeding will make mobs mate and create offspring. To enable, add
`rp_mobs.handle_breeding` in `on_step`.
`rp_mobs.handle_breeding` in `on_step`.
In particular, to breed, two adult mobs of the same type need to be “horny” and close
to each other. Then, a random mob of the pair gets pregnant and will soon
@ -137,6 +145,7 @@ There are two ways to make a mob horny:
Only adults should be horny.
## Mob field reference
Mob entities use a bunch of custom fields. You may read and edit them at runtime.
@ -155,6 +164,7 @@ These fields are available:
### Damage
* `_get_node_damage`: `true` when mob can take damage from nodes (`damage_per_second`) (default: false)
* `_get_fall_damage`: `true` when mob can take fall damage (default: false)
* `_can_drown`: `true` when mob has breath and can drown in nodes with `drowning` attribute (default: false)
* `_drowning_point`: See `rp_mobs.init_breath`.
* `_breath_max`: Maximum breath (ignored if `_can_drown` isnt true)
@ -306,6 +316,21 @@ Parameters:
* `self`: The mob
* `get_node_damage`: If `true`, mob will receive damage from nodes
### `rp_mobs.init_fall_damage(self, get_fall_damage)`
Initializes the fall damage for the given mob,
If you want the mob to have this, put this into `on_activate`.
If this is the first time the function is called, the
associated entity fields will be initialized. On subsequent
calls, this function does nothing because the fields are already
initialized.
Parameters:
* `self`: The mob
* `get_fall_damage`: If `true`, mob will receive fall damage
### `rp_mobs.attempt_capture = function(mob, capturer, capture_chances, force_take, replace_with)`
Attempt to capture mob by capturer (a player). This requires a mob to have a mob available as
@ -355,6 +380,25 @@ Otherwise, no damage will be taken.
Must be called in the `on_step` function in every step.
`dtime` is the `dtime` argument of `on_step`.
### `rp_mobs.handle_fall_damage(mob, dtime, moveresult)`
Handles fall damage for the mob if the entity field
`_get_fall_damage` is `true`.
Fall damage is calculated differently than for
players and there is no guarante the calculation
algorithm will be forwards-compatible. It increases
linearly with the fall height. Fall damage is further-
more modified by the `add_fall_damage_percent` group
if falling on a node. Adding the armor group
`add_fall_damage_percent` will also modify the
fall damage the mob will receive (like for players,
see `lua_api.md`).
This function must be called in the `on_step` function
in every step. `dtime` and `moveresult` are the same as
in `on_step`.
### `rp_mobs.handle_drowning(mob, dtime)`
Handles breath and drowning damage for the mob
@ -387,10 +431,18 @@ mobs yaw.
Must be called in the `on_step` function in every step.
`dtime` is the `dtime` argument of `on_step`.
### `rp_mobs.handle_environment_damage(mob, dtime)`
### `rp_mobs.handle_environment_damage(mob, dtime, moveresult)`
Handle all environment damages. This is is the same as
calling `rp_mobs.handle_node_damage` and `rp_mobs.handle_drowning`.
calling:
* `rp_mobs.handle_fall_damage`
* `rp_mobs.handle_node_damage`
* `rp_mobs.handle_drowning`
Must be called in `on_step` every step.
`mob` is the mob. The `dtime` and `moveresult`
arguments are the same as for `on_step`.
### `rp_mobs.on_death_default(mob, killer)`

View File

@ -13,6 +13,8 @@ local NODE_DAMAGE_TIME = 1.0
local DROWNING_TIME = 2.0
-- Interval (seconds) at which mobs regenerate breath (if they have breath)
local REBREATH_TIME = 0.5
-- Minimum Y fall height before starting to take fall damage
local FALL_DAMAGE_HEIGHT = 5
-- List of entity variables to store in staticdata
-- (so they are persisted when unloading)
@ -395,15 +397,24 @@ function rp_mobs.init_breath(self, can_drown, def)
return
end
self._can_drown = can_drown
self._breath_max = breath_max
self._breath = breath_max
self._breath_max = def.breath_max
self._breath = def.breath_max
self._drowning_point = def.drowning_point
end
function rp_mobs.init_node_damage(self, get_node_damage)
if self._get_node_damage ~= nil then
return
end
self._get_node_damage = get_node_damagee
self._get_node_damage = get_node_damage
end
function rp_mobs.init_fall_damage(self, get_fall_damage)
if self._get_fall_damage ~= nil then
return
end
self._get_fall_damage = get_fall_damage
if not self._standing_y then
self._standing_y = self.object:get_pos().y
end
end
function rp_mobs.handle_node_damage(self, dtime)
@ -501,14 +512,84 @@ function rp_mobs.handle_drowning(self, dtime)
end
end
function rp_mobs.handle_environment_damage(self, dtime)
function rp_mobs.handle_fall_damage(self, dtime, moveresult)
if not self._get_fall_damage then
return
end
if not rp_mobs.is_alive(self) then
return
end
local mob_fall_factor = 1
local armor = self.object:get_armor_groups()
-- Apply mobs fall_damage_add_percent modifier
if armor.fall_damage_add_percent then
mob_fall_factor = 1 + armor.fall_damage_add_percent/100
end
local is_immortal = armor.immortal ~= nil and armor.immortal ~= 0
if moveresult.collides then
local collisions = moveresult.collisions
for c=1, #collisions do
local collision = collisions[c]
local old_v = collision.old_velocity
local new_v = collision.new_velocity
local speed_diff = vector.subtract(new_v, old_v)
-- We only care about floor collision
if (not (speed_diff.y < 0 or old_v.y >= 0)) then
-- Apply nodes fall_damage_add_percent modifier
local node_fall_factor = 1.0
if collision.type == "node" then
local node = minetest.get_node(collision.node_pos)
local g = minetest.get_item_group(node.name, "fall_damage_add_percent")
if g ~= 0 then
node_fall_factor = 1 + g/100
end
end
-- Calculate final fall damage modifier
local pre_factor = mob_fall_factor * node_fall_factor
-- Fall damage is based on fall height. When falling at least the
-- FALL_DAMAGE_HEIGHT, mob may take 1 damage per extra node fallen
local y_diff = self._standing_y - self.object:get_pos().y
-- Apply damage modifier
y_diff = y_diff * pre_factor
if (y_diff >= FALL_DAMAGE_HEIGHT and (not is_immortal) and pre_factor > 0) then
local damage_f = y_diff - FALL_DAMAGE_HEIGHT
local damage = math.floor(math.min(damage_f + 0.5, 65535))
if damage > 0 then
local hp = self.object:get_hp() - damage
self.object:set_hp(hp, { type = "fall" })
if hp <= 0 then
self._dying = true
return
end
end
end
end
end
end
if moveresult.touching_ground then
self._standing_y = self.object:get_pos().y
end
end
function rp_mobs.handle_environment_damage(self, dtime, moveresult)
rp_mobs.handle_fall_damage(self, dtime, moveresult)
rp_mobs.handle_node_damage(self, dtime)
rp_mobs.handle_drowning(self, dtime)
end
-- Entity variables to persist:
rp_mobs.add_persisted_entity_vars({
"_get_node_damage", -- true when mob can take damage from nodes (damage_per_second)
"_get_fall_damage", -- true when mob can take fall damage
"_standing_y", -- Y coordinate when mob was standing on ground. Internally used for fall damage calculations
"_can_drown", -- true when mob has breath and can drown in nodes with `drowning` attribute
"_drowning_point", -- The position offset that will be checked when doing the drowning check
"_breath_max", -- Maximum breath

View File

@ -38,6 +38,7 @@ rp_mobs.register_mob("rp_mobs_mobs:boar", {
textures = { "mobs_boar.png" },
makes_footstep_sound = true,
on_activate = function(self)
rp_mobs.init_fall_damage(self, true)
rp_mobs.init_breath(self, true, {
breath_max = 10,
drowning_point = vector.new(0, -0.1, 0.49)
@ -48,8 +49,8 @@ rp_mobs.register_mob("rp_mobs_mobs:boar", {
rp_mobs.activate_gravity(self)
rp_mobs.init_tasks(self)
end,
on_step = function(self, dtime)
rp_mobs.handle_environment_damage(self, dtime)
on_step = function(self, dtime, moveresult)
rp_mobs.handle_environment_damage(self, dtime, moveresult)
rp_mobs.handle_physics(self)
rp_mobs.handle_tasks(self, dtime)
rp_mobs.handle_breeding(self, dtime)

View File

@ -7,17 +7,9 @@ local dummy_texture = "mobs_dummy.png"
rp_mobs.register_mob("rp_mobs_mobs:dummy", {
description = S("Dummy"),
decider = function(self)
local task = rp_mobs.create_task({label="Dummy stuff"})
rp_mobs.add_microtask_to_task(self, rp_mobs.microtasks.set_yaw("random"), task)
local yaw = (math.random(0, 10000) / 10000) * (math.pi*2)
rp_mobs.add_microtask_to_task(self, rp_mobs.microtasks.walk_straight_towards(1, "pos", vector.zero(), 0.2), task)
local sleep_time = math.random(500, 2000)/1000
local mt_sleep = rp_mobs.microtasks.sleep(sleep_time)
rp_mobs.add_microtask_to_task(self, mt_sleep, task)
rp_mobs.add_task(self, task)
end,
entity_definition = {
hp_max = 1,
hp_max = 20,
physical = true,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
selectionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5, rotate=true},
@ -25,10 +17,14 @@ rp_mobs.register_mob("rp_mobs_mobs:dummy", {
textures = { dummy_texture, dummy_texture, dummy_texture, dummy_texture, dummy_texture, dummy_texture },
makes_footstep_sound = false,
on_activate = function(self)
rp_mobs.init_fall_damage(self, true)
rp_mobs.init_physics(self)
rp_mobs.init_tasks(self)
rp_mobs.activate_gravity(self)
self._get_fall_damage = true
end,
on_step = function(self, dtime)
on_step = function(self, dtime, moveresult)
rp_mobs.handle_environment_damage(self, dtime, moveresult)
rp_mobs.handle_physics(self)
rp_mobs.handle_tasks(self, dtime)
rp_mobs.decide(self)

View File

@ -80,6 +80,7 @@ rp_mobs.register_mob("rp_mobs_mobs:sheep", {
})
end
rp_mobs.init_fall_damage(self, true)
rp_mobs.init_breath(self, true, {
breath_max = 10,
drowning_point = vector.new(0, -0.5, 0.49)
@ -91,8 +92,8 @@ rp_mobs.register_mob("rp_mobs_mobs:sheep", {
rp_mobs.init_tasks(self)
end,
get_staticdata = rp_mobs.get_staticdata_default,
on_step = function(self, dtime)
rp_mobs.handle_environment_damage(self, dtime)
on_step = function(self, dtime, moveresult)
rp_mobs.handle_environment_damage(self, dtime, moveresult)
rp_mobs.handle_physics(self)
rp_mobs.handle_tasks(self, dtime)
rp_mobs.handle_breeding(self, dtime)