VoxeLibre/mods/ENTITIES/mobs_mc/horse.lua
Saku Laesvuori c5e1734c1c Make horse taming more similar to minecraft (#1249)
In minecraft horses are tamed by trying to ride them and they can also
be fed to speed up taming. This commit implements both of those
features and disables the old and broken taming system for horses.
2021-04-04 23:24:28 +03:00

523 lines
15 KiB
Lua

--MCmobs v0.4
--maikerumine
--made for MC like Survival game
--License for code WTFPL and otherwise stated in readmes
local S = minetest.get_translator("mobs_mc")
--###################
--################### HORSE
--###################
-- Return overlay texture for horse/donkey/mule, e.g. chest, saddle or horse armor
local horse_extra_texture = function(horse)
local base = horse._naked_texture
local saddle = horse._saddle
local chest = horse._chest
local armor = horse._horse_armor
local textures = {}
if armor and minetest.get_item_group(armor, "horse_armor") > 0 then
textures[2] = base .. "^" .. minetest.registered_items[armor]._horse_overlay_image
else
textures[2] = base
end
if saddle then
textures[3] = base
else
textures[3] = "blank.png"
end
if chest then
textures[1] = base
else
textures[1] = "blank.png"
end
return textures
end
-- Helper functions to determine equipment rules
local can_equip_horse_armor = function(entity_id)
return entity_id == "mobs_mc:horse" or entity_id == "mobs_mc:skeleton_horse" or entity_id == "mobs_mc:zombie_horse"
end
local can_equip_chest = function(entity_id)
return entity_id == "mobs_mc:mule" or entity_id == "mobs_mc:donkey"
end
local can_breed = function(entity_id)
return entity_id == "mobs_mc:horse" or "mobs_mc:mule" or entity_id == "mobs_mc:donkey"
end
--[[ Generate all possible horse textures.
Horse textures are a combination of a base texture and an optional marking overlay. ]]
-- The base horse textures (fur) (fur)
local horse_base = {
"mobs_mc_horse_brown.png",
"mobs_mc_horse_darkbrown.png",
"mobs_mc_horse_white.png",
"mobs_mc_horse_gray.png",
"mobs_mc_horse_black.png",
"mobs_mc_horse_chestnut.png",
"mobs_mc_horse_creamy.png",
}
-- Horse marking texture overlay, to be appended to the base texture string
local horse_markings = {
"", -- no markings
"mobs_mc_horse_markings_whitedots.png", -- snowflake appaloosa
"mobs_mc_horse_markings_blackdots.png", -- sooty
"mobs_mc_horse_markings_whitefield.png", -- paint
"mobs_mc_horse_markings_white.png", -- stockings and blaze
}
local horse_textures = {}
for b=1, #horse_base do
for m=1, #horse_markings do
local fur = horse_base[b]
if horse_markings[m] ~= "" then
fur = fur .. "^" .. horse_markings[m]
end
table.insert(horse_textures, {
"blank.png", -- chest
fur, -- base texture + markings and optional armor
"blank.png", -- saddle
})
end
end
-- Horse
local horse = {
type = "animal",
spawn_class = "passive",
visual = "mesh",
mesh = "mobs_mc_horse.b3d",
visual_size = {x=3.0, y=3.0},
collisionbox = {-0.69825, -0.01, -0.69825, 0.69825, 1.59, 0.69825},
animation = {
stand_speed = 25,
stand_start = 0,
stand_end = 0,
walk_speed = 25,
walk_start = 0,
walk_end = 40,
run_speed = 60,
run_start = 0,
run_end = 40,
},
textures = horse_textures,
sounds = {
random = "mobs_mc_horse_random",
-- TODO: Separate damage sound
damage = "mobs_mc_horse_death",
death = "mobs_mc_horse_death",
eat = "mobs_mc_animal_eat_generic",
distance = 16,
},
fear_height = 4,
fly = false,
walk_chance = 60,
view_range = 16,
follow = mobs_mc.follow.horse,
passive = true,
hp_min = 15,
hp_max = 30,
xp_min = 1,
xp_max = 3,
floats = 1,
makes_footstep_sound = true,
jump = true,
jump_height = 5.75, -- can clear 2.5 blocks
drops = {
{name = mobs_mc.items.leather,
chance = 1,
min = 0,
max = 2,
looting = "common",},
},
do_custom = function(self, dtime)
-- set needed values if not already present
if not self._regentimer then
self._regentimer = 0
end
if not self.v2 then
self.v2 = 0
self.max_speed_forward = 7
self.max_speed_reverse = 2
self.accel = 6
self.terrain_type = 3
self.driver_attach_at = {x = 0, y = 4.17, z = -1.75}
self.driver_eye_offset = {x = 0, y = 3, z = 0}
self.driver_scale = {x = 1/self.visual_size.x, y = 1/self.visual_size.y}
end
-- Slowly regenerate health
self._regentimer = self._regentimer + dtime
if self._regentimer >= 4 then
if self.health < self.hp_max then
self.health = self.health + 1
end
self._regentimer = 0
end
-- Some weird human is riding. Buck them off?
if self.driver and not self.tamed and self.buck_off_time <= 0 then
if math.random() < 0.2 then
mobs.detach(self.driver, {x = 1, y = 0, z = 1})
-- TODO bucking animation
else
-- Nah, can't be bothered. Think about it again in one second
self.buck_off_time = 20
end
end
-- Tick the timer for trying to buck the player off
if self.buck_off_time then
if self.driver then
self.buck_off_time = self.buck_off_time - 1
else
-- Player isn't riding anymore so no need to count
self.buck_off_time = nil
end
end
-- if driver present and horse has a saddle allow control of horse
if self.driver and self._saddle then
mobs.drive(self, "walk", "stand", false, dtime)
return false -- skip rest of mob functions
end
return true
end,
on_die = function(self, pos)
-- drop saddle when horse is killed while riding
if self._saddle then
minetest.add_item(pos, mobs_mc.items.saddle)
end
-- also detach from horse properly
if self.driver then
mobs.detach(self.driver, {x = 1, y = 0, z = 1})
end
end,
on_rightclick = function(self, clicker)
-- make sure player is clicking
if not clicker or not clicker:is_player() then
return
end
local item = clicker:get_wielded_item()
local iname = item:get_name()
local heal = 0
-- Taming
self.temper = self.temper or (math.random(1,100))
if not self.tamed then
local temper_increase = 0
-- Feeding, intentionally not using mobs:feed_tame because horse taming is
-- different and more complicated
if (iname == mobs_mc.items.sugar) then
temper_increase = 3
elseif (iname == mobs_mc.items.wheat) then
temper_increase = 3
elseif (iname == mobs_mc.items.apple) then
temper_increase = 3
elseif (iname == mobs_mc.items.golden_carrot) then
temper_increase = 5
elseif (iname == mobs_mc.items.golden_apple) then
temper_increase = 10
-- Trying to ride
elseif not self.driver then
self.object:set_properties({stepheight = 1.1})
mobs.attach(self, clicker)
self.buck_off_time = 40 -- TODO how long does it take in minecraft?
if self.temper > 100 then
self.tamed = true -- NOTE taming can only be finished by riding the horse
if not self.owner or self.owner == "" then
self.owner = clicker:get_player_name()
end
end
temper_increase = 5
-- Clicking on the horse while riding ==> unmount
elseif self.driver and self.driver == clicker then
mobs.detach(clicker, {x = 1, y = 0, z = 1})
end
-- If nothing happened temper_increase = 0 and addition does nothing
self.temper = self.temper + temper_increase
return
end
if can_breed(self.name) then
-- Breed horse with golden apple or golden carrot
if (iname == mobs_mc.items.golden_apple) then
heal = 10
elseif (iname == mobs_mc.items.golden_carrot) then
heal = 4
end
if heal > 0 and mobs:feed_tame(self, clicker, heal, true, false) then
return
end
end
-- Feed with anything else
-- TODO heal amounts don't work
if (iname == mobs_mc.items.sugar) then
heal = 1
elseif (iname == mobs_mc.items.wheat) then
heal = 2
elseif (iname == mobs_mc.items.apple) then
heal = 3
elseif (iname == mobs_mc.items.hay_bale) then
heal = 20
end
if heal > 0 and mobs:feed_tame(self, clicker, heal, false, false) then
return
end
if mobs:protect(self, clicker) then
return
end
-- Make sure tamed horse is mature and being clicked by owner only
if self.tamed and not self.child and self.owner == clicker:get_player_name() then
local inv = clicker:get_inventory()
-- detatch player already riding horse
if self.driver and clicker == self.driver then
mobs.detach(clicker, {x = 1, y = 0, z = 1})
-- Put on saddle if tamed
elseif not self.driver and not self._saddle
and iname == mobs_mc.items.saddle then
-- Put on saddle and take saddle from player's inventory
local w = clicker:get_wielded_item()
self._saddle = true
if not minetest.is_creative_enabled(clicker:get_player_name()) then
w:take_item()
clicker:set_wielded_item(w)
end
-- Update texture
if not self._naked_texture then
-- Base horse texture without chest or saddle
self._naked_texture = self.base_texture[2]
end
local tex = horse_extra_texture(self)
self.base_texture = tex
self.object:set_properties({textures = self.base_texture})
minetest.sound_play({name = "mcl_armor_equip_leather"}, {gain=0.5, max_hear_distance=12, pos=self.object:get_pos()}, true)
-- Put on horse armor if tamed
elseif can_equip_horse_armor(self.name) and not self.driver and not self._horse_armor
and minetest.get_item_group(iname, "horse_armor") > 0 then
-- Put on armor and take armor from player's inventory
local armor = minetest.get_item_group(iname, "horse_armor")
self._horse_armor = iname
local w = clicker:get_wielded_item()
if not minetest.is_creative_enabled(clicker:get_player_name()) then
w:take_item()
clicker:set_wielded_item(w)
end
-- Set horse armor strength
self.armor = armor
local agroups = self.object:get_armor_groups()
agroups.fleshy = self.armor
self.object:set_armor_groups(agroups)
-- Update texture
if not self._naked_texture then
-- Base horse texture without chest or saddle
self._naked_texture = self.base_texture[2]
end
local tex = horse_extra_texture(self)
self.base_texture = tex
self.object:set_properties({textures = self.base_texture})
local def = w:get_definition()
if def.sounds and def.sounds._mcl_armor_equip then
minetest.sound_play({name = def.sounds._mcl_armor_equip}, {gain=0.5, max_hear_distance=12, pos=self.object:get_pos()}, true)
end
-- Mount horse
elseif not self.driver and self._saddle then
self.object:set_properties({stepheight = 1.1})
mobs.attach(self, clicker)
-- Used to capture horse
elseif not self.driver and iname ~= "" then
mobs:capture_mob(self, clicker, 0, 5, 60, false, nil)
end
end
end,
on_breed = function(parent1, parent2)
local pos = parent1.object:get_pos()
local child = mobs:spawn_child(pos, parent1.name)
if child then
local ent_c = child:get_luaentity()
local p = math.random(1, 2)
local child_texture
-- Randomly pick one of the parents for the child texture
if p == 1 then
if parent1._naked_texture then
child_texture = parent1._naked_texture
else
child_texture = parent1.base_texture[2]
end
else
if parent2._naked_texture then
child_texture = parent2._naked_texture
else
child_texture = parent2.base_texture[2]
end
end
local splt = string.split(child_texture, "^")
if #splt >= 2 then
-- Randomly mutate base texture (fur) and markings
-- with chance of 1/9 each
local base = splt[1]
local markings = splt[2]
local mutate_base = math.random(1, 9)
local mutate_markings = math.random(1, 9)
if mutate_base == 1 then
local b = math.random(1, #horse_base)
base = horse_base[b]
end
if mutate_markings == 1 then
local m = math.random(1, #horse_markings)
markings = horse_markings[m]
end
child_texture = base
if markings ~= "" then
child_texture = child_texture .. "^" .. markings
end
end
ent_c.base_texture = { "blank.png", child_texture, "blank.png" }
ent_c._naked_texture = child_texture
child:set_properties({textures = ent_c.base_texture})
return false
end
end,
}
mobs:register_mob("mobs_mc:horse", horse)
-- Skeleton horse
local skeleton_horse = table.copy(horse)
skeleton_horse.breath_max = -1
skeleton_horse.armor = {undead = 100, fleshy = 100}
skeleton_horse.textures = {{"blank.png", "mobs_mc_horse_skeleton.png", "blank.png"}}
skeleton_horse.drops = {
{name = mobs_mc.items.bone,
chance = 1,
min = 0,
max = 2,},
}
skeleton_horse.sounds = {
random = "mobs_mc_skeleton_random",
death = "mobs_mc_skeleton_death",
damage = "mobs_mc_skeleton_hurt",
eat = "mobs_mc_animal_eat_generic",
base_pitch = 0.95,
distance = 16,
}
skeleton_horse.harmed_by_heal = true
mobs:register_mob("mobs_mc:skeleton_horse", skeleton_horse)
-- Zombie horse
local zombie_horse = table.copy(horse)
zombie_horse.breath_max = -1
zombie_horse.armor = {undead = 100, fleshy = 100}
zombie_horse.textures = {{"blank.png", "mobs_mc_horse_zombie.png", "blank.png"}}
zombie_horse.drops = {
{name = mobs_mc.items.rotten_flesh,
chance = 1,
min = 0,
max = 2,},
}
zombie_horse.sounds = {
random = "mobs_mc_horse_random",
-- TODO: Separate damage sound
damage = "mobs_mc_horse_death",
death = "mobs_mc_horse_death",
eat = "mobs_mc_animal_eat_generic",
base_pitch = 0.5,
distance = 16,
}
zombie_horse.harmed_by_heal = true
mobs:register_mob("mobs_mc:zombie_horse", zombie_horse)
-- Donkey
local d = 0.86 -- donkey scale
local donkey = table.copy(horse)
donkey.textures = {{"blank.png", "mobs_mc_donkey.png", "blank.png"}}
donkey.animation = {
speed_normal = 25,
stand_start = 0, stand_end = 0,
walk_start = 0, walk_end = 40,
}
donkey.sounds = {
random = "mobs_mc_donkey_random",
damage = "mobs_mc_donkey_hurt",
death = "mobs_mc_donkey_death",
eat = "mobs_mc_animal_eat_generic",
distance = 16,
}
donkey.visual_size = { x=horse.visual_size.x*d, y=horse.visual_size.y*d }
donkey.collisionbox = {
horse.collisionbox[1] * d,
horse.collisionbox[2] * d,
horse.collisionbox[3] * d,
horse.collisionbox[4] * d,
horse.collisionbox[5] * d,
horse.collisionbox[6] * d,
}
donkey.jump = true
donkey.jump_height = 3.75 -- can clear 1 block height
mobs:register_mob("mobs_mc:donkey", donkey)
-- Mule
local m = 0.94
local mule = table.copy(donkey)
mule.textures = {{"blank.png", "mobs_mc_mule.png", "blank.png"}}
mule.visual_size = { x=horse.visual_size.x*m, y=horse.visual_size.y*m }
mule.sounds = table.copy(donkey.sounds)
mule.sounds.base_pitch = 1.15
mule.collisionbox = {
horse.collisionbox[1] * m,
horse.collisionbox[2] * m,
horse.collisionbox[3] * m,
horse.collisionbox[4] * m,
horse.collisionbox[5] * m,
horse.collisionbox[6] * m,
}
mobs:register_mob("mobs_mc:mule", mule)
--===========================
--Spawn Function
mobs:spawn_specific("mobs_mc:horse", mobs_mc.spawn.grassland_savanna, {"air"}, 0, minetest.LIGHT_MAX+1, 30, 15000, 4, mobs_mc.spawn_height.water+3, mobs_mc.spawn_height.overworld_max)
mobs:spawn_specific("mobs_mc:donkey", mobs_mc.spawn.grassland_savanna, {"air"}, 0, minetest.LIGHT_MAX+1, 30, 15000, 4, mobs_mc.spawn_height.water+3, mobs_mc.spawn_height.overworld_max)
-- spawn eggs
mobs:register_egg("mobs_mc:horse", S("Horse"), "mobs_mc_spawn_icon_horse.png", 0)
mobs:register_egg("mobs_mc:skeleton_horse", S("Skeleton Horse"), "mobs_mc_spawn_icon_horse_skeleton.png", 0)
--mobs:register_egg("mobs_mc:zombie_horse", S("Zombie Horse"), "mobs_mc_spawn_icon_horse_zombie.png", 0)
mobs:register_egg("mobs_mc:donkey", S("Donkey"), "mobs_mc_spawn_icon_donkey.png", 0)
mobs:register_egg("mobs_mc:mule", S("Mule"), "mobs_mc_spawn_icon_mule.png", 0)